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

  本文着重讲述经典BP神经网络的数学推导过程,并辅助一个小例子。本文不会介绍机器学习库(好比sklearn, TensorFlow等)的使用。 欲了解卷积神经网络的内容,请参见个人另外一篇博客一文搞定卷积神经网络——从原理到应用html

  本文不免会有叙述不合理的地方,但愿读者能够在评论区反馈。我会及时吸纳你们的意见,并在以后的chat里进行说明。python

本文参考了一些资料,在此一并列出。git

0. 什么是人工神经网络?

  首先给出一个经典的定义:“神经网络是由具备适应性的简单单元组成的普遍并行互连网络,它的组织可以模拟生物神经系统对真实世界物体所做出的交互反应”[Kohonen, 1988]。github

  这种说法虽然很经典,可是对于初学者并非很友好。好比我在刚开始学习的时候就把人工神经网络想象地很高端,以致于很长一段时间都不能理解为何神经网络可以起做用。类比最小二乘法线性回归问题,在求解数据拟合直线的时候,咱们是采用某种方法让预测值和实际值的“误差”尽量小。同理,BP神经网络也作了相似的事情——即经过让“误差”尽量小,使得神经网络模型尽量好地拟合数据集。web

1. 神经网络初探

1.1 神经元模型

  神经元模型是模拟生物神经元结构而被设计出来的。典型的神经元结构以下图1所示:
在这里插入图片描述算法

【图1 典型神经元结构 (图片来自维基百科)】网络

  神经元大体能够分为树突、突触、细胞体和轴突。树突为神经元的输入通道,其功能是将其它神经元的动做电位传递至细胞体。其它神经元的动做电位借由位于树突分支上的多个突触传递至树突上。神经细胞能够视为有两种状态的机器,激活时为“是”,不激活时为“否”。神经细胞的状态取决于从其余神经细胞接收到的信号量,以及突触的性质(抑制或增强)。当信号量超过某个阈值时,细胞体就会被激活,产生电脉冲。电脉冲沿着轴突并经过突触传递到其它神经元。(内容来自维基百科“感知机”)app

  同理,咱们的神经元模型就是为了模拟上述过程,典型的神经元模型以下:dom

在这里插入图片描述

【图2 典型神经元模型结构 (摘自周志华老师《机器学习》第97页)】机器学习

  这个模型中,每一个神经元都接受来自其它神经元的输入信号,每一个信号都经过一个带有权重的链接传递,神经元把这些信号加起来获得一个总输入值,而后将总输入值与神经元的阈值进行对比(模拟阈值电位),而后经过一个“激活函数”处理获得最终的输出(模拟细胞的激活),这个输出又会做为以后神经元的输入一层一层传递下去。

1.2 神经元激活函数

  本文主要介绍2种激活函数,分别是 s i g m o i d sigmoid r e l u relu 函数,函数公式以下:
