by museonghwang

시퀀스-투-시퀀스(Sequence-to-Sequence, seq2seq) 이해하기

|

RNN을 이용하면 다 대 일(many-to-one) 구조로 텍스트 분류를 풀 수 있고, 다 대 다(many-to-many) 구조로는 개체명 인식이나 품사 태깅과 같은 문제를 풀 수 있습니다. 하지만 이번에 살펴볼 RNN의 구조는 바닐라 RNN과 다소 차이가 있는데, 하나의 RNN을 인코더. 또 다른 하나의 RNN을 디코더라는 모듈로 명명하고 두 개의 RNN을 연결해서 사용하는 인코더-디코더 구조입니다.

이러한 인코더-디코더 구조는 주로 입력 문장과 출력 문장의 길이가 다를 경우에 사용하는데, 대표적인 분야가 번역기나 텍스트 요약과 같은 경우가 있습니다. 영어 문장을 한국어 문장으로 번역한다고 하였을 때, 입력 문장인 영어 문장과 번역된 결과인 한국어 문장의 길이는 똑같을 필요가 없습니다. 텍스트 요약의 경우에는 출력 문장이 요약된 문장이므로 입력 문장보다는 당연히 길이가 짧을 것입니다.



시퀀스-투-시퀀스(Sequence-to-Sequence, seq2seq)

시퀀스-투-시퀀스(Sequence-to-Sequence, seq2seq)입력된 시퀀스로부터 다른 도메인의 시퀀스를 출력 하는 다양한 분야에서 사용되는 Encoder-Decoder 모델 입니다. 문자 그대로 인코더는 입력 데이터를 인코딩(부호화)하고, 디코더는 인코딩된 데이터를 디코딩(복호화), 즉 인코더는 입력을 처리하고 디코더는 결과를 생성 합니다.

  • 챗봇(Chatbot)
    • 입력 시퀀스와 출력 시퀀스를 각각 질문과 대답으로 구성하면 챗봇으로 만들 수 있습니다.
  • 기계 번역(Machine Translation)
    • 입력 시퀀스와 출력 시퀀스를 각각 입력 문장과 번역 문장으로 만들면 번역기로 만들 수 있습니다.
  • 내용 요약(Text Summarization)
  • STT(Speech to Text) 등


seq2seq 는 번역기에서 대표적으로 사용되는 모델로, 기본적으로 RNN을 어떻게 조립했느냐에 따라서 seq2seq라는 구조가 만들어집니다. 기계 번역을 예제로 시퀀스-투-시퀀스를 설명하겠습니다.

image


위 그림은 seq2seq 모델로 만들어진 번역기가 ‘I am a student’라는 영어 문장을 입력 받아서, ‘je suis étudiant’라는 프랑스 문장을 출력 하는 모습을 보여줍니다. 그렇다면, seq2seq 모델 내부의 모습은 어떻게 구성되어있는지 보겠습니다.

image


seq2seq 는 크게 인코더(Encoder) 와 디코더(Decoder)라는 두 개의 모듈로 구성 됩니다.

인코더와 디코더 각각의 역할을 살펴보면, 인코더는 ‘I am a student’라는 입력 문장을 받아 Context 벡터를 만듭니다. Context 벡터는 ‘I am a student’에 대한 정보를 압축하고 있으며 컴퓨터가 이해할 수 있는 숫자로 이루어진 벡터인데, Context 벡터는 다시 디코더로 전달되며 디코더는 이를 활용하여 최종적으로 ‘je suis étudiant’라는 불어 문장을 생성합니다. 즉, 인코더는 문장을 가지고 Context 벡터를 만들어주는데, 이 Context 벡터에는 문장에 대한 정보가 응축되어 있습니다. 반면 디코더는 정보가 응축되어 있는 Context 벡터로부터 다른 문장을 생성해줍니다.

image


인코더 아키텍처와 디코더 아키텍처의 내부는 두 개의 RNN 아키텍처 입니다. 물론, RNN의 성능 문제인 Gradient Vanishing 으로 인해 앞 정보가 뒤로 온전히 전달되지 못하므로 주로 LSTM 셀 또는 GRU 셀 들로 구성됩니다.

  • 인코더(Encoder)
    • 입력 문장을 받는 RNN 셀
    • 입력 문장은 단어 토큰화를 통해서 단어 단위로 쪼개지고, 단어 토큰 각각은 RNN 셀의 각 시점의 입력이 됩니다.
    • 인코더 RNN 셀은 모든 단어를 입력받은 뒤에 인코더 RNN 셀의 마지막 시점의 은닉 상태인 컨텍스트 벡터를 디코더 RNN 셀로 넘겨주며, 컨텍스트 벡터는 디코더 RNN 셀의 첫번째 은닉 상태에 사용됩니다.
    • 입력 문장의 정보가 하나의 컨텍스트 벡터로 모두 압축되면 인코더는 컨텍스트 벡터를 디코더로 전송 합니다.
  • 컨텍스트 벡터(context vector)
    • Encoder에서 입력 문장의 모든 단어들을 순차적으로 입력받은 뒤에 마지막에 이 모든 단어 정보들이 압축된 하나의 벡터
    • 입력 문장의 정보가 하나의 컨텍스트 벡터로 모두 압축되므로 모든 인코더 셀의 과거 정보를 담고 있을 것 입니다.
    • 컨텍스트 벡터는 디코더 RNN 셀의 첫번째 은닉 상태에 사용 됩니다.
  • 디코더(Decoder)
    • 출력 문장을 출력하는 RNN 셀
    • 컨텍스트 벡터를 받아서 번역된 단어를 한 개씩 순차적으로 출력 합니다.


