持续创造,加速生长!这是我参与「日新计划 10 月更文应战」的第4天,点击检查活动概况

写代码之前先回忆一下RNN的核算公式:

躲藏层核算公式:

Ht=(XtWxh+Ht−1Whh+bh)\mathbf{H}_t = \phi(\mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh} + \mathbf{b}_h)

输出核算公式:

Ot=HtWhq+bq\mathbf{O}_t = \mathbf{H}_t \mathbf{W}_{hq} + \mathbf{b}_q

留意:之前我写过这么一篇:手动完成RNN – () 这个没有调用Pytorch的RNN,是自己从零开端写的。本文是调用了人家现成的RNN,两篇文章尽管都是RNN的代码完成,但是二者有本质区别。


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

python人必备导包技术,这段代码不用解说吧。

batch_size, num_steps =32,35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens)
  • 设置批量巨细batch_size和时刻步长度num_step,时刻步长度就是能够想象成一个样本中RNN要核算的时刻步长度是32。

  • d2l.load_data_time_machine加载数据集。

    留意:这儿为了方便,加载数据集时分进行数据预处理,运用的是长度为28的语料库词汇表,不是单词表。词汇表是a~z的字母外加 空格 和<unk>。

  • 设置躲藏层巨细的256

  • 在这儿RNN层直接运用pytorch提供的RNN。

state = torch.zeros((1, batch_size, num_hiddens))
state.shape
  • 运用torch.zeros用零向量初始化隐状况。隐状况是三维的,形状是隐状况层数、批量巨细、躲藏层巨细。

  • RNN隐状况核算Ht=(XtWxh+Ht−1Whh+bh)\mathbf{H}_t = \phi(\mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh} + \mathbf{b}_h),这一步就是要初始化H0\mathbf{H}_{0}

  • 运用state.shape看一下H0\mathbf{H}_{0}的形状。

  • 测验一下:

    X = torch.rand(size=(num_steps, batch_size, len(vocab)))
    Y, state_new = rnn_layer(X, state)
    Y.shape, state_new.shape
    

    随便搞一个X来试试pytorch自带的rnn层。

    • X就是随机初始化的,形状是(时刻步长、批量巨细、语料库词汇表长度)。

    • 运用rnn层进行核算返回Ystate_new,留意这儿的Y不是咱们说的那个RNN的输出,Ot=HtWhq+bq\mathbf{O}_t = \mathbf{H}_t \mathbf{W}_{hq} + \mathbf{b}_q,←不是这个玩意儿,是躲藏层,是H\mathbf{H}

    • 最终一句是输出一下Ystate_new的形状:

      (torch.Size([35, 32, 256]), torch.Size([1, 32, 256]))

      这儿Y是一切的隐状况,state_new是最终一个隐状况。

      用pytorch写个RNN 循环神经网络

    到这儿咱们能够得出:pytorch自带的RNN层核算的返回值是整个核算过程的隐状况和最终一个隐状况。

class RNNModel(nn.Module):
    def __init__(self, rnn_layer, vocab_size, **kwargs):
        super(RNNModel, self).__init__(**kwargs)
        self.rnn = rnn_layer
        self.vocab_size = vocab_size
        self.num_hiddens = self.rnn.hidden_size
        if not self.rnn.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.rnn(X, state)
        output = self.linear(Y.reshape((-1, Y.shape[-1])))
        return output, state
    def begin_state(self, device, batch_size=1):
        if not isinstance(self.rnn, nn.LSTM):
            return  torch.zeros((self.num_directions * self.rnn.num_layers,
                                 batch_size, self.num_hiddens), 
                                device=device)
        else:
            return (torch.zeros((
                self.num_directions * self.rnn.num_layers,
                batch_size, self.num_hiddens), device=device),
                    torch.zeros((
                        self.num_directions * self.rnn.num_layers,
                        batch_size, self.num_hiddens), device=device))

这个是完整的RNN模型。

  • __init__中进行基本设置。

    • 设定RNN层rnn_layer,设定字典巨细vocab_size,设定躲藏层巨细num_hiddens

    • 然后这个if-else句子,这个句子是为后边预备的。现在这儿仍是没有什么效果的。由于之后的双向rnn会用到。

      not self.rnn.bidirectional也就是说当这个RNN不是双向的时分,进入if句子。此刻设定它只有一个躲藏层,而且设定它的vocab_size。反之则进入else句子,此刻设定它有两个躲藏层(由于是双向RNN一个正向的一个反向的),而且设定它的vocab_size

  • forward设定前向传达函数。

    • 先对X进行处理,使其变为one-hot向量。再将数据类型转换为浮点型。
    • 留意在这儿Y是咱们说的隐状况,不是咱们常规意义上的输出。
    • 输出output这儿,全连接层首先将Y的形状改为(时刻步数批量巨细, 躲藏单元数)。再输出output输出形状是 (时刻步数批量巨细, 词表巨细)。
  • begin_state设定初始化的函数。里面也是一个if句子。根据rnn的类型来决议初始化的状况。 isinstance(self.rnn, nn.LSTM)是看咱们的self.rnn是不是nn.LSTM,如果他只是一个普通的rnn,那就直接对其进行躲藏状况的初始化就能够了;如果他是LSTM的时分,它的隐形状况需要初始化为张量。

device = d2l.try_gpu()
net = RNNModel(rnn_layer, vocab_size=len(vocab))
net = net.to(device)
d2l.predict_ch8('time traveller', 10, net, vocab, device)

输出:

‘time travellerxsszsuxsss’

在练习之前,咱们先运用咱们大号的模型来看一下它的预测成果。能够看出成果非常不好,由于他后边现已开端重复输出xss了。

如果你的输出和我的输出不一样也没有关系。由于它初始化的时分是随机初始化。所以每次运转的成果可能是不同的。

num_epochs, lr = 500, 1
d2l.train_ch8(net, train_iter, vocab, lr, num_epochs, device)

对其进行练习。500个epoch,学习率设定为1。 这张代码的向量函数中还有一个可视化的函数,所以你在跑这张代码的过程中应该能够看到一个下降的过程图表。的那个坐标跑完了之后再运转下边这个预测输出的代码。

d2l.predict_ch8('time traveller', 10, net, vocab, device)

输出:

‘time travellerit s again’

练习之后再看一下他预测生成的成果。至少比上面那一个靠谱许多。