1、背景
裁剪梯度可以应对梯度爆炸,但无法解决梯度衰减的问题。门控循环神经网络(gated recurrent neural network)的提出,是为了更好地捕捉时间序列中时间步距离较大的依赖关系。其中,门控循环单元(gated recurrent unit,简称GRU)是一种常用的门控循环神经网络 。(其他门控循环神经网络如:长短期记忆)
2、相对RNN的改进
入了门的概念,修改了循环神经网络中隐藏状态的计算方式,输出层的设计不变。
(1)重置门和更新门
激活函数σ是sigmoid函数,重置门 和更新门 中每个元素的值域都是[0,1]。
(2)候选隐藏状态
如果重置门近似0,上一个隐藏状态将被丢弃。因此,重置门可以丢弃与预测未来无关的历史信息。(⊙是按元素乘法符)
(3)隐藏状态
更新门可以控制隐藏状态应该如何被包含当前时间步信息的候选隐藏状态所更新。假设更新门在时间步 到 ( < )之间一直近似1。那么,在时间步 到 之间的输入信息几乎没有流入时间步t的隐藏状态 , 实际上,这可以看作是较早时刻的隐藏状态 一直通过时间保存并传递至当前时间步t。 这个设计可以应对循环神经网络中的梯度衰减问题,并更好地捕捉时间序列中时间步距离较大的依赖关系。
3、总结
重置门有助于捕捉时间序列里短期的依赖关系。更新门有助于捕捉时间序列里长期的依赖关系。
4、代码
ctx = gb.try_gpu()
num_inputs = vocab_size
num_hiddens = 256
num_outputs = vocab_size
def get_params():
# 更新门参数。
W_xz = nd.random_normal(scale=0.01, shape=(num_inputs, num_hiddens),
ctx=ctx)
W_hz = nd.random_normal(scale=0.01, shape=(num_hiddens, num_hiddens),
ctx=ctx)
b_z = nd.zeros(num_hiddens, ctx=ctx)
# 重置门参数。
W_xr = nd.random_normal(scale=0.01, shape=(num_inputs, num_hiddens),
ctx=ctx)
W_hr = nd.random_normal(scale=0.01, shape=(num_hiddens, num_hiddens),
ctx=ctx)
b_r = nd.zeros(num_hiddens, ctx=ctx)
# 候选隐藏状态参数。
W_xh = nd.random_normal(scale=0.01, shape=(num_inputs, num_hiddens),
ctx=ctx)
W_hh = nd.random_normal(scale=0.01, shape=(num_hiddens, num_hiddens),
ctx=ctx)
b_h = nd.zeros(num_hiddens, ctx=ctx)
# 输出层参数。
W_hy = nd.random_normal(scale=0.01, shape=(num_hiddens, num_outputs),
ctx=ctx)
b_y = nd.zeros(num_outputs, ctx=ctx)
params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hy, b_y]
for param in params:
param.attach_grad()
return params
def gru_rnn(inputs, H, *params):
W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hy, b_y = params
outputs = []
for X in inputs:
Z = nd.sigmoid(nd.dot(X, W_xz) + nd.dot(H, W_hz) + b_z)
R = nd.sigmoid(nd.dot(X, W_xr) + nd.dot(H, W_hr) + b_r)
H_tilda = nd.tanh(nd.dot(X, W_xh) + R * nd.dot(H, W_hh) + b_h)
H = Z * H + (1 - Z) * H_tilda
Y = nd.dot(H, W_hy) + b_y
outputs.append(Y)
return (outputs, H)