神经网络(Neural Network)是由具有适应性的简单单元组成的广泛并行互连的网络,它的组织能够模拟生物神经系统对真实世界物体所作出的交互反应。

1. 神经元模型

1.1 感知机

  最早的神经元模型是由感知机来构建的,感知机工作原理:一个感知机接受几个二进制输入($x_1, x_2, \dots, x_n$),并产生一个二进制输出。

  类似于生物神经网络中刺激度到达一个阈值时神经元就被激活,感知机代数形式如下:

  可以将多个这样的感知机组合起来提高在深层次上的抽象能力,并且将上述代数形式转化成向量形式方便实现。基于此感知机可以实现“与”、“或”和“与非”等逻辑运算。

1.2 S 型神经元

  为了防止对权重(或者偏置)做微小的变动就会对输出的影响太大,可以采用 Sigmoid 作为激活函数,这样的神经元就叫 S 型神经元。

1.3 神经网络架构

  由于历史的原因,尽管是由 S 型神经元⽽不是感知器构成,这种多层⽹络有时被称为多层感知器或者 MLP。此类神经网络都是以上一层的输出作为下一层的输入,被称为前馈神经网络(埋头向前,没有回路),而多层结构(两层或更多隐藏层)的网络被称为深度神经网络。

2. 反向传播算法

2.1 前向传播

  一般的模型训练都会使用梯度下降来学习权重和偏置,经过发展神经网络采用了反向传播(BackPropagation,BP)算法来快速计算梯度。反向传播的核心是一个对损失函数 $C$ 关于任何权重 $w$(或者偏置 $b$)的偏导 $\partial C / \partial w$ 的表达式。这个表达式告诉我们在改变权重和偏置时,损失函数变化的快慢。反向传播就是⼀种巧妙地追踪权重(和偏置)微⼩变化的传播,抵达输出层影响损失函数的技术。在介绍后向传播算法之前,我们先搞清楚前向传播的过程,即从输入 $x$ 到输出是怎么得到的。

  首先定义权重的符号,使用 $w_{j k}^{l}$ 表示从 $(l-1)^{\mathrm{th}}$ 层的 $k^{\mathrm{th}}$ 个神经元到 $l^{\mathrm{th}}$ 层的 $j^{\mathrm{th}}$ 个神经元的链接上的权重。

  接着定义偏置和激活值的符号,我们使⽤ $b_{j}^{l}$ 表⽰在 $l^{\mathrm{th}}$ 层第 $j^{\mathrm{th}}$ 个神经元的偏置,使⽤ $a_{j}^{l}$ 表⽰ $l^{\mathrm{th}}$ 层第 $j^{\mathrm{th}}$ 个神经元的激活值。

  有了以上的符号基础,就能将 $l^{\mathrm{th}}$ 层第 $j^{\mathrm{th}}$ 个神经元的激活值 $a_{j}^{l}$ 就和 $(l-1)^{\mathrm{th}}$ 层的激活值通过⽅程关联起来:

  其中求和是在 $(l-1)^{\mathrm{th}}$ 层的所有 $k$ 个神经元上进⾏的。以向量形式改写得到:

  这个表达式给出了一种更加全局的思考每层激活值和前一层激活值的关联方式:我们仅仅用权重矩阵作用在激活值上,然后加上一个偏置向量,最后作用 $\sigma$ 函数。在这里我们继续定义上式$\sigma$ 函数传入的部分,即带权输入

