그림으로 보는 Transformer
— 상자를 하나씩 열어 보기
Jay Alammar의 The Illustrated Transformer는 Transformer를 처음 배우는 사람들이 가장 많이 읽는 글 중 하나입니다. 이 페이지는 그 글의 흐름을 따라가면서, 그림을 보는 데서 그치지 않고 그림 속 숫자를 직접 계산해 보도록 새로 쓴 한국어 해설입니다. 블랙박스 하나에서 시작해 인코더·디코더 스택, 셀프 어텐션 한 칸, 멀티헤드, 위치 인코딩, 출력 확률과 손실까지 — 상자를 하나씩 열어 가며, 열 때마다 안에서 도는 계산을 브라우저에서 돌려 봅니다.
§1 멀리서 보기 — 블랙박스 하나에서 12개의 상자로
번역기를 생각해 봅시다. 밖에서 보면 한국어 문장이 들어가 영어 문장이 나오는 상자 하나입니다. Transformer도 처음엔 그렇게 보입니다. 한 겹 열면 상자가 두 개로 갈라집니다 — 입력 문장을 읽어서 이해한 결과를 만드는 쪽(인코더)과, 그 결과를 참고해 출력 문장을 한 단어씩 써 내려가는 쪽(디코더). 한 겹 더 열면 각각이 같은 모양의 층을 6개 쌓은 더미입니다.
중요한 점 두 가지만 먼저 짚어 둡니다. 첫째, 인코더 6개 층은 구조는 같지만 가중치는 따로 가집니다(복사본이 아닙니다). 둘째, 디코더의 모든 층이 인코더 맨 위 층의 출력을 참고합니다 — 인코더 1층이 디코더 1층에, 2층이 2층에 연결되는 게 아닙니다. 아래 그림에서 단계 버튼을 눌러 상자를 열어 보세요.
§2 단어가 벡터가 되는 순간
신경망은 글자를 못 읽습니다. 그래서 맨 처음 하는 일은 단어(정확히는 토큰)마다 숫자 벡터 하나를 찾아 붙이는 것입니다.
큰 표(임베딩 행렬)에서 그 단어의 행을 꺼내는 것이고, 논문에서 이 벡터의 길이는 d_model = 512입니다.
문장이 토큰 n개라면, 인코더에 들어가는 것은 n × 512 행렬 하나입니다.
여기서부터 기억해 둘 성질이 있습니다. 모든 인코더 층은 n × 512를 받아서 n × 512를 내보냅니다. 모양이 변하지 않으니 같은 층을 몇 개든 쌓을 수 있고, 각 토큰은 층을 지나는 내내 자기 자리의 벡터로 흘러갑니다. 이 페이지의 데모는 손으로 따라갈 수 있도록 512 대신 4차원으로 줄였습니다.
§3 인코더 한 층 안 — 섞는 층과 혼자 가공하는 층
인코더 한 층을 열면 부품이 두 개 나옵니다. 아래쪽의 셀프 어텐션과 위쪽의 피드포워드 신경망(FFN)입니다. 둘은 하는 일이 정반대라서 같이 기억하면 좋습니다.
- 셀프 어텐션은 토큰끼리 섞습니다. 각 자리의 출력이 문장의 다른 모든 자리를 조금씩 참고해 만들어집니다. 자리 사이에 의존성이 생기는 유일한 곳입니다.
- FFN은 토큰마다 혼자 가공합니다. 모든 자리에 같은 가중치의 작은 2층 MLP를 따로따로 적용합니다. 자리끼리 정보가 오가지 않으니 n개를 완전히 병렬로 돌릴 수 있습니다.
그래서 "섞기 → 각자 소화하기"를 한 층에서 한 번씩 하는 셈이고, 이것을 6번 반복하는 것이 인코더입니다. 아래 데모에서 토큰 순서를 섞어 보면 두 부품의 차이가 숫자로 드러납니다.
§4 셀프 어텐션, 무엇을 하려는 것인가
다음 문장을 번역한다고 해 봅시다.
The trophy didn't fit in the suitcase because it was too big.
it은 트로피일까요, 가방일까요? 사람은 "너무 커서 안 들어갔다"면 트로피, "너무 작아서"라면 가방이라고 바로 압니다.
한국어로 옮길 때 "그것"으로 뭉개지 않고 제대로 옮기려면, 모델도 it의 벡터를 만들 때 문장의 다른 단어를 들여다봐야 합니다.
셀프 어텐션은 바로 그 일을 합니다 — 각 단어의 새 벡터를, 문장 속 모든 단어의 벡터를 얼마나 관련 있는지에 따라 가중 평균해서 만듭니다.
RNN이라면 it에 도달할 때까지 앞 단어들을 은닉 상태 하나에 눌러 담아 와야 했습니다. 셀프 어텐션은 거리가 몇 칸이든 한 번에 직접 봅니다.
it은 어디를 보는가it의 어텐션 가중치가 옮겨 갑니다. 단어를 클릭하면 그 단어가 보는 곳이 표시됩니다.⚠ 이 가중치는 개념을 보여 주려고 손으로 정한 값입니다. 학습된 모델에서 한 헤드가 이렇게 깔끔하게 한 곳을 가리키는 경우는 드뭅니다(§7).
§5 셀프 어텐션 한 칸을 손으로 — 6단계
이제 "관련 있는 정도"를 실제로 어떻게 재는지 봅니다. 각 토큰의 벡터 x에서 세 개의 새 벡터를 만듭니다. 세 가중치 행렬 WQ, WK, WV를 곱하는 것뿐이고, 이 행렬들이 학습되는 파라미터입니다.
| 이름 | 비유 | 쓰이는 곳 |
|---|---|---|
| Query (q) | "나는 이런 정보를 찾고 있어" — 검색어 | 내 자리의 출력을 만들 때, 다른 자리들에 던지는 질문 |
| Key (k) | "나는 이런 정보를 갖고 있어" — 색인 딱지 | 다른 자리의 q와 내적되어 점수가 됨 |
| Value (v) | "뽑혀 가면 실제로 넘겨줄 내용" | 점수로 가중 평균되는 재료 |
논문에서 q·k·v는 512가 아니라 64차원입니다(§7에서 이유가 나옵니다). 여기서는 4차원 입력에서 3차원 q·k·v를 만듭니다. 한 자리(쿼리 토큰)의 출력 z가 만들어지는 과정은 다음 여섯 단계입니다.
3단계에서 √d_k로 나누는 이유를 조금 더 봅시다. q와 k의 성분이 평균 0·분산 1 정도의 값이면, 내적은 dk개 항의 합이므로
분산이 dk로 커집니다(표준편차 √dk). dk=64면 점수가 ±8 규모로 흩어지고, softmax가 한 칸에 거의 1을 몰아주게 됩니다.
그러면 나머지 칸의 기울기가 사실상 0이 되어 학습이 멈춥니다. √dk로 나누면 분산이 다시 1 근처로 돌아옵니다. 아래 슬라이더로 확인하세요.
§6 같은 계산을 행렬 한 번에
§5는 쿼리 토큰 하나씩 계산했지만, 실제로는 모든 토큰을 행렬 곱 몇 번으로 한꺼번에 처리합니다. 토큰 벡터들을 행으로 쌓은 X(n × dmodel)에 가중치를 곱하면 Q, K, V가 한 번에 나오고, 여섯 단계는 공식 한 줄로 접힙니다.
Q·KT는 n × n 행렬이고, (i, j) 칸은 "i번 토큰의 질문과 j번 토큰의 딱지"의 내적입니다. 모든 쌍의 점수를 한 장에 담은 것이죠. GPU가 가장 잘하는 일이 큰 행렬 곱이라서, 이 형태가 RNN보다 훨씬 빠르게 학습되는 이유이기도 합니다.
§7 머리가 여럿인 어텐션
어텐션 한 번은 결국 가중 평균 한 번입니다. 그런데 한 단어가 동시에 여러 관계를 가질 수 있습니다 —
it은 "무엇을 가리키는가"와 "어떤 동사의 주어인가"를 동시에 알아야 합니다. 평균 하나로는 두 관계가 뭉개집니다.
그래서 WQ·WK·WV를 h벌 두고 어텐션을 h번 따로 돌립니다(논문은 h = 8). 각 "헤드"는 서로 다른 부분 공간에서 서로 다른 관계를 봅니다. 결과 Z1…Zh는 다음 FFN이 행렬 하나를 기대하므로 옆으로 이어 붙인 뒤(concat) 행렬 WO를 곱해 다시 dmodel 폭으로 돌려놓습니다.
헤드를 늘린다고 계산이 h배가 되지는 않습니다. 각 헤드의 폭을 d_k = d_model / h로 줄이기 때문입니다(512 / 8 = 64 — §5에서 64였던 이유).
전체 파라미터 수는 h와 상관없이 같습니다.
§8 순서를 알려 주는 법 — 위치 인코딩
데모 3에서 확인했듯, 지금까지의 계산에는 순서 개념이 없습니다. 셀프 어텐션은 문장을 "순서 있는 줄"이 아니라 "토큰 주머니"로 봅니다. 토큰을 섞으면 출력도 똑같이 섞일 뿐 각 토큰의 값은 그대로라, "개가 사람을 물었다"와 "사람이 개를 물었다"를 구분할 방법이 없습니다. 그래서 임베딩에 자리마다 다른 벡터를 더해 넣습니다. 이것이 위치 인코딩(PE)입니다.
이렇게 만들면 각 위치가 고유한 무늬를 갖고(시계의 초침·분침·시침을 함께 읽으면 시각이 하나로 정해지듯), 두 위치의 내적이 거리에만 의존하게 됩니다. 또 공식이라서 학습 때 본 적 없는 긴 문장에도 값을 만들 수 있습니다.
그림 한 가지는 주의해서 보세요. 논문 공식은 sin과 cos를 한 칸씩 번갈아(짝수 차원 sin, 홀수 차원 cos) 놓지만, 여러 구현(예: 초기 tensor2tensor)과 해설 그림은 앞 절반 sin, 뒤 절반 cos으로 이어 붙입니다. 차원의 순서만 다를 뿐 담긴 정보는 같습니다 — 아래 토글로 비교해 보세요.
아래 곡선: 기준 위치의 PE와 다른 모든 위치 PE의 내적. 기준 위치를 옮겨도 봉우리 모양이 그대로 평행이동합니다 — 내적이 절대 위치가 아니라 거리의 함수라는 뜻입니다.
§9 잔차 연결과 Layer Normalization
인코더 층 그림에서 빠뜨린 부품이 있습니다. 셀프 어텐션과 FFN 각각을 우회로(잔차 연결)가 감싸고 있고, 그 뒤에 LayerNorm이 붙습니다.
잔차 연결 덕분에 부품은 입력을 통째로 바꿀 필요 없이 고칠 부분만 더하면 되고, 기울기는 덧셈 경로를 따라 깊은 층까지 줄지 않고 내려갑니다(ResNet과 같은 발상). LayerNorm은 토큰 하나의 벡터 안에서 평균을 빼고 표준편차로 나눠 크기를 맞춥니다. 배치의 다른 문장이나 문장의 다른 토큰과는 섞지 않습니다.
§10 디코더 쪽 — 한 단어씩 만들어 내기
인코더가 입력 문장을 다 읽고 나면, 맨 위 층의 출력에서 K와 V를 만들어 디코더의 모든 층에 넘깁니다. 디코더 층에는 부품이 하나 더 있습니다 — 셀프 어텐션과 FFN 사이에 끼어 있는 인코더-디코더 어텐션입니다. 여기서 Q는 디코더 쪽에서, K·V는 인코더 쪽에서 옵니다. "지금 쓰려는 단어를 위해 원문의 어디를 볼까"를 정하는 곳입니다.
디코더의 셀프 어텐션에는 제약이 하나 있습니다. 아직 쓰지 않은 미래 단어를 보면 안 되므로, 자기보다 오른쪽 자리의 점수를
softmax 전에 −∞로 덮어 가중치를 0으로 만듭니다(마스킹). 출력은 한 번에 한 단어씩 나오고, 방금 나온 단어가 다음 단계의 입력 끝에 붙습니다.
<eos>(문장 끝)가 나오면 멈춥니다.
⚠ 교차 어텐션 가중치는 설명용으로 손으로 정한 값입니다.
§11 마지막 Linear와 Softmax
디코더 맨 위에서 나오는 것은 여전히 dmodel짜리 실수 벡터입니다. 이것을 단어로 바꾸는 것이 마지막 두 부품입니다.
- Linear — dmodel 벡터를 어휘 크기 V만큼 긴 벡터로 늘립니다. 어휘가 1만 개면 점수 1만 개, 이것을 로짓(logits)이라 부릅니다.
- Softmax — 로짓을 합이 1인 확률로 바꿉니다. 가장 확률이 높은 칸의 단어를 고르면 그 단계의 출력입니다.
softmax는 로짓의 차이만 봅니다. 모든 로짓에 같은 수를 더해도 확률은 그대로이고, 차이를 키우면(= 온도를 낮추면) 1등이 확률을 독식합니다.
§12 학습은 무엇을 맞추는가 — 원-핫 정답과 손실
학습할 때는 정답 문장을 알고 있습니다. 각 자리의 정답을 어휘 크기의 벡터로 적으면, 정답 단어 칸만 1이고 나머지는 0인 원-핫 벡터가 됩니다. 모델의 출력 분포를 이 원-핫 분포에 가깝게 만드는 것이 학습의 목표이고, 두 분포가 얼마나 다른지를 재는 것이 교차 엔트로피 손실입니다.
처음에는 가중치가 무작위라 모든 단어에 비슷한 확률을 줍니다(손실 ≈ log V). 역전파로 가중치를 조금씩 고치면 정답 칸의 확률이 올라가고 손실이 줄어듭니다. 또 학습 때는 디코더에 정답 문장을 한 칸 밀어서 통째로 넣고, 마스킹 덕분에 모든 자리를 한 번에 병렬로 학습합니다 — 추론(데모 10)처럼 한 단어씩 돌 필요가 없습니다.
§13 고르는 법 — 탐욕 디코딩과 빔 서치
추론할 때 매 단계 1등만 고르는 것을 탐욕(greedy) 디코딩이라고 합니다. 간단하지만, 지금 1등인 단어가 문장 전체로 보면 최선이 아닐 수 있습니다. 빔 서치는 매 단계 상위 k개(빔 크기)의 부분 문장을 들고 가면서, 각각을 한 단어씩 늘린 후보 중 누적 확률 상위 k개만 남깁니다.
§14 더 나아가기
여기까지가 Transformer의 뼈대입니다. 다음으로 읽을 것들:
- Attention Is All You Need — 원 논문. 이 페이지를 본 뒤라면 3장이 훨씬 잘 읽힙니다.
- The Annotated Transformer — 논문을 한 줄씩 PyTorch 코드로. 최신 라이브러리에서 돌아가게 고친 판은 Annotated Transformer Modern.
- How Transformer Works — 이 페이지에서 일부러 뺀 것들: 파라미터 65M의 분포, √dmodel을 곱하는 이유, Post-LN vs Pre-LN, 워밍업, 레이블 스무딩, KV 캐시.
- How BERT Works — 인코더만 떼어 쓴 모델.
- The Illustrated Transformer 원문과 한국어 번역 — 이 페이지가 흐름을 빌려 온 글.