前面学习的内容,都是因为“过去”的时间步的状态对“未来”的时间步的状态有影响,在本节中,我们将学习一种双向影响的结构,即双向循环神经网络。
比如在一个语音识别的模型中,可能前面的一个词听上去比较模糊,会产生多个猜测,但是后面的词都很清晰,于是可以用后面的词来为前面的词提供一个最有把握(概率最大)的猜测。再比如,在手写识别应用中,前面的笔划与后面的笔划是相互影响的,特别是后面的笔划对整个字的识别有较大的影响。
在本节中会出现两组相似的词:前向计算、反向传播、正向循环、逆向循环。区别如下:
- 前向计算:是指神经网络中通常所说的前向计算,包括正向循环的前向计算和逆向循环的前向计算。
- 反向传播:是指神经网络中通常所说的反向传播,包括正向循环的反向传播和逆向循环的反向传播。
- 正向循环:是指双向循环神经网络中的从左到右时间步。在正向过程中,会存在前向计算和反向传播。
- 逆向循环:是指双向循环神经网络中的从右到左时间步。在逆向过程中,也会存在前向计算和反向传播。
很多资料中关于双向循环神经网络的示意如图19-22所示。
图19-22 双向循环神经网络结构图(不正确)
在图19-22中,$h_{tn}$中的n表示时间步,在图中取值为1至4。
-
$h_{t1}$ 至$h_{t4}$是正向循环的四个隐层状态值,$U$、$V$、$W$ 分别是它们的权重矩阵值; -
$h'_ {t1}$ 至$h'_ {t4}$是逆向循环的四个隐层状态值,$U'$、$V'$、$W'$ 分别是它们的权重矩阵值; -
$S_{t1}$ 至$S_{t4}$是正逆两个方向的隐层状态值的和。
但是,请大家记住,图19-22和上面的相关解释是不正确的!主要的问题集中在
正向循环的最后一个时间步$h_{t4}$和逆向循环的第一个时间步$h_{t4}'$共同生成$s_{t4}$,这也是不对的。因为对于正向循环来说,用
正确的双向循环神经网络图应该如图19-23所示。
图19-23 双向循环神经网络结构图
用$h1/s1$表示正向循环的隐层状态,$U1$、$W1$表示权重矩阵;用$h2/s2$表示逆向循环的隐层状态,$U2$、$W2$表示权重矩阵。$s$ 是
请注意上下两组$x_{t1}$至$x_{t4}$的顺序是相反的:
- 对于正向循环的最后一个时间步来说,$x_{t4}$ 作为输入,$s1_{t4}$是最后一个时间步的隐层值;
- 对于逆向循环的最后一个时间步来说,$x_{t1}$ 作为输入,$s2_{t4}$是最后一个时间步的隐层值;
- 然后
$s1_{t4}$ 和$s2_{t4}$ 拼接得到$s_{t4}$ ,再通过与权重矩阵$V$ 相乘得出$Z$ 。
这就解决了图19-22中的逆向循环在第一个时间步的输出不准确的问题,对于两个方向的循环,都是用最后一个时间步的输出。
图19-23中的
如果需要在每个时间步都有输出,那么图19-23也是一种合理的结构,而图19-22就无法解释了。
我们先假设应用场景只需要在最后一个时间步有输出(比如19.4节和19.5节中的应用就是如此),所以t2所代表的所有中间步都没有a、loss、y三个节点(用空心的圆表示),只有最后一个时间步有输出。
与前面的单向循环网络不同的是,由于有逆向网络的存在,在逆向过程中,t3是第一个时间步,t1是最后一个时间步,所以t1也应该有输出。
注意公式1在t1时,$s1_{t-1}$是空,所以加法的第二项不存在。
注意公式3在t1时,$s2_{t-1}$是空,所以加法的第二项不存在。而且
公式5有几种实现方式,比如sum(矩阵求和)、concat(矩阵拼接)、mul(矩阵相乘)、ave(矩阵平均),我们在这里使用矩阵求和,这样在反向传播时的公式比较容易推导。
公式4、5、6、7只在最后一个时间步发生。
由于是双向的,所以在主过程中,存在一正一反两个计算链,1表示正向,2表示逆向,3表示输出时的计算。
class timestep(object):
def forward_1(self, x1, U1, bU1, W1, prev_s1, isFirst):
...
def forward_2(self, x2, U2, bU2, W2, prev_s2, isFirst):
...
def forward_3(self, V, bV, isLast):
...
先推导正向循环的反向传播公式,即关于h1、s1节点的计算。
对于最后一个时间步(即$\tau$):
对于其它时间步来说$dz_t=0$,因为不需要输出。
因为$s=s1 + s2$,所以$\frac{\partial s}{\partial s1}=1$,代入下面的公式中:
其中,下标$\tau$表示最后一个时间步,$\sigma'(s1)$表示激活函数的导数,$s1$是激活函数的数值。下同。
比较公式9和19.3节通用循环神经网络模型中的公式9,形式上是完全相同的,原因是$\frac{\partial s}{\partial s1}=1$,并没有给我们带来任何额外的计算,所以关于其他时间步的推导也应该相同。
对于中间的所有时间步,除了本时间步的$loss_t$回传误差外,后一个时间步的$h1_{t+1}$也会回传误差:
公式10中的$dh1_{t+1}$,就是上一步中计算得到的$dh1_t$,如果追溯到最开始,即公式9中的$dh1_\tau$。因此,先有最后一个时间步的$dh1_\tau$,然后依次向前推,就可以得到所有时间步的$dh1_t$。
对于$V$来说,只有当前时间步的损失函数会给它反向传播的误差,与别的时间步没有关系,所以有:
对于$U1$,后面的时间步都会给它反向传播误差,但是我们只从$h1$节点考虑:
对于$W1$,和$U1$的考虑是一样的,只从当前时间步的$h1$节点考虑:
对于第一个时间步,$s1_{t-1}$不存在,所以没有$dW1$:
逆向循环的反向传播和正向循环一模一样,只是把
为了与单向的循环神经网络比较,笔者在Level3_Base的基础上实现了一个MNIST分类,超参如下:
net_type = NetType.MultipleClassifier # 多分类
output_type = OutputType.LastStep # 只在最后一个时间步输出
num_step = 28
eta = 0.005 # 学习率
max_epoch = 100
batch_size = 128
num_input = 28
num_hidden = 32 # 隐层神经元32个
num_output = 10
得到的训练结果如下:
...
99:42784:0.005000 loss=0.212298, acc=0.943200
99:42999:0.005000 loss=0.200447, acc=0.947200
save last parameters...
testing...
loss=0.186573, acc=0.948800
load best parameters...
testing...
loss=0.176821, acc=0.951700
最好的时间点的权重矩阵参数得到的准确率为95.17%,损失函数值为0.176821。
eta = 0.01
max_epoch = 100
batch_size = 128
num_step = 28
num_input = 28
num_hidden1 = 20 # 正向循环隐层神经元20个
num_hidden2 = 20 # 逆向循环隐层神经元20个
num_output = 10
得到的结果如图19-23所示。
图19-23 训练过程中损失函数值和准确率的变化
下面是打印输出:
...
save best parameters...
98:42569:0.002000 loss=0.163360, acc=0.955200
99:42784:0.002000 loss=0.164529, acc=0.954200
99:42999:0.002000 loss=0.163679, acc=0.955200
save last parameters...
testing...
loss=0.144703, acc=0.958000
load best parameters...
testing...
loss=0.146799, acc=0.958000
最好的时间点的权重矩阵参数得到的准确率为95.59%,损失函数值为0.153259。
表19-12 单向和双向循环神经网络的比较
单向 | 双向 | |
---|---|---|
参数个数 | 2281 | 2060 |
准确率 | 95.17% | 95.8% |
损失函数值 | 0.176 | 0.144 |
ch19, Level7
其中,Level7_Base_MNIST.py是单向的循环神经网络,Level7_BiRnn_MNIST.py是双向的循环神经网络。