8.6. Implementação Concisa de Redes Neurais Recorrentes¶ 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
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
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¶
Você pode fazer o modelo RNN sobreajustar usando as APIs de alto nível?
O que acontece se você aumentar o número de camadas ocultas no modelo RNN? Você pode fazer o modelo funcionar?
Implemente o modelo autoregressivo de Section 8.1 usando um RNN.