前言: 一直觉得 Neural Networks and Deep Learning 这本书写得特别好, 尤其是反向传播章节由浅入深的解释简直妙不可言, 所以我准备将它翻译成中文, 顺便自己重新学习一下. 翻译过程中在保证内容完整的前提下, 删减了一些啰嗦的段落. 原文对反向传播的阐述美中不足的地方是, 将传播过程看做以层为单位, 没有将激活函数看做是独立的操作符. 这样对于理解某些深度学习框架并不友好.

1970年代反向传播算法被发现, 那时候并没有引起人们的重视, 直到1986年 David Rumelhart, Geoffrey HintonRonald Williams 发表了一篇非常著名的论文. 论文阐述反向传播能够比其它方法更有有效地训练神经网络, 使得先前用神经网络不可解的问题变得可解. 如今, 反向传播算法已然是神经网络训练的根本驱动动力了.

学习反向传播的原理, 能够让我们更透彻地理解神经网络乃至深度学习的训练过程. 反向传播最核心的公式无疑是目标函数或者代价函数$C$对于权重项 $w$ 和是偏置项 $b$ 的偏导$\partial{C}/ \partial{w}$. 直观地理解, 偏导数表达了目标函数随着权重和偏置变化的快慢程度. 虽然这个式子有些复杂, 但是式子当中每个部分都有一个自然而直观的解释. 并且反向传播不仅仅只是一个快速的训练方法而已, 它同时也让我们能够洞察网络权重和偏置的变化对网络整体行为的影响.

神经网络向量化表示

在介绍反向传播之前, 我们得先说明一下表达神经网络相关元素和计算的记号规则. 我们用 $w_{jk}^{l}$ 表示 $(l-1)$ 层第 $k$ 个神经元到 $l$ 层的第 $j$ 个神经元连接的权重. 如下图所示$w_{24}^3$表示第2层的第4个神经元与第3层的第2个神经元连接的权重.

刚开始你可能会觉得这个注记法略显累赘, 难以记忆. 但是随着你对神经网络的了解和接下来的学习你会觉得它简单, 自然. 值得注意的是下标$j$, $k$ 是反着的. 似乎颠倒一下顺序更科学自然, 毕竟$k$是前一层的, 而 $j$ 是当前层的. 接下来我会解释为什么要反着注记.

我们用类似的方式注记偏置和激活值. $b^l_j$ 表示 $l$ 层, $j$ 神经元的偏置, 用 $a^l_j$ 表示 $l$ 层, $j$ 神经元的激活值. 下面的图表说明了我们的注记规则:

通过上面的记号, 我们便可以将 $(l-1)$ 层的神经元激活与 $l$ 层的 $j$ 神经元的激活 $a_j^l$ 用如下式子联系起来.

$$
\begin{eqnarray}
a^{l}_j = \sigma\left( \sum_k w^{l}_{jk} a^{l-1}_k + b^l_j \right),
\tag{23}\label{23}\end{eqnarray}
$$

其中求和符号对 $(l-1)$ 层的所有 $k$ 个神经元进行了累加. 将上面的式子改写成矩阵形式, 我们用矩阵 $w^l$ 表示 $l$ 层权重. 权重矩阵 $w^l$ 与 $l$ 层的神经元相关联, 即其中第 $j$ 行和 $k$ 列元素便是上文中的 $w_{jk}^l$ . 类似地, 我们用 $b^l$ 表示 $l$ 的偏置向量. 向量中每个元素对应上文中的 $b_j^l$. 同时我们用 $a^l$ 表示激活向量.

为了将式子 $\ref{23}$ 改写成矩阵形式, 我们还需要一个向量化的激活函数 $\sigma$. 它只是将函数作用到输入矩阵的每个元素上, 用 $\sigma(v)$ 表示. 换句话说就是 $\sigma{(v)}_j = \sigma(v_j)$. 例如函数 $f(x) = x^2$ 向量化的形式如下:

$$
\begin{eqnarray}
f\left(\left[ \begin{array}{c} 2 \\ 3 \end{array} \right] \right)
= \left[ \begin{array}{c} f(2) \\ f(3) \end{array} \right]
= \left[ \begin{array}{c} 4 \\ 9 \end{array} \right],
\tag{24}\label{24}\end{eqnarray}
$$

即函数 $f$ 对每个元素求平方.

用上面的记号, 我们便可以将等式 $\ref{23}$ 重新写成如下向量形式

$$
\begin{eqnarray}
a^{l} = \sigma(w^l a^{l-1}+b^l).
\tag{25}\label{25}\end{eqnarray}
$$

