一文搞定BP神经网络——从原理到应用(原理篇)

时间:2024-04-11 10:28:33

Hello,对于神经网络的原理,我入门了好多次,每次都觉得懂了,但是其实内部原理并没有理解透彻。经过不懈努力,终于茅塞顿开,遂总结此文。

本文难免会有打字错误和叙述不合理的地方,希望读者可以在评论区反馈。我会及时吸纳大家的意见,并对文章进行修改。

本文参考了一些资料,在此一并列出。
- http://neuralnetworksanddeeplearning.com/chap2.html
- 斯坦福大学CS229线性代数补充讲义http://cs229.stanford.edu/
- https://www.deeplearning.ai/ coursera对应课程视频讲义
- coursera华盛顿大学机器学习专项
- 周志华《机器学习》
- 李航《统计学习方法》
- 张明淳《工程矩阵理论》

1. 神经网络结构以及前向传播过程

我们给出如下神经网络结构,第一层是输入层(3个神经元), 第二层是隐含层(2个神经元),第三层是输出层。

一文搞定BP神经网络——从原理到应用(原理篇)

我们使用以下符号约定w[l]jk表示从网络第(l1)thkth个神经元指向第lth中第jth个神经元的连接权重,比如上图中w[2]21即从第1层第一个神经元指向第2层第2个神经元的权重。同理,我们使用b[l]j来表示第lth层中第jth神经元的偏差,用z[l]j来表示第lth层中第jth神经元的线性结果,用a[l]j来表示第lth层中第jth神经元的**。

因此,第lth层中第jth神经元的**为:

a[l]j=σ(kw[l]jka[l1]k+b[l]j)

现在,我们使用矩阵形式重写这个公式:

定义w[l]表示权重矩阵,它的每一个元素表示一个权重,即每一行都是连接第l层的权重,用上图举个例子就是:

w[2]=[w[2]11w[2]21w[2]12w[2]22w[2]13w[2]23]

同理,
b[2]=[b[2]1b[2]2]

z[2]=[w[2]11w[2]21w[2]12w[2]22w[2]13w[2]23]a[1]1a[1]2a[1]3+[b[2]1b[2]2]=[w[2]11a[1]1+w[2]12a[1]2+w[2]13a[1]3+b[2]1w[2]21a[1]1+w[2]22a[1]2+w[2]23a[1]3+b[2]2]

更一般地,我们可以把前向传播过程表示:

a[l]=σ(w[l]a[l1]+b[l])

到这里,我们已经说清楚了前向传播的过程,值得注意的是,这里我们只有一个输入样本,对于多个样本同时输入的情况是一样的,只不过我们的输入向量不再是一列,而是m列,每一个都表示一个输入样本。

多样本输入情况下的表示为:

z[l]=w[l]a[l1]

其中,此时
a[l1]=|a[l1](1)||a[l1](2)|..................|a[l1](m)|

每一列都表示一个样本,从样本1到m;
w[l]的含义和原来完全一样,z[l]也会变成m列,每一列表示一个样本的计算结果。

之后我们的叙述都是先讨论单个样本的情况,再扩展到多个样本同时计算。

2. 损失函数和代价函数

说实话,损失函数(Loss Function)代价函数(Cost Function)并没有一个公认的区分标准,很多论文和教材似乎把二者当成了差不多的东西。

为了后面描述的方便,我们把二者稍微做一下区分:

损失函数主要指的是对于单个样本的损失或误差;
代价函数表示多样本同时输入模型的时候总体的误差——每个样本误差的和然后取平均值。

举个例子,如果我们把损失函数定义为:

L(a,y)=[ylog(a)+(1y)log(1a)]

