2

学习神经网络的一点笔记

 1 year ago
source link: https://blog.codingnow.com/2023/03/feedforward_nn.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

学习神经网络的一点笔记

最近 AI 很火。我从读书的时候就对 AI 有兴趣,90 年代的时候读过一本关于神经网络的书。但当时没有能力完全理解。2000 年初刚毕业那会儿想搞明白点,又研究过一段时间。了解了很多相关知识,感觉自己弄明白了,但却没有真正动手实践过。

现在,所有人都看得到,AI 已经变成了一个可以真正帮助我们提高生产力的工具了。我觉得有必要好好学习一下。我并不期望几篇文章就可以搞清楚,那样只会让自己好像懂了,和人聊天估计够用,想理解它还是得自己动手。真正写几段代码,才有触碰的感觉,明白别人到底在解决什么问题。

一开始,我是从维基百科的 Machine learning 开始读的。顺着看了一整天,了解了近年发展的脉络。好多词条,乱七八糟的笔记记了一大堆,感觉快消化不了了。疑问也积累了很多,觉得看下去效率会越来越低。不如换一条路。

然后我开始读 Neural Networks and Deep Learning 这本书。读了两章后,我想,手写数字识别看起来是一个非常好的实践手段,不如自己写一遍最快。有这么一个基础,后面可以继续修修补补,沿着前人的轨迹走一下,可以更好的理解为什么。

现在的条件远超 20 年前,训练这么一个简单的前馈神经网络估计在我的机器只要不到一分钟,可以很快得到反馈。有很多现成的代码可以比对,出了 bug 容易查。练习需要的数据也是唾手可得:THE MNIST DATABASE of handwritten digits 。虽然神经网络到底怎么工作的,人类恐怕还无法弄清楚每个神经元传递的信号的意义。这会导致程序出了 bug 很容易被掩盖起来,但有了数据库里的数据,我们还是可以从结果的正确率推测自己有没有实现对。所以用这个手写数字识别这个课题来学习是非常好的。

我所理解的神经网络,就是模拟大脑的工作方式,用非常简单的神经单元,相互连接起来,通过传递非常简单的信号,最后可以从网络的一端输入信息,另一端得到预期的结果。用程序模拟现实事物本来就是我的爱好,游戏就是干这个的。所以写这么一个程序让我感到非常快乐。

我们的信念是:模拟神经网络,尤其是实现单个神经元的代码肯定是异常简单的。起作用的不是单个神经元多么奇妙,能思考问题;而起作用的是网络结构本身。如果神给定了一个合适的网络结构,我们从一端输入若干信号,经由若干连接(神经元之间的突触),发送给众多神经元。一个神经元会被很多突触连接到多个神经元,每个通过突触的信号被突触上的权重所修正累积在一起,在接收的神经元内再由此神经元内的阈值修正一次,把该信号传播下去。(也可以把阈值看成连接到这个神经元的一个常量信号)最后,一定能得到正确的结果。如果结果不正确,并不是方法有问题,只是网络结构不对。

所谓人工神经网络,只要得到了正确的网络结构,用它来解决问题是非常简单的:模拟信号传播的过程,把信号通一遍就好了。我的代码的最初版本就是用已有的正确的程序训练出网络,把数据导出,然后导入到我的代码中验证一下。这样,我只用实现最简单的部分,可以快速检验实现的对不对(避免后面加入更复杂的部分时,难以定位 bug )。

最简单的神经网络就是把所有神经元相互全连接。这显然不是生物上神经网络的结构,但它是最简单的。因为,如果神经元是一组组聚集在一起,相互间只有部分连接的话,无非是把全连接中的部分连接权重设为 0 而已。

但这也是真正的困难所在:到底该怎样得到一个正确解决问题的网络结构。也就是所谓的训练。如果我们有无限的时间,固然可以穷举所有的可能性(每个连接上的权重),但穷举所需的时间一定超过了宇宙的寿命。而神经元之间的连接数量也远超了人为去每个设定的能力范畴。我们必须用某种启发式方法减少这个寻找正确网络结果的时间,同时还需要简化数据规模,比如卷积神经网络把神经元分组分层,就极大的减少了连接数量。

作为学习,我想先从一个最简单的前馈神经网络开始。也就是根据问题:从 28x28 = 784 个像素的灰度图中识别出手写的数字 0 - 9 。输入是 784 个灰度信号,输出则是 0 - 9 十个独立信号。其中每个信号表示结果是或不是某一个具体的数字。固然,10 个独立信号有很大的冗余。理论上 4 个信号就够了(可以表示 0-15 个状态)。但是,如果采用 4 个信号的输出,相当于除了让网络识别出数字外,还需要理解数字的二进制表示。这肯定需要更复杂的网络结构,势必大大增加训练出网络的难度。