上式能够让我们更全面地认识网络层之间激活的关系, 即用权重矩阵乘上上一层的激活, 然后加上偏置, 再对每个元素执行激活函数 $\sigma$, 便得到当前层的激活. 向量化的形式更加简洁明了, 省去了烦人下标. 同时这样的表示方式能够让你更自如地应用某些运算库, 它们都提供了类似的向量化计算方式.

在式子 $\ref{25}$ 中, 计算激活 $a^l$ 之前必须先计算中间结果, $z^l = w^la^{l-1} + b^l$. 这是一个非常中要的中间变量. 式子 $\ref{25}$ 有时候还写成 $a^l = \sigma(z^l)$. 另外 $z^l_j= \sum_k w^l_{jk} a^{l-1}_k+b^l_j$ 即$z^l$ 可以看做对上一层激活的加权求和再加上偏置.

代价函数的两个假设

反向传播的目的是计算目标函数 $C$ 对于权重和偏置的偏导数 $\partial C / \partial w$, $\partial C / \partial b$. 反向传播能够实现是建立在对目标函数的两个假设之上. 在讲解假设前提之前, 让我们先看看一个具体的目标函数. 如下面的公式是一个平方代价函数:

$$
\begin{eqnarray}
C = \frac{1}{2n} \sum_x \|y(x)-a^L(x)\|^2,
\tag{26}\end{eqnarray}
$$

式子中, $n$ 是总的训练样本, 式子对每个样本的代价进行求和; $y = (x)$ 是输入$x$对应的目标值; $L$ 表示网络的层数; $a^L = a^L(x)$ 是网络的输出向量.

那前文所说的假设是什么呢? 第一个假设是代价函数可以写成各个样本代价的均值$C = \frac{1}{n}\sum_{x}C_x$. 上面的平方代价函数满足这个要求.

需要这个假设是因为, 我们只能计算单个样本的偏导数$\partial C / \partial w$, $\partial C / \partial b$, 但是我们在更新参数的时候对其进行了均值处理.

第二个假设是, 代价函数必须是网络输出的函数:

显然平方代价函数也满足要求, 将平方代价函数写成如下形式便一目了然了:

$$
\begin{eqnarray}
C = \frac{1}{2} \|y-a^L\|^2 = \frac{1}{2} \sum_j (y_j-a^L_j)^2,
\tag{27}\end{eqnarray}
$$

虽然代价函数还必须输入样本的目标值, 但是为何我们不把代价函数看做是目标值的函数呢? 因为, 当输入 $x$ 确定的时候其目标值就确定了, 也就是说它不因为我们改变网络参数而改变, 也即它不是网络要学习的参数. 所以目标函数$C$只是网络输出$a^L$的函数, 而 $y$ 只是表式这个函数的一个符号而已.

哈达玛积, $s \odot t$

反向传播除了需要基本的线性代数运算之外, 还需要用到另一个运算哈达玛积.矩阵对应元素相乘便是哈达玛积(Hadamard product). 即$s \odot t$的元素由 $(s \odot t)_j = s_j t_j$ 组成. 例如:

$$
\begin{eqnarray}
\left[
\begin{array}{c}
1 \\ 2
\end{array}
\right]
\odot \left[\begin{array}{c} 3 \\ 4\end{array} \right]
= \left[ \begin{array}{c} 1 * 3 \\ 2 * 4 \end{array} \right]
= \left[ \begin{array}{c} 3 \\ 8 \end{array} \right].
\tag{28}
\end{eqnarray}
$$

反向传播的四个核心公式

理解反向传播就是明白, 改变权重和和偏置是如何影响目标函数的, 说到底就是计算目标函数对于权重和偏置的偏导数$\partial C / \partial w^l_{jk}$, $\partial C / \partial b^l_{j}$. 为了计算它们, 我们先引入一个中间变量 $\sigma_j^l$ , 它代表的是 $l$ 层 $j$ 神经元的误差. 反向传播就是教我们如何计算 $\sigma_j^l$, 以及如何将 $\sigma_j^l$ 与 $\partial C / \partial w^l_{jk}$, $\partial C / \partial b^l_{j}$ 联系起来.

想要理解这个误差, 我们可以想象网络中有一只精灵坐在 $l$ 层的 $j$ 神经元上:

当输入来临的时候, 这个精灵变干扰神经元的计算, 它往 $z_j^l$ 加上一个小量 $\Delta z_j^l$, 使得原本神经元的输出从 $\sigma(z_j^l)$ 变成了 $\sigma(z^l_j + \Delta z^l_j)$, 这个误差向后传播, 最终使得目标函数变化了 $\frac{\partial C}{\partial z^l_j} \Delta z^l_j$.

现在我们有一只”好”精灵, 它通过寻找恰当的 $\Delta z_j^l$ 使得网络的 Loss 降低. 如果 $\frac{\partial C}{\partial z^l_j}$ 数值很大, 精灵可以通过寻找一个与 $\frac{\partial C}{\partial z^l_j}$ 符号相反的小量 $\Delta z_j^l$ 来降低网络的 Loss. 相反, 如果$\frac{\partial C}{\partial z^l_j}$很小, 接近于零, 那么精灵则就很难通过改变 $z^l_j$ 来降低网络的 Loss. 这个时候, 精灵可以认为网络已经接近最优了. 这也给了我们一个启发: $\frac{\partial C}{\partial z^l_j}$ 在一定程度上衡量了神经元的误差.

受上面的故事的启发, 我们定义神经元的误差:

$$
\begin{eqnarray}
\delta^l_j \equiv \frac{\partial C}{\partial z^l_j}.
\tag{29}\end{eqnarray}
$$

类似上文向量化化形式, 我们用 $\sigma^l$ 表示 $l$ 层相关联的误差向量. 反向传播便是寻找计算每一层的误差 $\sigma^l$ 的方法, 并将其和我们所关系的偏导 $\partial C / \partial w^l_{jk}$, $\partial C / \partial b^l_{j}$ 关联起来.

前方高能 反向传播基于最基本的四个公式. 它们让我们能够计算误差 $\sigma^l$ 和目标函数的导数. 注意, 不要指望自己能够立马理解这些公式, 那样只会给你带来失望. 反向传播相关的公式内涵丰富, 需要一定的时间和耐心慢慢深入才能理解. 不过这些时间和精力是非常值得的, 它将带给你成倍的收益. 因此这个章节的讨论仅仅是开始.

输出层的误差 $\sigma^L$:

$$ \sigma_j^L = \frac{\partial{C}}{\partial{a_j^L}}\sigma’(z_j^L) \label{BP1} \tag{BP1} $$

公式右边第一部分部分 $ \frac{\partial{C}}{\partial{a_j^L}}$ 衡量了Loss相对于$j$层激活变化的速率. 举个例子, 如果 $C$ 不依赖某个输出神经元 $j$, 那么 $\sigma_j^L$ 就会很小, 这就是我们所期望看到的. 右边第二部分 $\sigma’(z_j^L)$ 衡量的是激活函数随着$z_j^L$ 变化的快慢程度.

公式 $\ref{BP1}$ 所有部分都是很容易计算的. $z_j^L$ 是前向过程必须要计算的量, 而 $\sigma’(z_j^L)$ 计算起来也很简单. 右边第一部分则视目标函数而定. 尽管如此, 目标函数确定之后, $\frac{\partial{C}}{\partial{a_j^L}}$ 的计算也不难. 例如, 之间的二范距离目标函数 $C = \frac{1}{2} \sum_j(y_j - a_j^L)^2$ 的导函数是 $\frac{\partial{C}}{\partial{a_j^L}} = (a_j^L - y_j)$, 是不是非常简单直接.

公式 $\ref{BP1}$ 含有逐元素运算, 已经非常漂亮, 但是每种不足的是, 没有向量化. 把 $\ref{BP1}$ 写成向量形式就是:

$$ \sigma^L = \Delta_aC \odot \sigma’(z^L). \label{BP1a} \tag{BP1a}$$

其中 $\Delta_aC$ 每个元素由 $\frac{\partial{C}}{\partial{a_j^L}}$ 组成, 它可以看做目标函数随着输出激活变化的速率. 显而易见 $\ref{BP1a}$ 和 $\ref{BP1}$ 是等价的, 因此下文用$\ref{BP1a}$不加区分地引用这两个公式. 在我们的例子中, 平方代价函数有 $\Delta_aC = (a^L - y)$, 所以我们有:

$$\sigma^L = (a^L - y) \odot \sigma’(z^L)$$

式子向量化的形式非常简洁明了, 并且用一些矩阵库例如Numpy是很容易计算的.

上层误差$\sigma^l$ 和下层误差 $\sigma^{l+1}$ 的关系:

$$\begin{eqnarray}
\delta^l = ((w^{l+1})^T \delta^{l+1}) \odot \sigma’(z^l),
\tag{BP2} \label{BP2}\end{eqnarray}$$