s i g m o i d ( z ) = 1 1 + e z sigmoid(z)=\frac{1}{1+e^{-z}}
r e l u ( z ) = { z z > 0 0 z 0 relu(z)= \left\{ \begin{array}{rcl} z & z>0\\ 0&z\leq0\end{array} \right.
  作函数图以下:

在这里插入图片描述
s i g m o i d ( z ) sigmoid(z)
在这里插入图片描述
r e l u ( z ) relu(z)
【图3 激活函数】

补充说明
【补充说明的内容建议在看完后文的反向传播部分以后再回来阅读,我只是为了文章结构的统一把这部份内容添加在了这里】

  引入激活函数的目的是在模型中引入非线性。若是没有激活函数,那么不管你的神经网络有多少层,最终都是一个线性映射,单纯的线性映射没法解决线性不可分问题。引入非线性可让模型解决线性不可分问题。

  通常来讲,在神经网络的中间层更加建议使用 r e l u relu 函数,两个缘由:

  • r e l u relu 函数计算简单,能够加快模型速度;
  • 因为反向传播过程当中须要计算偏导数,经过求导能够获得 s i g m o i d sigmoid 函数导数的最大值为0.25,若是使用 s i g m o i d sigmoid 函数的话,每一层的反向传播都会使梯度最少变为原来的四分之一,当层数比较多的时候可能会形成梯度消失,从而模型没法收敛。

1.3 神经网络结构

  咱们使用以下神经网络结构来进行介绍,第0层是输入层(3个神经元), 第1层是隐含层(2个神经元),第2层是输出层:

enter image description here
【图4 神经网络结构(手绘)】

  咱们使用如下符号约定 w j k [ l ] w_{jk}^{[l]} 表示从网络第 ( l 1 ) t h (l-1)^{th} k t h k^{th} 个神经元指向第 l t h l^{th} 中第 j t h j^{th} 个神经元的链接权重,好比上图中 w 21 [ 1 ] w^{[1]}_{21} 即从第0层第1个神经元指向第1层第2个神经元的权重。同理,咱们使用 b j [ l ] b^{[l]}_j 来表示第 l t h l^{th} 层中第 j t h j^{th} 神经元的误差,用 z j [ l ] z^{[l]}_j 来表示第 l t h l^{th} 层中第 j t h j^{th} 神经元的线性结果,用 a j [ l ] a^{[l]}_j 来表示第 l t h l^{th} 层中第 j t h j^{th} 神经元的激活函数输出。

  激活函数使用符号 σ \sigma 表示,所以,第 l t h l^{th} 层中第 j t h j^{th} 神经元的激活为:
a j [ l ] = σ ( k w j k [ l ] a k [ l 1 ] + b j [ l ] ) a^{[l]}_j=\sigma(\sum_kw^{[l]}_{jk}a^{[l-1]}_k+b^{[l]}_j)

  如今,咱们使用矩阵形式重写这个公式:

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

w [ 1 ] = [ w 11 [ 1 ] w 12 [ 1 ] w 13 [ 1 ] w 21 [ 1 ] w 22 [ 1 ] w 23 [ 1 ] ] w^{[1]}=\left[ \begin{array}{cc} w_{11}^{[1]} & w_{12}^{[1]} & w_{13}^{[1]} \\ w_{21}^{[1]}& w_{22}^{[1]} & w_{23}^{[1]}\end{array}\right]
  同理,
b [ 1 ] = [ b 1 [ 1 ] b 2 [ 1 ] ] b^{[1]}=\left[ \begin{array}{cc}b^{[1]}_1 \\ b^{[1]}_2 \end{array}\right]
z [ 1 ] = [ w 11 [ 1 ] w 12 [ 1 ] w 13 [ 1 ] w 21 [ 1 ] w 22 [ 1 ] w 23 [ 1 ] ] [ a 1 [ 0 ] a 2 [ 0 ] a 3 [ 0 ] ] + [ b 1 [ 1 ] b 2 [ 1 ] ] = [ w 11 [ 1 ] a 1 [ 0 ] + w 12 [ 1 ] a 2 [ 0 ] + w 13 [ 1 ] a 3 [ 0 ] + b 1 [ 1 ] w 21 [ 1 ] a 1 [ 0 ] + w 22 [ 1 ] a 2 [ 0 ] + w 23 [ 1 ] a 3 [ 0 ] + b 2 [ 1 ] ] z^{[1]}=\left[ \begin{array}{cc} w_{11}^{[1]} & w_{12}^{[1]} & w_{13}^{[1]} \\ w_{21}^{[1]}& w_{22}^{[1]} & w_{23}^{[1]}\end{array}\right]\cdot \left[ \begin{array}{cc} a^{[0]}_1 \\ a^{[0]}_2 \\ a^{[0]}_3 \end{array}\right] +\left[ \begin{array}{cc}b^{[1]}_1 \\ b^{[1]}_2 \end{array}\right]=\left[ \begin{array}{cc} w_{11}^{[1]}a^{[0]}_1+w_{12}^{[1]}a^{[0]}_2+w_{13}^{[1]}a^{[0]}_3+b^{[1]}_1 \\ w^{[1]}_{21}a^{[0]}_1+w_{22}^{[1]}a^{[0]}_2+w_{23}^{[1]}a^{[0]}_3+b^{[1]}_2\end{array}\right]

  更通常地,咱们能够把前向传播过程表示:
a [ l ] = σ ( w [ l ] a [ l 1 ] + b [ l ] ) a^{[l]}=\sigma(w^{[l]}a^{[l-1]}+b^{[l]})

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

  多样本输入状况下的表示为:
Z [ l ] = w [ l ] A [ l 1 ] + b [ l ] Z^{[l]}=w^{[l]}\cdot A^{[l-1]}+b^{[l]}
A [ l ] = σ ( Z [ l ] ) A^{[l]}=\sigma(Z^{[l]})
其中,此时 A [ l 1 ] = [ a [ l 1 ] ( 1 ) a [ l 1 ] ( 2 ) a [ l 1 ] ( m ) ] A^{[l-1]}=\left[ \begin{array}{cc} |&|&\ldots&| \\a^{[l-1](1)}&a^{[l-1](2)}&\ldots&a^{[l-1](m)} \\ |&|&\ldots&|\end{array}\right]
每一列都表示一个样本,从样本1到m

   w [ l ] w^{[l]} 的含义和原来彻底同样, Z [ l ] Z^{[l]} 也会变成m列,每一列表示一个样本的计算结果。

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

2. 损失函数和代价函数

  说实话,**损失函数(Loss Function)代价函数(Cost Function)**并无一个公认的区分标准,不少论文和教材彷佛把两者当成了差很少的东西。

  为了后面描述的方便,咱们把两者稍微作一下区分(这里的区分仅仅对本文适用,对于其它的文章或教程须要根据上下文自行判断含义):

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

  举个例子,若是咱们把单个样本的损失函数定义为:
L ( a , y ) = [ y l o g ( a ) + ( 1 y ) l o g ( 1 a ) ] L(a,y)=-[y \cdot log(a)+(1-y)\cdot log(1-a)]
  那么对于m个样本,代价函数则是:
C = 1 m i = 0 m ( y ( i ) l o g ( a ( i ) ) + ( 1 y ( i ) ) l o g ( 1 a ( i ) ) ) C=-\frac{1}{m}\sum_{i=0}^m(y^{(i)}\cdot log(a^{(i)})+(1-y^{(i)})\cdot log(1-a^{(i)}))

3. 反向传播

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

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

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

3.1 矩阵补充知识

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

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

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

3.1.1 求梯度矩阵

  假设函数 f : R m × n R f:R^{m\times n}\rightarrow R 能够把输入矩阵(shape: m × n m\times n )映射为一个实数。那么,函数 f f 的梯度定义为:

A f ( A ) = [ f ( A ) A 11 f ( A ) A 12 f ( A ) A 1 n f ( A ) A 21 f ( A ) A 22 f ( A ) A 2 n f ( A ) A m 1 f ( A ) A m 2 f ( A ) A m n ] \nabla_Af(A)=\left[ \begin{array}{cc} \frac{\partial f(A)}{\partial A_{11}}&\frac{\partial f(A)}{\partial A_{12}}&\ldots&\frac{\partial f(A)}{\partial A_{1n}} \\ \frac{\partial f(A)}{\partial A_{21}}&\frac{\partial f(A)}{\partial A_{22}}&\ldots&\frac{\partial f(A)}{\partial A_{2n}} \\\vdots &\vdots &\ddots&\vdots\\ \frac{\partial f(A)}{\partial A_{m1}}&\frac{\partial f(A)}{\partial A_{m2}}&\ldots&\frac{\partial f(A)}{\partial A_{mn}}\end{array}\right]
  即 ( A f ( A ) ) i j = f ( A ) A i j (\nabla_Af(A))_{ij}=\frac{\partial f(A)}{\partial A_{ij}}

  同理,一个输入是向量(向量通常指列向量,本文在没有特殊声明的状况下默认指的是列向量)的函数 f : R n × 1 R f:R^{n\times 1}\rightarrow R ,则有:

x f ( x ) = [ f ( x ) x 1 f ( x ) x 2 f ( x ) x n ] \nabla_xf(x)=\left[ \begin{array}{cc}\frac{\partial f(x)}{\partial x_1}\\ \frac{\partial f(x)}{\partial x_2}\\ \vdots \\ \frac{\partial f(x)}{\partial x_n} \end{array}\right]

  注意:这里涉及到的梯度求解的前提是函数 f f 返回的是一个实数若是函数返回的是一个矩阵或者向量,那么咱们是没有办法求梯度的。好比,对函数 f ( A ) = i = 0 m j = 0 n A i j 2 f(A)=\sum_{i=0}^m\sum_{j=0}^nA_{ij}^2 ,因为返回一个实数,咱们能够求解梯度矩阵。若是 f ( x ) = A x ( A R m × n , x R n × 1 ) f(x)=Ax (A\in R^{m\times n}, x\in R^{n\times 1}) ,因为函数返回一个 m m 行1列的向量,所以不能对 f f 求梯度矩阵。

  根据定义,很容易获得如下性质:

   x ( f ( x ) + g ( x ) ) = x f ( x ) + x g ( x ) \nabla_x(f(x)+g(x))=\nabla_xf(x)+\nabla_xg(x)
   ( t f ( x ) ) = t f ( x ) , t R \nabla(tf(x))=t\nabla f(x), t\in R

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

  定义函数 f : R m R , f ( z ) = z T z f:R^m\rightarrow R, f(z)=z^Tz ,那么很容易获得 z f ( z ) = 2 z \nabla_zf(z)=2z ,具体请读者本身证实。

3.1.2 海塞矩阵

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

x 2 f ( x ) = [ 2 f ( x ) x 1 2 2 f ( x ) x 1 x 2 2 f ( x ) x 1 x n 2 f ( x ) x 2 x 1 2 f ( x ) x 2 2 2 f ( x ) x 2 x n 2 f ( x ) x n x 1 2 f ( x ) x n x 2 2 f ( x ) x n 2 ] \nabla^2_xf(x)=\left[ \begin{array}{cc} \frac{\partial^2f(x)}{\partial x_1^2}&\frac{\partial^2f(x)}{\partial x_1\partial x_2}&\ldots &\frac{\partial^2f(x)}{\partial x_1\partial x_n}\\ \frac{\partial^2f(x)}{\partial x_2\partial x_1}&\frac{\partial^2f(x)}{\partial x_2^2}&\ldots&\frac{\partial^2f(x)}{\partial x_2\partial x_n}\\ \vdots&\vdots&\ddots&\vdots\\\frac{\partial^2f(x)}{\partial x_n\partial x_1}&\frac{\partial^2f(x)}{\partial x_n\partial x_2}&\ldots&\frac{\partial^2f(x)}{\partial x_n^2}\end{array}\right]

  由上式能够看出,海塞矩阵老是对称阵

  注意:不少人把海塞矩阵当作 x f ( x ) \nabla _xf(x) 的导数,这是不对的。只能说,海塞矩阵的每一个元素都是函数 f f 二阶偏导数。那么,有什么区别呢?

  首先,来看正确的解释。**海塞矩阵的每一个元素是函数 f f 的二阶偏导数。**拿 2 f ( x ) x 1 x 2 \frac{\partial^2f(x)}{\partial x_1\partial x_2} 举个例子,函数 f f x 1 x_1 求偏导获得的是一个实数,好比 2 f ( x ) x 1 = x 2 3 x 1 \frac{\partial^2f(x)}{\partial x_1}=x_2^3x_1 ,所以继续求偏导是有意义的,继续对 x 2 x_2 求偏导能够获得 3 x 1 x 2 2 3x_1x_2^2

  而后,来看一下错误的理解。把海塞矩阵当作 x f ( x ) \nabla _xf(x) 的导数,也就是说错误地觉得 x 2 f ( x ) = x ( x f ( x ) ) \nabla^2_xf(x)=\nabla_x(\nabla_xf(x)) ,要知道, x f ( x ) \nabla_xf(x) 是一个向量,而在上一小节咱们已经重点强调过,在咱们的定义里对向量求偏导是没有定义的

  可是 x f ( x ) x i \nabla_x\frac{\partial f(x)}{\partial x_i} 是有意义的,由于 f ( x ) x i \frac{\partial f(x)}{\partial x_i} 是一个实数,具体地:

x f ( x ) x i = [ 2 f ( x ) x i x 1 2 f ( x ) x i x 2 2 f ( x ) x i x n ] \nabla_x\frac{\partial f(x)}{\partial x_i}=\left[ \begin{array}{cc} \frac{\partial^2f(x)}{\partial x_i\partial x_1}\\\frac{\partial^2f(x)}{\partial x_i\partial x_2}\\\vdots\\\frac{\partial^2f(x)}{\partial x_i\partial x_n}\end{array}\right]

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

  但愿读者能够好好区分。

3.1.3 总结

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

   b R n , x R n , A R n × n A b\in R^{n}, x\in R^n, A\in R^{n\times n}而且A 是对称矩阵
   b , x b,x 均为列向量
  那么,
   x b T x = b \nabla_xb^Tx=b
   x x T A x = 2 A x ( A ) \nabla_xx^TAx=2Ax(A是对称阵)
   x 2 x T A x = 2 A ( A ) \nabla^2_xx^TAx=2A(A是对称阵)

  这些公式能够根据前述定义自行推导,有兴趣的读者能够本身推导一下。
####3.2 矩阵乘积和对应元素相乘
  在下一节讲解反向传播原理的时候,尤为是把公式以矩阵形式表示的时候,须要你们时刻区分何时须要矩阵相乘,何时须要对应元素相乘。

  好比对于矩阵 A = [ 1 2 3 4 ] B = [ 1 2 3 4 ] A=\left[ \begin{array}{cc} 1&2\\3&4\end{array}\right],矩阵B=\left[ \begin{array}{cc} -1&-2\\-3&-4\end{array}\right]
  矩阵相乘

A B = [ 1 × 1 + 2 × 3 1 × 2 + 2 × 4 3 × 1 + 4 × 3 3 × 2 + 4 × 4 ] = [ 7 10 15 22 ] AB=\left[\begin{array}{cc}1\times -1+2\times -3&1\times -2+2\times -4\\3\times -1+4\times -3&3\times -2+4\times -4\end{array}\right]=\left[\begin{array}{cc}-7&-10\\-15&-22\end{array}\right]

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

A B = [ 1 × 1 2 × 2 3 × 3 4 × 4 ] = [ 1 4 9 16 ] A\odot B=\left[\begin{array}{cc}1\times -1&2\times -2 \\ 3\times -3&4\times -4\end{array}\right]=\left[\begin{array}{cc}-1&-4 \\ -9&-16\end{array}\right]

3.3 梯度降低法原理

  经过以前的介绍,相信你们均可以本身求解梯度矩阵(向量)了。

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

在这里插入图片描述

【图5 梯度降低法 图片来自百度】

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

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

  反向传播可以知道如何更改网络中的权重 w w 和误差 b b 来改变代价函数值。最终这意味着它可以计算偏导数 L ( a [ l ] , y ) w j k [ l ] \frac{\partial L(a^{[l]},y)} {\partial w^{[l]}_{jk}} L ( a [ l ] , y ) b j [ l ] \frac{\partial L(a^{[l]},y)}{\partial b^{[l]}_j}
  为了计算这些偏导数,咱们首先引入一个中间变量 δ j [ l ] \delta^{[l]}_j ,咱们把它叫作网络中第 l t h l^{th} 层第 j t h j^{th} 个神经元的偏差。后向传播可以计算出偏差 δ j [ l ] \delta^{[l]}_j ,而后再将其对应回 L ( a [ l ] , y ) w j k [ l ] \frac{\partial L(a^{[l]},y)}{\partial w^{[l]}_{jk}} L ( a [ l ] , y ) b j [ l ] \frac{\partial L(a^{[l]},y)}{\partial b^{[l]}_j}

  那么,如何定义每一层的偏差呢?若是为第 l l 层第 j j 个神经元添加一个扰动 Δ z j [ l ] \Delta z^{[l]}_j ,使得损失函数或者代价函数变小,那么这就是一个好的扰动。经过选择 Δ z j [ l ] \Delta z^{[l]}_j L ( a [ l ] , y ) z j [ l ] \frac{\partial L(a^{[l]}, y)}{\partial z^{[l]}_j} 符号相反(梯度降低法原理),就能够每次都添加一个好的扰动最终达到最优。

  受此启发,咱们定义网络层第 l l 层中第 j j 个神经元的偏差为 δ j [ l ] \delta^{[l]}_j :

δ j [ l ] = L ( a [ L ] , y ) z j [ l ] \delta^{[l]}_j=\frac{\partial L(a^{[L], y})}{\partial z^{[l]}_j}

  因而,每一层的偏差向量能够表示为:

δ [ l ] = [ δ 1 [ l ] δ 2 [ l ] δ n [ l ] ] \delta ^{[l]}=\left[\begin{array}{cc}\delta ^{[l]}_1\\\delta ^{[l]}_2\\ \vdots \\ \delta ^{[l]}_n\end{array} \right]

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

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

  • 等式1 :输出层偏差

δ j [ L ] = L a j [ L ] σ ( z j [ L ] ) \delta^{[L]}_j=\frac{\partial L}{\partial a^{[L]}_j}\sigma^{'}(z^{[L]}_j)
  其中, L L 表示输出层层数。如下用 L \partial L 表示 L ( a [ L ] , y ) \partial L(a^{[L]}, y)

  写成矩阵形式是:

δ [ L ] = a L σ ( z [ L ] ) \delta^{[L]}=\nabla _aL\odot \sigma^{'}(z^{[L]})
  【注意是对应元素相乘,想一想为何?】

  说明

  根据本小节开始时的叙述,咱们指望找到 L   / z j [ l ] \partial L \ /\partial z^{[l]}_j ,而后朝着方向相反的方向更新网络参数,并定义偏差为:

δ j [ L ] = L z j [ L ] \delta^{[L]}_j=\frac{\partial L}{\partial z^{[L]}_j}

  根据链式法则,
δ j [ L ] = k L a k [ L ] a k [ L ] z j [ L ] \delta^{[L]}_j = \sum_k \frac{\partial L}{\partial a^{[L]}_k} \frac{\partial a^{[L]}_k}{\partial z^{[L]}_j}
  当 k j k\neq j 时, a k [ L ] / z j [ L ] \partial a^{[L]}_k / \partial z^{[L]}_j 就为零。结果咱们能够简化以前的等式为
δ j [ L ] = L a j [ L ] a j [ L ] z j [ L ] \delta^{[L]}_j = \frac{\partial L}{\partial a^{[L]}_j} \frac{\partial a^{[L]}_j}{\partial z^{[L]}_j}
  从新拿出定义: a j [ L ] = σ ( z j [ L ] ) a^{[L]}_j = \sigma(z^{[L]}_j) ,就能够获得:
δ j [ L ] = L a j [ L ] σ ( z j [ L ] ) \delta^{[L]}_j = \frac{\partial L}{\partial a^{[L]}_j} \sigma'(z^{[L]}_j)
  再"堆砌"成向量形式就获得了咱们的矩阵表示式(这也是为何使用矩阵形式表示须要 对应元素相乘 的缘由)。

  • 等式2: 隐含层偏差
    δ j [ l ] = k w k j [ l + 1 ] δ k [ l + 1 ] σ ( z j [ l ] ) \delta^{[l]}_j = \sum_k w^{[l+1]}_{kj} \delta^{[l+1]}_k \sigma'(z^{[l]}_j)

  写成矩阵形式:

δ [ l ] = [ w [ l + 1 ] T δ [ l + 1 ] ] σ ( z [ l ] ) \delta^{[l]}=[w^{[l+1]T}\delta^{[l+1]}]\odot \sigma ^{'}(z^{[l]})

  说明:

z k [ l + 1 ] = j w k j [ l + 1 ] a j [ l ] + b k [ l + 1 ] = j w k j [ l + 1 ] σ ( z j [ l ] ) + b k [ l + 1 ] z^{[l+1]}_k=\sum_jw^{[l+1]}_{kj}a^{[l]}_j+b^{[l+1]}_k=\sum_jw^{[l+1]}_{kj}\sigma(z^{[l]}_j)+b^{[l+1]}_k
  进行偏导能够得到:
z k [ l + 1 ] z j [ l ] = w k j [ l + 1 ] σ ( z j [ l ] ) \frac{\partial z^{[l+1]}_k}{\partial z^{[l]}_j} = w^{[l+1]}_{kj} \sigma'(z^{[l]}_j)
  代入获得:
δ j [ l ] = k w k j [ l + 1 ] δ k [ l + 1 ] σ ( z j [ l ] ) \delta^{[l]}_j = \sum_k w^{[l+1]}_{kj} \delta^{[l+1]}_k \sigma'(z^{[l]}_j)

  • 等式3:参数变化率

L b j [ l ] = δ j [ l ] \frac{\partial L}{\partial b^{[l]}_j}=\delta^{[l]}_j

L w j k [ l ] = a k [ l 1 ] δ j [ l ] \frac{\partial L}{\partial w^{[l]}_{jk}}=a^{[l-1]}_k\delta^{[l]}_j

  写成矩阵形式:
L b [ l ] = δ [ l ] \frac{\partial L}{\partial b^{[l]}}=\delta^{[l]} L w [ l ] = δ [ l ] a [ l 1 ] T \frac{\partial L}{\partial w^{[l]}}=\delta^{[l]}a^{[l-1]T}

  说明:

  根据链式法则推导。
  因为
z j [ l ] = k w j k [ l ] a k [ l ] + b k [ l ] z^{[l]}_j=\sum_kw^{[l]}_{jk}a^{[l]}_k+b^{[l]}_k
  对 b j [ l ] b^{[l]}_j 求偏导获得:
L b j [ l ] = L z j [ l ] z j [ l ] b j [ l ] = δ j [ l ] \frac{\partial L}{\partial b^{[l]}_j}=\frac{\partial L}{\partial z^{[l]}_j}\frac{\partial z^{[l]}_j}{b^{[l]}_j}=\delta^{[l]}_j
  对 w j k [ l ] w^{[l]}_{jk} 求偏导获得:
L w j k [ l ] = L z j [ l ] z j [ l ] w j k [ l ] = a k [ l 1 ] δ j [ l ] \frac{\partial L}{\partial w^{[l]}_{jk}}=\frac{\partial L}{\partial z^{[l]}_j}\frac{\partial z^{[l]}_j}{w^{[l]}_{jk}}=a^{[l-1]}_k\delta^{[l]}_j
  最后再变成矩阵形式就行了。

  对矩阵形式来讲,须要特别注意维度的匹配。强烈建议读者在本身编写程序以前,先列出这些等式,而后仔细检查维度是否匹配。

  很容易看出 L w [ l ] \frac{\partial L}{\partial w^{[l]}} 是一个 d i m ( δ [ l ] ) dim(\delta^{[l]}) d i m ( a [ l 1 ] ) dim(a^{[l-1]}) 列的矩阵,和 w [ l ] w^{[l]} 的维度一致; L b [ l ] \frac{\partial L}{\partial b^{[l]}} 是一个维度为 d i m ( δ [ l ] ) dim(\delta^{[l]}) 的列向量

  • 等式4:参数更新规则

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

b j [ l ] b j [ l ] α L b j [ l ] b^{[l]}_j\leftarrow b^{[l]}_j-\alpha \frac{\partial L}{\partial b^{[l]}_j}
w j k [ l ] w j k [ l ] α L w j k [ l ] w^{[l]}_{jk}\leftarrow w^{[l]}_{jk}-\alpha\frac{\partial L}{\partial w^{[l]}_{jk}}
  写成矩阵形式:

b [ l ] b [ l ] α L b [ l ] b^{[l]}\leftarrow b^{[l]}-\alpha\frac{\partial L}{\partial b^{[l]}}

w [ l ] w [ l ] α L w [ l ] w^{[l]}\leftarrow w^{[l]}-\alpha\frac{\partial L}{\partial w^{[l]}}

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

3.5 反向传播总结

  咱们能够获得以下最终公式:

3.5.1 单样本输入公式表
说明 公式 备注
输出层偏差 δ [ L ] = a L σ ( z [ L ] ) \delta^{[L]}=\nabla _aL\odot \sigma^{'}(z^{[L]})
隐含层偏差 δ [ l ] = [ w [ l + 1 ] T δ [ l + 1 ] ] σ ( z [ l ] ) \delta^{[l]}=[w^{[l+1]T}\delta^{[l+1]}]\odot \sigma ^{'}(z^{[l]})
参数变化率 L b [ l ] = δ [ l ] \frac{\partial L}{\partial b^{[l]}}=\delta^{[l]} L w [ l ] = δ [ l ] a [ l 1 ] T \frac{\partial L}{\partial w^{[l]}}=\delta^{[l]}a^{[l-1]T} 注意维度匹配
参数更新 b [ l ] b [ l ] α L b [ l ] b^{[l]}\leftarrow b^{[l]}-\alpha\frac{\partial L}{\partial b^{[l]}} w [ l ] w [ l ] α L w [ l ] w^{[l]}\leftarrow w^{[l]}-\alpha\frac{\partial L}{\partial w^{[l]}} α \alpha 是学习率
3.5.2 多样本输入公式表

  多样本:须要使用代价函数,若是有m个样本,那么因为代价函数有一个 1 m \frac{1}{m} 的常数项,所以全部的参数更新规则都须要有一个 1 m \frac{1}{m} 的前缀。

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

说明 公式 备注
输出层偏差 d Z [ L ] = A C σ ( Z [ L ] ) dZ^{[L]}=\nabla _AC\odot \sigma^{'}(Z^{[L]}) 此时 d Z [ l ] dZ^{[l]} 再也不是一个列向量,变成了一个 m m 列的矩阵,每一列都对应一个样本的向量
隐含层偏差 d Z [ l ] = [ w [ l + 1 ] T d Z [ l + 1 ] ] σ ( Z [ l ] ) dZ^{[l]}=[w^{[l+1]T}dZ^{[l+1]}]\odot \sigma ^{'}(Z^{[l]}) 此时 d Z [ l ] dZ^{[l]} 的维度是 n × m n\times m n n 表示第l层神经元的个数,m表示样本数
参数变化率 d b [ l ] = C b [ l ] = 1 m m e a n O f E a c h R o w ( d Z [ l ] ) d w [ l ] = C w [ l ] = 1 m d Z [ l ] A [ l 1 ] T db^{[l]}=\frac{\partial C}{\partial b^{[l]}}=\frac{1}{m}meanOfEachRow(dZ^{[l]})\\dw^{[l]}=\frac{\partial C}{\partial w^{[l]}}=\frac{1}{m}dZ^{[l]}A^{[l-1]T} 更新 b [ l ] b^{[l]} 的时候须要对每行求均值; 注意维度匹配; m m 是样本个数
参数更新 b [ l ] b [ l ] α C b [ l ] b^{[l]}\leftarrow b^{[l]}-\alpha\frac{\partial C}{\partial b^{[l]}} w [ l ] w [ l ] α C w [ l ] w^{[l]}\leftarrow w^{[l]}-\alpha\frac{\partial C}{\partial w^{[l]}} α \alpha 是学习率
3.5.3 关于超参数

  经过前面的介绍,相信读者能够发现BP神经网络模型有一些参数是须要设计者给出的,也有一些参数是模型本身求解的。

  那么,哪些参数是须要模型设计者肯定的呢?

  好比,学习率 α \alpha ,隐含层的层数,每一个隐含层的神经元个数,激活函数的选取,损失函数(代价函数)的选取等等,这些参数被称之为超参数

  其它的参数,好比权重矩阵 w w 和偏置系数 b b 在肯定了超参数以后是能够经过模型的计算来获得的,这些参数称之为普通参数,简称参数

  超参数的肯定实际上是很困难的。由于你很难知道什么样的超参数会让模型表现得更好。好比,学习率过小可能形成模型收敛速度过慢,学习率太大又可能形成模型不收敛;再好比,损失函数的设计,若是损失函数设计很差的话,可能会形成模型没法收敛;再好比,层数过多的时候,如何设计网络结构以免梯度消失和梯度爆炸……

  神经网络的程序比通常程序的调试难度大得多,由于它并不会显式报错,它只是没法获得你指望的结果,做为新手也很难肯定到底哪里出了问题(对于本身设计的网络,这种现象尤甚,我目前也基本是新手,因此这些问题也在困扰着我)。固然,使用别人训练好的模型来微调看起来是一个捷径……

  总之,神经网络至少在目前来看感受仍是黑箱的成分居多,但愿经过你们的努力慢慢探索吧。

4. 是否是猫?

  本小节主要使用上述公式来完成一个小例子,这个小小的神经网络能够告诉咱们一张图片是否是猫。本例程参考了coursera的做业,有改动。

  在实现代码以前,先把用到的公式列一个表格吧,这样对照着看你们更清晰一点(若是你没有2个显示器建议先把这些公式抄写到纸上,以便和代码对照):

编号 公式 备注
1 Z [ l ] = w [ l ] A [ l 1 ] + b [ l ] Z^{[l]}=w^{[l]}A^{[l-1]}+b^{[l]}
2 A [ l ] = σ ( Z [ l ] ) A^{[l]}=\sigma(Z^{[l]})
3 d Z [ L ] = A C σ ( Z [ L ] ) dZ^{[L]}=\nabla_AC\odot\sigma^{'}(Z^{[L]})
4 d Z [ l ] = [ w [ l + 1 ] T d Z [ l + 1 ] ] σ ( Z [ l ] ) dZ^{[l]}=[w^{[l+1]T}dZ^{[l+1]}]\odot \sigma ^{'}(Z^{[l]})
5 d b [ l ] = C b [ l ] = 1 m m e a n O f E a c h R o w ( d Z [ l ] ) db^{[l]}=\frac{\partial C}{\partial b^{[l]}}=\frac{1}{m}meanOfEachRow(dZ^{[l]})
6 d w [ l ] = C w [ l ] = 1 m d Z [ l ] A [ l 1 ] T dw^{[l]}=\frac{\partial C}{\partial w^{[l]}}=\frac{1}{m}dZ^{[l]}A^{[l-1]T}
7 b [ l ] b [ l ] α d b [ l ] b^{[l]}\leftarrow b^{[l]}-\alpha \cdot db^{[l]}
8 w [ l ] w [ l ] α d w [ l ] w^{[l]}\leftarrow w^{[l]}-\alpha\cdot dw^{[l]}
9 d A [ l ] = w [ l ] T d Z [ l ] dA^{[l]}=w^{[l]T}\odot dZ^{[l]}

  准备工做作的差很少了,让咱们开始吧?等等,好像咱们尚未定义代价函数是什么?OMG!好吧,看来咱们得先把这个作好再继续了。

  那先看结果吧,咱们的代价函数是:
C = 1 m i = 1 m ( y ( i ) l o g ( a [ L ] ( i ) ) + ( 1 y ( i ) ) l o g ( 1 a [ L ] ( i ) ) ) C =-\frac{1}{m} \sum^{m}_{i=1}(y^{(i)}log(a^{[L](i)})+(1-y^{(i)})log(1-a^{[L](i)}))
  其中, m m 是样本数量;

  下面简单介绍一下这个代价函数是怎么来的(做者非数学专业,不严谨的地方望海涵)。
.
  代价函数的肯定用到了统计学中的**“极大似然法”**,既然这样,那就不可避免地要介绍一下“极大似然法”了。极大似然法简单来讲就是“在模型已定,参数未知的状况下,根据结果估计模型中参数的一种方法",换句话说,极大似然法提供了一种给定观察数据来评估模型参数的方法。

  举个例子(本例参考了知乎相关回答),一个不透明的罐子里有黑白两种球(球仅仅颜色不一样,大小重量等参数都同样)。有放回地随机拿出一个小球,记录颜色。重复10次以后发现7次是黑球,3次是白球。问你罐子里白球的比例?

  相信不少人能够一口回答“30%”,那么,为何呢?背后的原理是什么呢?

  这里咱们把每次取出一个球叫作一次抽样,把“抽样10次,7次黑球,3次白球”这个事件发生的几率记为 P ( M o d e l ) P(事件结果|Model) ,咱们的Model须要一个参数 p p 表示白球的比例。那么 P ( M o d e l ) = p 3 ( 1 p ) 7 P(事件结果|Model)=p^3(1-p)^7

  好了,如今咱们已经有事件结果的几率公式了,接下来求解模型参数 p p ,根据极大似然法的思想,既然这个事件发生了,那么为何不让这个事件(抽样10次,7次黑球,3次白球)发生的几率最大呢?由于显然几率大的事件发生才是合理的。因而就变成了求解 p 3 ( 1 p ) 7 p^3(1-p)^7 取最大值的 p p ,即导数为0,通过求导:
d ( p 3 ( 1 p ) 7 ) = 3 p 2 ( 1 p ) 7 7 p 3 ( 1 p ) 6 = p 2 ( 1 p ) 6 ( 3 10 p ) = 0 d(p^3(1-p)^7)=3p^2(1-p)^7-7p^3(1-p)^6=p^2(1-p)^6(3-10p)=0
  求解可得 p = 0.3 p=0.3

  极大似然法有一个重要的假设:

假设全部样本独立同分布!!!

  好了,如今来看看咱们的神经网络模型。

  最后一层咱们用sigmoid函数求出一个激活输出a,若是a大于0.5,就表示这个图片是猫( y = 1 y=1 ),不然就不是猫( y = 0 y=0 )。所以:
P ( y = 1 x ; θ ) = a P(y=1|x;\theta)=a
P ( y = 0 x ; θ ) = 1 a P(y=0|x;\theta)=1-a

公式解释:
上述第一个公式表示,给定模型参数 θ \theta 和输入 x x ,是猫的几率是 P ( y = 1 x ; θ ) = a P(y=1|x;\theta)=a

  把两个公式合并成一个公式,即
p ( y x ; θ ) = a y ( 1 a ) ( 1 y ) p(y|x;\theta)=a^y(1-a)^{(1-y)}

这里的 θ \theta 指的就是咱们神经网络的权值参数和偏置参数。

  那么似然函数
L ( θ ) = p ( Y X ; θ ) = i = 1 m p ( y ( i ) x ( i ) ; θ ) = i = 1 m ( a [ L ] ( i ) ) y ( i ) ( 1 a [ L ] ( i ) ) ( 1 y ( i ) ) L(\theta)=p(Y|X;\theta)=\prod^m_{i=1}p(y^{(i)}|x^{(i)};\theta)=\prod^m_{i=1}(a^{[L](i)})^{y^{(i)}}(1-a^{[L](i)})^{(1-y^{(i)})}
  变成对数形式:
l o g ( L ( θ ) ) = i = 1 m ( y ( i ) l o g ( a [ L ] ( i ) ) + ( 1 y ( i ) ) l o g ( 1 a [ L ] ( i ) ) ) log(L(\theta))=\sum^m_{i=1}(y^{(i)}log(a^{[L](i)})+(1-y^{(i)})log(1-a^{[L](i)}))
  因此咱们的目标就是最大化这个对数似然函数,也就是最小化咱们的代价函数:
C = 1 m i = 1 m ( y ( i ) l o g ( a [ L ] ( i ) ) + ( 1 y ( i ) ) l o g ( 1 a [ L ] ( i ) ) ) C =-\frac{1}{m} \sum^{m}_{i=1}(y^{(i)}log(a^{[L](i)})+(1-y^{(i)})log(1-a^{[L](i)}))
  其中, m m 是样本数量;

  好了,终于能够开始写代码了,码字手都有点酸了,不得不说公式真的好难打。

因为代码比较简单就没有上传github。本文代码和数据文件能够在这里下载: https://pan.baidu.com/s/1q_PzaCSXOhRLOJVF5-vy2Q,密码: d7vx

其余下载源:
https://drive.google.com/file/d/0B6exrzrSxlh3TmhSV0ZNeHhYUmM/view?usp=sharing

4.1 辅助函数

  辅助函数主要包括激活函数以及激活函数的反向传播过程函数:
其中,激活函数反向传播代码对应公式4和9.

def sigmoid(z):
    """ 使用numpy实现sigmoid函数 参数: Z numpy array 输出: A 激活值(维数和Z彻底相同) """
    return 1/(1 + np.exp(-z))

def relu(z):
    """ 线性修正函数relu 参数: z numpy array 输出: A 激活值(维数和Z彻底相同) """
    return np.array(z>0)*z

def sigmoidBackward(dA, cacheA):
    """ sigmoid的反向传播 参数: dA 同层激活值 cacheA 同层线性输出 输出: dZ 梯度 """
    s = sigmoid(cacheA)
    diff = s*(1 - s)
    dZ = dA * diff
    return dZ

def reluBackward(dA, cacheA):
    """ relu的反向传播 参数: dA 同层激活值 cacheA 同层线性输出 输出: dZ 梯度 """
    Z = cacheA
    dZ = np.array(dA, copy=True) 
    dZ[Z <= 0] = 0
    return dZ

  另一个重要的辅助函数是数据读取函数和参数初始化函数:

def loadData(dataDir):
    """ 导入数据 参数: dataDir 数据集路径 输出: 训练集,测试集以及标签 """
    train_dataset = h5py.File(dataDir+'/train.h5', "r")
    train_set_x_orig = np.array(train_dataset["train_set_x"][:]) # your train set features
    train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # your train set labels

    test_dataset = h5py.File(dataDir+'/test.h5', "r")
    test_set_x_orig = np.array(test_dataset["test_set_x"][:]) # your test set features
    test_set_y_orig = np.array(test_dataset["test_set_y"][:]) # your test set labels

    classes = np.array(test_dataset["list_classes"][:]) # the list of classes
    
    train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0]))
    test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0]))
    
    return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes

def iniPara(laydims):
    """ 随机初始化网络参数 参数: laydims 一个python list 输出: parameters 随机初始化的参数字典(”W1“,”b1“,”W2“,”b2“, ...) """
    np.random.seed(1)
    parameters = {}
    for i in range(1, len(laydims)):
        parameters['W'+str(i)] = np.random.randn(laydims[i], laydims[i-1])/ np.sqrt(laydims[i-1])
        parameters['b'+str(i)] = np.zeros((laydims[i], 1))
    return parameters

4.2 前向传播过程

对应公式1和2.

def forwardLinear(W, b, A_prev):
    """ 前向传播 """
    Z = np.dot(W, A_prev) + b
    cache = (W, A_prev, b)
    return Z, cache

def forwardLinearActivation(W, b, A_prev, activation):
    """ 带激活函数的前向传播 """
    Z, cacheL = forwardLinear(W, b, A_prev)
    cacheA = Z
    if activation == 'sigmoid':
        A = sigmoid(Z)
    if activation == 'relu':
        A = relu(Z)
    cache = (cacheL, cacheA)
    return A, cache

def forwardModel(X, parameters):
    """ 完整的前向传播过程 """
    layerdim = len(parameters)//2
    caches = []
    A_prev = X
    for i in range(1, layerdim):
        A_prev, cache = forwardLinearActivation(parameters['W'+str(i)], parameters['b'+str(i)], A_prev, 'relu')
        caches.append(cache)
        
    AL, cache = forwardLinearActivation(parameters['W'+str(layerdim)], parameters['b'+str(layerdim)], A_prev, 'sigmoid')
    caches.append(cache)
    
    return AL, caches

4.3 反向传播过程

线性部分反向传播对应公式5和6。

def linearBackward(dZ, cache):
    """ 线性部分的反向传播 参数: dZ 当前层偏差 cache (W, A_prev, b)元组 输出: dA_prev 上一层激活的梯度 dW 当前层W的梯度 db 当前层b的梯度 """
    W, A_prev, b = cache
    m = A_prev.shape[1]
    
    dW = 1/m*np.dot(dZ, A_prev.T)
    db = 1/m*np.sum(dZ, axis = 1, keepdims=True)
    dA_prev = np.dot(W.T, dZ)
    
    return dA_prev, dW, db

非线性部分对应公式三、四、5和6 。

def linearActivationBackward(dA, cache, activation):
    """ 非线性部分的反向传播 参数: dA 当前层激活输出的梯度 cache (W, A_prev, b)元组 activation 激活函数类型 输出: dA_prev 上一层激活的梯度 dW 当前层W的梯度 db 当前层b的梯度 """
    cacheL, cacheA = cache
    
    if activation == 'relu':
        dZ = reluBackward(dA, cacheA)
        dA_prev, dW, db = linearBackward(dZ, cacheL)
    elif activation == 'sigmoid':
        dZ = sigmoidBackward(dA, cacheA)
        dA_prev, dW, db = linearBackward(dZ, cacheL)
    
    return dA_prev, dW, db

完整反向传播模型:

def backwardModel(AL, Y, caches):
    """ 完整的反向传播过程 参数: AL 输出层结果 Y 标签值 caches 【cacheL, cacheA】 输出: diffs 梯度字典 """
    layerdim = len(caches)
    Y = Y.reshape(AL.shape)
    L = layerdim
    
    diffs = {}
    
    dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
    
    currentCache = caches[L-1]
    dA_prev, dW, db =  linearActivationBackward(dAL, currentCache, 'sigmoid')
    diffs['dA' + str(L)], diffs['dW'+str(L)], diffs['db'+str(L)] = dA_prev, dW, db
    
    for l in reversed(range(L-1)):
        currentCache = caches[l]
        dA_prev, dW, db =  linearActivationBackward(dA_prev, currentCache, 'relu')
        diffs['dA' + str(l+1)], diffs['dW'+str(l+1)], diffs['db'+str(l+1)] = dA_prev, dW, db
        
    return diffs

4.4 测试结果

  打开你的jupyter notebook,运行咱们的BP.ipynb文件,首先导入依赖库和数据集,而后使用一个循环来肯定最佳的迭代次数大约为2000:

在这里插入图片描述
【图6】

  最后用一个例子来看一下模型的效果——判断一张图片是否是猫:

在这里插入图片描述
【图7】

好了,测试到此结束。你也能够本身尝试其它的神经网络结构和测试其它图片。

5. 本文小结

  本文主要叙述了经典的全链接神经网络结构以及前向传播和反向传播的过程。经过本文的学习,读者应该能够独立推导全链接神经网络的传播过程,对算法的细节烂熟于心。另外,因为本文里的公式大部分是我本身推导的,瑕疵之处,但愿读者不吝赐教。

  虽然这篇文章实现的例子并无什么实际应用场景,可是本身推导一下这些数学公式并用代码实现对理解神经网络内部的原理颇有帮助,继这篇博客以后,我还计划写一个如何本身推导并实现卷积神经网络的教程,若是有人感兴趣,请继续关注我!

  本次内容就到这里,谢谢你们。

订正与答疑:

前向传播过程比较简单,我就再也不赘述了。

这里主要针对反向传播过程当中可能会出现的问题作一个总结:

1. 具体解释一下公式1里面的“堆砌”是什么意思?

δ j [ L ] = k L a k [ L ] a k [ L ] z j [ L ] \delta^{[L]}_j = \sum_k \frac{\partial L}{\partial a^{[L]}_k} \frac{\partial a^{[L]}_k}{\partial z^{[L]}_j}

有读者对这里不太理解,这实际上是由于,咱们的输出层不必定是只有一个神经元,可能有好多个神经元,所以损失函数是每一个输出神经元“偏差”之和,所以才会出现这种 \sum 的形式,而后每一个输出神经元的偏差函数与其它神经元没有关系,因此只有 k = j k=j 的时候值不是0.

另外,这里说的“堆砌”指的就是:

δ [ l ] = [ L a 1 [ L ] L a 2 [ L ] ] [ σ ( z 1 [ L ] ) σ ( z 2 [ L ] ) ] \delta^{[l]}=\left[ \begin{array}{cc} \frac{\partial L}{\partial a^{[L]}_1} \\ \frac{\partial L}{\partial a^{[L]}_2} \\\vdots \end{array}\right]\odot\left[\begin{array}{cc}\sigma^{&#x27;}(z^{[L]}_1) \\\sigma^{&#x27;}(z^{[L]}_2)\\\vdots\end{array}\right]

2. 公式2写成矩阵形式为何系数矩阵会有转置?本身没搞懂。

这里可能有一点绕,有的读者感受个人推导不是很明白,因此有必要详细说明一下。

不少读者不明白,写成矩阵形式的时候

δ [ l ] = [ w [ l + 1 ] T δ [ l + 1 ] ] σ ( z [ l ] ) \delta^{[l]}=[w^{[l+1]T}\delta^{[l+1]}]\odot \sigma ^{&#x27;}(z^{[l]})

里面的“系数矩阵转置”是怎么来的。这里就主要说明一下:

相信你们都已经理解了下面这个前向传播公式:

z k [ l + 1 ] = j w k j [ l + 1 ] a j [ l ] + b k [ l + 1 ] = j w k j [ l + 1 ] σ ( z j [ l ] ) + b k [ l + 1 ] z^{[l+1]}_k=\sum_jw^{[l+1]}_{kj}a^{[l]}_j+b^{[l+1]}_k=\sum_jw^{[l+1]}_{kj}\sigma(z^{[l]}_j)+b^{[l+1]}_k

求偏导这里在原文中有一点错误,应该是:

z k [ l + 1 ] z j [ l ] = k w k j [ l + 1 ] σ ( z j [ l ] ) \frac{\partial z^{[l+1]}_k}{\partial z^{[l]}_j} =\sum_k w^{[l+1]}_{kj} \sigma&#x27;(z^{[l]}_j)

为了你们有一个直观的感觉,来一个具体的例子:

第 1 层的系数矩阵比方是:

w [ 2 = [ w 11 [ 2 ] w 12 [ 2 ] w 13 [ 2 ] w 21 [ 2 ] w 22 [ 2 ] w 23 [ 2 ] ] w^{[2}=\left[ \begin{array}{cc} w_{11}^{[2]} &amp; w_{12}^{[2]} &amp; w_{13}^{[2]} \\ w_{21}^{[2]}&amp; w_{22}^{[2]} &amp; w_{23}^{[2]}\end{array}\right]

b [ 2 ] = [ b 1 [ 2 ] b 2 [ 2 ] b^{[2]}=\left[ \begin{array}{cc}b^{[2]}_1 \\ b^{[2}_2 \end{array}\right]

z [ 2 ] = [ w 11 [ 2 ] w 12 [ 2 ] w 13 [ 2 ] w 21 [ 2 ] w 22 [ 2 ] w 23 [ 2 ] ] [ a 1 [ 1 ] a 2 [ 1 ] a 3 [ 1 ] ] + [ b 1 [ 2 ] b 2 [ 2 ] ] = [ w 11 [ 2 ] a 1 [ 1 ] + w 12 [ 2 ] a 2 [ 1 ] + w 13 [ 2 ] a 3 [ 1 ] + b 1 [ 2 ] w 21 [ 2 ] a 1 [ 1 ] + w 22 [ 2 ] a 2 [ 1 ] + w 23 [ 2 ] a 3 [ 1 ] + b 2 [ 2 ] ] z^{[2]}=\left[ \begin{array}{cc} w_{11}^{[2]} &amp; w_{12}^{[2]} &amp; w_{13}^{[2]} \\ w_{21}^{[2]}&amp; w_{22}^{[2]} &amp; w_{23}^{[2]}\end{array}\right]\cdot \left[ \begin{array}{cc} a^{[1]}_1 \\ a^{[1]}_2 \\ a^{[1]}_3 \end{array}\right] +\left[ \begin{array}{cc}b^{[2]}_1 \\ b^{[2]}_2 \end{array}\right]=\left[ \begin{array}{cc} w_{11}^{[2]}a^{[1]}_1+w_{12}^{[2]}a^{[1]}_2+w_{13}^{[2]}a^{[1]}_3+b^{[2]}_1 \\ w^{[2]}_{21}a^{[1]}_1+w_{22}^{[2]}a^{[1]}_2+w_{23}^{[2]}a^{[1]}_3+b^{[2]}_2\end{array}\right]

那么,

z 1 [ 2 ] a 1 [ 1 ] = ( w 11 [ 2 ] a 1 [ 1 ] + w 12 [ 2 ] a 2 [ 1 ] + w 13 [ 2 ] a 3 [ 1 ] + b 1 [ 2 ] ) a 1 [ 1 ] = w 11 [ 2 ] \frac{\partial z^{[2]}_1}{\partial a^{[1]}_1}=\frac{\partial(w_{11}^{[2]}a^{[1]}_1+w_{12}^{[2]}a^{[1]}_2+w_{13}^{[2]}a^{[1]}_3+b^{[2]}_1)}{\partial a^{[1]}_1}=w^{[2]}_{11}

那么,根据以前介绍的求解梯度向量的定义:

z 1 [ 2 ] a [ 1 ] = [ z 1

相关文章
相关标签/搜索