# %% # code by Tae Hwan Jung @graykode import numpy as np import torch import torch.nn as nn # 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, output_batch, target_batch = [], [], [] for seq in seq_data: for i in range(2): seq[i] = seq[i] + 'P' * (n_step - len(seq[i])) input = [num_dic[n] for n in seq[0]] output = [num_dic[n] for n in ('S' + seq[1])] target = [num_dic[n] for n in (seq[1] + 'E')] input_batch.append(np.eye(n_class)[input]) output_batch.append(np.eye(n_class)[output]) target_batch.append(target) # not one-hot # make tensor return torch.FloatTensor(input_batch), torch.FloatTensor(output_batch), torch.LongTensor(target_batch) # make test batch def make_testbatch(input_word): input_batch, output_batch = [], [] input_w = input_word + 'P' * (n_step - len(input_word)) input = [num_dic[n] for n in input_w] output = [num_dic[n] for n in 'S' + 'P' * n_step] input_batch = np.eye(n_class)[input] output_batch = np.eye(n_class)[output] return torch.FloatTensor(input_batch).unsqueeze(0), torch.FloatTensor(output_batch).unsqueeze(0) # Model class Seq2Seq(nn.Module): def __init__(self): super(Seq2Seq, 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) self.fc = nn.Linear(n_hidden, n_class) def forward(self, enc_input, enc_hidden, dec_input): enc_input = enc_input.transpose(0, 1) # enc_input: [max_len(=n_step, time step), batch_size, n_class] dec_input = dec_input.transpose(0, 1) # dec_input: [max_len(=n_step, time step), batch_size, n_class] # enc_states : [num_layers(=1) * num_directions(=1), batch_size, n_hidden] _, enc_states = self.enc_cell(enc_input, enc_hidden) # outputs : [max_len+1(=6), batch_size, num_directions(=1) * n_hidden(=128)] outputs, _ = self.dec_cell(dec_input, enc_states) model = self.fc(outputs) # model : [max_len+1(=6), batch_size, n_class] return model if __name__ == '__main__': n_step = 5 n_hidden = 128 char_arr = [c for c in 'SEPabcdefghijklmnopqrstuvwxyz'] num_dic = {n: i for i, n in enumerate(char_arr)} seq_data = [['man', 'women'], ['black', 'white'], ['king', 'queen'], ['girl', 'boy'], ['up', 'down'], ['high', 'low']] n_class = len(num_dic) batch_size = len(seq_data) model = Seq2Seq() criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) input_batch, output_batch, target_batch = make_batch() for epoch in range(5000): # make hidden shape [num_layers * num_directions, batch_size, n_hidden] hidden = torch.zeros(1, batch_size, n_hidden) optimizer.zero_grad() # input_batch : [batch_size, max_len(=n_step, time step), n_class] # output_batch : [batch_size, max_len+1(=n_step, time step) (becase of 'S' or 'E'), n_class] # target_batch : [batch_size, max_len+1(=n_step, time step)], not one-hot output = model(input_batch, hidden, output_batch) # output : [max_len+1, batch_size, n_class] output = output.transpose(0, 1) # [batch_size, max_len+1(=6), n_class] loss = 0 for i in range(0, len(target_batch)): # output[i] : [max_len+1, n_class, target_batch[i] : max_len+1] loss += criterion(output[i], target_batch[i]) if (epoch + 1) % 1000 == 0: print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss)) loss.backward() optimizer.step() # Test def translate(word): input_batch, output_batch = make_testbatch(word) # make hidden shape [num_layers * num_directions, batch_size, n_hidden] hidden = torch.zeros(1, 1, n_hidden) output = model(input_batch, hidden, output_batch) # output : [max_len+1(=6), batch_size(=1), n_class] predict = output.data.max(2, keepdim=True)[1] # select n_class dimension decoded = [char_arr[i] for i in predict] end = decoded.index('E') translated = ''.join(decoded[:end]) return translated.replace('P', '') print('test') print('man ->', translate('man')) print('mans ->', translate('mans')) print('king ->', translate('king')) print('black ->', translate('black')) print('upp ->', translate('upp'))