1.概要
本文来自博客 A Neural Network in 13 lines of Python
这篇博客只用了13行代码就实现了Python版本的反向传播,虽然有很多文章都对它进行了翻译,但在最关键反向传播部分,并没有给出详细公式,下面就对代码中的反向传播进行重点介绍。
2.全部反向传播代码
下面是13行完整代码1
2
3
4
5
6
7
8
9
10
11
12
13
14import numpy as np
X = np.array([ [0,0,1],[0,1,1],[1,0,1],[1,1,1] ])
y = np.array([[0,1,1,0]]).T
lr,hidden_dim = (0.5,4)
w0 = 2*np.random.random((3,hidden_dim)) - 1
w1 = 2*np.random.random((hidden_dim,1)) - 1
for j in range(60000):
layer1 = 1/(1+np.exp(-(np.dot(X,w0))))
layer2 = 1/(1+np.exp(-(np.dot(layer1,w1))))
layer2_delta = (layer2 - y)*(layer2*(1-layer2))
layer1_delta = layer2_delta.dot(w1.T) * (layer1 * (1-layer1))
w1 -= (lr * layer1.T.dot(layer2_delta))
w0 -= (lr * X.T.dot(layer1_delta))
print(layer2)
3.关键行解释
变量 | 定义 |
---|---|
X | 输入数据矩阵,每行是一个训练样例 |
y | 输出数据矩阵,每行是一个训练样例 |
layer1 | 神经网络的隐含层,输入来自X |
layer2 | 神经网络的输出层,输入来自layer1 |
w1 | 输出层权重,连接layer1与layer2 |
w0 | 隐含层权重,连接X与layer1 |
line1-6: 前6行都是在定义初始化参数,学习率和权重矩阵等等,这些应该很好理解。
line8-9: 这两行是在进行前向传播,激活函数为sigmoid。
line10-13: 这四行是在进行反向传播,下面来说说代码为什么这么写
首先整体误差:
$E_{total}=\frac{1}{2}(y-out)^2 $
先计算输出层权重矩阵w1,如果我们想知道w1对整体误差产生了多少影响,可以用整体误差对w1求偏导求出:(链式法则)
$\frac{dE}{dw_1} = \frac{dE}{dout}\ast\frac{dout}{dnet_o}\ast\frac{dnet_o}{dw_1}$
分别计算上面的每一项
$\frac{dE}{dout} = -(y-out)$
$\frac{dout}{dnet_o} = out(1-out)$ 这一项就是sigmoid的导数
$\frac{dnet_o}{dw_1}=out_h$ 这一项就是隐含层的输出结果
为了表达方便,$\delta_o$用来表示输出层的误差:
$\delta=\frac{dE}{dout}*\frac{dout}{dnet_o}=\frac{dE}{dnet_o}$
最终的结果可以简化为
$\frac{dE}{dw_1}=\delta_o*out_h$
最后我们来更新w1的值:
$w_1 =w_1-\eta\ast\frac{dE}{dw_1} $
现在让我们回过头来看看代码
layer2_delta = (layer2 - y)* (layer2*(1-layer2))
layer2_delta正是在计算$\delta_0$
w1 -= (lr * layer1.T.dot(layer2_delta))
w1正是在计算$\delta_o\ast out_h$ 然后乘以学习率,在更新到原有的w1上
下面我们再来看看隐含层权重矩阵w0的更新情况
$\frac{dE}{dw_0} = \frac{dE}{dout_h}\ast\frac{dout_h}{dnet_h}\ast\frac{dnet_h}{dw_0}$
总共有三项,我们先计算第一项,将第一项展开
$\frac{dE}{dout_h}=\frac{dE}{dnet_o}\ast\frac{dnet_o}{dout_h}$
再展开$\frac{dE}{dnet_o}$
$\frac{dE}{dnet_o}=\frac{dE}{dout}\ast\frac{dout}{dnet_o}$
仔细看这两项的已经在上面计算输出层权重偏导的时候计算过了
然后我们又知道
$\frac{dnet_o}{dout_h}=w_1$
所以$\frac{dE}{dout_h}$ 这一项已经计算完成,还剩后面两项
$\frac{dout_h}{dnet_h}=out_h\ast(1-out_h)$ 这一项依旧是sigmoid的导数
最后还剩$\frac{dnet_h}{dw_0}$
$\frac{dnet_h}{dw_0}=X$ 这一项就是输入X
最后我们将3项相乘,可以得到结果
为了简化公式,用$\delta_h$表示隐含层单元的误差:
化简公式如下
$\frac{dE}{dw_0}=\delta_o*w_1\ast out_h(1-out_h)\ast X$
$\frac{dE}{dw_0}=\delta_h\ast X$
$w_0 =w_0-\eta\ast\frac{dE}{dw_0} $
现在我们再回到代码,看看更新w0的部分
layer1_delta = layer2_delta.dot(w1.T) (layer1 (1-layer1))
w0 -= (lr * X.T.dot(layer1_delta))
layer1_delta正是在计算$\delta_h$
最终再乘以学习率,完成对隐含层权重w0的更新
以上就是全部的反向传播计算过程。