当网络给出预测之后,需要根据预测值与实际标签的差异调整网络中的权重和偏置,以便模型在将来能够更好地预测。这个调整过程称为反向传播(误差计算 → 梯度计算 → 参数更新)。

神经网络的结构·

假设一共有 LL 层网络,激活函数为 σ\sigmazjlz_j^l 表示未激活的状态,ajla_j^l 表示激活后的状态。

zj(l)=k=1Kωjk(l)ak(l1)+bj(l)aj(l)=σ(zj(l))\begin{aligned} z_j^{(l)} &= \sum_{k = 1}^K{\omega_{jk}^{(l)}a_k^{(l-1)}+b_j^{(l)}}\\ a_j^{(l)} &= \sigma(z_j^{(l)}) \end{aligned}

损失函数为

C=12ya(L)2=12k=1K(ykak(L))2C = \frac{1}{2}||y-a^{(L)}||^2 = \frac{1}{2}\sum_{k = 1}^{K}(y_k-a_k^{(L)})^2

损失函数对 aj(L)a_j^{(L)} 的偏导数为

Caj(L)=aj(L)yj\frac{\partial C}{\partial a_j^{(L)}}= a_j^{(L)}-y_j

基本方程·

为了实现参数更新,我们需要计算 Cωjk(l)\dfrac{\partial C}{\partial \omega_{jk}^{(l)}}Cbj(l)\dfrac{\partial C}{\partial b_j^{(l)}}

Cωjk(l)=Czj(l)zj(l)ωjk(l)=Czj(l)ak(l1)Cbj(l)=Czj(l)zj(l)bj(l)=Czj(l)1\begin{aligned} \dfrac{\partial C}{\partial \omega_{jk}^{(l)}} &=\dfrac{\partial C}{\partial z_j^{(l)}}\dfrac{\partial z_j^{(l)}}{\partial \omega_{jk}^{(l)}}= \dfrac{\partial C}{\partial z_j^{(l)}}a_k^{(l-1)}\\ \dfrac{\partial C}{\partial b_j^{(l)}} &= \dfrac{\partial C}{\partial z_j^{(l)}}\dfrac{\partial z_j^{(l)}}{\partial b_j^{(l)}}= \dfrac{\partial C}{\partial z_j^{(l)}}\cdot 1 \end{aligned}

其中涉及到激活函数即 aj(l)=σ(zj(l))a_j^{(l)}=\sigma(z_j^{(l)}),为了简化计算,先定义一个中间变量 δj(l)\delta_j^{(l)}

δj(l)=Czj(l)\delta_j^{(l)} = \frac{\partial C}{\partial z_j^{(l)}}

输出层的 δj(L)\delta_j^{(L)}

δj(L)=Czj(L)=Caj(L)aj(L)zj(L)=(aj(L)yj)σ(zj(L))\delta_j^{(L)} = \frac{\partial C}{\partial z_j^{(L)}}=\frac{\partial C}{\partial a_j^{(L)}}\frac{\partial a_j^{(L)}}{\partial z_j^{(L)}} = (a_j^{(L)}-y_j)\sigma'(z_j^{(L)})

推广到 δ(L)\delta^{(L)},得到

δ(L)=[Ca1(L)σ(z1(L))Ca2(L)σ(z2(L))Caj(L)σ(zj(L))]=aCσ(z(L))\begin{aligned} \delta^{(L)} = \begin{bmatrix} \dfrac{\partial C}{\partial a_1^{(L)}}\sigma'(z_1^{(L)})\\ \dfrac{\partial C}{\partial a_2^{(L)}}\sigma'(z_2^{(L)})\\ \cdots\\ \dfrac{\partial C}{\partial a_j^{(L)}}\sigma'(z_j^{(L)})\\ \end{bmatrix} = \nabla_a C \cdot \sigma'(z^{(L)}) \end{aligned}

对于 L1L-1 层,

δj(L1)=Czj(L1)=Caj(L1)aj(L1)zj(L1)=Caj(L1)σ(zj(L1))=(k=1Kδk(L)ωkj(L))σ(zj(L1))\begin{aligned} \delta_j^{(L-1)} &= \dfrac{\partial C}{\partial z_j^{(L-1)}} =\dfrac{\partial C}{\partial a_j^{(L-1)}}\dfrac{\partial a_j^{(L-1)}}{\partial z_j^{(L-1)}}=\dfrac{\partial C}{\partial a_j^{(L-1)}} \sigma'(z_j^{(L-1)})\\ &= (\sum_{k = 1}^{K}\delta_k^{(L)}\omega_{kj}^{(L)}) \sigma'(z_j^{(L-1)}) \end{aligned}

其中 Caj(L1)\dfrac{\partial C}{\partial a_j^{(L-1)}}aj(L1)a_j^{(L-1)} 影响了图中红线所示部分

