Text Gen AI/자연어 처리 (NLP)

Transformer - 모델의 인코더-디코더 구조 이해하기

Vento AI 연구소 2026. 5. 4.
반응형

Transformer 구조 개요 (Encoder-Decoder)

Transformer는 인코더-디코더(encoder-decoder) 구조를 갖춘 딥러닝 모델로, 입력 시퀀스를 받아 출력 시퀀스를 생성하는 시퀀스-투-시퀀스(sequence-to-sequence) 모델이다. 예를 들어 번역기를 생각하면, 인코더가 원문 문장을 입력받아 내부 표현으로 인코딩하고, 디코더가 이 표현을 참고하여 번역된 문장을 한 단어씩 만들어낸다.

 

Transformer의 큰 특징은 RNN과 달리 입력 전체를 한꺼번에 처리하면서도, 어텐션(attention) 메커니즘을 통해 각 단어의 중요도를 동적으로 파악하는 점이다. 이를 통해 문맥을 더 잘 이해하고 병렬 처리로 속도를 높였다.

Transformer의 전체 구조를 간략히 나타낸 그림이다. 왼쪽 Encoder 블록들은 입력 문장을 처리하고, 오른쪽 Decoder 블록들은 출력을 생성한다. 인코더와 디코더 각각 여러 층(layer)을 여러 겹 쌓아서 구성되며(원 논문에서는 6층씩 사용), 각 층은 어텐션과 피드포워드 신경망으로 이루어진 아키텍처를 동일하게 반복한다.

인코더는 자기-어텐션(Self-Attention) 기법으로 입력 문장 내 단어들 사이의 관계를 파악하고, 디코더는 마스크드 자기-어텐션(Masked Self-Attention)과 인코더-디코더 어텐션(Encoder-Decoder Attention, Cross-Attention)으로 이미 생성된 단어들과 인코더 출력 사이의 관계를 고려하며 출력을 만든다. 인코더의 최종 출력은 디코더에서 컨텍스트(context)로 활용되어, 디코더가 어떤 부분에 집중해서(output을) 만들어낼지 결정하는 데 도움을 준다.

요약하면, 인코더-디코더 구조에서 인코더는 입력을 맥락이 반영된 벡터 표현으로 변환하고, 디코더는 이 벡터와 지금까지의 출력을 참고하여 다음 출력을 한 단계씩 만들어낸다. 이제 인코더와 디코더 내부를 차례대로 자세히 살펴보자.

인코더(Encoder) 구조와 흐름

Transformer 인코더는 입력 시퀀스를 받아 내부 표현으로 변환하는 역할을 한다. 하나의 인코더 블록(layer)은 크게 두 부분으로 이루어져 있다. 첫째, 멀티-헤드 셀프-어텐션(Multi-Head Self-Attention) 층, 그리고 둘째, 피드포워드 신경망(Feed-Forward Network) 층이다. 각 층을 통과할 때 입력 벡터들이 다른 단어들과 상호작용하며 정보가 섞이고, 또 개별적으로 비선형 변환을 거쳐 더 풍부한 표현으로 변화한다.

1단계 – 임베딩 및 위치 인코딩

인코더의 맨 앞부분에서는 각 단어(토큰)를 고정 길이 벡터로 변환하는 워드 임베딩(word embedding)을 적용한다. 그리고 위치 인코딩(Positional Encoding) 벡터를 임베딩에 더해준다. 이는 시퀀스 내 단어의 순서 정보를 모델에 알려주기 위한 것으로, Transformer는 순환 구조가 없어 위치 정보를 따로 주입해야 한다.

 

위치 인코딩은 보통 사인/코사인 함수를 이용해 규칙적인 패턴으로 생성되며, 단어 임베딩 벡터에 단순 더하기(Add)로 합쳐진다. 이렇게 하면 모델이 벡터들만 보고도 "이 벡터가 몇 번째 위치"인지를 느낄 수 있게 된다.

예시: 세 단어 “Je – suis – étudiant”에 대해 임베딩 벡터(녹색)와 위치 인코딩 벡터(노랑/파랑 숫자)를 더해 최종 입력 임베딩을 구성한다. 이렇게 위치 정보까지 포함된 임베딩 x1, x2, x3들이 인코더로 들어간다.

2단계 – 멀티-헤드 셀프-어텐션

각 인코더 층의 첫 부분은 셀프-어텐션(self-attention) 메커니즘이다. 셀프-어텐션이란, 한 문장 안에서 단어들이 서로를 참고하여 의미를 더 잘 파악하도록 하는 장치다.

 

