前馈神经网络原理与实现

本文适用于已经对感知机、神经网络有初步了解,但上手比较困难,愿意推导公式,更深入了解神经网络的朋友

#引言

前馈神经网络是所有神经网络中最简单,也是最有效的一种。从单个神经元的角度看,不过就是设计权值与偏置的简单感知机运算。但到底网络是如何实现的?特别是back propagation的原理?我相信刚刚入门的很多朋友与我一样有很多疑惑,但又不想总是调包,那么我们就慢慢推导公式吧。

#需要准备

  1. 良好的线性代数知识,特别是矩阵的运算。
  2. 微积分知识,特别是偏导和链式公式的应用。
  3. 基本的python技巧。

#符号说明

$w_{jk}^{l}$:表示从$(l-1)$层的第$k$个神经元到第$l$层的第$j$个神经元的连接上的权重。虽然从直观上不太好理解为什么要这样表示(通常应该表示为$w_{kj}^{l}$),但请先接受这种写法。可以对相邻两层的所有权重用矩阵的形式表示为$w^l$。

$\sigma $:表示激活函数,本文都使用Sigmoid function

$b_{j}^{l}$:表示第$l$层$j$神经元的偏置,可以对同一层的神经元表示为$b^l$,记为偏置向量。

$a_{j}^l$:表示第$l$层$j$神经元的激活值,可以对同一层的神经元表示为$a^l$,记为激活向量。 由BP神经网络的定义可得:$a^l = \sigma (w^la^{l-1}+b^l)$。

$z^l$:表示带权输入,$z^l = w^la^{l-1}+b^l\quad a^l = \sigma(z^l)$。

$C$ :表示代价函数,定义为$C = \frac {1}{2n} \sum ||y(x) - a^L(x)||^2$,其中$y(x)$表示每个样本的真实输出,$L$表示神经网络的总层数。

#代价函数

BP神经网络的向前传播很简单,就使用之前提到的矩阵形式就可以计算,当我们初始化所有权重和偏置时,得到的结果输出与目标输出肯定有较大差距,我们使用代价函数来度量这种差距。定义如下:

$$C = \frac {1}{2n} \sum ||y(x) - a^L(x)||^2$$

那么,当输入和输出固定时,$C$就是关于$w和b$的函数,我们需要对其进行求偏导,以此来更新代价函数。

我们需要对代价函数进行如下定义(假设):

  1. 代价函数可以写成一个在每个训练样本$x$上的代价函数$C_x$的均值$C = \frac{1}{n}\sum _xC_x$。
  2. 将$C$仅看成输出激活值$a^L$的函数。

以下公式,不加说明,$C$都指特定的$C_x$。

#反向传播的四个方程

反向传播其实就是对权重和偏置变化影响函数过程的理解。最终就是需要计算$\frac{\partial C}{\partial w_{jk}^{l}}和 \frac{\partial C}{\partial b_{j}^{l}}$。

我们首先定义一个中间量$\delta_j^l = \frac{\partial C}{\partial z_{j}^{l}} $,表示为第$l层第j$个神经元的误差,然后将$\delta_j^l $关联到$\frac{\partial C}{\partial w_{jk}^{l}}和 \frac{\partial C}{\partial b_{j}^{l}}$。

这里可能会感到疑惑,为什么会定义这样一个误差呢? 我们想象,当在一个神经元的带权输入上增加一个很小的变化$\Delta z_j^l$,使得神经元输出由$\sigma (z_j^l)$变为$\sigma (z_j^l + \Delta z_j^l)$,那么这个变化将会向网络后面的层进行传播,最终导致整个代价产生$\frac{\partial C}{\partial z_{j}^{l}}\Delta z_j^l$的变化。因此,这里有一种启发式的认识,$\frac{\partial C}{\partial z_{j}^{l}}$是神经元误差的度量:

$$\delta_j^l = \frac{\partial C}{\partial z_{j}^{l}} $$

