上⼀节介绍了循环神经网络中的梯度计算方法。我们发现,当时间步数T较大或者当前时间步t较小时,循环神经⽹络的梯度较容易出现衰减或爆炸。虽然裁剪梯度可以应对梯度爆炸,但⽆法解决梯度衰减的问题。 通常由于这个原因,循环神经网络在实际中较难捕捉时间序列中时间步距离较大的依赖关系。
⻔控循环神经网络(gated recurrent neural network)的提出,正是为了更好地捕捉时间序列中时间步距离较大的依赖关系。它通过可以学习的⻔来控制信息的流动。其中,⻔控循环单元(gated recurrent unit,GRU)是一种常⽤的⻔控循环神经⽹络。另一种常⽤的⻔控循环神经网络则将在下一节中介绍。
目录
1. 门控循环单元
下⾯将介绍⻔控循环单元的设计。它引⼊了重置门(reset gate)和更新门(update gate)的概念,从⽽修改了循环神经网络中隐藏状态的计算方式。
- 重置门和更新门
如下图所示,⻔控循环单元中的重置门和更新门的输⼊均为当前时间步输入

具体来说,假设隐藏单元个数为h,给定时间步t的小批量输入




其中


- 候选隐藏状态
接下来,⻔控循环单元将计算候选隐藏状态来辅助稍后的隐藏状态计算。如下图所示,我们将当前时间步重置门的输出与上一时间步隐藏状态做按元素乘法(符号为
具体来说,时间步t的候选隐藏状态

其中


- 隐藏状态
最后,时间步t的隐藏状态


值得注意的是,更新门可以控制隐藏状态应该如何被包含当前时间步信息的候选隐藏状态所更新,如上图所示。假设更新门在时间步

我们对门控循环单元的设计稍作总结:
1)重置⻔有助于捕捉时间序列里短期的依赖关系;
2)更新⻔有助于捕捉时间序列里长期的依赖关系。
2. 读取数据集
为了实现并展示门控循环单元,下⾯依然使用周杰伦歌词数据集来训练模型作词。这里除门控循环单元以外的实现已在(循环神经网络)中介绍过。以下为读取数据集部分。
import numpy as np import torch from torch import nn, optim import torch.nn.functional as F import sys sys.path.append(".") import d2lzh_pytorch as d2l device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') (corpus_indices, char_to_idx, idx_to_char, vocab_size) = d2l.load_data_jay_lyrics() print(torch.__version__, device)
3. 从0开始实现
- 初始化模型参数
num_inputs, num_hiddens, num_outputs = vocab_size, 256, vocab_size print('will use', device) def get_params(): def _one(shape): ts = torch.tensor(np.random.normal(0, 0.01, size=shape), device=device, dtype=torch.float32) return torch.nn.Parameter(ts, requires_grad=True) def _three(): return (_one((num_inputs, num_hiddens)), _one((num_hiddens, num_hiddens)), torch.nn.Parameter(torch.zeros(num_hiddens, device=device, dtype=torch.float32), requires_grad=True)) W_xz, W_hz, b_z = _three() # 更新门参数 W_xr, W_hr, b_r = _three() # 重置门参数 W_xh, W_hh, b_h = _three() # 候选隐藏状态参数 # 输出层参数 W_hq = _one((num_hiddens, num_outputs)) b_q = torch.nn.Parameter(torch.zeros(num_outputs, device=device, dtype=torch.float32), requires_grad=True) return nn.ParameterList([W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq,b_q])
- 定义模型
下面的代码定义隐藏状态初始化函数init_gru_state.同(循环神经⽹络的从零开始实现)中定义的init_rnn_state函数一样,它返回由一个形状为(批量⼤小, 隐藏单元个数)的值为0的tensor组成的元组。
def init_gru_state(batch_size, num_hiddens, device): return (torch.zeros((batch_size, num_hiddens), device=device), )
下⾯根据⻔控循环单元的计算表达式定义模型。
def gru(inputs, state, params): W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = params H, = state #state是元组 outputs = [] for X in inputs: #(num_steps,batch_size,input_size) #更新门 Z = torch.sigmoid(torch.matmul(X, W_xz) + torch.matmul(H, W_hz) + b_z) #重置门 R = torch.sigmoid(torch.matmul(X, W_xr) + torch.matmul(H, W_hr) + b_r) #候选隐藏状态 H_tilda = torch.tanh(torch.matmul(X, W_xh) + R * torch.matmul(H, W_hh) + b_h) #隐藏状态 H = Z * H + (1 - Z) * H_tilda #输出层 Y = torch.matmul(H, W_hq) + b_q outputs.append(Y) return outputs, (H,)
- 训练模型并创作歌词
我们在训练模型时只使用相邻采样。设置好超参数后,我们将训练模型并根据前缀“分开”和“不分开”分别创作⻓度为50个字符的一段歌词。
num_epochs, num_steps, batch_size, lr, clipping_theta = 160, 35, 32, 1e2, 1e-2 pred_period, pred_len, prefixes = 40, 50, ['分开', '不分开']
我们每过40个迭代周期便根据当前训练的模型创作一段歌词。
d2l.train_and_predict_rnn(gru, get_params, init_gru_state, num_hiddens, vocab_size, device, corpus_indices, idx_to_char, char_to_idx, False, num_epochs, num_steps, lr, clipping_theta, batch_size, pred_period, pred_len, prefixes)
4. 简洁实现
在PyTorch中我们直接调⽤nn模块中的GRU类即可。
lr = 1e-2 gru_layer = nn.GRU(input_size=vocab_size, hidden_size=num_hiddens) model = d2l.RNNModel(gru_layer, vocab_size).to(device) d2l.train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device, corpus_indices, idx_to_char, char_to_idx, num_epochs, num_steps, lr, clipping_theta, batch_size, pred_period, pred_len, prefixes)
5.小结
1)⻔控循环神经网络可以更好地捕捉时间序列中时间步距离较大的依赖关系。
2)⻔控循环单元引⼊了⻔的概念,从⽽修改了循环神经网络中隐藏状态的计算⽅式。它包括重置门、更新门、候选隐藏状态和隐藏状态。
3)重置门有助于捕捉时间序列里短期的依赖关系。
4)更新⻔有助于捕捉时间序列里长期的依赖关系。
来源:CSDN
作者:CoreJT
链接:https://blog.csdn.net/sdu_hao/article/details/103245236