2.2 关于损失函数的两个假设

  反向传播的目标是计算损失函数 $C$ 分别关于 $w$ 和 $b$ 的偏导数 $\partial C / \partial w$ 和 $\partial C / \partial b$,二次损失函数如下:

  其中 $n$ 是训练样本的总数;求和运算遍历了每个训练样本 $x$;$y = y(x)$ 是对应的⽬标输出;$L$ 表⽰⽹络的层数;$a^L = a^L (x)$ 是当输⼊是 $x$ 时的⽹络输出的激活值向量。

  为了应用反向传播,我们对损失函数 $C$ 有两个假设,第一个假设是损失函数可以被写成一个在每个训练样本 $x$ 上的损失函数 $C_x$ 的均值:

  其中对每个独立的训练样本其损失是 $C_{x}=\frac{1}{2}\left|y-a^{L}\right|^{2}$。需要这个假设是因为反向传播实际上是对一个独立的训练样本计算了 $\partial C_{x} / \partial w$ 和 $\partial C_{x} / \partial b$,然后通过在所有训练样本上平均化获得 $\partial C / \partial w$ 和 $\partial C / \partial b$。

  第二个假设是损失可以写成神经网络输出的函数:

  ⼆次损失函数满⾜这个要求,因为对于⼀个单独的训练样本 $x$ 其⼆次损失函数可以写作:

  值得注意的是这是一个关于输出激活值的函数,而不是以往的 $\hat{y}$。

2.3 反向传播的四个基本方程

  反向传播其实是对权重和偏置变化影响损失函数过程的理解,最终极的含义其实就是计算偏导 $\partial C / \partial w_{j k}^{l}$ 和 $\partial C / \partial b_{j}^{l}$。在计算这些值前,我们先引入一个中间量,$\delta_{j}^{l}$,其被称为 $l^{t h}$ 层第 $j^{t h}$ 个神经元上的误差。反向传播将给出计算误差 $\delta_{j}^{l}$ 的流程,然后将其关联到计算 $\partial C / \partial w_{j k}^{l}$ 和 $\partial C / \partial b_{j}^{l}$ 上。

  偏差的绝对值较大说明还有进一步优化,而接近于零时我们就假定其收敛了,据此我们定义 $l$ 层的第 $j^{t h}$ 个神经元上的误差 $\delta_{j}^{l}$ 为:

  单独定义这一项是为了后面计算方便,这一项就是我们从前往后尝试计算参数对损失的偏导时,这一项总是未知,需要后面层偏导的计算。

  基于以上的定义,又根据链式法则将激活函数加进来,我们考虑输出层误差的方程 $\delta^{L}$ 中,其中每个神经元误差(假设多类问题)定义如下:

  这里表现出求和主要是为了体现求导是在输出层所有神经元上计算的(其实可以省掉这步,直接通过链式法则得到就好),但是输出层神经元的输出激活值只跟当前神经元有关,所以上式简化成:

  用回激活函数的表达 $a_j^L = \sigma(z_j^L)$,那么输出层第 $j$ 个神经元的误差计算公式:

  其中右式第一项 $\partial C / \partial a_{j}^{L}$ 表示损失随着 $j^{t h}$ 输出激活值的变化而变化的速度,假如 $C$ 不太依赖⼀个特定的输出神经元 $j$,那么 $\delta_{j}^{L}$ 就会很⼩,这也是我们想要的效果。右式第二项 $\sigma^{\prime}\left(z_{j}^{L}\right)$ 刻画了在 $z_{j}^{L}$ 处激活函数 $\sigma$ 变化的速度。如果用矩阵形式表示方程(BP1):

  我们利用链式法则分析可以看到计算当前层某个神经元的参数对损失函数的偏导时需要依赖下一层,依据链式法则,我们可以将某一层的某个神经元的误差改写一下。

  使用下一层的误差 $\delta^{l+1}$ 来表示当前层的误差 $\delta^{l}$:

  公式中第一项很好计算,我们知道下一层的带权输入是上一层所有连接神经元的计算结果之和:

  计算微分:

  再代入刚才改写的 $\delta^{l}$ 公式:

  有了上面的式子,我们就可以从输出层开始,从后向前计算任意层任意神经元对应参数对损失函数偏导了,对于权重:

  第一项对 $z_j^{l} = \sum_k w_{jk} ^ {l} a_k^{l-1} + b_j ^ {l}$ 求 $w_{j k}^{l}$ 的偏导可得:

  代入方才的式子可以得到权重的偏导(改变率):

  类似的,很容易求出损失函数关于网络中任意偏置的改变率:

