Hello PyTorch developers,
I’m solving Exercise 4 from Chapter 9.7 in the Dive into Deep Learning book. It pertains to the sequence-to-sequence Encoder-Decoder architecture. I have an error, I know what it is, but I don’t know how to fix it. The exercise goes as follows:
In training, replace teacher forcing with feeding the prediction at the previous time step into the decoder. How does this influence the performance?
Below is the original training code which works; it uses teacher forcing to the decoder:
#@save
def train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device):
"""Train a model for sequence to sequence."""
def xavier_init_weights(m):
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
if type(m) == nn.GRU:
for param in m._flat_weights_names:
if "weight" in param:
nn.init.xavier_uniform_(m._parameters[param])
net.apply(xavier_init_weights)
net.to(device)
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
loss = MaskedSoftmaxCELoss()
net.train()
animator = d2l.Animator(xlabel='epoch', ylabel='loss',
xlim=[10, num_epochs])
for epoch in range(num_epochs):
timer = d2l.Timer()
metric = d2l.Accumulator(2) # Sum of training loss, no. of tokens
for batch in data_iter:
optimizer.zero_grad()
X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch]
bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0],
device=device).reshape(-1, 1)
dec_input = torch.cat([bos, Y[:, :-1]], 1) # Teacher forcing
Y_hat, _ = net(X, dec_input, X_valid_len)
l = loss(Y_hat, Y, Y_valid_len)
l.sum().backward() # Make the loss scalar for `backward`
d2l.grad_clipping(net, 1)
num_tokens = Y_valid_len.sum()
optimizer.step()
with torch.no_grad():
metric.add(l.sum(), num_tokens)
if (epoch + 1) % 10 == 0:
animator.add(epoch + 1, (metric[0] / metric[1],))
print(f'loss {metric[0] / metric[1]:.3f}, {metric[1] / timer.stop():.1f} '
f'tokens/sec on {str(device)}')
embed_size, num_hiddens, num_layers, dropout = 32, 32, 2, 0.1
batch_size, num_steps = 64, 10
lr, num_epochs, device = 0.005, 300, d2l.try_gpu()
train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)
encoder = Seq2SeqEncoder(len(src_vocab), embed_size, num_hiddens, num_layers,
dropout)
decoder = Seq2SeqDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers,
dropout)
net = d2l.EncoderDecoder(encoder, decoder)
train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)
Here are the encoder and the decoder classes, alongside the interfaces they’re implementing. There’s also a custom loss function defined here, but it’s irrelevant, so I’m ommiting it.
from torch import nn
#@save
class Encoder(nn.Module):
"""The base encoder interface for the encoder-decoder architecture."""
def __init__(self, **kwargs):
super(Encoder, self).__init__(**kwargs)
def forward(self, X, *args):
raise NotImplementedError
#@save
class Seq2SeqEncoder(d2l.Encoder):
"""The RNN encoder for sequence to sequence learning."""
def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
dropout=0, **kwargs):
super(Seq2SeqEncoder, self).__init__(**kwargs)
# Embedding layer
self.embedding = nn.Embedding(vocab_size, embed_size)
self.rnn = nn.GRU(embed_size, num_hiddens, num_layers,
dropout=dropout)
def forward(self, X, *args):
# The output `X` shape: (`batch_size`, `num_steps`, `embed_size`)
X = self.embedding(X)
# In RNN models, the first axis corresponds to time steps
X = X.permute(1, 0, 2)
# When state is not mentioned, it defaults to zeros
output, state = self.rnn(X)
# `output` shape: (`num_steps`, `batch_size`, `num_hiddens`)
# `state` shape: (`num_layers`, `batch_size`, `num_hiddens`)
return output, state
#@save
class Decoder(nn.Module):
"""The base decoder interface for the encoder-decoder architecture."""
def __init__(self, **kwargs):
super(Decoder, self).__init__(**kwargs)
def init_state(self, enc_outputs, *args):
raise NotImplementedError
def forward(self, X, state):
raise NotImplementedError
class Seq2SeqDecoder(d2l.Decoder):
"""The RNN decoder for sequence to sequence learning."""
def __init__(self, vocab_size, embed_size, num_hiddens, num_layers,
dropout=0, **kwargs):
super(Seq2SeqDecoder, self).__init__(**kwargs)
self.embedding = nn.Embedding(vocab_size, embed_size)
self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers,
dropout=dropout)
self.dense = nn.Linear(num_hiddens, vocab_size)
def init_state(self, enc_outputs, *args):
return enc_outputs[1]
def forward(self, X, state):
# The output `X` shape: (`num_steps`, `batch_size`, `embed_size`)
X = self.embedding(X).permute(1, 0, 2)
# Broadcast `context` so it has the same `num_steps` as `X`
context = state[-1].repeat(X.shape[0], 1, 1)
X_and_context = torch.cat((X, context), 2)
output, state = self.rnn(X_and_context, state)
output = self.dense(output).permute(1, 0, 2)
# `output` shape: (`batch_size`, `num_steps`, `vocab_size`)
# `state` shape: (`num_layers`, `batch_size`, `num_hiddens`)
return output, state
#@save
class EncoderDecoder(nn.Module):
"""The base class for the encoder-decoder architecture."""
def __init__(self, encoder, decoder, **kwargs):
super(EncoderDecoder, self).__init__(**kwargs)
self.encoder = encoder
self.decoder = decoder
def forward(self, enc_X, dec_X, *args):
enc_outputs = self.encoder(enc_X, *args)
dec_state = self.decoder.init_state(enc_outputs, *args)
return self.decoder(dec_X, dec_state)
Now, here’s my modified training code, alongside with its output. I added some print
statements so I can see what’s going on:
import numpy as np
#@save
def train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device):
"""Train a model for sequence to sequence."""
def xavier_init_weights(m):
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
if type(m) == nn.GRU:
for param in m._flat_weights_names:
if "weight" in param:
nn.init.xavier_uniform_(m._parameters[param])
net.apply(xavier_init_weights)
net.to(device)
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
loss = MaskedSoftmaxCELoss()
net.train()
animator = d2l.Animator(xlabel='epoch', ylabel='loss',
xlim=[10, num_epochs])
for epoch in range(num_epochs):
timer = d2l.Timer()
metric = d2l.Accumulator(2) # Sum of training loss, no. of tokens
for batch in data_iter:
optimizer.zero_grad()
X, X_valid_len, Y, Y_valid_len = [x.to(device) for x in batch]
print("Y.shape:")
print(Y.shape)
bos = torch.tensor([tgt_vocab['<bos>']] * Y.shape[0],
device=device).reshape(-1, 1)
print("bos.shape:")
print(bos.shape)
# if it's the first time this is running, we don't have Y_hat, and we need it:
if not "Y_hat" in locals(): # https://stackoverflow.com/questions/843277/how-do-i-check-if-a-variable-exists
print("Here")
Y_hat = torch.rand(size=(Y.shape[0], Y.shape[1], len(tgt_vocab)), device=device)
print("Y_hat.shape:")
print(Y_hat.shape)
Y_hat = np.argmax(Y_hat.cpu().detach().numpy(), axis=2)
Y_hat = torch.tensor(Y_hat).to(device)
dec_input = torch.cat([bos, Y_hat[:bos.shape[0], :-1]], 1)
Y_hat, _ = net(X, dec_input, X_valid_len)
print("Y_hat.shape after net:")
print(Y_hat.shape)
l = loss(Y_hat, Y, Y_valid_len)
l.sum().backward() # Make the loss scalar for `backward`
d2l.grad_clipping(net, 1)
num_tokens = Y_valid_len.sum()
optimizer.step()
with torch.no_grad():
metric.add(l.sum(), num_tokens)
if (epoch + 1) % 10 == 0:
animator.add(epoch + 1, (metric[0] / metric[1],))
print(f'loss {metric[0] / metric[1]:.3f}, {metric[1] / timer.stop():.1f} '
f'tokens/sec on {str(device)}')
embed_size, num_hiddens, num_layers, dropout = 32, 32, 2, 0.1
batch_size, num_steps = 64, 10
lr, num_epochs, device = 0.005, 300, d2l.try_gpu()
train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)
encoder = Seq2SeqEncoder(len(src_vocab), embed_size, num_hiddens, num_layers,
dropout)
print("len(src_vocab):")
print(len(src_vocab))
decoder = Seq2SeqDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers,
dropout)
print("len(tgt_vocab):")
print(len(tgt_vocab))
net = d2l.EncoderDecoder(encoder, decoder)
train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)
Here's the output:
len(src_vocab):
184
len(tgt_vocab):
201
Y.shape:
torch.Size([64, 10])
bos.shape:
torch.Size([64, 1])
Here
Y_hat.shape:
torch.Size([64, 10, 201])
Y_hat.shape after net:
torch.Size([64, 10, 201])
Y.shape:
torch.Size([64, 10])
bos.shape:
torch.Size([64, 1])
Y_hat.shape:
torch.Size([64, 10, 201])
Y_hat.shape after net:
torch.Size([64, 10, 201])
Y.shape:
torch.Size([64, 10])
bos.shape:
torch.Size([64, 1])
Y_hat.shape:
torch.Size([64, 10, 201])
Y_hat.shape after net:
torch.Size([64, 10, 201])
Y.shape:
torch.Size([64, 10])
bos.shape:
torch.Size([64, 1])
Y_hat.shape:
torch.Size([64, 10, 201])
Y_hat.shape after net:
torch.Size([64, 10, 201])
Y.shape:
torch.Size([64, 10])
bos.shape:
torch.Size([64, 1])
Y_hat.shape:
torch.Size([64, 10, 201])
Y_hat.shape after net:
torch.Size([64, 10, 201])
Y.shape:
torch.Size([64, 10])
bos.shape:
torch.Size([64, 1])
Y_hat.shape:
torch.Size([64, 10, 201])
Y_hat.shape after net:
torch.Size([64, 10, 201])
Y.shape:
torch.Size([64, 10])
bos.shape:
torch.Size([64, 1])
Y_hat.shape:
torch.Size([64, 10, 201])
Y_hat.shape after net:
torch.Size([64, 10, 201])
Y.shape:
torch.Size([64, 10])
bos.shape:
torch.Size([64, 1])
Y_hat.shape:
torch.Size([64, 10, 201])
Y_hat.shape after net:
torch.Size([64, 10, 201])
Y.shape:
torch.Size([64, 10])
bos.shape:
torch.Size([64, 1])
Y_hat.shape:
torch.Size([64, 10, 201])
Y_hat.shape after net:
torch.Size([64, 10, 201])
Y.shape:
torch.Size([25, 10])
bos.shape:
torch.Size([25, 1])
Y_hat.shape:
torch.Size([64, 10, 201])
Y_hat.shape after net:
torch.Size([25, 10, 201])
Y.shape:
torch.Size([64, 10])
bos.shape:
torch.Size([64, 1])
Y_hat.shape:
torch.Size([25, 10, 201])
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
/tmp/ipykernel_71062/1885358373.py in <module>
13 print(len(tgt_vocab))
14 net = d2l.EncoderDecoder(encoder, decoder)
---> 15 train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)
/tmp/ipykernel_71062/3336883265.py in train_seq2seq(net, data_iter, lr, num_epochs, tgt_vocab, device)
39 Y_hat = np.argmax(Y_hat.cpu().detach().numpy(), axis=2)
40 Y_hat = torch.tensor(Y_hat).to(device)
---> 41 dec_input = torch.cat([bos, Y_hat[:bos.shape[0], :-1]], 1) # added :bos.shape[0], otherwise I get an error
42 Y_hat, _ = net(X, dec_input, X_valid_len)
43 print("Y_hat.shape after net:")
RuntimeError: Sizes of tensors must match except in dimension 0. Got 25 and 64 (The offending index is 0)
I know why I get this error. It’s because when I go over the last batch in an epoch (the batch size then is 25) and I continue onto the next epoch, the first dimension of Y_hat
is 25 (because that’s the way it’s stored from the last epoch), while it should be 64 (64 is the batch size).
How do I fix this? I need to have Y_hat
before computing dec_input
so I can concatenate the bos
with Y_hat
, but my current implementation has issues with that approach. How can I fix this?