那么对于损失函数就是上式。
然而对于m个样本的代价函数则是:
J=1mi=0my(i)log(a(i)+(1y(i)log(1a(i)))

3. 反向传播

  反向传播的基本思想就是通过计算输出层与期望值之间的误差来调整网络参数,从而使得误差变小。

  反向传播的思想很简单,然而人们认识到它的重要作用却经过了很长的时间。后向传播算法产生于1970年,但它的重要性一直到David Rumelhart,Geoffrey Hinton和Ronald Williams于1986年合著的论文发表才被重视。

  事实上,人工神经网络的强大力量几乎就是建立在反向传播算法基础之上的。反向传播基于四个基础等式,数学是优美的,仅仅四个等式就可以概括神经网络的反向传播过程,然而理解这种优美可能需要付出一些脑力。事实上,反向传播如此之难,以至于相当一部分初学者很难进行独立推导。所以如果读者是初学者,希望读者可以耐心地研读本节。对于初学者,我觉得拿出1-3个小时来学习本小节是比较合适的,当然,对于熟练掌握反向传播原理的读者,你可以在十几分钟甚至几分钟之内快速浏览本节的内容。

3.1 矩阵补充知识

  对于大部分理工科的研究生,以及学习过矩阵论或者工程矩阵理论相关课程的读者来说,可以跳过本节。

  本节主要面向只学习过本科线性代数课程或者已经忘记矩阵论有关知识的读者。

  总之,具备了本科线性代数知识的读者阅读这一小节应该不会有太大问题。本节主要在线性代数的基础上做一些扩展。(不排除少数本科线性代数课程也涉及到这些内容,如果感觉讲的简单的话,勿喷)

3.1.1 求梯度矩阵

  假设函数 f:Rm×nR可以把输入矩阵(shape: m×n)映射为一个实数。那么,函数f的梯度定义为:

Af(A)=f(A)A11f(A)A21f(A)Am1f(A)A12f(A)A22f(A)Am2f(A)A1nf(A)A2nf(A)Amn

  即
(Af(A))ij=f(A)Aij

  同理,一个输入是向量(向量一般指列向量,本文在没有特殊声明的情况下默认指的是列向量)的函数f:Rn×1R,则有:

xf(x)=f(x)x1f(x)x2f(x)xn

  注意:这里涉及到的梯度求解的前提是函数f 返回的是一个实数如果函数返回的是一个矩阵或者向量,那么我们是没有办法求梯度的。比如,对函数f(A)=mi=0nj=0A2ij,由于返回一个实数,我们可以求解梯度矩阵。如果f(x)=Ax(ARm×n,xRn×1),由于函数返回一个m行1列的向量,因此不能对f求梯度矩阵。

  根据定义,很容易得到以下性质:

  x(f(x)+g(x))=xf(x)+xg(x)
  (tf(x))=tf(x),tR

  有了上述知识,我们来举个例子:

  定义函数f:RmR,f(z)=zTz,那么很容易得到zf(z)=2z,具体请读者自己证明。

3.1.2 海塞矩阵

  定义一个输入为n维向量,输出为实数的函数f:RnR,那么海塞矩阵(Hessian Matrix)定义为多元函数f的二阶偏导数构成的方阵:

2xf(x)=2f(x)x212f(x)x2x12f(x)xnx12f(x)x1x22f(x)x222f(x)xnx22f(x)x1xn2f(x)x2xn2f(x)x2n

  由上式可以看出,海塞矩阵总是对称阵

  注意:很多人把海塞矩阵看成xf(x)的导数,这是不对的。只能说,海塞矩阵的每个元素都是函数f二阶偏导数。那么,有什么区别呢?

  首先,来看正确的解释海塞矩阵的每个元素是函数f的二阶偏导数。2f(x)x1x2举个例子,函数fx1求偏导得到的是一个实数,比如2f(x)x1=x32x1,因此继续求偏导是有意义的,继续对x2求偏导可以得到3x1x22

  然后,来看一下错误的理解。把海塞矩阵看成xf(x)的导数,也就是说错误地以为2xf(x)=x(xf(x)),要知道,xf(x)是一个向量,而在上一小节我们已经重点强调过,在我们的定义里对向量求偏导是没有定义的

  但是xf(x)xi是有意义的,因为f(x)xi是一个实数,具体地:

xf(x)xi=2f(x)xix12f(x)xix22f(x)xixn

  即海塞矩阵的第i行(或列)。

  希望读者可以好好区分。

3.1.3 总结

  根据3.1.1和3.1.2小节的内容很容易得到以下等式:

  bRn,xRn,ARn×nA
  b,x均为列向量
  那么,
  xbTx=b
  xxTAx=2Ax(A)
  2xxTAx=2A(A)

  这些公式可以根据前述定义自行推导,有兴趣的读者可以自己推导一下。

3.2 矩阵乘积和对应元素相乘

  在下一节讲解反向传播原理的时候,尤其是把公式以矩阵形式表示的时候,需要大家时刻区分什么时候需要矩阵相乘,什么时候需要对应元素相乘。

  比如对于矩阵A=[1324]B=[1324]
  矩阵相乘

AB=[1×1+2×33×1+4×31×2+2×43×2+4×4]=[7151022]

  对应元素相乘使用符号表示:

AB=[1×13×32×24×4]=[19416]

3.3 梯度下降法原理

  通过之前的介绍,相信大家都可以自己求解梯度矩阵(向量)了。

  那么梯度矩阵(向量)求出来的意义是什么?从几何意义讲,梯度矩阵代表了函数增加最快的方向,因此,沿着与之相反的方向就可以更快找到最小值。如图5所示:


一文搞定BP神经网络——从原理到应用(原理篇)
【图5 梯度下降法 图片来自百度】

  反向传播的过程就是利用梯度下降法原理,慢慢的找到代价函数的最小值,从而得到最终的模型参数。梯度下降法在反向传播中的具体应用见下一小节。

3.4 反向传播原理(四个基础等式)

  反向传播能够知道如何更改网络中的权重w 和偏差b 来改变代价函数值。最终这意味着它能够计算偏导数

L(a[l],y)w[l]jk
L(a[l],y)b[l]j

  为了计算这些偏导数,我们首先引入一个中间变量δ[l]j,我们把它叫做网络中第lth层第jth个神经元的误差。后向传播能够计算出误差δ[l]j,然后再将其对应回L(a[l],y)w[l]jkL(a[l],y)b[l]j

  那么,如何定义每一层的误差呢?如果为第l 层第j 个神经元添加一个扰动Δz[l]j,使得损失函数或者代价函数变小,那么这就是一个好的扰动。通过选择 Δz[l]jL(a[l],y)z[l]j符号相反(梯度下降法原理),就可以每次都添加一个好的扰动最终达到最优。

  受此启发,我们定义网络层第l 层中第j 个神经元的误差为δ[l]j:

δ[l]j=L(a[L],y)z[l]j

  于是,每一层的误差向量可以表示为:

δ[l]=δ[l]1δ[l]2δ[l]n

  下面开始正式介绍四个基础等式【确切的说是四组等式】

  注意:这里我们的输入为单个样本(所以我们在下面的公式中使用的是损失函数而不是代价函数)。多个样本输入的公式会在介绍完单个样本后再介绍。

  • 等式1 :输出层误差

δ[L]j=La[L]jσ(z[L]j)

  其中,L表示输出层层数。以下用L 表示 L(a[L],y)

  写成矩阵形式是:

δ[L]=aLσ(z[L])

  【注意是对应元素相乘,想想为什么?】

  说明

  根据本小节开始时的叙述,我们期望找到L /z[l]j,然后朝着方向相反的方向更新网络参数,并定义误差为:

δ[L]j=Lz[L]j

  根据链式法则,

δ[L]j=kLa[L]ka[L]kz[L]j

  当kj时,a[L]k/z[L]j就为零。结果我们可以简化之前的等式为
δ[L]j=La[L]ja[L]jz[L]j

  重新拿出定义:a[L]j=σ(z[L]j),就可以得到:
δ[L]j=La[L]jσ(z[L]j)

  再”堆砌”成向量形式就得到了我们的矩阵表示式(这也是为什么使用矩阵形式表示需要 对应元素相乘 的原因)。

  • 等式2: 隐含层误差
    δ[l]j=kw[l+1]kjδ[l+1]kσ(z[l]j)

  写成矩阵形式:

δ[l]=[w[l+1]Tδ[l+1]]σ(z[l])

  说明:

z[l+1]k=jw[l+1]kja[l]j+b[l+1]k=jw[l+1]kjσ(z[l]j)+b[l+1]k

  进行偏导可以获得:
z[l+1]kz[l]j=kw[l+1]kjσ(z[l]j)

  代入得到:
δ[l]j=kw[l+1]kjδ[l+1]kσ(z[l]j)

  • 等式3:参数变化率

Lb[l]j=δ[l]j

Lw[l]jk=a[l1]kδ[l]j

  写成矩阵形式:

Lb[l]=δ[l]
Lw[l]=δ[l]a[l1]T

  说明:

  根据链式法则推导。
  由于

z[l]j=kw[l]jka[l1]k+b[l]k

  对b[l]j求偏导得到:
Lb[l]j=Lz[l]jz[l]jb[l]j=δ[l]j

  对w[l]jk求偏导得到:
Lw[l]jk=Lz[l]jz[l]jw[l]jk=a[l1]kδ[l]j

  最后再变成矩阵形式就好了。

  对矩阵形式来说,需要特别注意维度的匹配。强烈建议读者在自己编写程序之前,先列出这些等式,然后仔细检查维度是否匹配。

  很容易看出Lw[l]是一个dim(δ[l])dim(a[l1])列的矩阵,和w[l]的维度一致;Lb[l]是一个维度为dim(δ[l])的列向量

  • 等式4:参数更新规则

  这应该是这四组公式里最简单的一组了,根据梯度下降法原理,朝着梯度的反方向更新参数:

b[l]jb[l]jαLb[l]j

w[l]jkw[l]jkαLw[l]jk

  写成矩阵形式:

b[l]b[l]αLb[l]

w[l]w[l]αLw[l]

  这里的α指的是学习率。学习率指定了反向传播过程中梯度下降的步长。

3.4 反向传播总结

  我们可以得到如下最终公式:

3.4.1 单样本输入公式表

说明 公式 备注
输出层误差
δ[L]=aLσ(z[L])
隐含层误差
δ[l]=[w[l+1]Tδ[l+1]]σ(z[l])
参数变化率
Lb[l]=δ[l]
Lw[l]=δ[l]a[l1]T
注意维度匹配
参数更新
b[l]b[l]αLb[l]
w[l]w[l]αLw[l]
α是学习率

3.4.2 多样本输入公式表

  多样本:需要使用代价函数,如果有m个样本,那么由于代价函数有一个1m的常数项,因此所有的参数更新规则都需要有一个1m的前缀。

  多样本同时输入的时候需要格外注意维度匹配,一开始可能觉得有点混乱,但是不断加深理解就会豁然开朗。

说明 公式 备注
输出层误差 dZ[L]=ACσ(Z[L]) 此时dZ[l]不再是一个列向量,变成了一个m列的矩阵,每一列都对应一个样本的向量
隐含层误差 dZ[l]=[w[l+1]TdZ[l+1]]σ(Z[l]) 此时dZ[l]的维度是n×mn表示第l层神经元的个数,m表示样本数
参数变化率 db[l]=Cb[l]=1mmeanOfEachRow(dZ[l])dw[l]=Cw[l]=1mdZ[l]A[l1]T 更新b[l]的时候需要对每行求均值; 注意维度匹配; m是样本个数
参数更新
b[l]b[l]αCb[l]
w[l]w[l]αCw[l]
α是学习率

4. 本文小结

本文主要叙述了经典的全连接神经网络结构以及前向传播和反向传播的过程。通过本文的学习,读者应该可以独立推导全连接神经网络的传播过程,对算法的细节烂熟于心。

之后一篇博文将会详细介绍如何从零开始构建自己的神经网络。我们一开始最好不用一些成熟的深度学习库,虽然使用深度学习库诸如Tensorflow等会使得构建模型很简单,但是对于初学者,我还是建议自己把经典的算法写一下,加深对算法细节理解。

后一篇博文我将会使用一些基础的数学计算库比如numpy等从零开始实现一个识别手写数字的神经网络。如果读者希望继续“一文入门神经网络——从原理到应用”的应用篇,请继续关注我的博客,我会尽快更新。