另外运用对数除法运算,上面的求导过程可以简化:
\[
\mathcal{L}(y, a) = -\sum\limits_{i=1}^k y_i\, \text{log}\, a_i = -\sum\limits_{i=1}^k y_i\, \text{log}\, \frac{e^{z_i}}{\sum_c e^{z_c}} = -\sum\limits_{i=1}^k y_i z_i + y_i \text{log} \sum_c e^{z_c}
\]
\[ \frac{\partial {\mathcal{L}}}{\partial z_i} = -y_i + \frac{e^{z_i}}{\sum_c e^{z_c}} = a_i - y_i \]
通常所谓的“学习”指的是通过最小化损失函数进而求得相应参数的过程,神经网络中一般采用梯度下降来实现这个过程,即:
\[
\theta = \theta - \alpha \cdot \frac{\partial}{\partial \theta}\mathcal{L}(\theta)
\]
用神经网络中的常用参数符号替换,并用矩阵形式表示:
\[
\begin{align*}
\mathbf{W}^{(l)} &= \mathbf{W}^{(l)} - \alpha \frac{\partial \mathcal{L}}{\partial\, \mathbf{W}^{(l)}} \\[2ex]
\mathbf{b}^{(l)} &=\,\, \mathbf{b}^{(l)} - \alpha \frac{\partial \mathcal{L}}{\partial\, \mathbf{b}^{(l)}}
\end{align*}
\]
其中 \((l)\) 表示第 \(l\) 层。
导数是梯度的组成部分,通常采用数值微分的方法近似下式:
\[
f'(x) = \lim\limits_{h \rightarrow 0}\frac{f(x + h) - f(x)}{h}
\]
\(f'(x)\) 表示函数 \(f(x)\) 在 \(x\) 处的斜率,但是由于运算时 \(h\) 不可能无限接近于零,上式容易引起数值计算问题,所以实际中常采用中心差分来近似:
\[
f'(x) = \lim\limits_{h \rightarrow 0} \frac{f(x + h) - f(x - h)}{2h}
\]
这样整个梯度的计算可以用以下代码实现:
import numpy as np def numerical_gradient(f, x): # f为函数,x为输入向量 h = 1e-4 grad = np.zeros_like(x) it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite']) while not it.finished: idx = it.multi_index temp = x[idx] x[idx] = temp + h fxh1 = f(x) x[idx] = temp - h fxh2 = f(x) grad[idx] = (fxh1 + fxh2) / (2*h) x[idx] = temp it.iternext() return grad由于数值微分对每个参数都要计算 \(f(x+h)\) 和 \(f(x-h)\) ,假设神经网络中有100万个参数,则需要计算200万次损失函数。如果是使用 SGD,则是每个样本计算200万次,显然是不可承受的。所以才需要反向传播算法这样能够高效计算梯度的方法。
接下来先定义神经网络中前向传播的式子 (\(l\) 表示隐藏层, \(L\) 表示输出层):
\[ \begin{align*} &\mathbf{z}^{(l)} = \mathbf{W}^{(l)} \mathbf{a}^{(l-1)} + \mathbf{b}^{(l)} \tag{2.1} \\[0.5ex] &\mathbf{a}^{(l)} = f(\mathbf{z}^{(l)}) \tag{2.2} \\[0.5ex] &\mathbf{\hat{y}} = \mathbf{a}^{(L)} = f(\mathbf{z}^{(L)}) \tag{2.3} \\[0.5ex] &\mathcal{L} = \mathcal{L}(\mathbf{y}, \mathbf{\hat{y}}) \tag{2.4} \end{align*} \]
现在我们的终极目标是得到 \(\frac{\partial {\mathcal{L}}}{\partial \mathbf{W}^{(l)}}\) 和 \(\frac{\partial \mathcal{L}}{\partial \mathbf{b}^{(l)}}\) ,为了计算方便,先来看各自的分量 \(\frac{\partial {\mathcal{L}}}{\partial {W}_{jk}^{(l)}}\) 和 \(\frac{\partial \mathcal{L}}{\partial b_j^{(l)}}\) 。
这里定义 \(\delta_j^{(l)} = \frac{\partial \mathcal{L}}{\partial z_j^{(l)}}\) , 根据 \((2.1)\) 式使用链式法则:
\[
\begin{align*}
& \frac{\partial {\mathcal{L}}}{\partial {W}_{jk}^{(l)}} = \frac{\partial \mathcal{L}}{\partial z_j^{(l)}} \frac{\partial z_j^{(l)}}{\partial W_{jk}^{(l)}} = \delta_j^{(l)} \frac{\partial}{\partial W_{jk}^{(l)}} \left(\sum_i W_{ji}^{(l)}a_i^{(l-1)}\right) = \delta_j^{(l)} a_k^{(l-1)} \tag{2.5} \\[1ex]
& \frac{\partial {\mathcal{L}}}{\partial {b}_{j}^{(l)}} = \frac{\partial \mathcal{L}}{\partial z_j^{(l)}} \frac{\partial z_j^{(l)}}{\partial b_{j}^{(l)}} =\delta_j^{(l)} \tag{2.6}
\end{align*}
\]
所以接下来的问题就是求 \(\delta_j^{(l)}\) :
(1) 对于输出层 \(L\) :
\[
\delta_j^{L} = \frac{\partial \mathcal{L}}{\partial z_j^{(L)}} = \frac{\partial \mathcal{L}}{\partial a_j^{(L)}} \frac{\partial a_j^{(L)}}{\partial z_j^{(L)}} = \frac{\partial \mathcal{L}}{\partial a_j^{(L)}} f'(z_j^{(L)}) \tag{2.7}
\]