8.6. Implementação Concisa de Redes Neurais Recorrentes
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in SageMaker Studio Lab

Embora Section 8.5 tenha sido instrutivo para ver como RNNs são implementados, isso não é conveniente ou rápido. Esta seção mostrará como implementar o mesmo modelo de linguagem de forma mais eficiente usando funções fornecidas por APIs de alto nível de uma estrutura de aprendizado profundo. Começamos como antes, lendo o conjunto de dados da máquina do tempo.

from mxnet import np, npx
from mxnet.gluon import nn, rnn
from d2l import mxnet as d2l

npx.set_np()

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l

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

8.6.1. Definindo o Modelo

APIs de alto nível fornecem implementações de redes neurais recorrentes. Construímos a camada de rede neural recorrente rnn_layer com uma única camada oculta e 256 unidades ocultas. Na verdade, ainda não discutimos o que significa ter várias camadas — isso vai acontecer em Section 9.3. Por enquanto, basta dizer que várias camadas simplesmente equivalem à saída de uma camada de RNN sendo usada como entrada para a próxima camada de RNN.

num_hiddens = 256
rnn_layer = rnn.RNN(num_hiddens)
rnn_layer.initialize()

A inicialização do estado oculto é simples. Invocamos a função de membro begin_state. Isso retorna uma lista (estado) que contém um estado inicial oculto para cada exemplo no minibatch, cuja forma é (número de camadas ocultas, tamanho do lote, número de unidades ocultas). Para alguns modelos a serem apresentados mais tarde (por exemplo, memória longa de curto prazo), essa lista também contém outras informações.

state = rnn_layer.begin_state(batch_size=batch_size)
len(state), state[0].shape
(1, (1, 32, 256))
num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens)

Usamos um tensor para inicializar o estado oculto, cuja forma é (número de camadas ocultas, tamanho do lote, número de unidades ocultas).

state = torch.zeros((1, batch_size, num_hiddens))
state.shape
torch.Size([1, 32, 256])

Com um estado oculto e uma entrada, podemos calcular a saída com o estado oculto atualizado. Deve ser enfatizado que a “saída” (Y) dernn_layer não envolve computação de camadas de saída: isso se refere a o estado oculto em cada passo de tempo, e eles podem ser usados como entrada para a camada de saída subsequente.

Além do mais, o estado oculto atualizado (state_new) retornado por rnn_layer refere-se ao estado oculto na última etapa de tempo do minibatch. Ele pode ser usado para inicializar o estado oculto para o próximo minibatch dentro de uma época no particionamento sequencial. Para várias camadas ocultas, o estado oculto de cada camada será armazenado nesta variável (estado_novo). Para alguns modelos para ser apresentado mais tarde (por exemplo, memória longa de curto prazo), esta variável também contém outras informações.

X = np.random.uniform(size=(num_steps, batch_size, len(vocab)))
Y, state_new = rnn_layer(X, state)
Y.shape, len(state_new), state_new[0].shape
((35, 32, 256), 1, (1, 32, 256))
X = torch.rand(size=(num_steps, batch_size, len(vocab)))
Y, state_new = rnn_layer(X, state)
Y.shape, state_new.shape
(torch.Size([35, 32, 256]), torch.Size([1, 32, 256]))

Semelhante a Section 8.5, nós definimos uma classe RNNModel para um modelo RNN completo. Observe que rnn_layer contém apenas as camadas recorrentes ocultas, precisamos criar uma camada de saída separada.

#@save
class RNNModel(nn.Block):
    """The RNN model."""
    def __init__(self, rnn_layer, vocab_size, **kwargs):
        super(RNNModel, self).__init__(**kwargs)
        self.rnn = rnn_layer
        self.vocab_size = vocab_size
        self.dense = nn.Dense(vocab_size)

    def forward(self, inputs, state):
        X = npx.one_hot(inputs.T, self.vocab_size)
        Y, state = self.rnn(X, state)
        # The fully-connected layer will first change the shape of `Y` to
        # (`num_steps` * `batch_size`, `num_hiddens`). Its output shape is
        # (`num_steps` * `batch_size`, `vocab_size`).
        output = self.dense(Y.reshape(-1, Y.shape[-1]))
        return output, state

    def begin_state(self, *args, **kwargs):
        return self.rnn.begin_state(*args, **kwargs)
#@save
class RNNModel(nn.Module):
    """The RNN model."""
    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 the RNN is bidirectional (to be introduced later),
        # `num_directions` should be 2, else it should be 1.
        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)
        # The fully connected layer will first change the shape of `Y` to
        # (`num_steps` * `batch_size`, `num_hiddens`). Its output shape is
        # (`num_steps` * `batch_size`, `vocab_size`).
        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):
            # `nn.GRU` takes a tensor as hidden state
            return  torch.zeros((self.num_directions * self.rnn.num_layers,
                                 batch_size, self.num_hiddens),
                                device=device)
        else:
            # `nn.LSTM` takes a tuple of hidden states
            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))

8.6.2. Treinamento e Previsão

Antes de treinar o modelo, façamos uma previsão com um modelo que possui pesos aleatórios.

device = d2l.try_gpu()
net = RNNModel(rnn_layer, len(vocab))
net.initialize(force_reinit=True, ctx=device)
d2l.predict_ch8('time traveller', 10, net, vocab, device)
'time travellervmoopwrrrr'
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 traveller<unk>j<unk>j<unk>j<unk>j<unk>j'

Como é bastante óbvio, este modelo não funciona. Em seguida, chamamos train_ch8 com os mesmos hiperparâmetros definidos em Section 8.5 e treinamos nosso modelo com APIs de alto nível.

num_epochs, lr = 500, 1
d2l.train_ch8(net, train_iter, vocab, lr, num_epochs, device)
perplexity 1.2, 114752.8 tokens/sec on gpu(0)
time traveller held in his hand was a glitteringmetal in four di
travellery three dimensions of space generally recognized b
../_images/output_rnn-concise_eff2f4_53_1.svg
num_epochs, lr = 500, 1
d2l.train_ch8(net, train_iter, vocab, lr, num_epochs, device)
perplexity 1.3, 296232.7 tokens/sec on cuda:0
time travellerif f uplyond ur very ctispsoubstorne filburmy the
travelleryou can spon whis asmered the provincill sorkent m
../_images/output_rnn-concise_eff2f4_56_1.svg

Comparado com a última seção, este modelo atinge perplexidade comparável, embora dentro de um período de tempo mais curto, devido ao código ser mais otimizado por APIs de alto nível da estrutura de aprendizado profundo.

8.6.3. Resumo

  • APIs de alto nível da estrutura de deep learning fornecem uma implementação da camada RNN.

  • A camada RNN de APIs de alto nível retorna uma saída e um estado oculto atualizado, em que a saída não envolve computação da camada de saída.

  • Usar APIs de alto nível leva a um treinamento RNN mais rápido do que usar sua implementação do zero.

8.6.4. Exercícios

  1. Você pode fazer o modelo RNN sobreajustar usando as APIs de alto nível?

  2. O que acontece se você aumentar o número de camadas ocultas no modelo RNN? Você pode fazer o modelo funcionar?

  3. Implemente o modelo autoregressivo de Section 8.1 usando um RNN.