神经网络中正则化是为了干什么
神经⽹络中BP算法的原理与Python实现源码解析
最近这段时间系统性的学习了 BP 算法后写下了这篇学习笔记,因为能⼒有限,若有明显错误,还请指正。
什么是梯度下降和链式求导法则
假设我们有⼀个函数 J(w),如下图所⽰。
梯度下降⽰意图
现在,我们要求当 w 等于什么的时候,J(w) 能够取到最⼩值。从图中我们知道最⼩值在初始位置的左边,也就意味着如果想要使 J(w) 最⼩,w的值需要减⼩。⽽初始位置的切线的斜率a > 0(也即该位置对应的导数⼤于0),w = w – a 就能够让 w 的值减⼩,循环求导更新w直到 J(w) 取得最⼩值。如果函数J(w)包含多个变量,那么就要分别对不同变量求偏导来更新不同变量的值。
所谓的链式求导法则,就是求复合函数的导数:
链式求导法则
放个例题,会更加明⽩⼀点:
链式求导的例⼦
神经⽹络的结构
神经⽹络由三部分组成,分别是最左边的输⼊层,隐藏层(实际应⽤中远远不⽌⼀层)和最右边的输出层。层与层之间⽤线连接在⼀起,每条连接线都有⼀个对应的权重值 w,除了输⼊层,⼀般来说每个神经元还有对应的偏置 b。
神经⽹络的结构图
除了输⼊层的神经元,每个神经元都会有加权求和得到的输⼊值 z 和将 z 通过 Sigmoid 函数(也即是激活函数)⾮线性转化后的输出值 a,他们之间的计算公式如下
神经元输出值 a 的计算公式
其中,公式⾥⾯的变量l和j表⽰的是第 l 层的第 j 个神经元,ij 则表⽰从第 i 个神经元到第 j 个神经元之间的连线,w 表⽰的是权重,b 表⽰的是偏置,后⾯这些符号的含义⼤体上与这⾥描述的相似,所以不会再说明。下⾯的 Gif 动图可以更加清楚每个神经元输⼊输出值的计算⽅式(注意,这⾥的动图并没有加上偏置,但使⽤中都会加上)
动图显⽰计算神经元输出值
使⽤激活函数的原因是因为线性模型(⽆法处理线性不可分的情况)的表达能⼒不够,所以这⾥通常需要加⼊ Sigmoid 函数来加⼊⾮线性因素得到神经元的输出值。
关于为什么线性函数模型表达能⼒不够,可以查看知乎上⾯的讨论。
sigmoid 函数
可以看到 Sigmoid 函数的值域为 (0,1) ,若对于多分类任务,输出层的每个神经元可以表⽰是该分类的概率。当然还存在其他的激活函数,他们的⽤途和优缺点也都各异。
BP 算法执⾏的流程(前向传递和逆向更新)
在⼿⼯设定了神经⽹络的层数,每层的神经元的个数,学习率η(下⾯会提到)后,BP 算法会先随机初始化每条连接线权重和偏置,然后对于训练集中的每个输⼊ x 和输出 y,BP 算法都会先执⾏前向传输得到预测值,然后根据真实值与预测值之间的误差执⾏逆向反馈更新神经⽹络中每条连接线的权重和每层的偏好。在没有到达停⽌条件的情况下重复上述过程。
其中,停⽌条件可以是下⾯这三条
●权重的更新低于某个阈值的时候
●预测的错误率低于某个阈值
●达到预设⼀定的迭代次数
譬如说,⼿写数字识别中,⼀张⼿写数字1的图⽚储存了28*28 = 784个像素点,每个像素点储存着灰度值(值域为[0,255]),那么就意味着有784个神经元作为输⼊层,⽽输出层有10个神经元代表数字0~9,每个神经元取值为0~1,代表着这张图⽚是这个数字的概率。
每输⼊⼀张图⽚(也就是实例),神经⽹络会执⾏前向传输⼀层⼀层的计算到输出层神经元的值,根据哪个输出神经元的值最⼤来预测输⼊图⽚所代表的⼿写数字。
然后根据输出神经元的值,计算出预测值与真实值之间的误差,再逆向反馈更新神经⽹络中每条连接线的权重和每个神经元的偏好。
前向传输(Feed-Forward)
从输⼊层=>隐藏层=>输出层,⼀层⼀层的计算所有神经元输出值的过程。
逆向反馈(Back Propagation)
因为输出层的值与真实的值会存在误差,我们可以⽤均⽅误差来衡量预测值和真实值之间的误差。
均⽅误差
逆向反馈的⽬标就是让E函数的值尽可能的⼩,⽽每个神经元的输出值是由该点的连接线对应的权重值和该层对应的偏好所决定的,因此,要让误差函数达到最⼩,我们就要调整w和b值,使得误差函数的值最⼩。
权重和偏置的更新公式
对⽬标函数 E 求 w 和 b 的偏导可以得到 w 和 b 的更新量,下⾯拿求 w 偏导来做推导。
其中η为学习率,取值通常为 0.1 ~ 0.3,可以理解为每次梯度所迈的步伐。注意到 w_hj 的值先影响到第 j 个输出层神经元的输⼊值a,再影响到输出值y,根据链式求导法则有:
使⽤链式法则展开对权重求偏导
根据神经元输出值 a 的定义有:
对函数 z 求 w 的偏导
Sigmoid 求导数的式⼦如下,从式⼦中可以发现其在计算机中实现也是⾮常的⽅便:
Sigmoid 函数求导
所以
则权重 w 的更新量为:
类似可得 b 的更新量为:
但这两个公式只能够更新输出层与前⼀层连接线的权重和输出层的偏置,原因是因为δ值依赖了真实值y这个变量,但是我们只知道输出层的真实值⽽不知道每层隐藏层的真实值,导致⽆法计算每层隐藏层的δ值,所以我们希望能够利⽤ l+1 层的δ值来计算 l 层的δ值,⽽恰恰通过⼀些列数学转换后可以做到,这也就是逆向反馈名字的由来,公式如下:
从式⼦中我们可以看到,我们只需要知道下⼀层的权重和神经元输出层的值就可以计算出上⼀层的δ值,我们只要通过不断的利⽤上⾯这个式⼦就可以更新隐藏层的全部权重和偏置了。
在推导之前请先观察下⾯这张图:
l 和 l+1 层的神经元
⾸先我们看到 l 层的第 i 个神经元与 l+1 层的所有神经元都有连接,那么我们可以将δ展开成如下的式⼦:
也即是说我们可以将 E 看做是 l+1 层所有神经元输⼊值的 z 函数,⽽上⾯式⼦的 n 表⽰的是 l+1 层神经元的数量,再进⾏化简后就可以得到上⾯所说的式⼦。
在这⾥的推导过程只解释了关键的部分,如果要查看更加详细的推导内容,可以点击此处下载我在学习过程中参考的,⾥⾯的推导过程⾮常详细。另外也参考了周志华所写的机器学习中的神经⽹络部分的内容和的内容。
Python 源码解析
使⽤ Python 实现的神经⽹络的代码⾏数并不多,仅包含⼀个 Network 类,⾸先来看看该类的构造⽅法。
向前传输(FreedForward)的代码。
源码⾥使⽤的是随机梯度下降(Stochastic Gradient Descent,简称 SGD),原理与梯度下降相似,不同的是随机梯度下降算法每次迭代只取数据集中⼀部分的样本来更新 w 和 b 的值,速度⽐梯度下降快,但是,它不⼀定会收敛到局部极⼩值,可能会在局部极⼩值附近徘徊。
根据 backprop ⽅法得到的偏导数更新 w 和 b 的值。
下⾯这块代码是源码最核⼼的部分,也即 BP 算法的实现,包含了前向传输和逆向反馈,前向传输在 Network ⾥有单独⼀个⽅法(上⾯提到的 feedforward ⽅法),那个⽅法是⽤于验证训练好的神经⽹络的精确度的,在下⾯有提到该⽅法。
接下来则是 evaluate 的实现,调⽤ feedforward ⽅法计算训练好的神经⽹络的输出层神经元值(也即预测值),然后⽐对正确值和预测值得到精确率。
最后,我们可以利⽤这个源码来训练⼀个⼿写数字识别的神经⽹络,并输出评估的结果,代码如下:
可以看到,在经过 30 轮的迭代后,识别⼿写神经⽹络的精确度在 95% 左右,当然,设置不同的迭代次数,学习率以取样数对精度都会有影响,如何调参也是⼀门技术活,这个坑就后期再填吧。
总结
神经⽹络的优点:
⽹络实质上实现了⼀个从输⼊到输出的映射功能,⽽数学理论已证明它具有实现任何复杂⾮线性映射的功能。这使得它特别适合于求解内部机制复杂的问题。
⽹络能通过学习带正确答案的实例集⾃动提取“合理的”求解规则,即具有⾃学习能⼒。
⽹络具有⼀定的推⼴、概括能⼒。
神经⽹络的缺点:
对初始权重⾮常敏感,极易收敛于局部极⼩。
容易 Over Fitting 和 Over Training。
如何选择隐藏层数和神经元个数没有⼀个科学的指导流程,有时候感觉就是靠猜。
应⽤领域:
常见的有图像分类,⾃动驾驶,⾃然语⾔处理等。
TODO
但其实想要训练好⼀个神经⽹络还⾯临着很多的坑(譬如下⾯四条):
1. 如何选择超参数的值,譬如说神经⽹络的层数和每层的神经元数量以及学习率;
2. 既然对初始化权重敏感,那该如何避免和修正;
3. Sigmoid 激活函数在深度神经⽹络中会⾯临梯度消失问题该如何解决;
4. 避免 Overfitting 的 L1 和 L2正则化是什么。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。