【循环神经网络系列】三、GRU


参考资料:

参考博客

  人人都能看懂的GRU

  【机器学习】详解 GRU


1.简介

【机器学习】详解 RNN 介绍了 RNN,当时间步数较大或者时间步较小时,RNN 的梯度较容易出现衰减或爆炸。虽然裁剪梯度可以应对梯度爆炸,但无法解决梯度衰减的问题。因此,RNN 在实际中较难捕捉时间序列中时间步距离较大的依赖关系。

门控循环神经网络 (Gated Recurrent Neural Network,GRNN) 的提出,旨在更好地捕捉时间序列中时间步距离较大的依赖关系。它通过可学习的门来控制信息的流动。其中,门控循环单元 (Gated Recurrent Unit,GRU) 是一种常用的 GRNN。GRU 对 【机器学习】详解 LSTM 介绍的 LSTM 做了很多简化,同时却保持着和 LSTM 相同的效果。


2.GRU网络结构

在这里插入图片描述

在这里插入图片描述

3.Pytroch实现GRU

3.1 通过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

3.2 通过GRUCell实现

这种实现方法与方法一的不同之处主要有两点:

  1. GRUCell需要显式地传入隐藏层状态。
  2. 方法一当中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 X X 是在每一步前向传播(图中黑线)开始时由外部提供的(称为计算图的一个叶子),隐层状态 H H H 是通过上一步的前向传播产生的。在这种情况下,在进行反向传播时,梯度计算就会一直追溯到该网络的初始状态 H 0 H_0 H0 X 1 X_1 X1 。而正确的情况应当是反向传播过程到达上一隐藏层状态 H H H 后即停止,将 H H H X X X同等地作为叶子节点。如下图所示。

在这里插入图片描述


3.3 通过自定义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网络了。


  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

travellerss

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值