디코더 는 기본적으로 RNNLM(RNN Language Model), 즉 입력 문장을 통해 출력 문장을 예측하는 언어 모델 형식입니다. 위 그림에서 <sos> 는 문장의 시작(start of string)을 뜻하고 <eos> 는 문장의 끝(end of string)을 뜻합니다.

디코더에 인코더로부터 전달받은 Context 벡터와 <sos> 가 입력 되면, 다음에 등장할 확률이 높은 단어를 예측 합니다. 첫번째 시점(time step)의 디코더 RNN 셀은 다음에 등장할 단어로 je를 예측하고, 예측된 단어 je를 다음 시점의 RNN 셀의 입력으로 입력하며. 이후 두번째 시점의 디코더 RNN 셀은 입력된 단어 je로부터 다시 다음에 올 단어인 suis를 예측하고, 또 다시 이것을 다음 시점의 RNN 셀의 입력으로 보냅니다. 디코더는 이런 식으로 기본적으로 다음에 올 단어를 예측하고, 그 예측한 단어를 다음 시점의 RNN 셀의 입력으로 넣는 행위를 반복하고, 문장의 끝을 의미하는 \<eos\> 가 다음 단어로 예측될 때까지 반복 됩니다.


하지만 이는 모델의 학습 후 Test 단계 에서의 디코더 작동 원리입니다. Training 단계 에서는 교사 강요(teacher forcing) 방식으로 디코더 모델을 훈련시킵니다. 즉, seq2seq는 훈련 과정과 테스트 과정의 작동 방식이 조금 다릅니다.

디코더의 훈련 과정 을 살펴보면 디코더에게 인코더가 보낸 컨텍스트 벡터와 실제 정답인 상황인 ‘<sos> je suis étudiant’를 입력 받았을 때, ‘je suis étudiant <eos>‘가 나와야 된다고 정답을 알려주면서 훈련합니다. 반면 디코더의 테스트 과정 은 앞서 설명한 과정과 같이 디코더는 오직 컨텍스트 벡터와 ‘' 만을 입력으로 받은 후에 다음에 올 단어를 예측하고, 그 단어를 다음 시점의 RNN 셀의 입력으로 넣는 행위를 반복합니다. **즉, 위 그림은 테스트 과정에 해당됩니다.**


교사 강요(teacher forcing)

교사 강요(teacher forcing) 란, 테스트 과정에서 $t$ 시점의 출력이 $t+1$ 시점의 입력으로 사용되는 RNN 모델을 훈련시킬 때 사용하는 훈련 기법 입니다.

훈련할 때 교사 강요를 사용 할 경우, 모델이 $t$ 시점에서 예측한 값을 $t+1$ 시점에 입력으로 사용하지 않고, $t$ 시점의 레이블. 즉, 실제 알고있는 정답을 $t+1$ 시점의 입력으로 사용합니다. 교사 강요를 사용하는 이유는 $(n-1)$ 스텝의 예측값이 실제값과 다를 수 있기 때문 입니다. 예측은 예측일 뿐 실제와 다를 수 있으므로, 따라서 정확한 데이터로 훈련하기 위해 예측값을 다음 스텝으로 넘기는 것이 아니라 실제값을 매번 입력값으로 사용하는 것입니다. 이런 방식을 교사 강요라고 합니다.


정리하면, 디코더의 훈련 단계에서는 교사 강요 방식으로 훈련하지만 테스트 단계에서는 일반적인 RNN 방식으로 예측합니다. 즉, 테스트 단계에서는 Context 벡터를 입력값으로 받아 이미 훈련된 디코더로 다음 단어를 예측하고, 그 단어를 다시 다음 스텝의 입력값으로 넣어준다. 이렇게 반복하여 최종 예측 문장을 생성하는 것입니다. 위 그림을 기준으로 디코더의 훈련 단계에서는 필요한 데이터가 Context 벡터와 <sos>, je, suis, étudiant이다. 하지만 테스트 단계에서는 Context 벡터와 <sos>만 필요합니다. 훈련 단계에서는 교사 강요를 하기 위해 <sos> 뿐만 아니라 je, suis, étudiant 모두가 필요한 것입니다. 하지만 테스트 단계에서는 Context 벡터와 <sos> 만으로 첫 단어를 예측하고, 그 단어를 다음 스텝의 입력으로 넣습니다.

이제 입출력에 쓰이는 단어 토큰들이 있는 부분을 더 세분화해서 살펴보겠습니다.

image


seq2seq에서 사용되는 모든 단어들은 임베딩 벡터로 변환 후 입력으로 사용됩니다. 위 그림은 모든 단어에 대해서 임베딩 과정을 거치게 하는 단계인 임베딩 층(embedding layer) 의 모습을 보여줍니다.

image


위 그림은 컨텍스트 벡터와 I, am, a, student라는 단어들에 대한 임베딩 벡터의 모습을 보여줍니다. RNN 셀에 대해서 확대해보겠습니다.

image

image


하나의 RNN 셀은 각각의 시점(time step)마다 두 개의 입력을 받습니다. 현재 시점(time step)을 $t$ 라고 할 때, RNN 셀은 $t-1$ 에서의 은닉 상태와 $t$ 에서의 입력 벡터를 입력으로 받고, $t$ 에서의 은닉 상태를 만듭니다. 이때 $t$ 에서의 은닉 상태는 바로 위에 또 다른 은닉층이나 출력층이 존재할 경우에는 위의 층으로 보내거나, 필요없으면 값을 무시할 수 있습니다. 그리고 RNN 셀은 다음 시점에 해당하는 t+1의 RNN 셀의 입력으로 현재 t에서의 은닉 상태를 입력으로 보냅니다.

이런 구조에서 현재 시점 $t$ 에서의 은닉 상태과거 시점의 동일한 RNN 셀에서의 모든 은닉 상태의 값들의 영향을 누적해서 받아온 값 이라고 할 수 있습니다. 그렇기 때문에 앞서 언급했던 컨텍스트 벡터 는 사실 인코더에서의 마지막 RNN 셀의 은닉 상태값을 말하는 것이며, 이는 입력 문장의 모든 단어 토큰들의 정보를 요약해서 담고있다 고 할 수 있습니다.


테스트 단계에서 디코더는 인코더의 마지막 RNN 셀의 은닉 상태인 컨텍스트 벡터를 첫번째 은닉 상태의 값으로 사용합니다. 디코더의 첫번째 RNN 셀은 이 첫번째 은닉 상태의 값과, 현재 $t$ 에서의 입력값인 <sos> 로 부터, 다음에 등장할 단어를 예측합니다. 그리고 이 예측된 단어는 다음 시점인 $t+1$ RNN에서의 입력값이 되고, 이 $t+1$ 에서의 RNN 또한 이 입력값과 $t$ 에서의 은닉 상태로부터 $t+1$ 에서의 출력 벡터. 즉, 또 다시 다음에 등장할 단어를 예측하게 될 것입니다.

디코더가 다음에 등장할 단어를 예측하는 부분을 확대해보겠습니다.

image


출력 단어로 나올 수 있는 단어들은 다양한 단어들이 있습니다. seq2seq 모델은 선택될 수 있는 모든 단어들로부터 하나의 단어를 골라서 예측해야 하며, 이를 예측하기 위해서 쓸 수 있는 함수 소프트맥스 함수를 사용할 수 있습니다. 디코더에서 각 시점(time step)의 RNN 셀에서 출력 벡터가 나오면, 해당 벡터는 소프트맥스 함수를 통해 출력 시퀀스의 각 단어별 확률값을 반환하고, 디코더는 출력 단어를 결정합니다.

지금까지 가장 기본적인 seq2seq 를 소개했으며, seq2seq 는 어떻게 구현하느냐에 따라서 충분히 더 복잡해질 수 있습니다.

  • 컨텍스트 벡터를 디코더의 초기 은닉 상태로만 사용
  • 컨텍스트 벡터를 디코더가 단어를 예측하는 매 시점마다 하나의 입력으로 사용
  • 어텐션 메커니즘 방법을 통해 지금 알고있는 컨텍스트 벡터보다 더욱 문맥을 반영할 수 있는 컨텍스트 벡터를 구하여 매 시점마다 하나의 입력으로 사용


seq2seq 를 정리하면 인코더와 디코더로 구성되어 있으며, 인코더는 입력 문장의 정보를 압축하는 기능을 합니다. 압축된 정보는 Context 벡터라는 형식으로 디코더에 전달됩니다. 디코더는 훈련 단계에서는 교사 방식(teaching force)으로 훈련되며, 테스트 단계에서는 인코더가 전달해준 Context 벡터와 <sos> 를 입력값으로 하여 단어를 예측하는 것을 반복하며 문장을 생성합니다.


Seq2Seq 모델의 한계

seq2seq 모델은 인코더 에서 입력 시퀀스를 컨텍스트 벡터라는 하나의 고정된 크기의 벡터 표현으로 압축 하고, 디코더이 컨텍스트 벡터를 통해서 출력 시퀀스를 만들어 냈습니다. 하지만 이러한 RNN에 기반한 seq2seq 모델에는 크게 두 가지 문제 가 있습니다.

  1. 입력 시퀸스의 모든 정보를 하나의 고정된 크기의 벡터(컨텍스트 벡터)에 다 압축 요약하려 하다 보니 정보의 손실이 생길 수밖에 없습니다. 특히 시퀸스의 길이가 길다면 정보의 손실이 더 커집니다.
  2. RNN 구조로 만들어진 모델이다 보니, 필연적으로 gradient vaninshing/exploding 현상이 발생합니다.