AI 깊이 이해하기 — 자율 시스템 설계까지 · 04
문서 변경을 보고하는 에이전트가 “제출 기한은 연장됐지만, 지원 자격은 그대로다”라는 안내를 읽는다고 하자. 기한과 자격을 한데 묶어 모두 바뀌었다고 보고하면 틀린다. 문장 안의 여러 정보를 연결하되, 서로 다른 조건은 구분해야 한다.
Transformer의 attention은 이런 관계를 표현하는 데 쓰이는 계산이다. 다만 “중요한 단어에 집중한다”는 설명만으로는 실제로 무엇이 움직이는지 알기 어렵다. 이번에는 세 개의 숫자 벡터를 놓고, 어떤 비율로 섞이는지 직접 계산한다. 이 실험이 문장 이해나 사실 검증에 성공했다는 뜻은 아니다.
Q와 K로 비율을 구하고, V를 섞는다
Query(Q), Key(K), Value(V)는 각각 벡터다. self-attention에서는 같은 입력 표현을 서로 다른 학습된 선형 변환에 통과시켜 만든다. Q와 K의 내적이 결합 점수를 만들고, V가 실제로 합쳐질 내용을 담는다. 이름에 “질문”이 들어가도 Q가 사용자의 질문 문장 자체라는 뜻은 아니다. 원논문 3.2절은 이 관계를 다음 식으로 정리한다.
Attention(Q, K, V) = softmax(Q Kᵀ / √dₖ) V
dₖ는 Q와 K 벡터의 차원 수다. 여러 query를 계산할 때 softmax는 각 query에 대응하는 행별로 적용한다. 이 식의 가중치 합은 행마다 1이다. 여기서는 dropout을 넣지 않은 기본 계산만 다룬다. PyTorch 공식 문서의 참조 계산에서도 점수 계산, 마스크, softmax, V와의 곱을 차례로 확인할 수 있다.
세 위치의 벡터로 펼쳐 보기
아래 Q·K·V는 설명을 위해 임의로 정한 값이다. 실제 문장을 토큰화하거나 학습된 모델에서 꺼낸 값이 아니다. A·B·C도 단어의 의미가 없는 위치 이름이다. query 하나를 q = [1, 0]으로 두고 세 위치를 참조한다.
| 위치 | K | V |
|---|---|---|
| A | [1, 0] | [10, 0] |
| B | [0, 1] | [0, 10] |
| C | [1, 1] | [4, 6] |
q와 각 K의 내적은 순서대로 1, 0, 1이다. 두 차원이므로 √2로 나누면 점수는 약 0.707107, 0, 0.707107이 된다. softmax는 각 점수를 지수함수에 넣고 그 합으로 나눈다. 그러면 가중치는 약 0.401112, 0.197776, 0.401112다. A와 C의 점수가 같으므로 비율도 같다.
이 비율을 V에 곱해 더한다. 첫 좌표는 A에서 10, B에서 0, C에서 4를 가져온다. 둘째 좌표는 0, 10, 6을 같은 비율로 섞는다. 반올림 전 값으로 계산한 결과는 [5.615569, 4.384431]이다. 출력은 A·B·C 중 하나를 고른 번호가 아니라, 여러 V가 섞인 새 벡터다.
0.401112를 “A가 사실일 확률 40.1%”로 읽으면 안 된다. 이 예제에는 사실 여부를 나타내는 정답도, 그 확률을 추정하도록 학습하는 과정도 없다. 세 점수를 합계 1의 혼합 비율로 바꾸었을 뿐이다. 숫자 모양이 확률과 같다는 이유만으로 검증의 의미가 생기지는 않는다.
마스크를 바꾸면 참조할 수 있는 범위가 바뀐다
이번에는 C를 보지 못하게 해 보자. softmax 전에 C의 점수를 음의 무한대로 처리하면 지수값이 0이 된다. 코드에서는 같은 효과를 내도록 C의 지수값을 직접 0으로 둔다. A와 B만으로 다시 정규화하므로 가중치는 [0.669762, 0.330238, 0], 출력은 [6.697615, 3.302385]가 된다.
원래 비율에서 C만 지우고 나머지를 그대로 두는 계산과는 다르다. 남은 항목의 비율도 달라진다. 자기회귀 모델의 causal mask는 현재 위치보다 뒤에 있는 토큰을 참조하지 못하게 한다. A·B·C가 순서대로 놓였고 query가 B 위치에 있다면, 이번 C 차단은 그 행의 마스크에 해당한다. 전체 시퀀스에서는 query의 위치마다 허용 범위를 정해야 한다. 원논문의 decoder self-attention 설명을 함께 볼 수 있다.
외부 라이브러리 없이 계산하기
Python 3에서 아래 코드를 실행하면 된다. API 키나 별도 패키지는 필요 없다. 입력 차원과 길이는 위 예제에 맞춘 소형 실험 코드이며, 범용 attention 구현은 아니다. allowed의 True는 해당 위치를 허용한다는 뜻이다. 모든 위치를 차단하면 이 예제는 오류를 내도록 했다.
from math import exp, sqrt
q = [1.0, 0.0]
keys = [[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]
values = [[10.0, 0.0], [0.0, 10.0], [4.0, 6.0]]
def attend(query, allowed, vals=values):
if not any(allowed):
raise ValueError('at least one key must be allowed')
scores = [sum(a * b for a, b in zip(query, k))
/ sqrt(len(query)) for k in keys]
peak = max(s for s, ok in zip(scores, allowed) if ok)
mass = [exp(s - peak) if ok else 0.0
for s, ok in zip(scores, allowed)]
weights = [m / sum(mass) for m in mass]
output = [sum(w * v[d] for w, v in zip(weights, vals))
for d in range(len(vals[0]))]
return weights, output
for label, query, allowed in [
('all', q, [True, True, True]),
('mask-C', q, [True, True, False]),
('zero-Q', [0.0, 0.0], [True, True, True]),
]:
weights, output = attend(query, allowed)
print(label)
print('weights:', [round(x, 6) for x in weights])
print('output:', [round(x, 6) for x in output])
실행 결과는 다음과 같다. 표시할 때만 소수점 여섯 자리로 반올림했다.
all
weights: [0.401112, 0.197776, 0.401112]
output: [5.615569, 4.384431]
mask-C
weights: [0.669762, 0.330238, 0.0]
output: [6.697615, 3.302385]
zero-Q
weights: [0.333333, 0.333333, 0.333333]
output: [4.666667, 5.333333]
마지막 실행은 Q를 [0, 0]으로 바꾼다. 내적이 전부 0이어서 세 위치의 비율이 같아지고 V의 단순 평균이 나온다. K와 V를 그대로 두어도 query가 달라지면 혼합 결과가 달라질 수 있다는 것을 확인할 수 있다. 이것이 이번 실행에서 확인한 범위다. 학습 성능이나 언어 이해도를 측정한 결과는 아니다.
이 계산에서 Transformer 전체로 넘어가기 전에
한 head의 attention 출력만으로 문장이 생성되지는 않는다. 원래 Transformer에는 여러 head의 결과를 결합하는 과정, feed-forward 층, 잔차 연결과 정규화 등이 함께 들어간다. 또한 원논문은 입력에 위치 인코딩을 더한다. 위 실험은 이런 요소를 생략했으므로 토큰 순서까지 처리하는 완성된 모델로 볼 수 없다. 원논문 3장의 전체 구조와 구분해서 읽어야 한다.
가중치 그림을 모델 판단의 완전한 설명으로 받아들이는 것도 이르다. 간단한 반례를 만들 수 있다. V를 모두 [1, 1]로 바꾸면 위 세 경우의 비율이 달라도 출력은 모두 [1, 1]이다. 같은 출력이 반드시 같은 attention 분포에서 나오는 것은 아니다. 실제 모델의 설명 가능성은 더 넓은 문제이며, Jain·Wallace의 연구와 Wiegreffe·Pinter의 후속 논의도 어떤 조건과 검증 방법으로 attention을 해석할지 다룬다.
문서 변경 보고 에이전트로 돌아오면, attention이 어떤 내용을 많이 참조했는지와 보고서가 원문을 정확히 옮겼는지는 따로 확인해야 한다. 기한만 바뀐 문서, 자격만 바뀐 문서, 둘 다 유지된 문서를 비교해 실제 출력을 검사하는 일은 여전히 남는다. 이번 숫자 실험이 알려주는 것은 정보를 섞는 방식이다. 무엇을 배우게 할지는 다음 강의의 주제인 Pretraining과 Post-training의 차이: 지식·행동·선호는 어디서 바뀌는가에서 이어간다.
시리즈 시작: LLM의 학습과 추론: 프롬프트를 고치면 모델도 배우는가
출처와 실행 범위
핵심 계산과 구조: Vaswani 외, Attention Is All You Need (2017), 3.1~3.5절. 구현 대조: PyTorch 2.9 scaled_dot_product_attention. 해석의 조건: Jain·Wallace (2019), Wiegreffe·Pinter (2019), 위 본문 링크 참조. 예제는 Python 표준 라이브러리로 실행했으며 PyTorch나 실제 LLM은 실행하지 않았다.