Seq2Seq에서 Transformer까지: 문맥 누적에서 직접 참조로 · 1편

Vanilla Seq2Seq - 마지막 상태에 의존하던 구조

Seq2Seq의 기본 구조를 이해하려면 먼저 "입력 정보를 하나의 벡터로 압축한다"는 사실부터 받아들여야 합니다. 마지막 hidden state에 모든 정보를 담는 방식, 그리고 그 구조가 가진 근본적 한계.

들어가며

이 시리즈는 Seq2Seq에서 Transformer까지의 진화를 따라가면서, 각 단계에서 어떤 문제가 있었고 어떻게 해결했는지 살펴본다. 핵심 주제는 하나다: 정보를 어떻게 전달할 것인가.

첫 번째 편에서는 가장 기본적인 형태인 Vanilla Seq2Seq를 본다.


Encoder와 Decoder

Seq2Seq 모델은 두 부분으로 나뉜다.

  • Encoder: 입력 문장을 읽고 하나의 벡터로 압축한다
  • Decoder: 그 벡터를 받아 출력 문장을 한 단어씩 생성한다

두 부분 모두 보통 RNN, LSTM, GRU 계열로 구성된다.


문맥의 누적: Encoder의 동작 방식

Encoder는 입력 토큰을 순서대로 읽으며 hidden state를 업데이트한다. 입력이 $x_1, x_2, x_3$일 때:

\[h_1 = \text{encoder}(\text{emb}(x_1))\] \[h_2 = \text{encoder}(h_1,\; \text{emb}(x_2))\] \[h_3 = \text{encoder}(h_2,\; \text{emb}(x_3))\]

여기서 눈여겨 봐야할 부분은 $h_3$이 세 번째 토큰만의 정보가 아니라는 점이다. $h_1 \rightarrow h_2 \rightarrow h_3$으로 흐르며 앞선 문맥이 순서대로 누적된 상태다.


정보의 병목: Decoder에게 넘어가는 단 하나의 벡터

Encoder의 계산이 끝나면 Decoder가 출력을 시작한다. Vanilla Seq2Seq에서 Decoder는 $h_1, h_2, h_3$을 전부 보지 않는다. 오직 마지막 hidden state만 전달받는다. 이를 문맥 벡터(context vector, $c$)라고 부른다.

\[c = h_T\]

Decoder는 이 벡터 하나를 시작으로, 문장이 끝날 때까지 매 시점 동일한 $c$에 의존해 출력을 이어나간다.

\[S_k = f(S_{k-1},\; \text{emb}(y_{k-1}),\; c)\]

Vanilla Seq2Seq의 한계

이 구조는 단순하고 구현이 쉽지만, 문장이 길어질수록 문제가 생긴다.

정보 병목: 고정된 크기의 벡터 $c$ 하나에 입력 문장 전체의 정보를 담아야 한다. 문장이 길수록 앞쪽 정보가 $h_T$에 달하기까지 여러 단계를 거치며 희석된다. LSTM의 forget gate가 이를 어느 정도 완화하지만, 구조적 한계는 그대로 남는다.

고정된 참조: Decoder “제인이”를 생성할 때와 “가게에”를 생성할 때, 참고하는 정보는 항상 같은 $c$다. 출력 시점마다 입력의 다른 부분을 집중해서 봐야 한다는 직관과 맞지 않는다.


요약

  Vanilla Seq2Seq
Encoder 출력 $h_T$ 하나
Context vector 고정 ($c = h_T$)
Decoder 참조 항상 같은 $c$
긴 문장 성능 급격히 저하

핵심은 이것이다.

Vanilla Seq2Seq는 입력 문맥을 순서대로 누적하고, 마지막 상태 하나에 의존한다.


다음 편 예고

다음 편에서는 이 병목을 해결하는 방법을 본다. Decoder가 $h_1 \sim h_n$을 전부 보고, 필요한 상태를 매 시점마다 직접 참조하는 방식이다.




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • ROUGE
  • 덕 타이핑, 그리고 파이썬이 타입을 다루는 방식
  • 행렬 곱의 네 가지 관점
  • How to teach your embedding model new words
  • classmethod vs staticmethod in Python