中间模拟神经网络的部分,采用书上建议的 30 个神经元。采用无环结构,让它们一边和 784 个输入单元全连接,另一端和 10 个输出单元全连接。所谓的训练工作就是反复尝试找到每个连接上的权重,以及每个神经单位过滤信号用的阈值。一共 784 * 30 + 30 + 30 * 10 + 10 个需要测算的参数。

训练的方法是最基本的监督学习。就是用已知结果的信号去跑网络,告诉它这样一张图片是数字几。这样让网络知道人所需要的结果偏好。MINIST 数据集中有 5 万组预扫描好的图片,并标注了结果数字。一开始输入图片后,结果肯定是乱来的。但是我们可以根据错误的结果和正确的结果比对,来调整网络 。这里可以采用一种叫 stochastic gradient descent (SGD) 的方法。有点像调收音机,感觉频道不对,就稍微转一下旋钮。再听一下信号好不好,如果好了,方向就对了,如果差了就反着转一下。这个过程叫 backpropagation ,根据结果误差来修正源头,把误差反向传播回去。但是,对于神经元来说,每个神经元对应的源头有很多个,我们很难确定该转哪个源头的旋钮,该转多少。

为了更好的调整这些权重值,我们需要对信号做一些处理。因为每个神经元接收到的信号都是很多来源叠加起来的,所以,不应该让某些信号过于特殊。不然它的一点点变化就会极大的影响输出,掩盖掉另一些信号的调整影响。我们可以用一个函数,把所谓输出信号的范围从整个实数范围全部放到 [0,1] 之间,这样,对于每个神经元来说,它的每个输入看起来都差不多了,可以按一致的规则去调整。

其实,这个对信号做变形的函数具体是什么不重要,重要的是这个函数连续可微,它把输入信号都规范方便调整了。书上推荐的 sigmoid 函数之所以用得多,还在于对它求导非常简单。后面这点非常重要。

我在一开始觉得 backpropagation 的细节很难理解。后来看了另一篇文章 How Does Backpropagation in a Neural Network Work? 就明白了。原来,现在用的方法并不是人们一开始都知道的。现在我们用的从结果的差异反推来源的 delta ,公式非常简单,但数学原理并不简单。感谢 Andrew Ng 的公式 delta_0 = w . delta_1 . f'(z) ,我们只需要对正向信号传播时,每个神经元上得到的值求导,乘上该神经元输出端的神经元上的 delta 和输出权重就可以得到它的 delta 。总 delta 就是所有输出侧用该公式计算出的 delta 累积量。

我写程序的过程中,一共出过两个 bug。其中一个就是在 backpropagation 过程的,主要还是理解不到位引起的手误。花了两个小时重新读书、理解、再审查代码才找出来。

我将代码提交到了 github ,有兴趣的同学可以参考。https://github.com/cloudwu/neuralnet


说到另一个 bug 。一开始我的程序可以正确运行,后来我尝试调大了一些神经元的个数,比如从 30 改到 40 ,质量却急剧下降。这让我难以理解。花了 2 个多小时排查(其实整个程序连同读书的时间,一共也就花了 10 个小时),才发现问题并不是出在神经网络的实现上。

我在书中读到,初始化神经网络时可以用正态分布的随机值初始化,这样可以减少训练时间。暂时我没有去探究这样做为什么有效,或是有什么更好的方法。只是动手实现了一下。因为我没有使用任何第三方库,而 C 标准库中的随机数是均匀分布的。所以我随手 google 了一个公式,把均匀分布的随机数转换为正态分布:取两个均匀分布的随机数 r1, r2 ,然后计算 float x = sqrtf( -2.0 * logf ( r1 ) ) * cosf ( 2.0 * PI * r2 ); 。每次用一个随机数,记下另外一个,留给下一次迭代就可以了。

我没有怀疑这个公式有什么问题。但我疏忽了当输入的 r1 为 0 时,log(r1) 会是无穷大。而无穷大会污染神经网络,导致经过几次 backpropagation 后,很多权重都变成了 NaN 。


这次的实践只能说是一个开始。不过已经占用了很多工作时间。我们的游戏还在制作中,我得先放一放,等下周再试试深入一点的知识。

云风 提交于 March 8, 2023 10:08 AM | 固定链接


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK