Skip to content

Latest commit

 

History

History
223 lines (175 loc) · 9.13 KB

11-5. RNN seq2seq.md

File metadata and controls

223 lines (175 loc) · 9.13 KB

1. Seq2Seq

(1) Seq2Seq 이란?

  • 시퀀스 투 시퀀스 모델은, 시퀀스(문장)를 입력 받고 시퀀스(문장)를 출력하는 모델이다.
  • ex) 번역, 챗봇

(2) Seq2Seq과 RNN의 차이

  • RNN은 '우리 연애 그만하고, 결혼하자'을 바르게 해석하지 못한다. 문장을 다 듣기도 전에 번역을 시작하기 시작하기 때문이다.
  • Seq2Seq은 '끝까지 듣고 말하기' 부분이 보안된 모델이다.



2. Seq2Seq : Encoder - Decoder

  • Seq2Seq의 대표적인 특징은 Encoder - Decoder 구조이다.

(1) Encoder : input의 모든 문장을 듣는 과정

  • Encoder는 input의 모든 문장을 듣는 과정이다.
  • 모든 문장을 들은 후 입력된 시퀀스(문장)를 벡터의 형태로 압축한다.
  • 압축된 형태를 Decoder에 전달한다.

(2) Decoder : output으로 답변 문장 만드는 과정

  • Encoder에서 전달받은 벡터를 Decoder의 첫 셀의 hidden state에 넣어주고, 문장이 시작한다는 플래그와 함께 모델을 시작한다.
  • 첫 셀의 output을 문장의 첫 시작 단어로 만들고, 이 단어를 다시 두 번째 셀에 넣어 이전 셀의 hidden state와 함께 다음 단어를 예측한다.
  • 이런 방식으로 완성된 문장을 만들어 낸다.



3. Seq2Seq Encoder - Decoder 간략하게 알아보기

  • 번역 테스크를 수행하는 Seq2Seq 모델의 Encoder - Decoder를 살펴보자.
  • raw : 전체 데이터 셋
  • Source Text : 전체 데이터 셋 중 영어 문장
  • Target Text : 전체 데이터 셋 중 번역 한 한국어 문장
  • preprocess : 전체 데이터 셋을 Train과 Test로 나누고, 각각의 단어와 단어의 빈도를 체크한다.
  • SOURCE_MAX_LENGTH : Source Text의 최대 길이 제한
  • TARGET_MAX_LENGTH : Target Text의 최대 길이 제한
# 전처리
SOURCE_MAX_LENGTH = 10
TARGET_MAX_LENGTH = 12
load_pairs, load_source_vocab, load_target_vocab = preprocess(raw, SOURCE_MAX_LENGTH, TARGET_MAX_LENGTH)
print(random.choice(load_pairs))

# Hidden state 정의
enc_hidden_size = 16 # Encoder의 Hidden state 정의
dec_hidden_size = enc_hidden_size # Decoder의 Hidden state 정의 (Encoder와 같다)

# Encoder - Decoder
enc = Encoder(load_source_vocab.n_vocab, enc_hidden_size).to(device) # RNN의 레이어인 Encoder 선언
dec = Decoder(dec_hidden_size, load_traget_vocab.n_vocab).to(device) # RNN의 레이어인 Decoder 선언

# 학습
train(load_pairs, load_source_vocab, load_target_vocab, enc, dec, 5000, print_every=1000) # Encoder의 출력을 Decoder의 입력으로 연결하는 부분 존재

# 모델 평가
evaluate(load_pairs, load_source_vocab, load_target_vocab, enc, dec, TARGET_MAX_LENGTH)



4. Seq2Seq 전 과정 실습하기

import random
import torch
import torch.nn as nn
import torch.optim as optim

torch.manual_seed(0)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 전체 데이터셋
raw = ["I feel hungry.    나는 배가 고프다.",
       "Pytorch is very easy.   파이토치는 매우 쉽다.",
       "Pytorch is a framework for depp learning.   파이토치는 딥러닝을 위한 프레임워크이다.",
       "Pytorch is very clear to use.   파이토치는 사용하기 매우 직관적이다."]
       
# 문장을 시작할 때 EOS_token을 붙여서 문장의 시작을 알려 준다.
# 문장이 끝날 때 EOS_token을 붙여서 문장의 종료를 알려 준다.
SOS_token = 0 # Start of sentences
EOS_token = 1 # End of sentences

# preprocess : 전체 데이터 셋을 Train과 Test로 나누고, 그 안의 단어와 단어의 빈도를 체크한다.
def preprocess(corpus, source_max_length, target_max_length):
  print("reading corpus...")
  paris = []
  for line in corpus:
    pairs.append([s for s in line.strip().lower().split("\t")]) # tab으로 split 되어 있는 부분을 나눈다.
  print("Read {} sentence pairs".format(len(pairs)))
  
  pairs = [pair for pair in pairs if filter_pair(pair, source_max_length, target_max_length)]
  print("Trimmed to {} sentence pairs".format(len(pairs)))
  
  source_vocab = Vocab() # Vocab()으로 단어의 개수, 딕셔너리를 만든다.
  target_vocab = Vocab()
  
  print("Counting words...")
  for pair in pairs:
    source_vocab.add_vocab(pair[0])
    target_vocab.add_vocab(pair[1])
  print("source vocab size =", source_vocab.n_vocab)
  print("target vocab size =", target_vocab.n_vocab)
  
  return pairs, source_vocab, target_vocab
    
