Day 33: 처음부터 미니 트랜스포머 구축하기 (코드 워크스루)
요약
본 글은 NLP의 핵심 아키텍처인 트랜스포머를 처음부터 구축하는 과정을 코드 워크스루 형식으로 설명합니다. 트랜스포머는 어텐션 메커니즘을 통해 병렬 처리가 가능하며, RNN/CNN의 한계를 극복했습니다. 셀프 어텐션은 Query, Key, Value 개념과 점수 계산(dot product), 소프트맥스를 거쳐 문맥적 의미를 포착하는 핵심 원리를 다룹니다.
핵심 포인트
- 트랜스포머는 병렬 처리가 가능하여 GPU 활용에 효율적입니다.
- 셀프 어텐션은 Query, Key, Value 세 가지 벡터로 구현됩니다.
- 어텐션 가중치는 쿼리와 키의 점수 계산 후 소프트맥스로 얻습니다.
- 최종 임베딩은 어텐션 가중치를 이용한 값(Value) 벡터들의 가중 합입니다.
트랜스포머(Transformers)는 2017년에 자연어 처리(NLP) 분야를 재편한 아키텍처입니다. 트랜스포머 이전에는 최고 성능의 모델들이 순환 신경망(RNNs)이나 합성곱 신경망(CNNs) 중 하나를 사용했습니다. 트랜스포머는 기계 번역, 텍스트 요약, 질의응답과 같은 작업에서 극적인 개선을 가져왔습니다. 이들의 주요 강점은 **어텐션 메커니즘(attention mechanism)**인데, 이는 모델이 문장 내 어느 곳에 있는 중요한 단어에 집중할 수 있게 해주며, 단순히 근처의 단어에만 국한되지 않습니다. 오늘날 볼 수 있는 모든 대규모 언어 모델(LLMs)—GPT, BERT, Llama—은 트랜스포머 또는 그와 유사한 구조를 기반으로 구축되었습니다.
트랜스포머 이전에는 RNN과 CNN이 상당한 한계에 직면했습니다. RNN은 입력을 단계별로 처리하기 때문에 멀리 떨어진 단어 간의 연결성을 파악하는 데 어려움을 겪습니다. CNN은 국소적인 패턴을 포착할 수 있지만, 긴 텍스트 전체를 '보기' 위해서는 많은 레이어가 필요합니다. 두 유형 모두 계산이 완전히 병렬화될 수 없기 때문에 최신 GPU를 효율적으로 사용하기 어렵다는 문제가 있습니다. 입력의 일부는 다음으로 넘어가기 전에 먼저 처리되어야 합니다.
반면, 트랜스포머는 전체 입력 시퀀스를 한 번에 처리합니다. 이들의 핵심 발명품인 **셀프 어텐션(self-attention)**은 모든 단어가 입력의 다른 모든 단어를 '바라볼' 수 있게 합니다. 이는 풍부하고 문맥을 인식하는 표현을 가능하게 하며 빠르고 병렬적인 계산을 허용합니다.
트랜스포머의 주요 구성 요소를 개괄해 보겠습니다:
- 입력 임베딩(Input Embeddings): 모델은 단어나 토큰을 벡터—숫자 목록—로 변환하며, 이는 기본적인 의미와 구조를 포착합니다.
- 셀프 어텐션(Self-Attention): 각 임베딩은 같은 시퀀스 내의 다른 모든 임베딩으로부터 그 관계에 따라 정보를 수집할 수 있습니다. 이를 통해 각 토큰에 대해 전체 시퀀스의 문맥을 모읍니다.
- 피드포워드 네트워크(Feedforward Network): 이제 문맥을 인식하게 된 각 임베딩은 그것을 더욱 정제하는 간단한 신경망을 통과합니다.
- 출력(Output): 이 과정은 레이어를 쌓아 반복될 수 있습니다. 최종 출력은 텍스트 분류나 다음 단어 생성 같은 예측에 사용할 준비가 된 벡터 시퀀스입니다.
핵심 메커니즘인 **셀프 어텐션(self-attention)**에 집중하세요. 다음 문장을 예로 들어보겠습니다:
“The cat sat on the mat.”
셀프 어텐션은 "mat" 토큰이 "the", "cat", "sat", 그리고 "on"으로부터 정보를 가져와, 문맥 속에서 더 풍부한 의미를 구축할 수 있게 합니다.
다음은 셀프 어텐션의 핵심을 단계별로 설명합니다:
- 각 입력 토큰(token) (단어에 대한 모델의 내부 표현)에 대해 세 가지 다른 버전인 쿼리(Query), 키(Key), 그리고 **값(Value)**를 생성합니다. 각 버전은 토큰 임베딩을 작은 행렬과 곱하여 만들어진 벡터입니다.
- 모든 토큰 쌍에 대해, 한 토큰의 쿼리와 다른 토큰의 키를 사용하여 "점수(score)"를 계산합니다. 이는 점(dot product)을 통해 수행됩니다: 두 요소들을 곱하고 합산하는 방식입니다.
- 이러한 점수를 소프트맥스(softmax) 함수를 사용하여 스케일링 및 정규화하여, 각 쿼리에 대해 그 합이 1이 되도록 만듭니다. 이것이 바로 어텐션 가중치(attention weights)이며—어떤 토큰에 얼마나 "주의를 기울여야" 하는지를 나타냅니다.
- 각 토큰의 새로운 임베딩은 이러한 어텐션 가중치를 사용하여 모든 값 벡터(Value vectors)들의 가중 합(weighted sum)입니다.
예를 들어, A, B, C라는 세 개의 토큰이 있다고 가정하면, A에 대한 셀프 어텐션은 다음을 계산합니다:
- A가 자신에게 얼마나 집중해야 하는가? (A의 쿼리 $\cdot$ A의 키)
- B에게는 얼마나? (A의 쿼리 $\cdot$ B의 키)
- C에게는 얼마나? (A의 쿼리 $\cdot$ C의 키) A의 새로운 표현은 A, B, C의 값 벡터들의 가중 평균입니다. 이 과정을 각 토큰에 대해 반복합니다.
**트랜스포머 레이어(transformer layer)**는 두 가지 주요 부분으로 구성됩니다:
- 멀티 헤드 셀프 어텐션(Multi-Head Self-Attention) 블록 (여러 개의 어텐션 연산을 병렬로 실행하는 경우가 많습니다. 단순화를 위해 여기서는 하나만 사용합니다.)
- 피드포워드 블록(Feedforward block) (각 위치에 대한 작은 신경망)
트랜스포머는 이러한 레이어들을 순차적으로 여러 개 쌓아 깊고 유연한 표현을 만듭니다.
다음은 NumPy를 사용하여 단계별로 구축된, 실행 가능한 최소 트랜스포머입니다. 이 버전은 필수적인 요소만 간소화되었습니다: 단일 셀프 어텐션 헤드, 기본적인 피드포워드 네트워크, 추가 기능 없음.
import numpy as np
def softmax(x, axis=-1):
...
이 코드는 무엇을 하나요?
- 가짜 문장(fake sentence): 토큰 ID 목록을 받습니다.
- 해당 토큰들의 임베딩을 조회합니다.
- 셀프 어텐션(self-attention)을 적용하여 모든 토큰이 다른 모든 토큰으로부터 정보를 혼합하도록 합니다.
- 업데이트된 벡터들을 작은 신경망 레이어(neural network layer)를 통과시켜 정제합니다.
이는 프로덕션 시스템이 아닙니다. 학습 과정도, 멀티 헤드 어텐션(multi-head attention), 정규화(normalization), 잔여 연결(residual connections), 또는 위치 인코딩(positional encoding) 같은 것은 없습니다—단지 최소한의 메커니즘만 포함되어 있습니다. 그 목적은 트랜스포머의 움직이는 부품들을 눈에 보이게, 구체적으로 만드는 것입니다.
입력 토큰, 시퀀스 길이 또는 임베딩 크기를 변경해 보세요. 새로운 시퀀스를 model.forward()에 전달합니다. 출력 벡터들이 어떻게 업데이트되는지 관찰하세요.
이 간단한 트랜스포머를 줄 단위로 따라가면서, 이제 입력 토큰들이 어떻게 풍부하고 문맥을 인식하는 표현(context-aware representations)으로 변환되는지 알게 됩니다. 셀프 어텐션과 피드포워드 레이어(feedforward layers)—각각 행렬 곱셈(matrix multiplication)과 소프트맥스(softmax)의 간단한 조합—가 모든 트랜스포머의 핵심을 이룹니다. 현대의 대규모 언어 모델(large language models)들은 이러한 조각들을 더 높고 넓게 쌓아 올리지만, 그 기초는 동일합니다.
주요 시사점 (Key Takeaways)
- 트랜스포머는 셀프 어텐션을 사용하여 토큰들이 시퀀스의 다른 모든 토큰으로부터 정보를 가져올 수 있도록 합니다.
- RNN이나 CNN 같은 이전 모델들은 순차적 처리와 국소성(locality)에 의해 제한되었습니다.
- 트랜스포머의 핵심 구성 요소에는 입력 임베딩, 셀프 어텐션, 그리고 피드포워드 네트워크가 포함됩니다.
- 셀프 어텐션은 모든 토큰 쌍 간의 어텐션 점수(attention scores)를 계산하여 문맥 정보를 혼합합니다.
- 최소화된 트랜스포머는 오직 행렬 곱셈과 소프트맥스 연산만으로 구축할 수 있습니다.
직접 해보기 (Try It Yourself)
제공된 미니 트랜스포머 코드를 수정하여 5개의 토큰 ID 대신 6개의 입력 시퀀스를 사용하도록 하세요. 새로운 시퀀스에 대한 어텐션 행렬(attn_weights 코드 내)을 출력하세요. 모델이 위치 전반에 걸쳐 어떻게 어텐션을 재분배하는지, 그리고 가중치에서 어떤 패턴을 발견했는지 1~2문장으로 설명해 보세요.
추가 자료 (Further Resources)
• 🎥 PyTorch로 처음부터 트랜스포머 구현하기 (전체 설명, 학습 및 추론 포함)
• 🎥 처음부터 트랜스포머 코딩하는 방법!
• 📘 Mini Transformer from Scratch (PyTorch 기반 디코더 전용 GPT 스타일)
• 📘 Mini Transformer: End-to-End 워크스루 (NumPy 구현)
• 📄 처음부터 미니어처 트랜스포머 구축하기: 딥러닝의 '레고' 방식
Day 34에서 다룰 내용: 사전 학습이란 무엇인가? NLP를 위한 전이 학습
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기