反向传播算法是神经网络中最重要的部分之一。
对于上图中的这个网络,有三个输入层input layer,四个隐藏层hidden layer,一个输出层output layer。
使用\(n_i\)来表示网络的层数,\(L_{n_i}\)代表第i层,\(W^{(l)}_{ij}\)代表第\(l\)层中第\(j\)个单元与第\(l+1\)层中第\(i\)个单元之间连接的权重weight(注意顺序),\(b^{(l)}_i\)代表\(l+1\)层中第\(i\)个单元的偏离值bias
使用\(a^{(l)}_i\)代表第\(l\)层中第\(i\)个单元的输出值(经过了激活函数),以及\(a^{(1)}_i = x_i\)来表示输入,(在这里层数从1开始,注意与上图中的标号不一致)。于是我们就可以得到如下结果:
$$a_1^{(2)} = f(W_{11}^{(1)}x_1 + W_{12}^{(1)} x_2 + W_{13}^{(1)} x_3 + b_1^{(1)})$$ $$a_2^{(2)} = f(W_{21}^{(1)}x_1 + W_{22}^{(1)} x_2 + W_{23}^{(1)} x_3 + b_2^{(1)})$$ $$a_3^{(2)} = f(W_{31}^{(1)}x_1 + W_{32}^{(1)} x_2 + W_{33}^{(1)} x_3 + b_3^{(1)})$$ $$a_4^{(2)} = f(W_{41}^{(1)}x_1 + W_{42}^{(1)} x_2 + W_{43}^{(1)} x_3 + b_4^{(1)})$$ $$h_{W,b}(x) = a_1^{(3)} = f(W_{11}^{(2)}a_1^{(2)} + W_{12}^{(2)} a_2^{(2)} + W_{13}^{(2)} a_3^{(2)} + W_{14}^{(2)} a_4^{(2)} + b_1^{(2)})$$
使用这个方法一直计算就可以得到每一层的输出值,这个过程称为正向传播forward propagation或forward pass。
反向传播是用来优化每一层的参数的 首先定义一个代价函数cost function用来表示输出与预期的差值。对每个训练集(x, y),定义代价函数: $$J(W,b; x,y) = \frac{1}{2} \left| h_{W,b}(x) - y \right|^2.$$ 对整个训练集,加上正则项来控制过拟合后的代价函数为: $$J(W,b) = \left[ \frac{1}{m} \sum_{i=1}^m \left( \frac{1}{2} \left| h_{W,b}(x^{(i)}) - y^{(i)} \right|^2 \right) \right] + \frac{\lambda}{2} \sum_{l=1}^{n_l-1} \ \sum_{i=1}^{s_l} \ \sum_{j=1}^{s_{l+1}} \left( W^{(l)}_{ji} \right)^2 $$
使用梯度下降来更新参数的方法: $$W_{ij}^{(l)} = W_{ij}^{(l)} - \alpha \frac{\partial}{\partial W_{ij}^{(l)}} J(W,b)$$ $$b_{i}^{(l)} = b_{i}^{(l)} - \alpha \frac{\partial}{\partial b_{i}^{(l)}} J(W,b)$$
上面方法的关键步骤是求出偏导数,而反向传播算法就是一个非常有效并且粗暴的方法。 1. 对于输出层,计算误差\(\delta^{(l)}\) 2. 从后向前,对于\(l = n_l-1, n_l-2, n_l-3, \ldots, 2\),计算:\(\delta^{(l)} = \left((W^{(l)})^T \delta^{(l+1)}\right) \bullet f’(z^{(l)})\)
- 计算偏导数: $$\nabla_{W^{(l)}} J(W,b;x,y) = \delta^{(l+1)} (a^{(l)})^T$$ $$\nabla_{b^{(l)}} J(W,b;x,y) = \delta^{(l+1)}$$
对于最上图的三层神经网络,可以有:
# Forward pass
hidden_inputs = np.dot( X, weights_0_1 )
hidden_outputs = sigmoid(hidden_inputs)
final_inputs = np.dot( hidden_outputs, weights_1_2 )
final_outputs = final_inputs
# Backpropagation Algorithm
delta_output_layer = -(y - final_outputs) * final_outputs * (1 - final_outputs)
delta_hidden_layer = np.dot(delta_output_layer, weights_1_2.T) * hidden_outputs * (1 - hidden_outputs)
delta_weights_1_2 = np.dot(hidden_outputs.T, delta_output_layer)
delta_weights_0_1 = np.dot(X.T, delta_hidden_layer)
# Update the weights
weights_0_1 -= lr / n_records * delta_weights_0_1
weights_1_2 -= lr / n_records * delta_weights_1_2
算法参考 UFLDL