用PyTorch实现GRU网络的三个层次
通过GRU层实现
通过PyTorch提供的集成好的GRU层进行实现,简单快捷,但丧失了一些灵活性。
class GRUModel(nn.Module): def __init__(self, input_num, hidden_num, output_num): super(GRUModel, self).__init__() self.hidden_size = hidden_num # 这里设置了 batch_first=True, 所以应该 inputs = inputs.view(inputs.shape[0], -1, inputs.shape[1]) # 针对时间序列预测问题,相当于将时间步(seq_len)设置为 1。 self.GRU_layer = nn.GRU(input_size=input_num, hidden_size=hidden_num, batch_first=True) self.output_linear = nn.Linear(hidden_num, output_num) self.hidden = None def forward(self, x): # h_n of shape (num_layers * num_directions, batch, hidden_size) # 这里不用显式地传入隐层状态 self.hidden x, self.hidden = self.GRU_layer(x) x = self.output_linear(x) return x, self.hidden
通过GRUCell实现
这种实现方法与方法一的不同之处主要有两点:
- GRUCell需要显式地传入隐藏层状态。
- 方法一当中GRU层要求输入数据(x)具有三个维度,即(seq_len, batch, input_size),分别代表序列长度/时间步、batch size、输入特征维数;而GRUCell的输入形状则和一般的神经网络相同,为(batch, input_size)。
class GRUModel(nn.Module): def __init__(self, input_num, hidden_num, output_num): super(GRUModel, self).__init__() self.hidden_size = hidden_num self.grucell = nn.GRUCell(input_num, hidden_num) self.out_linear = nn.Linear(hidden_num, output_num) def forward(self, x, hid): if hid is None: hid = torch.randn(x.shape[0], self.hidden_size) next_hid = self.grucell(x, hid) # 需要传入隐藏层状态 y = self.out_linear(next_hid) return y, next_hid.detach() # detach()和detach_()都可以使用
这里需要对forward() 函数的第二个返回值 next_hid.detach()做一些说明。首先看一下PyTorch官方文档当中对于detach()和detach_()方法的介绍,
detach():
Returns a new Tensor, detached from the current graph. The result will never require gradient.
detach_():
Detaches the Tensor from the graph that created it, making it a leaf. Views cannot be detached in-place.
这两种方法有一个相似的作用,就是将张量从创造它的计算图当中分离出来。下图是一个在不对返回值中隐层状态进行detach()操作时GRU网络计算过程的示意图。
从图中可以比较清晰地看出,不同于X是在每一步前向传播(图中黑线)开始时由外部提供的(称为计算图的一个叶子),隐层状态H是通过上一步的前向传播产生的。在这种情况下,在进行反向传播时,梯度计算就会一直追溯到该网络的初始状态
和
。而正确的情况应当是反向传播过程到达上一隐藏层状态H后即停止,将H和X同等地作为叶子节点。如下图所示。
通过自定义Cell实现
GRUCell的内部实际上是实现了以下计算过程:
所以我们可以通过自定义的方式来实现GRUCell,并根据自己的想法来定义新的循环网络单元。
class GRUCell(nn.Module): """自定义GRUCell""" def __init__(self, input_size, hidden_size): super(TestGRUCell, self).__init__() # 输入变量的线性变换过程是 x @ W.T + b (@代表矩阵乘法, .T代表矩阵转置) # in2hid_w 的原始形状应是 (hidden_size, input_size), 为了编程的方便, 这里改成(input_size, hidden_size) lb, ub = -sqrt(1/hidden_size), sqrt(1/hidden_size) self.in2hid_w = nn.ParameterList([self.__init(lb, ub, input_size, hidden_size) for _ in range(3)]) self.hid2hid_w = nn.ParameterList([self.__init(lb, ub, hidden_size, hidden_size) for _ in range(3)]) self.in2hid_b = nn.ParameterList([self.__init(lb, ub, hidden_size) for _ in range(3)]) self.hid2hid_b = nn.ParameterList([self.__init(lb, ub, hidden_size) for _ in range(3)]) @staticmethod def __init(low, upper, dim1, dim2=None): if dim2 is None: return nn.Parameter(torch.rand(dim1) * (upper - low) + low) # 按照官方的初始化方法来初始化网络参数 else: return nn.Parameter(torch.rand(dim1, dim2) * (upper - low) + low) def forward(self, x, hid): r = torch.sigmoid(torch.mm(x, self.in2hid_w[0]) + self.in2hid_b[0] + torch.mm(hid, self.hid2hid_w[0]) + self.hid2hid_b[0]) z = torch.sigmoid(torch.mm(x, self.in2hid_w[1]) + self.in2hid_b[1] + torch.mm(hid, self.hid2hid_w[1]) + self.hid2hid_b[1]) n = torch.tanh(torch.mm(x, self.in2hid_w[2]) + self.in2hid_b[2] + torch.mul(r, (torch.mm(hid, self.hid2hid_w[2]) + self.hid2hid_b[2]))) next_hid = torch.mul((1 - z), n) + torch.mul(z, hid) return next_hid
定义好GRUCell后,结合方法二就可以定义出GRU网络了。
#学习路径#
查看9道真题和解析