# Encoder 모델 만들어 놓기
'''
    input의 문장이 100개의 단어로 구성되어 있다면, 100개의 차원으로 구성된 one-hot encoding으로 변환할 수 있다.
    즉, 100개의 단어 중 Pytorch라는 단어는 [0 0 0 0 1 0 ... 0 0 0] 과 같이 표현할 수 있다.
    이걸 embedding의 input으로 넣으면 이 차원은 hidden_size 차원 만큼의 벡터로 줄어든다.
'''
class Encoder(nn.Module):
  def __init__(self, input_size, hidden_size):
    super(Encoder, self).__init__()
    self.hidden_size = hidden_size
    self.embedding = nn.Embedding(input_size, hidden_size) # Embedding
    self.gru = nn.GRU(hidden_size, hidden_size) # 파라미터로 input과 output의 차원을 정의한다.
    
  def forward(self, x, hidden):
    x = self.embedding(x).view(1, 1, -1) # embedding
    x, hidden = self.gru(x, hidden) # gru
    return x, hidden
    
# Decoder 모델 만들어 놓기
'''
    Encoder의 최종 아웃풋이 Decoder의 셀로 들어온다.
    One-hot 벡터를 Embedding으로 처리한 값이 셀로 들어가서 아웃풋이 나온다.
    이 아웃풋을 Target text의 단어로 복원해야 하는데, 이때 out_size를 Target Text의 차원과 같은 벡터로 바꾸기 위해 Linear를 이용한다.
'''
class Decoder(nn.Module):
  def __init__(self, hidden_size, output_size):
    super(Decoder, self).__init__()
    self.hidden_size = hidden_size
    self.embedding = nn.Embedding(output_size, hidden_size)
    self.gru = nn.GRU(hidden_size, hidden_size)
    self.out = nn.Linear(hidden_size, output_size) # Linear
    self.softmax = nn.LogSoftmax(dim=1)
    
  def forward(self, x, hidden):
    x = self.embedding(x).view(1, 1, -1) # embedding
    x, hidden = self.gru(x, hidden) # gru
    x = self.softmax(self.out(x[0])) # softmax
    return x, hidden

# sentence를 one-hot encoding으로 바꾸고, 텐서화 한다.
def tensorize(vocab, sentence):
  indexes = [vocab.vocab2index[word] for word in sentence.split(" ")]
  indexes.append(vocab.vocab2index["<EOS>"])
  return torch.Tensor(indexes).long().to(device).view(-1, 1)
  
def train(pairs, source_vocab, target_vocab, encoder, decoder, n_iter, print_every = 1000, learning_rate = 0.01):
  loss_total = 0
  
  # optimizer
  encoder_optimizer = optim.SGD(encoder.parameters(), lr=learning_rate)
  decoder_optimizer = optim.SGD(decoder.parameters(), lr=learning_rate)
  
  training_batch = [random.choice(pairs) for _ in range(n_iter)] # 전체 데이터 셋 중 랜덤하게 학습 데이터 추출
  training_source = [tensorize(source_vocab, pair[0]) for pair in training_batch] # 배치를 source로 나눔
  training_target = [tensorize(target_vocab, pair[1]) for pair in training_batch] # 배치를 target으로 나눔
  
  # Loss
  criterion = nn.NLLLoss()

'''
    첫 번째 스텝에서의 input은 문장의 첫 단어이다.
    이 단어가 embedding을 거쳐 들어간다.
    Encoder의 첫 GRU에 들어가는 hidden state가 없는데, Encoder_hidden으로 0 벡터를 넣어 만들어 준다.
    for 문이 다 끝날 때 까지 반복문을 돌며 encoder에서 hidden state를 꺼내 온다.
    encoder의 마지막 hidden state를 decoder의 첫 hidden state로 넣어 준다.
    decoder의 첫 입력 값으로 SOS_token이 들어간다. 그 출력 값이 다음 GRU의 입력값으로 들어갈 수도 있고,
    아니면 정답 label로 변환한 값이 다음 GRU의 입력값으로 들어갈 수도 있다. (teacher forcing)
'''
  for i in range(1, n_iter + 1):
    source_tensor = training_source[i - 1]
    target_tensor = training_target[i - 1]
    
    encoder_hidden = torch.zeros([1, 1, encoder.hidden_size]).to(device)
    
    encoder_optimizer.zero_grad()
    decoder_optimizer.zero_grad()
    
    source_length = source_tensor.size(0)
    target_length = target_tensor.size(0)
    
    loss = 0
    
    for enc_input in range(source_length):
      _, encoder_hidden = encoder(source_tensor[enc_input], encoder_hidden)

    decoder_input = torch.Tensor([[SOS_token]]).long().to(device)
    decoder_hidden = encoder_hidden # encoder의 마지막 hidden state를 decoder의 첫 hidden state로 넣어 준다. 
    
    for di in range(target_length):
      decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)
      loss += criterion(decoder_output, target_tensor[di])
      decoder_input = target_tensor[di] # teacher forcing
      
    loss.backward()
    
    # 최적화
    encoder_optimizer.step()
    decoder_optimizer.step()
    
    loss_iter = loss.item() / target_length
    loss_total += loss_iter
    
    if i % print_every == 0:
      loss_avg = loss_total / print_every
      loss_total = 0
      print("[{} - {}%] loss = {05.4f}".format(i, i / n_iter * 100, loss_avg))