graykode--nlp-tutorial
121 行
5.2 KiB
Python
121 行
5.2 KiB
Python
# %%
|
|
# code by Tae Hwan Jung @graykode
|
|
# Reference : https://github.com/hunkim/PyTorchZeroToAll/blob/master/14_2_seq2seq_att.py
|
|
import numpy as np
|
|
import torch
|
|
import torch.nn as nn
|
|
import torch.nn.functional as F
|
|
import matplotlib.pyplot as plt
|
|
|
|
# S: Symbol that shows starting of decoding input
|
|
# E: Symbol that shows starting of decoding output
|
|
# P: Symbol that will fill in blank sequence if current batch data size is short than time steps
|
|
|
|
def make_batch():
|
|
input_batch = [np.eye(n_class)[[word_dict[n] for n in sentences[0].split()]]]
|
|
output_batch = [np.eye(n_class)[[word_dict[n] for n in sentences[1].split()]]]
|
|
target_batch = [[word_dict[n] for n in sentences[2].split()]]
|
|
|
|
# make tensor
|
|
return torch.FloatTensor(input_batch), torch.FloatTensor(output_batch), torch.LongTensor(target_batch)
|
|
|
|
class Attention(nn.Module):
|
|
def __init__(self):
|
|
super(Attention, self).__init__()
|
|
self.enc_cell = nn.RNN(input_size=n_class, hidden_size=n_hidden, dropout=0.5)
|
|
self.dec_cell = nn.RNN(input_size=n_class, hidden_size=n_hidden, dropout=0.5)
|
|
|
|
# Linear for attention
|
|
self.attn = nn.Linear(n_hidden, n_hidden)
|
|
self.out = nn.Linear(n_hidden * 2, n_class)
|
|
|
|
def forward(self, enc_inputs, hidden, dec_inputs):
|
|
enc_inputs = enc_inputs.transpose(0, 1) # enc_inputs: [n_step(=n_step, time step), batch_size, n_class]
|
|
dec_inputs = dec_inputs.transpose(0, 1) # dec_inputs: [n_step(=n_step, time step), batch_size, n_class]
|
|
|
|
# enc_outputs : [n_step, batch_size, num_directions(=1) * n_hidden], matrix F
|
|
# enc_hidden : [num_layers(=1) * num_directions(=1), batch_size, n_hidden]
|
|
enc_outputs, enc_hidden = self.enc_cell(enc_inputs, hidden)
|
|
|
|
trained_attn = []
|
|
hidden = enc_hidden
|
|
n_step = len(dec_inputs)
|
|
model = torch.empty([n_step, 1, n_class])
|
|
|
|
for i in range(n_step): # each time step
|
|
# dec_output : [n_step(=1), batch_size(=1), num_directions(=1) * n_hidden]
|
|
# hidden : [num_layers(=1) * num_directions(=1), batch_size(=1), n_hidden]
|
|
dec_output, hidden = self.dec_cell(dec_inputs[i].unsqueeze(0), hidden)
|
|
attn_weights = self.get_att_weight(dec_output, enc_outputs) # attn_weights : [1, 1, n_step]
|
|
trained_attn.append(attn_weights.squeeze().data.numpy())
|
|
|
|
# matrix-matrix product of matrices [1,1,n_step] x [1,n_step,n_hidden] = [1,1,n_hidden]
|
|
context = attn_weights.bmm(enc_outputs.transpose(0, 1))
|
|
dec_output = dec_output.squeeze(0) # dec_output : [batch_size(=1), num_directions(=1) * n_hidden]
|
|
context = context.squeeze(1) # [1, num_directions(=1) * n_hidden]
|
|
model[i] = self.out(torch.cat((dec_output, context), 1))
|
|
|
|
# make model shape [n_step, n_class]
|
|
return model.transpose(0, 1).squeeze(0), trained_attn
|
|
|
|
def get_att_weight(self, dec_output, enc_outputs): # get attention weight one 'dec_output' with 'enc_outputs'
|
|
n_step = len(enc_outputs)
|
|
attn_scores = torch.zeros(n_step) # attn_scores : [n_step]
|
|
|
|
for i in range(n_step):
|
|
attn_scores[i] = self.get_att_score(dec_output, enc_outputs[i])
|
|
|
|
# Normalize scores to weights in range 0 to 1
|
|
return F.softmax(attn_scores).view(1, 1, -1)
|
|
|
|
def get_att_score(self, dec_output, enc_output): # enc_outputs [batch_size, num_directions(=1) * n_hidden]
|
|
score = self.attn(enc_output) # score : [batch_size, n_hidden]
|
|
return torch.dot(dec_output.view(-1), score.view(-1)) # inner product make scalar value
|
|
|
|
if __name__ == '__main__':
|
|
n_step = 5 # number of cells(= number of Step)
|
|
n_hidden = 128 # number of hidden units in one cell
|
|
|
|
sentences = ['ich mochte ein bier P', 'S i want a beer', 'i want a beer E']
|
|
|
|
word_list = " ".join(sentences).split()
|
|
word_list = list(set(word_list))
|
|
word_dict = {w: i for i, w in enumerate(word_list)}
|
|
number_dict = {i: w for i, w in enumerate(word_list)}
|
|
n_class = len(word_dict) # vocab list
|
|
|
|
# hidden : [num_layers(=1) * num_directions(=1), batch_size, n_hidden]
|
|
hidden = torch.zeros(1, 1, n_hidden)
|
|
|
|
model = Attention()
|
|
criterion = nn.CrossEntropyLoss()
|
|
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
|
|
|
|
input_batch, output_batch, target_batch = make_batch()
|
|
|
|
# Train
|
|
for epoch in range(2000):
|
|
optimizer.zero_grad()
|
|
output, _ = model(input_batch, hidden, output_batch)
|
|
|
|
loss = criterion(output, target_batch.squeeze(0))
|
|
if (epoch + 1) % 400 == 0:
|
|
print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
|
|
|
|
loss.backward()
|
|
optimizer.step()
|
|
|
|
# Test
|
|
test_batch = [np.eye(n_class)[[word_dict[n] for n in 'SPPPP']]]
|
|
test_batch = torch.FloatTensor(test_batch)
|
|
predict, trained_attn = model(input_batch, hidden, test_batch)
|
|
predict = predict.data.max(1, keepdim=True)[1]
|
|
print(sentences[0], '->', [number_dict[n.item()] for n in predict.squeeze()])
|
|
|
|
# Show Attention
|
|
fig = plt.figure(figsize=(5, 5))
|
|
ax = fig.add_subplot(1, 1, 1)
|
|
ax.matshow(trained_attn, cmap='viridis')
|
|
ax.set_xticklabels([''] + sentences[0].split(), fontdict={'fontsize': 14})
|
|
ax.set_yticklabels([''] + sentences[2].split(), fontdict={'fontsize': 14})
|
|
plt.show() |