在给出方程严谨的证明前,我们不妨从直观上看一下这些方程,这有助于我们的进一步理解。

  • 输出层误差方程:
    • $$\delta _j^L =\frac{\partial C}{\partial a_j^L} {\sigma}' (z^L_j)$$
    • 右边的第一个项$\frac{\partial C}{\partial a_j^L}$表示代价随着第$j$个输出激活值的变化而变化的速度。第二项刻画了在$z_j^l$处激活函数$\sigma $变化的速度,可以理解为$\Delta a_j^l$。
    • 注意到这个式子的每一部分都是很好计算的。我们如果已知了一个代价函数和激活函数,那么在前向传播中就可以算得每一个$\delta _j^L$。
    • 用矩阵的形式表示第一个式子则更加简单和美妙,注意$\odot$表示矩阵对应元素相乘:
    • $$\delta ^L = \nabla_aC\odot {\sigma}' (z^L)$$
  • 使用下一层的误差来表示当前层的误差:
    • $$\delta^l = ((w^{l+1})^T\delta^{l+1})\odot {\sigma}'(z^l)$$
    • 当我们知道$l+1$层的误差$\delta^{l+1}$,当我们应用转置的权重矩阵$(w^{l+1})^T$,我们可以凭直觉理解为它是沿着网络反向移动误差,给我们度量在$l$层输出误差的计算方法
    • 然后,使用hadamard乘积运算,让误差通过$l$层的激活函数反向传递回来并给出在第$l$层带权输入的误差$\delta$。
    • 通过组合前两个公式,我们可以计算出任意一层的带权输入误差。
  • 代价函数关于网络中任意偏置的改变率:
    • $$\frac{\partial C}{\partial b_j^i} = \delta _j^l$$
    • 通过这个方程我们发现,我们需要计算的$\frac{\partial C}{\partial b_j^i} $与$\delta _j^l$完全一致。
  • 代价函数关于任何一个权重的改变率:
    • $$\frac{\partial C}{\partial w_{jk}^i} = a_k^{l-1}\delta_j^l$$
    • 这告诉我们如何求$\frac{\partial C}{\partial w_{jk}^i} $。其中$a_k^{l-1}和\delta_j^l$我们都已经知道如何计算了,便于理解,我们可以将其化简为:
    • $$\frac{\partial C}{\partial w} = a_{in} \delta_{out}$$
    • 我们发现,当激活值$a_{in}$很小时,$\frac{\partial C}{\partial w} $也会变得很小。这时候,我们就说权重缓慢学习,表示在进行梯度下降时,这个权重不会改变太多。

通过之前的式子,我们可以发现,如果输入神经元激活值很低,或者输出神经元已经饱和了,权重会学习的非常缓慢。这可以帮助我们选择激活函数。例如,我们可以选择一个不是sigmoid函数的激活函数,使得${\sigma}'$总是证书,不会趋近于0,这样会防止原始的S型神经元饱和时学习速率下降的情况。

#四个基本方程的推导

