一个小的例子mini_char_rnn
我们主要看代码中的backward部分:
dh = np.dot(Why.T, dy) + dhnext
dhraw = (1 - hs[t] * hs[t]) * dh
dhnext = np.dot(Whh.T, dhraw)
首先定义loss function:
E =
∑nt=0softmax−cross−ntropy−loss(yt,labelst)
这里n是batchsize,y为输出,损失函数为softmax交叉熵,RNN的BP和传统的BP的不同在于
ht
是和
ht−1
相关的,所以我们关心的是
∂y∂h
而
∂E∂y
和传统的BP并无两样,单隐层RNN的forward如下:
ht=f(Wxhxt+Whhht−1+bh)
yt=htWhy+by
所以在backward中
∂E∂ht=∂E∂yt∂yt∂ht+∂E∂yt−1∂yt−1∂ht−1
后面一项正是代码中的dhnext,我们可以把
∑nt=0yt
展开来看:
yt=htWhy+by=f(Wxhxt+Whhht−1+bh)
yt−1=ht−1Why+by=f(Wxhxt−1+Whhht−2+bh)
yt−2=ht−2Why+by=f(Wxhxt−2+Whhht−3+bh)
yt−3=ht−3Why+by=f(Wxhxt−3+Whhht−4+bh)
yt−4=ht−4Why+by=f(Wxhxt−4+Whhht−5+bh)
……
我们可以看到
ht
出现在
yt
中(因为t已经是最后一项了)
ht−1
出现在
yt−1
和
yt
中
ht−2
出现在
yt−2
和
yt−1
中
ht−3
出现在
yt−3
和
yt−2
中
正是因为RNN在时间轴上是关联的,所以前一时刻的输出和当前时刻的输入是相关联的,这也反映到了RNN的BP中,就是当前时刻的梯度是和前一时刻的梯度相关的