예를 들어 문장 "The animal didn't cross the street because it was too tired."에서 "it"이 가리키는 것이 "the animal"임을 인간은 쉽게 알지만, 모델은 명시적으로 알 수 없죠. 이때 인코더의 자기-어텐션이 단어 "it"이 문장의 다른 단어 "animal"과 강하게 연관되도록 만들어준다.

 

즉, 셀프-어텐션 층은 각 단어 벡터를 다른 모든 단어 벡터와 비교하여 관련성이 큰 단어의 정보를 현재 단어의 표현에 반영한다.

 

좀 더 구체적으로, 셀프-어텐션에서는 입력 벡터들로부터 세 종류의 벡터를 만들어낸다. 쿼리(Query), 키(Key), 값(Value) 벡터다. 각 단어마다 고유한 Query/Key/Value가 있고, Query와 Key의 유사도(내적 점수)를 계산하여 어텐션 가중치를 얻는다.

 

Query–Key 점수가 높을수록 해당 단어들끼리 연관성이 높다고 판단한다. 그리고 그 가중치를 이용해 Value 벡터들을 가중 합해 새로운 출력 벡터를 얻는다. 이 과정을 식으로 나타내면 다음과 같다.

여기서 dk는 Key 벡터의 차원이며, 분모 스퀘어 dk는 스케일 조정을 위한 값이다. 즉, Query–Key 내적을 dk의 제곱근으로 나눠주고 Softmax를 취해 합이 1인 확률 분포로 만든 뒤, 이를 Value에 곱해 합산한 결과가 어텐션 출력이 된다.

 

한마디로, 각 단어가 다른 단어들을 얼마나 참고할지 비율을 정해 새로운 표현을 만드는 것이다.

예시: 두 단어 “Thinking”(x₁)과 “Machines”(x₂)의 임베딩 벡터로부터 각각 Query(q₁,q₂), Key(k₁,k₂), Value(v₁,v₂) 벡터를 생성한다. Q, K, V는 입력 임베딩에 학습된 가중치 행렬을 곱하여 얻는다. 이제 q₁과 모든 k 벡터들의 내적을 구해 가중치를 계산한다.

예시 계속: 단어 "Thinking"의 Query q₁를 사용하여 k₁ (자신)과 k₂ ("Machines")에 대한 점수를 계산한다. (q₁·k₁=112, q₁·k₂=96). 차원 dk=64의 제곱근인 8로 나누면 14와 12가 되고, Softmax를 적용하면 약 [0.88, 0.12]의 가중치가 얻어진다.결국 "Thinking" 단어의 새로운 표현은 자신의 Value v₁에는 88%를, "Machines"의 Value v₂에는 12%를 반영하게 된다.

Transformer에서는 이러한 어텐션을 한 번만 쓰지 않고 멀티-헤드 어텐션(Multi-Head Attention)으로 확장한다. 멀티-헤드란 여러 개의 어텐션을 병렬로 수행한다는 뜻이다. 예를 들어 8개의 헤드(head)를 사용하면, Query/Key/Value를 서로 다른 8개의 부분 공간으로 투영한 후 각각 어텐션을 구하고 마지막에 합친다.

 

이렇게 하면 모델이 여러 종류의 관계를 동시에 포착할 수 있고, 한 헤드에서는 놓칠 수 있는 세부 정보를 다른 헤드가 보완할 수 있다. 예를 들어 한 헤드는 직전 단어와의 관계에 집중하고, 다른 헤드는 문장 전체 주제에 집중하는 식으로 역할을 나눌 수 있다.

어텐션 결과는 Residual Connection이라는 지름길 경로를 통해 입력과 더해지고(스킵 연결), Layer Normalization을 거친 후 다음 단계로 전달된다. Residual(잔차) 연결은 입력 x를 다음 층 계산 결과 f(x)와 더하여 x + f(x) 형태로 전달하는 것으로, 신경망 학습을 안정화하고 기울기 소실을 막아주는 역할을 한다. 정규화(LayerNorm)는 각 층 출력의 분포를 조정하여 학습을 원활하게 합니다. 이러한 부분들은 모델의 훈련 안정성을 높여주는 테크닉이므로, 핵심 개념을 이해하는 데 너무 걱정하지 않아도 된다.

3단계 – 피드포워드 신경망

어텐션 층을 통과한 각 단어의 출력 벡터는 개별적으로 짧은 피드포워드 신경망(FFN) 층을 통과한다. 이 신경망은 2개의 선형변환(layer)과 활성화 함수(ReLU 등)로 이루어진 작은 MLP로 생각할 수 있다. 어텐션이 단어들 사이의 관계를 반영했다면, FFN은 각 단어의 표현을 독립적으로 변형시켜 더 복잡한 특징을 추출한다.

 

