이미 손에 익은 세 가지를 순서대로 이어 붙이면 어텐션이 완성된다. 이 장에서 할 일은 이 조립을 직접 계산으로 확인하는 것뿐이다.
13.3 Q·K·V: 세 가지 역할
가중평균까지 가려면 먼저 “누가 누구를 참고하는가”를 정리할 언어가 필요하다. 어텐션은 각 단어에 세 개의 벡터를 부여한다.
정의: Query, Key, Value
Query (Q) — 내가 지금 찾고 있는 것. “나에게 필요한 정보는 무엇인가?”
Key (K) — 각 단어가 내건 색인(간판). “나는 이런 정보를 갖고 있다”
Value (V) — 각 단어가 실제로 전달할 내용
Query와 Key의 내적이 크면 “찾는 것과 간판이 잘 맞는다” → 그 단어의 Value를 많이 가져온다.
도서관 검색에 비유하면 정확하다. 여러분이 검색창에 입력하는 검색어가 Query, 각 책의 제목·키워드가 Key, 책의 본문이 Value다. 검색 엔진은 검색어와 잘 맞는 제목의 책일수록 결과 상단에 올리고(가중치), 여러분은 그 책들의 내용을 읽는다(Value를 가져온다). 어텐션이 다른 점은 하나뿐이다 — 1등 하나만 고르는 대신, 모든 책을 가중치만큼 섞어 읽는다.
Q·K·V는 어디서 오는가? 각 단어의 임베딩 벡터(Ch10)에 학습되는 행렬 \(W_Q, W_K, W_V\)를 곱해서 만든다. 즉 “무엇을 찾고, 무엇을 간판으로 내걸고, 무엇을 전달할지”조차 데이터로부터 학습되는 파라미터다 — 경사하강법(Ch03)으로 갱신된다는 점은 지금까지의 모든 가중치와 같다. 이 장의 손계산에서는 차원 폭발을 막기 위해, 이 곱셈이 끝난 Q·K·V 값이 이미 주어진 상태에서 시작한다.
13.4 어텐션 손계산 — 이 장의 심장
토큰 3개짜리 문장 “고양이가 / 생선을 / 먹었다”로 single-head self-attention 1회를 끝까지 계산한다. 각 토큰의 Q·K·V는 차원 2로 다음과 같이 주어졌다고 하자.
코드
import pandas as pdimport numpy as nptokens = ['고양이가', '생선을', '먹었다']Q = np.array([[2, 1], [1, 2], [1, 3]], dtype=float)K = np.array([[1, 0], [0, 2], [1, 1]], dtype=float)V = np.array([[1, 0], [0, 1], [1, 1]], dtype=float)pd.DataFrame({'토큰': tokens,'$q$ (Query)': [f'({q[0]:.0f}, {q[1]:.0f})'for q in Q],'$k$ (Key)': [f'({k[0]:.0f}, {k[1]:.0f})'for k in K],'$v$ (Value)': [f'({v[0]:.0f}, {v[1]:.0f})'for v in V],}).to_html(index=False, border=0, escape=False)
표 13.1: 주어진 Q·K·V (차원 2) — 임베딩에 W_Q, W_K, W_V를 곱한 결과라고 가정
토큰
\(q\) (Query)
\(k\) (Key)
\(v\) (Value)
고양이가
(2, 1)
(1, 0)
(1, 0)
생선을
(1, 2)
(0, 2)
(0, 1)
먹었다
(1, 3)
(1, 1)
(1, 1)
Value의 두 성분을 대략 (1번째 = “동물 성분”, 2번째 = “음식 성분”) 정도의 의미로 읽어 두면 마지막 결과가 눈에 들어올 것이다.
13.4.1 (a) 스코어: Q와 K의 내적
모든 (Query 토큰, Key 토큰) 쌍에 대해 내적을 계산한다. Ch10에서 한 그 내적 그대로다. 예를 들어 “먹었다”의 Query와 “생선을”의 Key라면:
S = Q @ K.T # 모든 쌍의 내적을 한 번에df = pd.DataFrame(S, columns=[f'{t}의 $k$'for t in tokens])df.insert(0, 'Query \\ Key', [f'**{t}의 $q$**'for t in tokens])df.to_html(index=False, border=0, escape=False)
표 13.2: 스코어 표 — 행: Query 토큰, 열: Key 토큰 (각 칸 = 내적)
Query \ Key
고양이가의 \(k\)
생선을의 \(k\)
먹었다의 \(k\)
고양이가의 \(q\)
2.0
2.0
3.0
생선을의 \(q\)
1.0
4.0
3.0
먹었다의 \(q\)
1.0
6.0
4.0
“먹었다” 행을 보라 — (1, 6, 4). 설계된 대로 “먹었다”의 Query는 “생선을”의 Key와 가장 잘 맞는다 (무엇을 먹었는지가 가장 필요한 정보다).
13.4.2 (b) √d로 나누기: 스케일링
스코어를 그대로 Softmax에 넣지 않고 차원의 제곱근 \(\sqrt{d}\)로 나눈다. 차원이 커지면 내적 값 자체가 커지고, 값이 커지면 Softmax가 한 곳에 확률을 몰아주는 극단적 분포가 되기 때문에 이를 완화하는 안전장치다. 여기서는 \(d = 2\)이므로 \(\sqrt{2} \approx 1.414\)로 나눈다. “먹었다” 행은 \((1, 6, 4) \rightarrow (0.71,\ 4.24,\ 2.83)\)이 된다.
13.4.3 (c) 행별 Softmax: 스코어를 가중치로
각 행(Query 토큰 하나)에 대해 Softmax를 적용한다. Ch03에서 배운 그 Softmax, 표기까지 그대로다:
“먹었다”의 새 표현은 \((0.214,\ 0.977)\) — 2번째 성분(“음식 성분”)이 압도적이다. 어텐션 1회를 거치며 “먹었다”라는 단어의 벡터에 “생선을 먹었다”라는 문맥이 스며든 것이다. 이것이 어텐션이 하는 일의 전부다: 내적으로 재고, Softmax로 나누고, 가중평균으로 섞는다.
손으로 따라 해 보라: “고양이가” 행에 대해 (b)~(d)를 반복하면 새 표현 \((0.752,\ 0.752)\)가 나와야 한다. 문제집의 손계산 문제가 정확히 이 형태다.
인터랙티브 — 어텐션 가중치를 눈으로 확인하세요
번역 예시 문장 “나는 어제 학교에서 친구를 만났다”의 토큰 5개에 Q·K 값(차원 2)을 미리 넣어 두었어요. Query 토큰을 선택하면, 그 토큰의 Q와 모든 K의 스코어 → Softmax 가중치가 연결선의 굵기와 막대로 표시됩니다. “만났다”를 선택해 보세요 — 번역가의 시선처럼 “친구를”에 가장 집중하는 것을 볼 수 있어요.
시도해 보세요: “어제”를 선택하면 “친구를”과 자기 자신에 가중치가 비슷하게 나뉩니다. 토큰마다 “어디를 보는가”가 달라진다는 것 — 이것이 행마다 Softmax를 따로 하는 이유입니다.
13.5 Self-Attention의 의미: 문맥을 입은 단어
위 계산에서 문장은 자기 자신을 참조했다 — Query도, Key도, Value도 같은 문장의 토큰들에서 나왔다. 그래서 Self-Attention이다. 그 결과가 무엇이었는지 다시 보자:
어텐션 전: “먹었다”의 벡터는 어느 문장에 있든 똑같은, 사전에 박제된 표현
어텐션 후: “먹었다”의 벡터는 “생선을”의 정보가 78.6% 섞인, 이 문장 안에서의 먹었다
즉 Self-Attention 1회는 모든 단어의 표현을 “문맥을 반영한 표현”으로 갱신하는 연산이다.
이것이 왜 중요한가? Ch10에서 임베딩의 한계를 하나 남겨 두었다 — 같은 단어는 늘 같은 벡터라는 것. “강가의 은행에 앉았다”와 “돈을 맡기러 은행에 갔다”에서 “은행”은 전혀 다른 뜻이지만, 임베딩 사전에는 “은행” 벡터가 하나뿐이다. Self-Attention을 통과하면 첫 문장의 “은행”은 “강가”의 Value를 많이 섞고, 둘째 문장의 “은행”은 “돈”과 “맡기러”의 Value를 많이 섞는다 — 같은 단어가 문장마다 다른 벡터가 된다. 다의어 문제가 구조적으로 풀리는 것이다.
13.6 개념 수준 확장: 실제 Transformer는 여기에 세 가지를 더한다
Multi-Head Attention — 여러 관점을 병렬로. 위에서 계산한 어텐션 1회는 “관점 하나”다. “먹었다”는 목적어(“생선을”)를 볼 수도 있지만, 주어(“고양이가”)를 보는 관점, 시제를 보는 관점도 필요하다. 그래서 실제 Transformer는 서로 다른 \(W_Q, W_K, W_V\)를 가진 어텐션을 여러 개(head) 병렬로 수행한 뒤 결과를 이어 붙인다. 각 head의 계산은 우리가 손으로 한 것과 완전히 같고, 단지 여러 번 동시에 할 뿐이다.
위치 인코딩 — 사라진 순서를 되살린다. 모든 단어를 동시에 보는 순간, 대가가 하나 생긴다 — 순서 정보가 사라진다. 내적과 가중평균 어디에도 “몇 번째 단어인가”는 등장하지 않으므로, “고양이가 생선을 먹었다”와 “생선이 고양이를 먹었다”의 구분이 위태로워진다. 해결책은 단순하다: 각 위치(1번째, 2번째, …)마다 고유한 위치 벡터를 임베딩에 더해서 입력 자체에 순서를 새겨 넣는다. RNN은 순서를 구조(순차 처리)로 갖고 있었지만, Transformer는 순서를 데이터에 더해서 갖는다.
블록 쌓기 — 어텐션을 층으로. Self-Attention 1회가 “문맥 반영 1단계”라면, 그 출력에 어텐션을 또 적용할 수 있다. CNN이 콘볼루션 층을 쌓아 에지 → 부품 → 물체로 추상화 수준을 높였듯이(Ch05), Transformer는 [어텐션 + 완전연결층] 블록을 수십 번 쌓아 단어 수준 → 구 수준 → 문장 수준의 관계를 만들어 간다.
13.7 Transformer 전체 그림
세 가지 확장을 합치면 Transformer의 전체 구조가 완성된다:
낯선 부품이 거의 없다는 것을 확인하라. 임베딩은 Ch10, 완전연결층은 Ch02, 출력의 Softmax는 Ch03이다. 잔차연결의 점선 화살표는 Ch07에서 본 ResNet의 우회로가 여기에도 있다는 표시다 — 블록을 수십 개 쌓아도 학습이 무너지지 않는 이유도 그때와 같다. 진짜 새 부품은 Self-Attention 하나뿐이고, 그것은 방금 손으로 계산했다.
13.8 왜 혁명이었나
2017년 논문 “Attention Is All You Need”가 제안한 이 구조는 몇 년 만에 자연어 처리 전체를 갈아치웠다. 이유는 성능만이 아니다 — 확장성이다.
RNN
Transformer
처리 방식
한 단어씩 순차 처리
모든 단어 동시 처리
단어 1,000개 문장
1,000단계 대기
행렬 곱 몇 번 (병렬)
먼 단어 참조
기억 상자를 거쳐 간접적으로
어텐션으로 직접 (거리 무관)
GPU 활용
구조적으로 제한
완전 활용 가능
스코어 계산이 결국 행렬 곱 \(QK^\top\) 하나라는 점을 상기하자. 행렬 곱은 GPU가 가장 잘하는 연산이다(Ch01). “순서대로 기다려야 하는” 구조가 사라지자, 데이터를 더 붓고 모델을 더 키우면 성능이 계속 오르는 시대가 열렸다 — 더 큰 모델, 더 많은 데이터, 더 많은 GPU의 스케일 경쟁이 시작된 것이다.
그렇다면 자연스러운 질문. 이 구조를 인터넷 규모의 텍스트로, 수천억 개의 파라미터로 학습시키면 무슨 일이 생기는가? 그 답이 다음 장의 주인공 — 거대 언어 모델(LLM)이다.
깊이 계약 확인
문제집과 시험은 single-head 어텐션 손계산 1회 — 즉 이 장에서 한 “내적 → \(\sqrt{d}\) 나누기 → Softmax → 가중합”까지만 다룬다. Q·K·V 값은 항상 문제에서 주어진다. Multi-head의 행렬 유도, LayerNorm의 계산, 위치 인코딩의 수식(sin/cos)은 모두 범위 밖이다 — “왜 필요한가”를 한 문장으로 설명할 수 있으면 충분하다.
13.9 핵심 요약
개념
핵심 내용
RNN의 병목
순차 처리(병렬 불가) + 먼 기억 소실 — 뿌리는 “순서대로, 기억 상자 하나”
어텐션
유사도(내적, Ch10) → 가중치(Softmax, Ch03) → 가중평균. 새 수학 없음
Q / K / V
찾는 것 / 색인 / 전달할 내용 — 임베딩 × 학습 행렬 \(W_Q, W_K, W_V\)
스케일링
스코어를 \(\sqrt{d}\)로 나눔 — Softmax의 극단화 완화
Self-Attention
문장이 스스로를 참조 → 모든 단어가 “문맥을 반영한 표현”으로 갱신
Multi-Head
서로 다른 관점의 어텐션을 병렬 수행 후 결합
위치 인코딩
동시에 보면 순서가 사라짐 → 위치 벡터를 임베딩에 더해 되살림
Transformer
[Self-Attention + FFN] 블록 × N, 잔차연결(Ch07)로 깊게 쌓음
혁명의 이유
완전 병렬 → GPU 스케일 가능 → 데이터·모델 크기 경쟁의 시작
다음 장 예고: 손계산의 등뼈는 여기서 완성되었다. 이제 이 구조를 인터넷 규모의 텍스트로 학습시킨 결과물 — 여러분이 매일 쓰는 그 챗봇 — 의 내부로 들어간다. “다음 단어 맞히기”라는 단순한 목표가 어떻게 지능처럼 보이는 능력이 되는가, 그리고 temperature라는 마지막 손계산이 남아 있다.