GRU 介绍
循环神经网络中如何通过时间反向传播?中介绍了循环神经网络中的梯度计算方法。我们发现,当时间步数较大或者时间步较小时,循环神经网络的梯度较容易出现衰减或爆炸。虽然裁剪梯度可以应对梯度爆炸,但无法解决梯度衰减的问题。通常由于这个原因,循环神经网络在实际中较难捕捉时间序列中时间步距离较大的依赖关系。
门控循环单元
门控循环神经网络(gated recurrent neural network)的提出,正是为了更好地捕捉时间序列中时间步距离较大的依赖关系。它通过可以学习的门来控制信息的流动。其中,门控循环单元(gated recurrent unit,GRU)是一种常用的门控循环神经网络。
重置门和更新门
如下图所示,门控循环单元中的重置门和更新门的输入均为当前时间步输入
与上一时间步隐藏状态
,输出由激活函数为 sigmoid 函数的全连接层计算得到。
具体来说,假设样本数为
、输入个数为
(即每个样本包含的元素数,一般为词典大小)、隐藏单元个数为
,给定时间步
的小批量输入
和上一时间步隐藏状态
。重置门
和更新门
的计算如下:
其中 和 是权重参数, 是偏差参数。sigmoid 函数可以将元素的值变换到0和1之间。因此,重置门 和更新门 中每个元素的值域都是 。
候选隐藏状态
接下来,门控循环单元将计算候选隐藏状态来辅助稍后的隐藏状态计算。如下图所示,我们将当前时间步重置门的输出与上一时间步隐藏状态做按元素乘法(符号为
)。如果重置门中元素值接近0,那么意味着重置对应隐藏状态元素为0,即丢弃上一时间步的隐藏状态。如果元素值接近1,那么表示保留上一时间步的隐藏状态。然后,将按元素乘法的结果与当前时间步的输入连结,再通过含激活函数 tanh 的全连接层计算出候选隐藏状态,其所有元素的值域为
。
具体来说,时间步
的候选隐藏状态
的计算为:
其中 和 是权重参数, 是偏差参数。从上面这个公式可以看出,重置门控制了上一时间步的隐藏状态如何流入当前时间步的候选隐藏状态。而上一时间步的隐藏状态可能包含了时间序列截至上一时间步的全部历史信息。因此,重置门可以用来丢弃与预测无关的历史信息。
隐藏状态
最后,时间步 的隐藏状态 的计算使用当前时间步的更新门 来对上一时间步的隐藏状态 和当前时间步的候选隐藏状态 做组合:
值得注意的是,更新门可以控制隐藏状态应该如何被包含当前时间步信息的候选隐藏状态所更新,如上图所示。假设更新门在时间步
到
(
)之间一直近似于1。那么,在时间步
到
之间的输入信息几乎没有流入时间步
的隐藏状态
。实际上,这可以看作是较早时刻的隐藏状态
一直通过时间保存并传递至当前时间步
。这个设计可以应对循环神经网络中的梯度衰减问题,并更好地捕捉时间序列中时间步距离较大的依赖关系。
输出结果
将上面得到的隐藏状态
输入神经网络,得到结果
:
总结来说:
- 重置门有助于捕捉时间序列里短期的依赖关系;
- 更新门有助于捕捉时间序列里长期的依赖关系。
低级API实现GRU
在Tensorflow2.0之从零开始实现循环神经网络中,我们已经实现了用低级 API 实现 RNN,在这里,我们只需要将其中的两个函数稍作修改即可。
1、修改定义参数函数
将 get_params() 函数修改为:
def get_param():
def _one(shape):
return tf.Variable(tf.random.normal(shape=shape,
stddev=0.01,
mean=0,
dtype=tf.float32))
def _three():
return (_one(shape=(num_inputs, num_hiddens)),
_one(shape=(num_hiddens, num_hiddens)),
tf.Variable(tf.zeros(shape=(num_hiddens, ), dtype=tf.float32)))
W_xr, W_hr, b_r = _three()
W_xz, W_hz, b_z = _three()
W_xh, W_hh, b_h = _three()
W_hq = _one(shape=(num_hiddens, num_outputs))
b_q = tf.Variable(tf.zeros(shape=(num_outputs, ), dtype=tf.float32))
params = [W_xr, W_hr, b_r, W_xz, W_hz, b_z, W_xh, W_hh, b_h, W_hq, b_q]
return params
2、修改定义模型函数
将 rnn(inputs, state, params) 函数修改为:
def gru(inputs, state, params):
W_xr, W_hr, b_r, W_xz, W_hz, b_z, W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
for X in inputs:
X = tf.reshape(X,[-1,W_xh.shape[0]])
R = tf.sigmoid(tf.matmul(X, W_xr)+tf.matmul(H, W_hr)+b_r)
Z = tf.sigmoid(tf.matmul(X, W_xz)+tf.matmul(H, W_hz)+b_z)
H_tilda = tf.tanh(tf.matmul(X, W_xh)+tf.matmul(R*H, W_hh)+b_h)
H = Z*H+(1-Z)*H_tilda
Y = tf.matmul(H, W_hq)+bq
outputs.append(Y)
return outputs, (H, )
Tensorflow2.0 实现GRU
在Tensorflow2.0之用循环神经网络生成周杰伦歌词中,我们已经用 Tensorflow2.0 中提供的高级 API 实现了循环神经网络,在这里,我们只需要修改实例化 RNN 的部分即可。
也就是将:
cell = keras.layers.SimpleRNNCell(num_hiddens,
kernel_initializer='glorot_uniform')
rnn_layer = keras.layers.RNN(cell,time_major=True,
return_sequences=True,return_state=True)
修改为:
rnn_layer = keras.layers.GRU(num_hiddens,time_major=True,
return_sequences=True,return_state=True)