深度学习之循环神经网络(10)GRU简介
LSTM具有更长的记忆能力,在大部分序列任务上面都取得了比基础RNN模型更好的性能表现,更重要的是,LSTM不容易出现梯度弥散现象。但是LSTM结构相对较复杂,计算代价较高,模型参数量较大。因此科学家们尝试简化LSTM内部的计算流程,特别是减少门控数量。研究发现,遗忘门是LSTM中最重要的门控 [1],甚至发现只有遗忘门的简化版网络在多个基准数据集上面优于标准LSTM网络。在众多的简化版LSTM中, 门控循环网络(Gated Recurrent Unit,简称GRU)是应用最广泛的RNN变种之一。GRU把内部状态向量和输出向量合并,统一为状态向量 h \boldsymbol h h,门控数量也较少到2个: 复位门(Reset Gate)和 更新门(Update Gate),如下图所示:

下面我们来分别介绍复位门和更新门的原理与功能。
[1] J. Westhuizen 和 J. Lasenby, “The unreasonable effectiveness of the forget gate,” CoRR, 卷 abs/1804.04849, 2018.
1. 复位门
复位门用于控制上一个时间戳的状态 h t − 1 \boldsymbol h_{t-1} ht−1进入GRU的量。门控向量 g r \boldsymbol g_r gr由当前时间戳输入 x t \boldsymbol x_t xt和上一时间戳状态 h t − 1 \boldsymbol h_{t-1} ht−1变换得到,关系如下:
g r = σ ( W r [ h t − 1 , x t ] + b r ) \boldsymbol g_r=σ(\boldsymbol W_r [\boldsymbol h_{t-1},\boldsymbol x_t ]+\boldsymbol b_r) gr=σ(Wr[ht−1,xt]+br)
其中 W r \boldsymbol W_r Wr和 b r \boldsymbol b_r br为复位门的参数,由反向传播算法自动优化, σ σ σ为激活函数,一般使用Sigmoid函数。门控向量 g r = 0 \boldsymbol g_r=0 gr=0时,新输入 h ~ t \tilde \boldsymbol h_t h~t全部来自于输入 x t \boldsymbol x_t xt,不接受 h t − 1 \boldsymbol h_{t-1} ht−1,此时相当于复位 h t − 1 \boldsymbol h_{t-1} ht−1。当 g r = 1 \boldsymbol g_r=1 gr=1时, h t − 1 h_{t-1} ht−1和输入 x t \boldsymbol x_t xt共同产生新输入 h ~ t \tilde\boldsymbol h_t h~t,如下图所示:

2. 更新门
更新门用控制上一时间戳状态 h t − 1 \boldsymbol h_{t-1} ht−1和新输入 h ~ t \tilde\boldsymbol h_t h~t对新状态向量 h t \boldsymbol h_t ht的影响程度。更新门控向量 g z \boldsymbol g_z gz由
g z = σ ( W z [ h t − 1 , x t ] + b z ) \boldsymbol g_z=σ(\boldsymbol W_z [\boldsymbol h_{t-1},\boldsymbol x_t ]+\boldsymbol b_z) gz=σ(Wz[ht−1,xt]+bz)
得到,其中 W z \boldsymbol W_z Wz和 b z \boldsymbol b_z bz为更新门的参数,由反向传播算法自动优化, σ σ σ为激活函数,一般使用Sigmoid函数。 g z \boldsymbol g_z gz用于控制新输入 h ~ t \tilde\boldsymbol h_t h~t信号, 1 − g z 1-\boldsymbol g_z 1−gz用于控制状态 h t − 1 \boldsymbol h_{t-1} ht−1信号:
h t = ( 1 − g z ) h t − 1 + g z h ~ t \boldsymbol h_t=(1-\boldsymbol g_z ) \boldsymbol h_{t-1}+\boldsymbol g_z \tilde\boldsymbol h_t ht=(1−gz)ht−1+gzh~t

可以看到, h ~ t \tilde\boldsymbol h_t h~t和 h t − 1 \boldsymbol h_{t-1} ht−1的更新量处于相互竞争、此消彼长的状态。当更新门 g z = 0 \boldsymbol g_z=0 gz=0时, h t \boldsymbol h_t ht全部来自上一时间戳状态 h t − 1 \boldsymbol h_{t-1} ht−1;当更新门 g z = 1 \boldsymbol g_z=1 gz=1时, h t \boldsymbol h_t ht全部来自新输入 h ~ t \tilde\boldsymbol h_t h~t。
3. GRU使用方法
同样地,在TensorFlow中,也有Cell方式和层方式实现GRU网络。GRUCell和GRU层的使用方法和之前的SimpleRNNCell、LSTMCell、SimpleRNN和LSTM非常类似。首先是GRUCell的使用,创建GRU Cell对象,并在时间轴上循环展开运算。例如:
import tensorflow as tf
from tensorflow.keras import layers
x = tf.random.normal([2, 80, 100])
xt = x[:, 0, :] # 得到一个时间戳的输入
# 初始化状态向量,GRU只有一个
h = [tf.zeros([2, 64])]
cell = layers.GRUCell(64) # 新建GRU Cell,向量长度为64
# 在时间戳维度上解开,循环通过cell
for xt in tf.unstack(x, axis=1):
out, h = cell(xt, h)
# 输出形状
print(out.shape)
运行结果如下所示:
(2, 64)
通过layers.GRU类可以方便创建一层GRU网络层,通过Sequential容器可以堆叠多层GRU层的网络。例如:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential
x = tf.random.normal([2, 80, 100])
xt = x[:, 0, :] # 得到一个时间戳的输入
# 初始化状态向量,GRU只有一个
h = [tf.zeros([2, 64])]
net = keras.Sequential([
layers.GRU(64, return_sequences=True),
layers.GRU(64)
])
out = net(x)
# 输出形状
print(out.shape)
运行结果如下所示:
(2, 64)