反向传播算法描述

  1. 输入一个 $x$:为输入层设置对应的激活值 $a^1$。
  2. 前向传播:对每个 $l=2, 3, \dots, L$ 计算相应的 $z^{l}=w^{l} a^{l-1}+b^{l}$ 和 $a^{l}=\sigma\left(z^{l}\right)$。
  3. 输出层误差 $\delta^{L}$:计算向量 $\delta^{L}=\nabla_{a} C \odot \sigma^{\prime}\left(z^{L}\right)$。
  4. 反向误差传播:对每个 $l=L-1, L-2, \dots, 2$,计算 $\delta^{l}=\left(\left(w^{l+1}\right)^{T} \delta^{l+1}\right) \odot \sigma^{\prime}\left(z^{l}\right)$。
  5. 输出权重和偏置:损失函数的梯度由 $\frac{\partial C}{\partial w_{j k}^{l}}=a_{k}^{l-1} \delta_{j}^{l}$ 和 $\frac{\partial C}{\partial b_{j}^{l}}=\delta_{j}^{l}$ 得出,然后通过权重更新的公式更新 $w^l$ 和 $b^l$。

  这里值得注意的是每次计算偏导只是针对一个样本 $x$,每次计算出来的偏导是 $\frac{\partial C_x}{\partial w}$ 和 $\frac{\partial C_x}{\partial b}$,需要计算出所有样本 $x$ 的偏导,平均之后得到最终的 $\frac{\partial C}{\partial w}$ 和 $\frac{\partial C}{\partial b}$,这是基于之前提到的关于损失函数的假设 $C=\frac{1}{n} \sum_{x} C_{x}$。

  在实践中,通常采用反向传播算法和诸如随机梯度下降这样的算法组合使用,给定一个大小为 $m$ 的小批量数据,模型构建流程如下:

  1. 输入训练样本的集合

  2. 对每个训练样本 $x$:计算对应的输入激活 $a^{x, 1}$,执行下述步骤:
    • 前向传播:对每个 $l=2,3, \dots, L$ 从前往后计算每层带权输入和激活
      • 带权输入:$z^{x, l}=w^{l} a^{x, l-1}+b^{l}$
      • 激活:$a^{x, l}=\sigma\left(z^{x, l}\right)$
    • 输出误差 $\delta^{x, L}$:计算输出层的误差 $\delta^{x, L}=\nabla_{a} C_{x} \odot \sigma^{\prime}\left(z^{x, L}\right)$
    • 反向传播误差:对每个 $l=L-1, L-2, \ldots, 2$,从后往前计算每层的误差
      • $l$ 层误差:$\delta^{x, l}=\left(\left(w^{l+1}\right)^{T} \delta^{x, l+1}\right) \odot \sigma^{\prime}\left(z^{x, l}\right)$
  3. 梯度下降:对每个 $l=L-1, L-2, \ldots, 2$ 更新每层的权重和偏置
    • 权重更新:$w^{l} \rightarrow w^{l}-\frac{\eta}{m} \sum_{x} \delta^{x, l}\left(a^{x, l-1}\right)^{T}$
    • 偏置更新:$b^{l} \rightarrow b^{l}-\frac{\eta}{m} \sum_{x} \delta^{x, l}$

  在实践过程中,会一次取 $\text{batch_size}$ 个样本的 batch 进行训练,这一次训练叫做一个 iteration,迭代更新了一次网络的参数。完整训练一次所有的样本叫做一个 epoch,一个 epoch 一共有 $\frac{\text{数据总样本数}}{\text{batch_size}}$ 个 batch。

  • 如果在输出神经元是 $S$ 型神经元时,交叉熵⼀般都是更好的选择。
  • 在输出层使用线性神经元时使用二次损失函数。

  Softmax(柔性最大值)的输出可以被看做是⼀个概率分布,即下面的式子中 $a_{j}^{L}$ 解释成⽹络估计正确数字分类为 $j$ 的概率。

  使⽤均值为 0 标准差为 1 的⾼斯分布来对偏置进⾏初始化。这其实是可⾏的,因为这样并不会让我们的神经⽹络更容易饱和。