其中 $(w^{l+1})^T$ 是 $l+1$ 权重矩阵 $w^{l+1}$ 的转置. 乍一看这个公式挺复杂, 但是每个元素都有很重要的含义. 用转置矩阵 $(w^{l+1})^T$ 乘上 $l+1$ 层的误差 $\delta$ 可以看做是将误差往前传播, 让我们能够知道 $l$ 的激活的误差. 然后对其 $\odot \sigma’(z^l)$ 可以看做是将误差继续向前传播, 计算出 $l$ 层加权偏置后的误差.

通过组合式子$\ref{BP2}$ 和 $\ref{BP1}$, 我们就能计算任意层的误差 $\delta^l$. 我们首先用 $\ref{BP1}$ 计算目标函数层的 $\delta^L$, 接着用公式 $\ref{BP2}$ 计算 $\delta^{L-1}$, 以此类推.

目标函数相对于偏置的变化率 – 偏置项的偏导数:

$$\begin{eqnarray} \frac{\partial C}{\partial b^l_j} =
\delta^l_j.
\tag{BP3}\end{eqnarray}$$

可见, 偏置的偏导就是误差本身. 把它写成向量的形式就是:

\begin{eqnarray}
\frac{\partial C}{\partial b} = \delta,
\tag{31}\end{eqnarray}

目标函数相对于权重的变化率 – 权重的偏导数:

$$\begin{eqnarray}
\frac{\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j.
\tag{BP4} \label{BP4}\end{eqnarray}
$$

这就是目标函数相对于权重的偏导数 $\partial C
/ \partial w^l_{jk}$. 式子右边的每一个部分我们都知道怎么算了, 把上面的式子写成更简洁的方式就是

$$\begin{eqnarray} \frac{\partial
C}{\partial w} = a_{\rm in} \delta_{\rm out},
\tag{32} \label{32}\end{eqnarray}$$

式子中 $a_{in}$ 表示神经元的输入, 即上一层的激活, 供权重矩阵和偏置项计算. 而 $\delta_{out}$ 则表示神经元在当前权重下产生的误差. 把层与层之间放大来看, 我们可以用下面的图来表示这个偏导数与输入和输出误差的关系:

从式子 $\ref{32}$ 可以看出, 如果输入的激活 $a_{in}$ 特别小, 接近于零, 那么对应权重的偏导数也会变得很小. 在这种情况下, 权重学习得很慢; 也就是说, 在梯度下降的过程中, 权重几乎不变或改变很小. 换句话说, 那些低激活相关联的权重学习得慢.

从公式 $\ref{BP1} - \ref{BP4}$ 我们还能洞察到许多其它的信息. 首先我们看层的输出, 例如公式 $\ref{BP1}$ 中的 $\sigma_j^L$, 再结合sigmoid函数的曲线, 激活函数输出接近于0或者接近于1的时候, 变得非常平缓. 这个时候, 激活函数的梯度接近于0. 这就是说, 如果激活接近于0或1, 网络的权重将学得非常缓慢. 这个时候, 我们可以说神经元已经饱和了, 此时权重停止学习(学得很慢). 这些类似的现象同样适用于偏置项. 同样, 网络前几层也会出现类似的现象. 所以说, 任何饱和的神经元, 权重都会学得很慢.

简而言之, 如果输入激活较低, 或是输出的神经元已经饱和, 权重都将学得很慢.

以上的观察结果并不意外, 而且, 它们帮我们更清晰地认识神经网络学习的时候发生的情况. 甚至, 我们可以改善这些情况. 四个核心公式不仅对Sigmoid函数适用, 对于几乎所有的激活函数都是使用的. 因此, 我们可以用这些公式设计我们要的激活函数.

sigmoid曲线

以下是反向传播的四个核心公式:

$$
\begin{eqnarray}
\delta^L_j &= \frac{\partial C}{\partial a^L_j} \sigma’(z^L_j).
\tag{BP1}\end{eqnarray}
$$

$$
\begin{eqnarray}
\delta^l = ((w^{l+1})^T \delta^{l+1}) \odot \sigma’(z^l),
\tag{BP2}\end{eqnarray}
$$

$$
\begin{eqnarray} \frac{\partial C}{\partial b^l_j} =
\delta^l_j.
\tag{BP3}\end{eqnarray}
$$

$$
\begin{eqnarray}
\frac{\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j.
\tag{BP4}\end{eqnarray}
$$

此处应有打赏