总结下来一共有四个重要公式:

  1. $\boldsymbol {\delta ^L = \nabla_aC\odot {\sigma}' (z^L)}$
    • $$\because \delta_j^L = \frac{\partial C}{\partial z_{j}^{L}} $$
    • $$\therefore \delta_j^L =\sum\limits _k \frac{\partial C}{\partial a_k^L} \frac {\partial a_k^L}{\partial z_{j}^{L}} = \frac{\partial C}{\partial a_j^L} \frac {\partial a_j^L}{\partial z_{j}^{L}} = \frac{\partial C}{\partial a_j^L} {\sigma}' (z^L_j)$$
  2. $\boldsymbol {\delta^l = ((w^{l+1})^T\delta^{l+1})\odot {\sigma}'(z^l)}$
    • $$\because \delta_j^l = \frac{\partial C}{\partial z_{j}^{l}} = \sum\limits _k \frac{\partial C}{\partial z_{k}^{l+1}} \frac{\partial z_k^{l+1}}{\partial z_{j}^{l}},表示这一层的神经元对下一层都有影响$$
    • $$\therefore \delta_j^l = \sum\limits _k\delta_k^{l+1} \frac{\partial z_k^{l+1}}{\partial z_{j}^{l}}$$
    • $$\because z_k^{l+1} =\sum\limits_j w_{kj}^{l+1}\sigma(z_j^l) + b_k^{l+1}$$
    • $$\therefore \frac {\partial z_k^{l+1}}{\partial z_j^l} = w_{kj}^{l+1}{\sigma}'(z_j^l)$$
    • $$带入可得:\delta_j^l = \sum\limits _k\delta_k^{l+1} w_{kj}^{l+1}{\sigma}'(z_j^l)$$
  3. $\boldsymbol {\frac{\partial C}{\partial b_j^i} = \delta _j^l}$
    • $$\because b_k^l = z_k^l - \sum\limits _j w_{kj}^l\sigma(z_j^{l-1})$$
    • $$\therefore \delta_j^l = \frac{\partial C}{\partial z_{j}^{l}} = \frac{\partial C}{\partial b_j^l} \frac{\partial b_j^l}{\partial z_j^l} = \frac{\partial C}{\partial b_j^l}$$
  4. $\boldsymbol {\frac{\partial C}{\partial w_{jk}^i} = a_k^{l-1}\delta_j^l}$
    • $$\because z_j^l = \sum\limits _k w_{jk}^l a_k^{l-1} + b_j^l$$
    • $$\therefore \frac {\partial z_j^l}{\partial w_{jk}^l} = a_k^{l-1} \Rightarrow \frac {\partial C}{\partial z_j^l} \frac {\partial z_j^l}{\partial w_{jk}^l} = a_k^{l-1} \frac {\partial C}{\partial z_{j}^l}$$
    • $$\therefore \frac{\partial C}{\partial w_{jk}^i} = a_k^{l-1}\sigma _j^l$$

首先我们可以通过第一个公式算出$\delta ^L$,然后利用第二个公式的递推关系可以算出所有的$\delta$,这样,我们就可以很轻松的算出我们想要的每一个$\frac{\partial C}{\partial b_j^i} 以及\frac{\partial C}{\partial w_{jk}^i}$。

在反向传播中,为了减少计算量,很常见的方法是使用随机梯度下降。思想也很简单,每一个样本都需要进行参与求导实在是计算量太大,但我们可以只去一小部分来进行更新权重,多算几次取平均。

#总结

我们使用Mini-batch BGD来进行BP神经网络训练,具体步骤为:

  1. 输入训练样本集合
  2. 对每个训练样本$x$:设置对应的输入激活$a_x^{1}$,并进行:
    • 前向传播:对每个$l = 2,3,4...,L$,计算$z_x^{l}$
    • 输出误差$\sigma _x^{l } = \nabla_aCx\odot {\sigma}' (z_x^L)$
    • 反向转播误差:对每个$l = L-1,L-2,...,2$,计算$\delta_x^l = ((w^{l+1})^T\delta_x^{l+1})\odot {\sigma}'(z_x^l)$
  3. 梯度下降:根据$w^l \rightarrow w^l - \frac {\eta}{m}\sum _x\delta_x^l(a_x^{l-1})^T$和$b^l \rightarrow b^l - \frac {\eta}{m}\sum _x\delta_x^l $更新权值和偏置。

#反向传播到底在干什么?

首先,反向传播算法加速了神经网络的学习。设想,我们如果只按照最基本的思路计算权重:

$$\frac {\partial C}{\partial w_j} \approx \frac{C(w+\epsilon e_j)- C(w)}{\epsilon }$$

那么,我们可以使用两个相近的权重来估计$\frac {\partial C}{\partial w_j}$。但如果我们这样做,需要对每个权重进行前向传播估算,计算量是非常大的。

反向传播聪明的地方就是它确保我们可以同时计算所有的偏导数$\frac {\partial C}{\partial w_j}$因此,比起直接计算导数,显然反向传播有着更大优势。

#参考资料

neuralnetworksanddeeplearning

Load Comments?