Ca1(L1)=k=1KCzk(L)zk(L)a1(L1)=k=1Kδk(L)ωk1(L)\dfrac{\partial C}{\partial a_1^{(L-1)}} =\sum_{k = 1}^{K}\dfrac{\partial C}{\partial z_k^{(L)}}\dfrac{\partial z_k^{(L)}}{\partial a_1^{(L-1)}}=\sum_{k = 1}^{K}\delta_k^{(L)}\omega_{k1}^{(L)}

同理,可以得到

Caj(L1)=k=1KCzk(L)zk(L)aj(L1)=k=1Kδk(L)ωkj(L)\dfrac{\partial C}{\partial a_j^{(L-1)}} =\sum_{k = 1}^{K}\dfrac{\partial C}{\partial z_k^{(L)}}\dfrac{\partial z_k^{(L)}}{\partial a_j^{(L-1)}}=\sum_{k = 1}^{K}\delta_k^{(L)}\omega_{kj}^{(L)}

对于任意第 ll 层,

δj(l)=(k=1Kδk(l+1)ωkj(l+1))σ(zj(l))=((ωj(l+1))Tδ(l+1))σ(zjl)\begin{aligned} \delta_j^{(l)} &= (\sum_{k = 1}^{K}\delta_k^{(l+1)}\omega_{kj}^{(l+1)}) \sigma'(z_j^{(l)})\\ &= ((\omega_j^{(l+1)})^T\delta^{(l+1)}) \sigma'(z_j^{l}) \end{aligned}


下面计算 Cωjk(l)\dfrac{\partial C}{\partial \omega_{jk}^{(l)}}Cbj(l)\dfrac{\partial C}{\partial b_j^{(l)}}

Cωjk(l)=Czj(l)zj(l)ωjk(l)=δj(l)ak(l1)Cbj(l)=Czj(l)zj(l)bj(l)=δj(l)\begin{aligned} \dfrac{\partial C}{\partial \omega_{jk}^{(l)}} &=\dfrac{\partial C}{\partial z_j^{(l)}}\dfrac{\partial z_j^{(l)}}{\partial \omega_{jk}^{(l)}}= \delta_j^{(l)}a_k^{(l-1)}\\ \dfrac{\partial C}{\partial b_j^{(l)}} &= \dfrac{\partial C}{\partial z_j^{(l)}}\dfrac{\partial z_j^{(l)}}{\partial b_j^{(l)}}= \delta_j^{(l)} \end{aligned}

算法流程·

  1. 输入数据
  2. 前向传播

zj(l)=ωj(l)Ta(l1)+bj(l)aj(l)=σ(zj(l))\begin{aligned} z_j^{(l)} &= \omega_{j}^{(l)^T}a^{(l-1)}+b_j^{(l)}\\ a_j^{(l)} &= \sigma(z_j^{(l)}) \end{aligned}

  1. 反向传播误差

δ(L)=aCσ(z(L))δj(l)=((ωj(l+1))Tδ(l+1))σ(zjl)\begin{aligned} \delta^{(L)} &= \nabla_a C \cdot \sigma'(z^{(L)})\\ \delta_j^{(l)} &= ((\omega_j^{(l+1)})^T\delta^{(l+1)}) \sigma'(z_j^{l}) \end{aligned}

  1. 梯度下降,更新参数

ωkj(l)=ωkj(l)αmδj(l)ak(l1)bj(l)=b(l)αmδ(l)\begin{aligned} \omega_{kj}^{(l)} &= \omega_{kj}^{(l)} - \dfrac{\alpha}{m}\delta_j^{(l)}a_k^{(l-1)}\\ b_j^{(l)} &= b^{(l)} - \dfrac{\alpha}{m}\delta^{(l)} \end{aligned}

代码实现·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def backprop(self, x, y):
"""Return a tuple ``(nabla_b, nabla_w)`` representing the
nablaient for the cost function C_x. ``nabla_b`` and
``nabla_w`` are layer-by-layer lists of numpy arrays, similar
to ``self.biases`` and ``self.weights``."""
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
# Forward propagation
activation = x
activations = [x] # list to store all the activations, layer by layer
zs = [] # list to store all the z vectors, layer by layer
for b, w in zip(self.biases, self.weights):
z = np.dot(w, activation)+b
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
# Backward propagation
delta = self.cost_derivative(activations[-1], y) * \
sigmoid_prime(zs[-1])
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
# Note that the variable l in the loop below is used a little
# differently to the notation in Chapter 2 of the book. Here,
# l = 1 means the last layer of neurons, l = 2 is the
# second-last layer, and so on. It's a renumbering of the
# scheme in the book, used here to take advantage of the fact
# that Python can use negative indices in lists.
for l in xrange(2, self.num_layers):
z = zs[-l]
sp = sigmoid_prime(z)
delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())

return (nabla_b, nabla_w)

参考资料·

  1. mnielsen/neural-networks-and-deep-learning