梯度消失和梯度爆炸

  我们考虑这样的简单网络结构:

  计算 $C$ 对 $w_1$ 和 $b_1$ 的偏导:

  我们知道 Sigmoid 函数的导数 $\sigma\prime(x)$ 如下图:

  即 $\sigma\prime(x)$ 的最大值为 $\frac{1}{4}$,而一般来说初始化的权重会满足 $\left \vert w_j \right \vert<1$,那么 $\vert\sigma^{\prime}\left(z\right)w_j\vert\leq\frac{1}{4}$,如此连乘之后,梯度的结果很容易就越来越小,这就是导致梯度消失的原因。同样的,如果选择不同的权重初始化方法 (导致权重比较大) 以及不同的激活函数 (导致偏导比较大),我们也有可能得到 $\vert\sigma^{\prime}\left(z\right)w_j\vert\gt1$ 的结果,那么经过累乘之后,梯度会迅速增长,造成梯度爆炸

  梯度消失和梯度爆炸是同一类情况,都是后向传播算法的先天不足,因为在求偏导时会有一系列的偏导连乘,导致如果偏导都比较小的话,越往前的层偏导结果越小;相对的如果偏导都大于 1,那么梯度的结果就会很大,有可能导致结果溢出。所以在深度网络中有一个常见的现象就是,往往受到不稳定的梯度的影响,不同层有较大差异的优化速度。

梯度消失容易出现的情况:

  • 网络为深度网络时;
  • 采用了不合适的损失函数,如 Sigmoid。

梯度消失的结果:

  • 靠近输出层的 hidden layer 梯度大,参数更新快,收敛快; 而靠近输入层的 hidden layer 梯度小, 参数更新的慢,几乎和初始状态一样随机分布。

梯度爆炸容易出现的情况:

  • 网络为深度网络时;
  • 权值初始化值太大;

梯度爆炸的结果:

  • 模型无法从训练数据中获得更新(如低损失);
  • 导致网络权重大幅度更新,使得学习过程不稳定,导致更新过程中的损失出现显著变化;
  • 极端情况下,权重太大溢出,导致 NaN;
  • 靠近输入层的 hidden layer 梯度通过训练变大, 而靠近输出层的 hidden layer 梯度呈指数级增大;
  • 训练过程中模型梯度快速变大;
  • 训练过程中,每个节点和层的误差梯度值持续超过 1.0;

梯度消失和梯度爆炸的解决方案

  细节描述可参见

  1. 预训练加微调 (pre-training & fine-tunning)
  2. 梯度剪切(Gradient Clipping)
  3. 损失函数加入正则项
  4. relu、leakrelu、elu等激活函数
  5. batch normalization
  6. 引入残差结构
  7. LSTM
附录

Hadmard

  反向传播算法基于常规的线性代数运算——诸如向量加法,向量矩阵乘法等。但是有⼀个运算不⼤常⻅。特别地,假设 $s$ 和 $t$ 是两个同样维度的向量。那么我们使⽤ $s \odot t$ 来表⽰按元素的乘积。所以 $s \odot t$ 的元素就是 $(s \odot t)_j=s_j t_j$。给个例⼦,

  这种类型的按元素乘法有时候被称为 Hadamard 乘积,或者 Schur 乘积。

References

  1. CHAPTER 1: Using neural nets to recognize handwritten digits
  2. 详解机器学习中的梯度消失、爆炸原因及其解决方法
  3. 機器學習技法 NNet