GRU实现方法简介

释放双眼,带上耳机,听听看~!
本文介绍了两种GRU实现方法,一种是手写实现,一种是调用pytorch自带的GRU。同时提醒读者手写实现速度较慢,建议了解流程熟悉操作。

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

注意:

  1. 我一共要写两篇文章来讲解两种写GRU的方法,一种是手写实现,一种是直接调用pytorch自带的GRU。

  2. 本文使用jupyter notebook写的代码,和pycharmh有一点不一样。比如x可以直接输出变量,但是在pycharm中需要使用print(x)才可以。

自己写

要注意,自己写的会和pytorch的有有出入,毕竟人家是经过优化的,所以同样的数据使用我们自己写的训练速度会很慢。这只是带你熟悉流程的。

import torch
from torch import nn
from d2l import torch as d2l

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
  • 调用其他的包。

  • 设定batch-size批量大小和时间步的长度num_step。这里需要注意时间步的长度是你一个要处理的序列的时间步有多少个。

  • 使用之前我们实现过的加载时光机器数据集。获得数据集的迭代器和词汇表的长度,这里为了方便,使用的是char进行分割,也就是说词汇表是a~z以及空格和<unk>。

def get_params(vocab_size, num_hiddens, device):
    num_inputs = num_outputs = vocab_size

    def normal(shape):
        return torch.randn(size=shape, device=device)*0.01

    def three():
        return (normal((num_inputs, num_hiddens)),
                normal((num_hiddens, num_hiddens)),
                torch.zeros(num_hiddens, device=device))

    W_xz, W_hz, b_z = three()  # 更新门参数
    W_xr, W_hr, b_r = three()  # 重置门参数
    W_xh, W_hh, b_h = three()  # 候选隐藏状态参数
    
    W_hq = normal((num_hiddens, num_outputs))
    b_q = torch.zeros(num_outputs, device=device)
 
    params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q]
    for param in params:
        param.requires_grad_(True)
    return params

看代码之前记得回顾一下GRU的计算公式:

重置门:Rt=σ(XtWxr+Ht−1Whr+br),更新门:Zt=σ(XtWxz+Ht−1Whz+bz),候选隐藏状态:H~t=tanh⁡(XtWxh+(Rt⊙Ht−1)Whh+bh)隐藏状态:Ht=Zt⊙Ht−1+(1−Zt)⊙H~tbegin{aligned}
&重置门:&mathbf{R}_t = sigma(mathbf{X}_t mathbf{W}_{xr} + mathbf{H}_{t-1} mathbf{W}_{hr} + mathbf{b}_r),
&更新门:&mathbf{Z}_t = sigma(mathbf{X}_t mathbf{W}_{xz} + mathbf{H}_{t-1} mathbf{W}_{hz} + mathbf{b}_z),
&候选隐藏状态:&tilde{mathbf{H}}_t = tanh(mathbf{X}_t mathbf{W}_{xh} + left(mathbf{R}_t odot mathbf{H}_{t-1}right) mathbf{W}_{hh} + mathbf{b}_h)
&隐藏状态:&mathbf{H}_t = mathbf{Z}_t odot mathbf{H}_{t-1} + (1 – mathbf{Z}_t) odot tilde{mathbf{H}}_t
end{aligned}

这一步是初始化模型参数:实例化与更新门、重置门、候选隐藏状态和输出层相关的所有权重和偏置。

  • num_hiddens 定义隐藏单元的数量,

  • normal函数用于从标准差为 0.01 的高斯分布中随机生成权重。

  • three函数用于给更新门、重置门、候选隐藏状态初始化权重和偏执,一次更新仨就叫three了。GRU实现方法简介

  • 后边三个w,w,b = three()分别对应更新门、重置门、候选隐藏状态的初始化。

  • w_hqb_q是初始化隐藏层到输出层的权重和偏执。

  • params将参数整理到一起,为其附加梯度。

def init_gru_state(batch_size, num_hiddens, device):
    return (torch.zeros((batch_size, num_hiddens), device=device), )

这个是隐状态初始化函数。将其初始化为0张量。

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
    outputs = []
    for X in inputs:
        Z = torch.sigmoid((X @ W_xz) + (H @ W_hz) + b_z)
        R = torch.sigmoid((X @ W_xr) + (H @ W_hr) + b_r)
        H_tilda = torch.tanh((X @ W_xh) + ((R * H) @ W_hh) + b_h)
        H = Z * H + (1 - Z) * H_tilda
        Y = H @ W_hq + b_q
        outputs.append(Y)
    return torch.cat(outputs, dim=0), (H,)

这是定义GRU的计算,这里就不重复写公式了,往上翻一下子看看公式。

  • 开始是用params设定gru的参数。

  • H获取初始的隐藏状态

  • for循环就是对其进行计算。对照公式一目了然的东西。

vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_epochs, lr = 500, 1
model = d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_params,
                            init_gru_state, gru)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)

这段代码是对我们手写GRU的训练和预测测试,会输出预测结果和训练过程的可视化。给你们看个好玩的,仔细看输出的句子和图像。随着训练句子会一直改变,困惑度也一直下降。在CPU上会训练很久,所以要等好长一会儿让他跑完500个epoch。

GRU实现方法简介

训练结束后,会分别打印输出训练集的困惑度和前缀“time traveler”和“traveler”的预测序列上的困惑度。因为是随机初始化,所以每次运行的结果都不太一样,我就不贴运行结果出来了。

调用人家的

import torch
from torch import nn
from d2l import torch as d2l
from torch.nn import functional as F

依旧是熟悉的配方熟悉的导包操作。

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

这里和之前没区别,设定一些超参数并加载数据集。

  • 设定batch-size批量大小和时间步的长度num_step。这里需要注意时间步的长度是你一个要处理的序列的时间步有多少个。

  • 使用之前我们实现过的加载时光机器数据集。获得数据集的迭代器和词汇表的长度,这里为了方便,使用的是char进行分割,也就是说词汇表是a~z以及空格和<unk>。

class GRUModel(nn.Module):
    def __init__(self, gru_layer, vocab_size, **kwargs):
        super(GRUModel, self).__init__(**kwargs)
        self.gru = gru_layer
        self.vocab_size = vocab_size
        self.num_hiddens = self.gru.hidden_size
        if not self.gru.bidirectional:
            self.num_directions = 1
            self.linear = nn.Linear(self.num_hiddens, self.vocab_size)
        else:
            self.num_directions = 2
            self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size)

    def forward(self, inputs, state):
        X = F.one_hot(inputs.T.long(), self.vocab_size)
        X = X.to(torch.float32)
        Y, state = self.gru(X, state)
        output = self.linear(Y.reshape((-1, Y.shape[-1])))
        return output, state

    def begin_state(self, device, batch_size=1):
        return  torch.zeros((self.num_directions * self.gru.num_layers,
                                 batch_size, self.num_hiddens), 
                                device=device)

RNN、GRU、LSTM一脉相承,用的类的都差不多,这个是适用于RNN和GRU的,但是不适用于LSTM。LSTM可以看我之后的文章,或者看前边的简洁实现RNN循环神经网络 实现的那个RNNModule类,那个类是涵盖了RNN、GRU、LSTM的通用模型。

  • __init__初始化这个类,这个类是继承了nn.Module的。

    • self.gru设定计算层是GRU层,这里是需要参数的,你在下一段代码中会传入nn.GRU

    • self.vocab_size设定字典的大小,这里大小是28,因为我们使用的是字母进行分词,所以其中只有a~z26个字母外加 <unk>(空格和unknown)。

    • self.num_hiddens设置隐藏层的大小。普通的RNN是隐藏层,在这里是带隐状态的隐藏层。不是说有隐状态之后就没隐藏层了。

    • if-else语句是设定GRU是单双向的。

  • forward定义前向传播网络。

    这里不用我们自己来实现计算过程了,nn.GRU会直接给我们计算。但是我们依旧需要对数据进行一下才操作。

    • 首先是将输入转化为对应的one-hot向量,这里F看前边导包部分,是使用nn.functional

      • torch.float32再将其类型转化为float。
    • Ystate是计算隐状态的,注意 在这里Y不是 输出。这里Y是输出全部的隐状态,state是输出最后一个时间步的隐状态。

      GRU实现方法简介

    • output是用于存储输出的。

  • begin_state是进行初始化。
    这里初始化和RNN初始化一样,都是初始化为一个零张量。之后可以留意一下LSTM,LSTM是返回一个元组,元组中有两个张量。

vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_epochs, lr = 100, 1
num_inputs = vocab_size
gru_layer = nn.GRU(num_inputs, num_hiddens)
model = GRUModel(gru_layer, len(vocab))
model = model.to(device)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)

设定一些基础数值:

  • vocab_size词典长度

  • num_hiddens隐藏层向量的长度

  • device在CPU还是GPU上执行

  • num_epochs训练的epoch数量

  • lr学习率learning rate

GRU层直接使用nn.GRU

之后对其进行训练并测试。输出训练集的困惑度和前缀“time traveler”和“traveler”的预测序列结果。

GRU实现方法简介

本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。
AI教程

LangChain框架:开启对话式人工智能之旅

2023-12-19 15:46:00

AI教程

LangChain框架介绍:简化开发工作量,实现文档问答、聊天机器人和代码分析

2023-12-19 15:48:00

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索