재미있는 점은, FFN은 각 위치에 똑같은 네트워크를 적용하지만 입력이 다르므로 출력은 각기 다르게 나온다. 이때도 Residual 연결로 입력이 더해지고 LayerNorm이 적용된다. 인코더의 모든 서브층에 잔차연결+정규화가 존재한다.

이 과정을 거치면 인코더의 한 층을 통과한 결과가 나오고, 이 출력이 다음 인코더 층의 입력으로 들어간다. 인코더를 여러 층 쌓았다면, 가장 위층 인코더의 출력까지 이런 식으로 계산된다. 최종적으로 인코더에서 나온 출력 벡터들은 입력 문장의 각 단어에 대응되는 문맥이 반영된 표현이라고 볼 수 있다. 이제 디코더는 이 인코더 출력을 활용하여 원하는 출력 시퀀스를 만들어낸다.

디코더(Decoder) 구조와 흐름

Transformer 디코더는 인코더의 출력(문맥 벡터들)을 받아 최종 출력 시퀀스(예: 번역 문장)를 만들어낸다. 디코더도 인코더와 유사하게 여러 개의 층을 쌓아서 구성되고, 각 층이 세 가지 서브층(sub-layer)으로 이루어진다.

디코더 층의 구성 요소는 다음과 같다.

  1. 마스크드 멀티-헤드 셀프-어텐션 (Masked Multi-Head Self-Attention)
  2. 인코더-디코더 멀티-헤드 어텐션 (Encoder-Decoder Attention, Cross-Attention)
  3. 피드포워드 신경망 (Feed-Forward Network)

Residual 연결과 LayerNorm도 각 서브층마다 인코더 때처럼 적용된다.

디코더 입력

디코더는 출력 시퀀스를 자동회귀적으로(auto-regressive) 생성한다. 이는 한 번에 한 토큰씩 순서대로 출력한다는 뜻으로, 디코더의 현재 단계에서는 이미 생성된 이전까지의 출력만을 활용하고 아직 생성되지 않은 미래 토큰은 보지 못하도록 차단해야 한다. 그래서 디코더에 입력으로 주어지는 문장은 일반적으로 이미 나온 출력들이다. 학습 시에는 정답 문장을 한 토큰씩 밀어(shifting) 넣어주고, 추론 시에는 이제 막 예측한 출력을 다시 다음 입력으로 넣어주는 방식으로 작동한다.

1단계 – 마스크드 셀프-어텐션

디코더 각 층의 첫 번째 서브층은 자기-어텐션으로, 디코더가 지금까지 생성한 출력 시퀀스 내부의 관계를 파악한다. 그러나 여기서는 미래 단어에 대한 정보를 참조하면 안 되므로, 마스킹(masking)을 통해 현재 위치 이후의 토큰들은 어텐션 계산에서 무시한다.

 

구체적으로는 미래 위치에 해당하는 점수에 −∞ 값을 넣어 Softmax 후 0이 되게 만드는 마스크 행렬을 활용한다. 이렇게 하면 디코더의 자기-어텐션은 오직 이전까지 나온 단어들에만 집중하게 되어, 올바른 auto-regressive 생성이 이루어진다. 예를 들어 디코더가 세 번째 단어를 예측할 때는 첫 번째와 두 번째 단어만 자기-어텐션에 사용된다.

2단계 – 인코더-디코더 어텐션(Cross-Attention)

디코더 층의 두 번째 서브층은 인코더의 출력에 대한 어텐션이다. 이를 통해 디코더는 입력 문장의 내용 중 어떤 부분이 현재 출력 단계를 만드는 데 중요한지 참고할 수 있다. 구조적으로는 멀티-헤드 어텐션이라는 점에서 앞의 자기-어텐션과 비슷하지만, Query는 디코더의 바로 이전 결과를 사용하고 Key, Value는 인코더의 최종 출력을 사용한다는 차이가 있다.

다시 말해, 디코더는 현재까지 생성된 문맥(Query)을 가지고 인코더가 만든 입력 문장 표현(Key, Value)을 훑어보며 어느 부분에 집중할지 가중치를 할당한다. 이는 과거의 seq2seq 모델에서 인코더-디코더 어텐션(예: Luong 어텐션, Bahdanau 어텐션 등)과 동일한 역할로, 출력 단어와 입력 단어의 정렬(alignment)을 학습하게 된다.

 

예를 들어 번역 디코더가 "tired"라는 출력을 낼 때, 인코더 출력 중 "피곤한"에 해당하는 부분에 강한 어텐션을 두는 식이다.

