Seq2Seq 이해하기



Seq2Seq란?

Seq2Seq는 Sequence to Sequence의 약자로써, 시퀀스 데이터(언어, 시계열 데이터 등)를 또 다른 시퀀스 데이터로 변환해주는 모델입니다.

Seq2Seq 모델은 RNN모델(RNN, LSTM, GRU)등을 순환성을 기반으로 사용 되기에 RNN(Recurrent Neural Network) 이해하기, LSTM(Long Short-Term Memory) 이해하기 부분을 먼저 학습하셔야 합니다.

seq2seq_gif_1

Seq2Seq 모델은 보이는 움짤과 같이 크게 인코더 (Encoder) 부분과 디코더 (Decoder) 부분으로 나눌 수 있습니다.
간단하게 인코더는 문자를 데이터로 변환시키고, 디코더는 데이터를 문자로 변환시키는 역할을 합니다.

RNN of Seq2Seq

여기 Seq2Seq의 Encoder와 Decoder에선 RNN 기반 모델들을 (RNN, LSTM, GRU) 사용합니다. 이 글에선 LSTM 모델을 사용한다 가정하겠습니다.

lstm_img_4

LSTM모델은 시간에 따른 데이터를 순환 시켜주는 “$h_{t}$” 값과 / 중요한 데이터는 기억하고, 아닌 데이터는 잊는 기억셀 “$C_{t}$”가 있습니다. 이 두 값은 다음셀로 계속 사용전달 되어, 다음셀에 영향을 줍니다.

이렇게 다음 층으로 계속 전달 되는 층을 HIdden state 라고도 부릅니다.

Seq2Seq Architecture

Seq2Seq에서 사용 될 RNN모델을 정했기에 이제 Encoder / Decoder 와 이 둘을 이어주는 Context(문맥) 부분을 이해해보겠습니다.

image

[1] Encoder

seq2seq_img_2

Encoder부분을 보면 “je suis étudiant” 라는 문장이 Word level로(단어마다) RNN모델에 한개씩 들어가는 것을 알 수 있습니다.

셀에 단어가 들어갈때 마다 HIdden state(LSTM의 기억셀 $C_{t}$와, 결과값 $h_{t}$) 값이 나와서 다음셀에 영향을 주는 것을 볼 수 있습니다.

이 후 마지막 셀에 “étudiant” 단어가 들어가는 순간 HIdden state값이 나오고 그 값이 Decoder에 들어가는 것을 볼 수 있습니다. 우리는 Encoder부분의 마지막 셀에서 나오는 HIdden state를 Context Vector(문맥)이라고도 부릅니다.

[2] Context Vector

seq2seq_img_4

Context Vector(문맥)으로 불리는 Encoder부분의 마지막 셀에서 나오는 HIdden state에 대해서 추가적인 설명을 하겠습니다.

Context Vector는 입력값(“je suis étudiant”)의 모든 정보를 모아 놓은 동시에, 고정된 크기의 벡터이기도 합니다.

고정된 크기의 벡터는 많은양의 데이터도 꾹꾹 눌러서 압축시켜야하기 때문에, 정보 손실률이 있다는 단점이 있지만, 차 후 Attention Mechanism을 통해 단점을 해결할 수 있게 됩니다. 이 글에선 Attention Mechanism에 대해선 다루지 않겠습니다.

[3] Decoder

seq2seq_img_3

Decoder도 Encoder과 마찬가지로 정보를 받아, RNN을 통해 결과를 예측하는 것 까지는 같습니다.

Decoder는 유일한 정보인 Context Vector만을 정보로 받아 “i am a student”와 같은 결과를 출력합니다.


Seq2Seq 개선

Seq2Seq 개선하는 방법 중 하나는 위에서 언급한 Attention Mechanism이 가장 인상 깊겠지만, 그전에 간단한 방법으로 개선하는 법을 언급하겠습니다.

=> 입력데이터의 순서를 뒤집는다.
ex) “안녕 나는 딥러닝을 좋아하는 파이썬돌이야 .” => “. 파이썬돌이야 좋아하는 딥러닝을 나는 안녕”

이렇게 언어 순서를 바꿔주는 것 만으로도 Context Vector에 다른 작용을해서 좋은 결과를 추출한다고 합니다.

Pytorch Code

class EncoderRNN(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(EncoderRNN, self).__init__()
        self.hidden_size = hidden_size
        # Layers
        self.embedding = nn.Embedding(input_size, hidden_size)
        self.gru = nn.GRU(hidden_size, hidden_size)

    def forward(self, input, hidden):
        embedded = self.embedding(input).view(1, 1, -1)
        output = embedded
        output, hidden = self.gru(output, hidden)
        return output, hidden

    def initHidden(self):
        return torch.zeros(1, 1, self.hidden_size, device=device)
class DecoderRNN(nn.Module):
    def __init__(self, hidden_size, output_size):
      super(DecoderRNN, self).__init__()
      self.hidden_size = hidden_size
      # Layers
      self.embedding = nn.Embedding(output_size, hidden_size)
      self.gru = nn.GRU(hidden_size, hidden_size)
      self.out = nn.Linear(hidden_size, output_size)
      self.softmax = nn.LogSoftmax(dim=1)

    
    def forward(self, input, hidden):
      output = self.embedding(input, hidden)
      output = F.relu(output)
      output, hidden = self.gru(output, hidden)
      ouptput = self.softmax(self.out(output[0]))    
      return output, hidden
    
    def initHidden(self):
      return torch.zeros(1, 1, self.hidden_size, device=device)


Reference
  • https://tutorials.pytorch.kr/intermediate/seq2seq_translation_tutorial.html
  • http://jalammar.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/
  • 밑바닥부터 시작하는 딥러닝 2 / 사이토 고키 / 한빛 미디어