1.前言
这篇博客是我在看 Neural Networks and Deep Learning (by Michael Nielsen)时的一些感想,以及对其中出现的公式的具体推导。这本书原文在
http://neuralnetworksanddeeplearning.com/ 上,可以在线阅读非常方便,是入门神经网络和深度学习的一个非常好的入门书,最开始没看这本书之前我大概翻了一下发现里面有很多数学公式觉得可能比较难,但实际看下来这本书关于数学原理的讲解是非常到位而且深入浅出的,强烈推荐大家阅读,如果读不惯英文,这本书在网上也流传着一份中文翻译版,翻译质量也是非常高的。
神经网络是由一层一层神经元构成的:
这是一个具有输入层,隐藏层和输出层的一个较为完整的神经网络,每层上的小圆圈叫做神经元,我们的目标就是找到输入数据和它们标签之间的关系,它们之间可能会存在某种映射\( f \)使得输入数据\( x_1,x_2,…x_n \) 和 它的label \(y \)之间存在$$f(x_1,x_2,…x_n) = y$$这样的函数关系
我们的目标是找到这样的\(f \) 但是要想精确的找出这个\(f \)是非常困难的,但是我们可以根据给定的数据集和标签找到近似这个\(f \)的 \(g \)函数,而\(g \)这个函数在给定的数据集上表现得非常好,错误率很低,那么我们的任务就完成了,那么你可能会问这个\( g \)只是在给定数据集上表现的很好,但它在未知数据集上表现的也会一样的好吗?这就牵扯到数据的量和过拟合的问题了,但我们可以完全放心的是在一个较大的数据集上表现得很好的模型是非常可靠的,它在未知数据集上的表现也会很好。
那么我们如何找出这个\(g \)?,具体来说我们要找的是\(g \)中的各个权重和偏执项 。在这里我们定义\( w_{jk}^l\),表示从第\( (l-1)\) 层 的 第 \(k \) 个神经元到 第 \(l\) 层的 第 \(j\) 个神经元上的权重
看上图,\( w_{24}^3 \) 明明是第2层第4个神经元到第3层第2个神经元之间的权重参数,我们为什么不用 \( w_{42}^3 \) 这种看起来更直观地形式表示?这个是有缘由的,后面我会解释。
我们定义\( b_j^{l}\) 表示在第 \(l\) 层 第 \(j\)个神经元的偏置,\(a_j^l\) 表示 第 \( l\)层第 \(j\)个神经元的激活值。而 \(a_j^1\)就是输入层神经元的数据,于是可以将\( a_j^l\)和 第\( (l-1) \) 层的激活值联系起来,这里面\(\sigma \)指的是激活函数:
$$a_j^l = \sigma \left(\sum_k w_{jk}^la_{k}^{l-1} + b_j^l \right)$$
定义\( z_j^l \):
$$z_j^l = \sum_k w_{jk}^la_{k}^{l-1} + b_j^l $$
我们也可以这样表示:
$$a_j^l = \sigma \left(\vec{w_j^l} \cdot \vec{a^{l-1}} + b_j^l \right)= \sigma(z_j^l)$$
那么在第\(l \)层上:
$$ \vec{a^l} = \sigma \left({W^l} \cdot \vec{a^{l-1}} + \vec{b^l} \right)=\sigma(\vec{z^l})$$
这也解释了我们上面的问题,为什么定义\( w_{jk}^l\),表示从第\( (l-1)\) 层 的 第 \(k \) 个神经元到 第 \(l\) 层的 第 \(j\) 个神经元上的权重。我们这样定义的目的是为了可以用矩阵乘积的方式表示出每一层激活值和上一层激活值之间的关系。
那么我们怎么衡量我们找出的权重和偏置的好坏呢?我们还需要定义一个损失函数:
$$C = \frac{1}{2n} \sum_{\vec{x}} {\left| {y}(\vec{x}) - {a^L}(\vec{x}) \right|}^2$$
其中n是训练样本的总数,但实际操作中由于样本量的巨大如果每次训练都计算所有样本的损失是比较费时间的而且计算效率不高,我们通常的做法是每次随机取出一个batch的样本,在这个batch上计算损失。\({a^L} = {a^L}(\vec{x}) \) 是 当出入为 \( \vec{x} \)时最后输出的激活值向量。我们的最终目标就是找出使得损失函数最小的权重和偏置项。
3.反向传播原理
如何找出这样的权重和偏置?我们最开始可以随机初始化一组权重和偏置项,然后得出一个损失值。而我们知道损失函数是关于权重和偏置的函数,那么我们可以用梯度下降法,从最开始的随机初始的点出发,每次沿着梯度方向前进最终达到谷底。
$$(w_{jk}^{l})_{new} = (w_{jk}^{l})_{old} - \eta \frac{\partial{C}}{\partial{(w_{jk}^{l}})_{old}}$$
$$(b_j^{l})_{new} = (b_j^{l})_{old} - \eta \frac{\partial{C}}{\partial{(b_j^{l}}_{old)}}$$
那么现在的问题是如何计算 \( \frac{\partial{C}}{\partial{w_{jk}^l}} \) 和 \( \frac{\partial{C}}{\partial{b_{j}^l}} \)。这看起来是一件挺复杂的事情,因为这些权重和偏置是一层叠一层的,你很难直观的看出\(w_{jk}^{l} \)是怎样直接影响到损失函数 \(C \)的,接下来我们就要讲清楚这样一件事:怎么计算关于权重和偏置的偏导数。
我们先引入一个中间量\( \delta_j^l\):
$$ \delta_j^l = \frac{\partial{C}}{\partial{z_j^l}}$$
称之为“误差”,为什要叫这个东西误差?假设某个权重参数的微小改变使得 \( z_j^l\) 变成了 \( z_j^l + \Delta z_j^l\) 那么
$$C \to C + \frac{\partial{C}}{\partial{z_j^l}} \Delta z_j^l $$
所以这里有一种启发式的认识,\(\frac{\partial{C}}{\partial{z_j^l}} \)是神经元误差的度量
输出层误差的方程 \( \delta^L\):
$$\delta_j^L = \frac{\partial{C}}{\partial{z_j^L}} = \frac{\partial{C}}{\partial{a_j^L}} \cdot \frac{\partial{a_j^L}}{\partial{z_j^L}} = \frac{\partial{C}}{\partial{a_j^L}} \sigma^{‘}(z_j^L) \ \ \ \ \ \ \ (1)$$
假设我们一个batch里面有N笔数据,那么:
$$C = C_1 + C_2 + … + C_N$$
$$\frac{\partial{C}}{\partial{x}} = \frac{\partial{C_1}}{\partial{x}} + \frac{\partial{C_2}}{\partial{x}}… + \frac{\partial{C_N}}{\partial{x}}$$
对于C关于某项的偏导数可以将其分到N笔数据上计算,现在只考虑对于一笔数据的偏导数:
$$C = \frac{1}{2} \sum_j {(y_j - a_j^L)}^2$$
所以:
$$\frac{\partial{C}}{\partial{a_j^L}} = a_j^L - y_j$$
注意这里我们还并没有说明采用哪个激活函数,所以 \( \sigma^{‘}(z_j^L)\) 暂且不表示出具体形式。
上面表示的是分量形式,我们更期望用矩阵的形式表示:
$$\vec{\delta^L} = \nabla_aC \odot \sigma^{‘}(\vec{z^L}) = (\vec{a^L}-\vec{y})\odot \sigma^{‘}(\vec{z^L})$$
其中\( \odot \)是hadamard乘积,表示按元素乘积。
使用下一层的误差 \( \vec{\delta^{l+1}} \)来表示 当前层的误差 \( \vec{\delta^{l}}\):
$$\delta_j^l = \frac{\partial{C}}{\partial{z_j^l}} = \frac{\partial{C}}{\partial{a_j^l}} \sigma^{‘}(z_j^l) = \left[ \sum_k (\frac{\partial{C}}{\partial{z_k^{l+1}}} \cdot \frac{\partial{z_k^{l+1}}}{\partial{a_j^l}}) \right] \cdot \sigma^{‘}(z_j^l) = \sum_k (\delta_k^{l+1} \cdot w_{kj}^{l+1}) \cdot \sigma^{‘}(z_j^l)$$
上面只是分量形式,同样的我们可以用矩阵表示:
$$\vec{\delta^l} = \left({(W^{l+1})}^T \vec{\delta^{l+1}} \right) \odot \sigma^{‘}(\vec{z^l}) \ \ \ \ \ \ (2)$$
那么结合(1)(2),我们可以得出任何层的误差 \(\delta^l \),首先计算出 \( \delta^L \),然后应用(2),计算出 \( \delta^{L-1} \) 这样一步一步,一层一层反向传播到第\(l\)层。
损失函数关于网络中任意偏置的偏导数:
$$\frac{\partial{C}}{\partial{b_j^l}} = \frac{\partial{C}}{\partial{z_j^l}} \cdot \frac{\partial{z_j^l}}{\partial{b_j^l}}=\delta_j^l \ \ \ \ \ \ \ (3)$$
我们可以看到误差 \( \delta_j^l \)和\(\frac{\partial{C}}{\partial{b_j^l}} \)是完全一样的,这就非常令人兴奋了。同样的将其写成矩阵形式。
$$\frac{\partial{C}}{\partial{\vec{b^l}}} = \vec{\delta^{l}}$$
损失函数关于任意一个权重的偏导数:
$$\frac{\partial{C}}{\partial{w_{jk}^{l}}} = \frac{\partial{C}}{\partial{a_{j}^{l}}} \cdot \frac{\partial{a_j^{l}}}{\partial{z_{j}^{l}}} \cdot \frac{\partial{z_j^{l}}}{\partial{w_{jk}^{l}}} = \delta_j^{l} \cdot a_k^{l-1} \ \ \ \ \ (4)$$
那么根据上面这四个等式我们就可以求出损失函数关于任意一个权重和偏置的偏导数,这也是它为什么被叫做反向传播的原因,我们从最后一层开始一层一层反向向前计算误差,并通过误差这个中间量计算损失函数关于权重和偏置的偏导数。
4.算法
- 输入\( \vec{x} \): 定义 \( a^1(\vec{x}) = \vec{x} \)
- 前向传播: 对每一层 \(l=1,2,3…L\),计算相应的 \( \vec{z_l}= {W^l} \cdot \vec{a^{l-1}} + \vec{b^l}\)和 \(\vec{a^l} = \sigma(\vec{z^l}) \) 我们需要把每一层的 \( \vec{a^l}, \vec{z^l}\)都保存下来,因为后面计算损失时需要用到。
- 输出层误差 \( \vec{\delta^L}\): \( \vec{\delta^L} = \nabla_aC \odot \sigma^{‘}(\vec{z^L}) \)
- 反向误差传播: 对每一个 \( l = L-1, L-2,…,2\) 计算
$$\vec{\delta^l} = \left({(W^{l+1})}^T \vec{\delta^{l+1}} \right) \odot \sigma^{‘}(\vec{z^l})$$ - 损失函数梯度:
$$\frac{\partial{C}}{\partial{w_{jk}^{l}}} = \delta_j^{l} \cdot a_k^{l-1} $$
$$\frac{\partial{C}}{\partial{b_j^l}} = \delta_j^l$$