인코더-디코더 어텐션 결과도 Residual 경로로 입력과 더해지고 정규화된다. 이 단계까지 거치면 디코더의 각 위치 벡터는 현재 출력 단어가 취해야 할 값에 대한 풍부한 정보를 담고 있게 된다 (이전 출력 맥락 + 입력 전체 맥락).

3단계 – 피드포워드 신경망

그 다음에는 인코더와 마찬가지로 각 위치별로 독립적인 FFN(피드포워드 신경망)을 통과한다. 이를 통해 어텐션 결과를 한 번 더 비선형 변환하여 최종 출력을 위한 특징을 추출한다. 이 출력 역시 Residual 연결로 어텐션 결과와 더해지고, LayerNorm으로 정규화된다.

최종 출력 생성

디코더의 맨 마지막 층까지 처리가 끝나면, 각 위치에 대해 하나의 출력 벡터가 얻어집니다. 하지만 우리가 필요한 것은 실제 단어(token)다. 따라서 디코더 마지막 출력에 선형 변환(Linear)을 적용하여 어휘집 크기의 차원으로 바꾼 뒤, 소프트맥스(Softmax) 함수를 통해 확률 분포로 변환한다. 이 분포에서 확률이 가장 높은 단어를 뽑으면 그것이 현재 시점의 출력 단어가 된다.

 

예컨대 첫 번째 출력에 대해 소프트맥스 결과 “I(0.1), We(0.7), You(0.2)”가 나왔다면 “We”를 선택하는 식이다. 이 단어는 최종 출력 시퀀스의 한 요소가 되고, 또 다음 출력 시점을 위해 디코더의 입력으로 들어간다. 디코더는 이러한 과정을 특수 토큰(e.g. <eos>)이 출력될 때까지 반복하여 전체 문장을 완성한다.

요약하면, 인코더-디코더 어텐션 덕분에 디코더는 인코더 출력(입력 문장의 의미)을 언제든 참고하며, 마스킹 덕분에 올바른 순서로 출력을 만들어낸다.

 

이제 이러한 흐름을 단계별로 정리해 보자.

인코더-디코더 협력: 단계별 예시

Transformer의 동작 과정을 기계 번역 예시로 간단히 정리해 본다.

1) 입력 임베딩 & 인코딩

원문 문장의 각 단어를 임베딩하고 위치 인코딩을 더하여 인코더에 넣는다. 인코더는 여러 층을 거치면서 문장 전체의 맥락 정보를 각 단어 벡터에 녹여낸다. 인코더의 최종 출력은 입력 문장의 의미와 문맥을 담은 벡터들이다.

2) 디코더 초기화

디코더는 시작 토큰(<sos>) 혹은 비어있는 출력에서 시작한다. 첫 디코더 층의 마스크드 자기-어텐션은 실질적으로 할 것이 없지만(출력이 없으므로), 개념적으로는 "이전 출력이 없다"는 마스크가 적용된 상태다.

3) 인코더-디코더 어텐션

디코더는 인코더의 출력 전체를 어텐션하여, 입력 문장의 어떤 부분을 참고해야 현재 출력에 도움이 될지 가중치를 얻는다. 처음 출력 단어를 만들 땐 주로 입력 문장의 시작 부분이나 주어 쪽에 주의가 간다.

4) 첫 번째 출력 생성

디코더의 계산 결과 벡터를 선형 변환하고 소프트맥스하여 첫 번째 출력 단어를 예측한다. 예를 들어 원문 "I am a student"에 대해 첫 출력으로 "저는"을 선택했다고 하자.

5) 다음 단계 반복

예측된 첫 번째 출력 "저는"을 디코더의 다음 입력으로 넣는다. 이제 디코더의 마스크드 자기-어텐션은 이전 출력 "저는"을 참고하여 두 번째 단어를 낼 준비를 한다. 다시 인코더-디코더 어텐션으로 입력 문장 전체를 살펴보고, 두 번째 출력 벡터를 산출한 뒤 소프트맥스로 두 번째 단어를 뽑는다 (예: "학생"). 이렇게 이전까지 생성된 출력들인코더 출력을 모두 활용하며 단어들을 하나씩 생성한다.

6.종료

디코더가 종료 토큰(<eos>)을 생성하면 출력 생성을 마친다. 완성된 출력 시퀀스는 최종 번역문이 됩니다. 예시의 경우 "저는 학생이다."와 같이 문장이 완성될 때까지 위 과정을 거친다.

이상 과정을 통해 인코더와 디코더가 상호작용하며 결과를 산출한다. 인코더는 입력을 이해하는 역할을, 디코더는 그것을 바탕으로 문장을 말하는 역할을 한다고 비유할 수 있다. 둘 사이를 연결해 주는 것이 바로 어텐션이다.

댓글