RNN 기반의 번역 모델인 Sequence to Sequence를 아주 간소화하여 영어 문자열을 스페인어 문자열로 번역하는 미니 Seq2Seq를 구현해보자.
우선 Seq2Seq는 다른 두 개의 RNN을 이어붙인 모델이다.
이 때 두 개의 RNN은 각각 인코더 / 디코더라는 역할을 하게된다.
우선 인코더는 원문의 내용을 학습하는 RNN이다. 원문속의 모든 단어를 입력받고 뜻을 내포하는 하나의 고정 크기 텐서를 만든다. 인코더 RNN은 원문 속의 토큰을 차례대로 입력받는다. 원문 마지막 토큰에 해당하는 은닉 벡터는 원문의 뜻을 모두 내포하는 문백 벡터라고 한다.
인코더와 마찬가지로 디코더 또한 RNN모델이다. 인코더에서 이어받어 번역문 속의 토큰을 차례대로 예상한다.
import torch
import torch.nn as nn
import random
import matplotlib.pyplot as plt
vocab_size = 256
x_ = list(map(ord, "hello"))
y_ = list(map(ord, "hola"))
x = torch.LongTensor(x_)
y = torch.LongTensor(y_)
class Seq2Seq(nn.Module):
def __init__(self, vocab_size, hidden_size):
super(Seq2Seq, self).__init__()
self.n_layers = 1
self.hidden_size = hidden_size #RNN의 hidden_size를 입력받도록 설정
#hidden_size를 임베딩된 토큰의 차원값으로 정의
self.embedding = nn.Embedding(vocab_size, hidden_size)
#인코더와 디코더를 GRU객체로 정의
self.encoder = nn.GRU(hidden_size, hidden_size)
self.decoder = nn.GRU(hidden_size, hidden_size)
#디코더가 번역문의 다음 토큰을 예상해내는 작은 신경망 하나 더 생성
self.project = nn.Linear(hidden_size, vocab_size)
def forward(self, inputs, targets):
initial_state = self._init_state()
embedding = self.embedding(inputs).unsqueeze(1)
encoder_output, encoder_state = self.encoder(embedding, initial_state)
decoder_state = encoder_state
decoder_input = torch.LongTensor([0])
outputs = []
for i in range(targets.size()[0]):
decoder_input = self.embedding(decoder_input).unsqueeze(1)
decoder_output, decoder_state = self.decoder(decoder_input, decoder_state)
projection = self.project(decoder_output)
outputs.append(projection)
decoder_input = torch.LongTensor([targets[i]])
outputs = torch.stack(outputs).squeeze()
return outputs
def _init_state(self, batch_size=1):
weight = next(self.parameters()).data
return weight.new(self.n_layers, batch_size,
self.hidden_size).zero_()
seq2seq = Seq2Seq(vocab_size, 16)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(seq2seq.parameters(), lr=1e-3)
log = []
for i in range(1000):
prediction = seq2seq(x,y)
loss = criterion(prediction, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
loss_val=loss.data
log.append(loss_val)
if i % 100 == 0:
print("\n 반복: %d 오차: %s" % (i, loss_val.item()))
_, top1 = prediction.data.topk(1,1)
print([chr(c) for c in top1.squeeze().numpy().tolist()])
plt.plot(log)
plt.ylabel('cross entropy loss')
plt.show()

