어텐션: 관련성에 의한 추론
📍 현재 위치: V부 · 어텐션과 트랜스포머 — 15장. 표현력의 천장은 고정된 그래프 위에서 이루어지는 메시지 전달(message passing)이 무엇을 구별할 수 있는지를 한계 지음으로써 IV부를 마무리했습니다. 이 장은 실행되는 동안 스스로 자신의 그래프를 계산해 내는 메커니즘, 즉 어텐션(attention)으로 V부를 엽니다.
지금까지 이 권에서 등장한 모든 신경 메커니즘은 누군가 다른 이가 미리 마련해 둔 구조를 그대로 소비했습니다. I–III부의 임베딩 모델들은 트리플을 건네받았고, IV부의 그래프 신경망은 엣지를 건네받았으며, 그 계산 전체는 훈련이 시작되기 전에 이미 고정된 배선을 따라 흘렀습니다. 어텐션(attention)은 그 의존성을 없앱니다. 어텐션은 한 집합의 모든 원소가 실행 시점에 "지금 나와 관련 있는 다른 원소는 무엇인가?"라고 스스로 묻고, 그 원소들 자신의 내용에 대한 산술 연산으로 그 물음에 답한 다음, 관련 있는 원소들이 담고 있는 것을 섞어서 읽어 내도록 해 줍니다. 그래서 어텐션은 위치가 아니라 내용으로 주소가 지정되는 메모리가 되며[1], 이 책이 결코 블랙박스로 남겨 둘 수 없는 단 하나의 메커니즘입니다: 트랜스포머(transformer)는 바로 이것으로 만들어지고, V부의 종착지인 소프트 단일화(soft unification)는 추론으로 읽힌 어텐션입니다. 그래서 이 장은 모든 숫자를 지면 위에 그대로 드러냅니다. 하나의 헤드(head)가 학술 세계의 13개 개체 위에서 실행되며, 그것이 의지하는 각각의 주장, 즉 스케일링 인자, 그래디언트, 조회 극한은 먼저 유도된 다음 커밋된 examples/neural/attention_demo.py의 출력과 대조하여 검증됩니다.
오래된 약국에 늘어선, 작은 이름표가 붙은 서랍들의 벽을 상상해 보세요. 당신은 무엇을 찾고 있는지 적힌 쪽지를 손에 들고 다가갑니다. 고전적인, 기호적인 절차는 정확한 조회입니다: 당신의 쪽지와 일치하는 이름표가 붙은 서랍을 딱 하나 찾아, 그것을 열고, 내용물을 꺼냅니다. 어텐션 절차는 더 부드럽습니다. 당신은 당신의 쪽지를 모든 이름표와 동시에 비교하고, 각 이름표가 쪽지와 얼마나 잘 맞는지에 비례해서 모든 서랍을 조금씩 엽니다. 그리고 그 틈의 너비에 따라 가중치를 둔, 모든 내용물의 혼합물을 가지고 나옵니다. 당신의 쪽지가 쿼리(query)이고, 이름표들이 키(key)이며, 내용물들이 값(value)이고, 틈의 너비들이 어텐션 가중치입니다. 이 부드러운 약국에 관한 두 가지 사실이 이 장 전체를 이끌어 갑니다: 틈의 너비가 쪽지에 따라 매끄럽게 변하기 때문에 경사 하강법(gradient descent)으로 무엇을 찾아야 하는지를 학습할 수 있다는 것과, 만약 이름표들이 모두 완전히 서로 다르고 일치 점수가 충분히 날카롭다면 이 부드러운 절차가 정확한 절차로 다시 무너져, 오직 하나의 서랍만 열리고 다른 어떤 서랍도 열리지 않게 된다는 것입니다.
이 장에서 다루는 내용
- 사전으로 읽기: 어떤 행렬이 등장하기도 전에 쿼리, 키, 값을 낱낱이 풀어내고, 이름표 일치도에 따른 서랍들의 가중 평균으로서의 부드러운 조회를 다룹니다.
- 완전한 행렬 형태: , 이며, 실행 예제 위에서 모든 형태에 주석을 답니다: 13개 개체, 특징 폭 , 키 폭 .
- 하나의 행을 처음부터 끝까지 추적하기: 개체 bob의 원점수, 스케일링된 점수, 소프트맥스 가중치, 그리고 상위 3개 주목 개체를 커밋된 실행 결과에서 그대로 인용하고, 행 합 검산도 함께 제시합니다.
- √d_k 스케일링, 유도 후 측정: 를 내주는 기댓값 대수, 에서의 경험적 분산표, 그리고 이 스케일링이 막아 주는 포화(saturation)를 다룹니다.
- 몫의 법칙으로 구하는 소프트맥스 야코비안: , 즉 이며, 중심 유한차분과 대조해 4e-12까지 검증하고, 포화된 소프트맥스가 왜 학습을 얼려 버리는지를 다룹니다.
- 조회 극한: 정규직교 키와 날카로운 로짓이 어텐션을 정확한 조회로 만든다는 것을 수치로 검증하며, 이는 하나의 실험에 담긴 이 부(Part)의 주제입니다.
- 다중 헤드 어텐션과 메시지 전달과의 관계: 형태 대수를 갖춘 부분공간 병렬성, 그리고 엣지 가중치가 주어지는 것이 아니라 계산되는 완전 그래프 위의 메시지 전달로 읽히는 트랜스포머 층을 다룹니다.
어떤 행렬보다 먼저, 하나의 사전
선형대수를 걷어내면 어텐션은 누구나 이미 알고 있는 자료구조입니다. 사전(dictionary, 해시 맵이나 서류 캐비닛)은 쌍을 저장합니다: 각 슬롯의 이름을 붙이는 키(key)와 그 슬롯이 담고 있는 값(value)입니다. 그것을 읽으려면 쿼리(query)를 제시합니다. 정확한 조회는 키가 쿼리와 같은 슬롯의 값을 돌려줍니다. 어텐션은 이 세 가지 역할을 그대로 유지하면서 그 동일성 검사를 완화합니다. 쿼리는 "내가 찾고 있는 것이 무엇인가"를 뜻하는 벡터가 되고, 각 키는 "이 슬롯이 무엇에 관한 것인가"를 뜻하는 벡터가 되며, 각 값은 "이 슬롯이 무엇을 담고 있는가"를 실어 나르는 벡터가 됩니다. 그리고 쿼리와 키 사이의 일치도는 좌표별 곱의 합인 , 즉 내적(dot product)으로 점수가 매겨지는데, 이는 두 벡터가 같은 방향을 가리킬 때 크고 양의 값이 됩니다.
내적은 예/아니오 답이 아니라 실수이기 때문에, 이 조회는 하나의 슬롯만을 돌려줄 수 없습니다. 대신 가중 평균을 돌려줍니다. 일치 점수들의 벡터를 소프트맥스(softmax, 아래에서 정확히 정의합니다)에 통과시킨 결과를 라고 쓰면, 그 점수들은 합이 1이 되는 음이 아닌 가중치가 됩니다. 그러면 부드러운 조회의 출력은
즉 더 잘 맞는 슬롯일수록 더 많이 기여하는, 저장된 모든 값의 혼합입니다. 내용(content)으로 주소를 지정하는 메모리라는 이 독법은 미분 가능한 메모리 구조에서 명시적으로 드러났으며[1], 내용 기반 어텐션 그 자체는 원래 번역 모델이 자기 자신의 입력을 되돌아보며 출력 단어 하나하나에 대해 어떤 입력 단어가 관련 있는지를 결정하는 방법으로 처음 등장했습니다[2]. 두 경우 모두에서 핵심 성질은 같습니다: 조회 안의 모든 양이 쿼리와 키에 따라 매끄럽게 변하므로, 그래디언트가 조회 행위 그 자체를 통해 흐릅니다. 모델은 무엇을 찾아야 하는지를 학습할 수 있습니다.
행렬 형태, 모든 형태에 주석 달기
이제 같은 조회를 실행 예제 위에서 여러 쿼리에 대해 한꺼번에 수행해 보겠습니다. 학술 세계의 13개 개체(교수 2명, 학생 3명, 논문 3편, 기관 2곳, 주제 3개, kg.py에서 가져온 것입니다) 각각은 폭 인 특징 벡터를 받습니다. 여기서 는 모델의 특징 차원, 즉 각 개체의 행의 길이입니다. 그 행들을 쌓아 13개 행과 8개 열을 가진 행렬 를 만드는데, 개체마다 한 행씩입니다. 데모는 발견할 만한 진짜 구조를 갖도록 를 만듭니다: 시드가 고정된 가우스 잡음에 결정적인 유형 신호를 더한 것으로, 개체의 개념에 배정된 차원에 만큼의 돌출을 준 것입니다(attention_demo.py 87–92행). 그래서 두 교수는 실제로 서로 닮았고, 세 논문도 마찬가지입니다.
하나의 개체의 행은 사전의 세 가지 역할을 모두 수행해야 하므로, 학습된 세 개의 행렬이 그것을 세 가지 방식으로 투영합니다. 를 쿼리/키 폭(그리고 여기서는 값의 폭이기도 합니다)이라고 합시다. 각각 8개 행과 4개 열을 가진 투영 는 다음을 만들어 냅니다:
즉 의 번째 행은 개체 의 쿼리 이고, 의 번째 행은 개체 의 키 이며, 의 번째 행은 그 값 입니다. 행렬 곱 (위첨자 은 전치(transpose)로, 행렬을 대각선을 기준으로 뒤집어 행을 열로 바꾸는 연산입니다)는 모든 쿼리-키 내적을 한꺼번에 계산합니다: 그 성분은 , 즉 개체 에 대한 개체 의 관련성과 같습니다. 로 스케일링하고(아래에서 유도합니다) 각 행에 독립적으로 소프트맥스를 적용하면 어텐션 행렬(attention matrix)이 되며, 여기에 를 곱하면 13개의 부드러운 조회가 동시에 수행됩니다[3]:
의 번째 행은 13개 개체에 대한 하나의 확률분포, 즉 개체 의 관련성 가중치이며, 의 번째 행은 가중 평균 로, 바로 비유에서 나온 서랍 혼합 그 자체입니다. 모든 형태를 정리하면:
| 대상 | 형태 | 의미 |
|---|---|---|
| 개체마다 하나의 특징 행 | ||
| 쿼리/키/값 공간으로의 학습된 투영 | ||
| 각 개체의 쿼리, 키, 값 | ||
| 원(raw) 관련성 점수, 성분 | ||
| 행별로 소프트맥스를 취한 가중치, 각 행의 합은 1 | ||
| 개체마다 하나의 혼합된 값 |
전체 파이프라인은 동반 파일의 다섯 줄입니다(attention_demo.py 102–106행):
Q, K, V = X @ Wq, X @ Wk, X @ Wv
raw = Q @ K.T # raw[i, j] = q_i · k_j
scaled = raw / np.sqrt(Wq.shape[1]) # temper the logits: /√d_k
A = softmax(scaled) # each row a distribution over keys
return raw, scaled, A, A @ V
부드러운 사전으로서의 어텐션: 쿼리와 키가 관련성을 결정하고, 값이 내용을 실어 나르며, 출력은 관련성으로 가중된 평균이고, 직교 키와 날카로운 로짓의 극한에서 부드러운 조회는 정확한 조회가 됩니다.
저자가 AI의 도움을 받아 직접 제작한 원본 도해.
하나의 쿼리 행, 모든 단계: bob
검증된 추상화만이 정당한 추상화이므로, 여기 파이프라인의 완전한 한 행을 커밋된 실행 결과(python3 attention_demo.py)에서 그대로 인용합니다. 쿼리 개체는 교수인 bob입니다. 열들은 원(raw) 내적 , 로 나눈 같은 값, 그리고 소프트맥스 가중치입니다:
[1] the pipeline for one query row: bob (a Professor)
key type raw q·k /√d_k softmax
alice Professor -2.3871 -1.1935 0.0248
bob Professor 1.0730 0.5365 0.1397
carol Student -2.0115 -1.0058 0.0299
cmu Institution -1.0990 -0.5495 0.0472
dave Student 0.1258 0.0629 0.0870
erin Student -0.8574 -0.4287 0.0532
logic Topic -1.4944 -0.7472 0.0387
mit Institution 0.5753 0.2876 0.1089
ml Topic -0.4054 -0.2027 0.0667
nesy Topic -3.6375 -1.8187 0.0133
p1 Paper 0.1508 0.0754 0.0881
p2 Paper 2.0572 1.0286 0.2286 <- top-1
p3 Paper -0.2016 -0.1008 0.0739
top-3 attended: p2 (0.2286), bob (0.1397), mit (0.1089)
output row O[bob] = A[bob]·V = [0.3032, -0.2232, 0.2016, 0.3022]
every softmax row sums to 1: max |row_sum - 1| = 2.22e-16 (< 1e-12)
이 트레이스를 열별로 읽은 다음, 요약 줄을 읽어 보겠습니다. 13개의 원점수는 에서 까지 걸쳐 있습니다. 스케일링은 이를 절반으로 줄이고, 소프트맥스는 이를 논문 p2에 , bob 자신에게 , mit에 를 두고 나머지 질량은 얇게 퍼뜨리는 분포로 바꿉니다. 그러면 bob의 출력 행은 13개의 값 벡터를 그에 대응해 혼합한 것이며, 이므로 네 개의 숫자로 이루어집니다. 마지막 줄은 소프트맥스 행들이 실제로 진짜 확률분포인지를 확인하는 검산입니다: 13개 행 전체에서 행 합이 1에서 벗어나는 최대 편차는 , 즉 배정도 부동소수점의 최하위 자리 한 단위이며, 하니스는 이를 아래로 통과시킵니다(attention_demo.py 255행).
이 행에 기대어 논의를 이어가기 전에 정직하게 짚어 둘 것이 있습니다. 이 헤드는 훈련되지 않았습니다: 그 투영들은 에서 뽑은 시드 값이며, 통상적인 초기화 축소를 일부러 적용하지 않았습니다. 그래서 훈련되지 않은 헤드조차도 눈에 띄게 균일하지 않은 행을 낼 만큼 충분히 큰 로짓을 내놓습니다(attention_demo.py 226–229행의 주석을 따른 것입니다. 통상적으로는 훈련이 이러한 날카로움을 만들어 냅니다). 따라서 bob이 p2에 주목한다는 사실 자체는 자의적인, 시드의 우연일 뿐입니다. 자의적이지 않은 것은 그 메커니즘입니다: 위의 모든 숫자는 앞서 인용한 다섯 줄에 의해 , , , 로부터 따라 나오며, 훈련은 관련성 패턴이 어떤 과제에 쓸모 있도록 그 투영들을 옮겨 놓을 것입니다. 이 장의 나머지 부분은 어떤 가중치를 고르든 살아남는 그 메커니즘의 세 가지 성질에 관한 것입니다.
왜 √d_k인가: 내적의 분산
로 나누는 것은 눈속임처럼 보일 수 있습니다. 하지만 이것은 분산에 관한 하나의 정리입니다[3]. 쿼리 와 키 의 성분들을, 평균이 0이고 분산이 1인 독립 확률변수로 모형화해 봅시다. 이는 잘 초기화된 활성값에 대한 합리적인 대역이며, 이제 원(raw) 로짓 가 일반적으로 얼마나 큰지를 물어봅니다. 여기서 기댓값(expectation) 은 확률량의 평균값이고, 분산(variance) 은 그 평균을 중심으로 한 제곱 퍼짐의 평균입니다.
먼저 평균부터 보겠습니다. 기댓값은 선형이므로 합 기호를 통과하며, 독립성은 곱의 기댓값을 기댓값들의 곱으로 인수분해할 수 있게 해 줍니다:
다음으로 항 하나의 분산을 보겠습니다. 분산의 정의를 쓰고 다시 독립성을 이용하면:
여기서 이며 에 대해서도 마찬가지입니다. 마지막으로, 항 은 서로 겹치지 않는 독립 변수 집합들로부터 만들어지므로 그 자체로도 서로 독립이며, 독립인 항들의 합의 분산은 각 분산의 합입니다:
그러므로 원(raw) 로짓은 표준편차 를 가집니다: 과제에 관해서는 아무것도 달라지지 않았는데도 키 폭에 따라 커지는 것입니다. 로 나누는 것은 이를 정확히 되돌리는데, 확률변수를 상수 로 스케일링하면 그 분산은 만큼 스케일링되기 때문입니다:
데모는 이 주장을 그저 믿는 대신 직접 측정합니다. (기호 은 "뒤에 오는 집합의 원소이다"라고 읽으므로, 는 나열된 폭을 차례로 취합니다) 각각에 대해 표준정규분포 성분을 갖는 10,000개의 독립적인 쿼리-키 쌍을 뽑고, 그 내적들의 표본분산을 구합니다(attention_demo.py 118–123행):
for d in VAR_DIMS:
q = rng.normal(size=(VAR_DRAWS, d))
k = rng.normal(size=(VAR_DRAWS, d))
dots = (q * k).sum(axis=1)
var = float(dots.var())
rows.append({"d_k": d, "var": var, "ratio": var / d})
커밋된 결과는 모든 폭에서 유도된 값의 3퍼센트 이내에 있습니다:
| 10,000회 추출에 대한 표본 | 에 대한 비율 | |
|---|---|---|
| 4 | 3.9980 | 0.9995 |
| 64 | 63.6778 | 0.9950 |
| 256 | 262.5068 | 1.0254 |
그런데 로짓의 크기가 왜 중요할까요? 소프트맥스는 그 입력에 대해 지수함수이기 때문이며, 에서 스케일링하지 않은 로짓은 표준편차 을 가집니다: 13개의 키 중 가장 큰 로짓은 통상 나머지보다 수십 단위나 위에 있게 되고, 지수를 취한 뒤에는 하나의 키가 사실상 모든 질량을 빨아들이게 됩니다. 데모는 정확히 이 현상을 보여 줍니다. 에서 13개의 무작위 키를 사용했을 때, 커밋된 실행 결과는 다음을 보고합니다:
saturation at d_k=256 (13 keys): max softmax prob 0.9903 unscaled vs 0.2282 with /√d_k
스케일링하지 않으면 이 분포는 아무것도 학습되기 전, 초기화 시점부터 이미 거의 원-핫에 가깝습니다. 스케일링하면 부드러운 상태를 유지합니다. 다음 절에서는 거의 원-핫인 소프트맥스가 그저 우아하지 못한 정도가 아니라 학습에 치명적인 이유를 보여 줍니다: 그 그래디언트가 사라져 버리기 때문입니다.
소프트맥스 야코비안, 유도와 검증
소프트맥스는 로짓 벡터 을 확률로 바꿉니다:
여기서 는 지수함수이며 어떤 입력에 대해서도 양수인데, 이 때문에 각 가 양수가 되고 행의 합이 정확히 1이 됩니다. (실제 구현은 먼저 그 행의 최댓값을 뺍니다. 모든 성분이 1인 벡터를 이라 쓰면, 은 모든 로짓을 같은 상수 만큼 이동시키는데, 이 성립하여 그 상수는 분자와 분모에서 서로 상쇄됩니다. 따라서 이 이동은 아무것도 바꾸지 않으면서 모든 지수를 0 이하로 유지해 주므로, 위에서 다룬 스케일링하지 않은 의 로짓도 오버플로되지 않습니다; attention_demo.py 51–61행.)
어텐션을 통해 무언가를 훈련하려면 이 사상의 야코비안(Jacobian)이 필요합니다: 그 성분이 인 행렬 로, 출력 확률 가 로짓 의 미세한 흔들림에 어떻게 반응하는지를 기록합니다. , 로 두고, 비 의 미분이 인 몫의 법칙(quotient rule)을 적용합니다. 필요한 두 재료는 다음과 같습니다: 는 일 때 이고 그 외에는 이며, 인데 이는 그 합의 항 중 오직 하나만이 를 포함하기 때문입니다.
경우 . 분자와 분모 모두 에 의존합니다:
경우 . 분자 는 에 의존하지 않으므로, 그 미분은 0을 기여합니다:
크로네커 델타(Kronecker delta) (일 때 1이고 그 외에는 0)는 이 두 경우를 하나의 식으로 합쳐 주며, 그 식은 하나의 행렬로 모입니다:
여기서 는 대각선에 를 놓고 나머지는 모두 0인 정사각행렬이며, 은 성분이 인 외적(outer product)입니다. 코드에서 이 유도는 단 한 줄입니다(attention_demo.py 74–75행):
# J = diag(p) − p pᵀ (the gradient formula derived above)
return np.diag(p) - np.outer(p, p)
이만큼 중대한 결과를 낳는 유도라면 독립적인 검증을 받을 자격이 있으며, 중심 유한차분이 바로 그것을 제공합니다: 수치 야코비안의 번째 열은 이며, 여기서 는 번째 표준기저 벡터이고, 에서 절단 오차는 정도입니다(attention_demo.py 153–158행):
h = 1e-5
J_num = np.zeros((5, 5))
for j in range(5):
dz = np.zeros(5)
dz[j] = h
J_num[:, j] = (softmax(z + dz) - softmax(z - dz)) / (2 * h)
다섯 개의 시드 로짓에 대한 커밋된 실행 결과:
[3] softmax Jacobian J = diag(p) - p·pᵀ (∂p_i/∂z_j = p_i(δ_ij - p_j))
p = softmax(z) on 5 seeded logits = [0.0204, 0.0382, 0.0611, 0.7896, 0.0907]
J[0] = [ 0.02001, -0.00078, -0.00125, -0.01613, -0.00185]
J[1] = [-0.00078, 0.03669, -0.00233, -0.03012, -0.00346]
J[2] = [-0.00125, -0.00233, 0.05736, -0.04824, -0.00554]
J[3] = [-0.01613, -0.03012, -0.04824, 0.16612, -0.07163]
J[4] = [-0.00185, -0.00346, -0.00554, -0.07163, 0.08248]
max |J - central finite difference| = 3.65e-12 (< 1e-6)
출력된 로부터 각 성분을 손으로 재현해 볼 수 있습니다. 지배적인 대각 성분은 로, 를 소수점 넷째 자리까지 반올림한 오차 범위 안에서 출력된 와 일치합니다. 비대각 성분 도 같은 방식으로 과 일치합니다. 해석적 야코비안과 수치적 야코비안은 까지 일치하며, 이는 하니스의 기준보다 다섯 자릿수 넘게 더 엄격합니다(attention_demo.py 256행).
이제 결실을 볼 차례입니다. 스케일링으로 다시 이어지는 연결 고리를 봅시다. 가 원-핫 벡터에 가까워질 때, 즉 어떤 이고 나머지 모든 일 때 에 무슨 일이 일어나는지 살펴봅시다. 모든 대각 성분 는 0으로 가고(두 인자 중 하나가 사라지므로), 모든 비대각 성분 도 0으로 갑니다(적어도 하나의 인자가 사라지는데, 인덱스 중 많아야 하나만 일 수 있기 때문입니다). 그래서 입니다: 행렬 전체가 사라집니다. 포화된 소프트맥스는 자신의 로짓으로 그래디언트를 전혀 돌려보내지 않습니다. 관련성 가중치는 초기화가 우연히 만들어 낸 값에 그대로 얼어붙고, 어떤 훈련 신호로도 그것을 움직일 수 없습니다. 위에서 측정한 스케일링하지 않은 최대 확률 에서, 가능한 가장 큰 야코비안 성분은 으로, 이는 가 에서 도달하는 상한 보다 이미 스물여섯 배 가까이 작습니다. 따라서 스케일링은 겉치레가 아닙니다: 그것은 가 살아 있고 관련성이 여전히 학습 가능한 영역에 를 붙들어 두는 장치입니다[3].
조회 극한: 정확한 조회로서의 어텐션
야코비안 논증은 날카로운 어텐션이 학습할 수 없다고 말합니다. 이 절은 그 반대 면을 보여줍니다: 날카로운 어텐션은 정확하게 조회할 수 있으며, 그 극한이야말로 V부가 건너가고자 지어진 다리입니다. 가능한 한 가장 깔끔한 사전을 설정해 봅시다: 네 개의 슬롯이 있고, 그 키는 항등행렬 의 행들이므로 정규직교(orthonormal, 각각 단위 길이이고 모든 쌍이 내적 0)이며, 네 개의 무작위 저장 값을 가집니다(attention_demo.py 177–183행):
K = np.eye(4) # 4 orthonormal keys (the slots)
V = rng.normal(size=(4, 4)) # 4 stored values
out = {}
for c in (3.0, 8.0):
q = c * K[2] # "fetch slot 2", at temperature 1/c
p = softmax(q @ K.T) # logits (0, 0, c, 0)
o = p @ V
쿼리는 입니다: "슬롯 2를 가져와라"는 뜻이며, 스칼라 가 날카로움을 조절합니다. 정규직교성 덕분에 로짓은 손으로 계산하기에도 간단합니다: 이므로 로짓 벡터는 입니다. 그러면 소프트맥스는 닫힌 형태를 가집니다: 목표 슬롯은 을 받고, 나머지 세 슬롯은 각각 을 받습니다. 일 때 이므로 이고, 일 때 이므로 입니다. 커밋된 실행 결과는 이 둘을 모두 확인해 주며, 어텐션 출력과 그것이 요청받은 저장 값 사이의 코사인 유사도(cosine similarity) 도 함께 보고합니다. 여기서 이중 세로선은 벡터의 유클리드 길이, 즉 노름(norm) 를 나타내므로, 코사인이 1이라는 것은 두 벡터가 같은 방향을 가리킨다는 뜻입니다:
[4] dictionary-lookup limit: keys = I_4 (orthonormal), q = c·key_2
c=3: attention = [0.0433, 0.0433, 0.8700, 0.0433] p(slot 2)=0.8700 cos(output, value_2)=0.9807
c=8: attention = [0.0003, 0.0003, 0.9990, 0.0003] p(slot 2)=0.9990 cos(output, value_2)=1.0000
에서 조회는 정말로 부드럽습니다: 질량의 13퍼센트가 잘못된 슬롯으로 새어 나가고, 출력은 참값과의 코사인이 겨우 인 혼합물입니다. 에서는 잘못된 슬롯 각각이 만 분의 세 정도만을 받아, 총 누출은 천 분의 일 정도이며, 출력은 출력된 정밀도 안에서 코사인 으로 값 2 그 자체가 됩니다(하니스는 이를 기준으로 통과시킵니다; attention_demo.py 259행). 두 재료 모두가 중요합니다. 직교성은 어떤 잘못된 키도 양의 로짓을 받지 않도록 보장하므로 섞여 들어올 혼선이 없고, 날카로움은 소프트맥스를 원-핫 극한으로 몰아붙여 평균이 하나의 선택으로 퇴화하게 만듭니다. 날카로움이 고정된 어떤 값에서든, 서로 상관된 키들은 내용을 새어 나가게 하며, 주어진 정확도가 요구하는 날카로움은 키들이 정렬될수록 한없이 커집니다(동일한 키는 결코 해소되지 않습니다). 직교성은 모든 날카로움 수준에서 혼선을 0으로 만들어 주는 깔끔한 충분조건이지만, 행이 보여주듯이 낮은 날카로움에서는 직교하는 키라도 여전히 흐릿합니다.
그러므로 여기, 한 문장으로 된 V부의 주제가 있습니다: 키가 직교하고 로짓이 날카로우면 어텐션은 정확한 기호적 조회로 퇴화하며, 그리하여 기호적 연산이 미분 가능한 연산의 극한으로 회복됩니다. 이산적인 사전이 할 수 있는 모든 것을, 부드러운 사전은 극한에서 해낼 수 있으며, 이산적인 사전과 달리 극한에 이르기 전 어디에서든 훈련이 가능합니다. 야코비안이 드러낸 긴장은 실제입니다(정확한 극한에서는 그래디언트가 0이므로, 모델은 그 극한에서 얼어붙지 않으면서 조회에 다가가야 합니다). 그러나 이제 나아갈 방향은 보입니다: 매끄러운 관련성 가중치 매기기로 태어난 메커니즘이 조회를 구현할 수 있으며, 17장의 소프트 단일화는 그 구현을 기호에 대한 추론으로 읽어 낼 것입니다.
다중 헤드 어텐션: 부분공간 병렬성
하나의 헤드는 하나의 관련성 패턴을 계산합니다: 단 하나의 행렬 가 개체들이 서로에게 미치는 영향의 모든 방식을 요약해야 합니다. 그러나 학술 세계에는 다섯 가지 서로 다른 관계(advises, authored, cites, affiliated, about)가 있고, 하나의 소프트맥스 행이 동시에 "bob의 지도 학생들"이면서 "bob의 논문들"일 수는 없습니다. 표준적인 해법은 다중 헤드 어텐션(multi-head attention)입니다: 모델의 폭을 개의 독립적인 헤드로 나누고, 각각에서 스케일드 닷프로덕트 어텐션을 따로 실행한 다음, 그 출력들을 이어 붙이고 마지막 행렬 하나로 뒤섞습니다[3]. 이고 폭 인 헤드가 개일 때, 헤드 는 각각 인 자기만의 투영 를 가지며,
여기서 는 특징 축을 따라 이어 붙이는 연결(concatenation)을 나타내며, 는 인 출력 혼합 행렬입니다. 데모는 정확히 이것을 구현합니다(attention_demo.py 196–204행):
heads = []
for _ in range(2):
Wq, Wk, Wv = (rng.normal(size=(D_MODEL, D_K)) / np.sqrt(D_MODEL)
for _ in range(3))
_, _, A_h, O_h = attention(X, Wq, Wk, Wv)
heads.append((A_h, O_h))
concat = np.concatenate([O for _, O in heads], axis=1) # (13, 8)
Wo = rng.normal(size=(D_MODEL, D_MODEL)) / np.sqrt(D_MODEL)
mh_out = concat @ Wo # (13, 8)
그리고 커밋된 형태 추적은 이 대수가 하나의 전체 폭 헤드가 도달했을 곳과 정확히 같은 곳에 이른다는 것을 확인해 줍니다:
[5] multi-head: 2 heads × d_k=4, concat, mix with W_o (8×8)
X (13, 8) -> per head Q,K,V (13, 4) -> O_1 (13, 4), O_2 (13, 4)
concat(O_1, O_2) (13, 8) @ W_o (8, 8) -> (13, 8) == full-width single head (13, 8)
bob's top-1 key per head: ['ml', 'logic'] (the heads attend differently)
마지막 줄이 핵심입니다. 훈련되지 않았음에도 두 헤드는 bob에게 서로 다른 최상위-1 키를 줍니다(한 부분공간에서는 주제 ml, 다른 부분공간에서는 주제 logic). 각 헤드가 특징에 대한 자기만의 투영으로 관련성을 측정하기 때문입니다: 두 개의 헤드, 두 가지의 "관련 있다"는 개념입니다. 훈련이 이루어지면 이것이 바로 한 헤드는 "내가 저술한 것에 주목하라"로, 다른 헤드는 "내가 지도하는 사람에게 주목하라"로 특화될 수 있게 하는 용량입니다: 부분공간마다 하나의 관계를 다루는 관계별 조회이며, 여러 헤드를 쌓은 것이 유형화된 그래프가 가진 만큼 서로 다른 관련성 패턴들을 담아낼 수 있는 방식의 씨앗입니다. V부는 이 이야기로 다시 돌아옵니다.
어텐션은 완전 그래프 위의 메시지 전달이다
IV부의 메시지 전달(message passing) 템플릿은 각 노드 에 대해, 입력 그래프가 주어진 것으로 제공하는 집합인 이웃 로부터 오는 메시지들의 집계를 계산했으며, 그 결합 가중치는 배선에 의해(균일하게, 또는 그래프 합성곱 신경망(GCN)에서처럼 차수로 정규화되어) 고정되어 있었습니다. 아래 템플릿에서 는 노드 의 현재 특징 벡터이고 는 그 갱신된 벡터입니다. 는 이웃의 특징을 메시지로 변환하는 메시지 함수이고, 는 노드 자신의 상태와 집계된 메시지들을 결합하는 갱신 함수이며, 는 배선이 엣지 에 배정하는 고정된 가중치입니다. 이 그래프 신경망(GNN) 템플릿을 어텐션의 출력 행과 비교해 봅시다:
구조적으로 이 둘은 두 가지 치환을 거친 같은 계산입니다. 첫째, 이웃입니다: 어텐션은 모든 개의 노드에 대해 합을 취하므로, 그 그래프는 완전 그래프이며 모든 개체는 다른 모든 개체의 이웃입니다. 둘째, 가중치입니다: 는 배선의 상수였던 반면, 는 실행 시점에 내용 로부터 계산됩니다. 트랜스포머 층은 내용에 의해 결정되는 엣지 가중치를 가진 완전 그래프 위의 메시지 전달입니다. 그래프 어텐션 네트워크(graph attention network)는 그 중간 지점을 차지하는데, 내용 기반 가중치를 계산하지만 오직 주어진 엣지들에 대해서만 그렇게 합니다[4].
이 치환이 사 오는 것은 정확히 IV부에 없던 것입니다: 정보는 국소적 교환을 번(여기서 은 쌓인 메시지 전달 층의 수입니다) 거쳐 스며드는 대신, 어떤 두 개체 사이든 단 한 걸음(one hop)으로 이동합니다. 그리고 배선은 동적입니다: 입력의 상수가 아니라 학습된 투영들의 함수입니다. 그 대가 역시 똑같이 구체적입니다. 어텐션 행렬은 개의 성분을 가지는데, 여기서는 대수롭지 않은 이지만 십만 개의 노드에서는 이 되므로, 이 이차식 비용은 확장 가능한 모든 변형이 가장 먼저 다시 협상해야 할 대상입니다. 게다가 이 메커니즘에는 내장된 희소성도 엣지 유형도 없습니다: 관계형 그래프 합성곱 신경망(R-GCN)이 관계별 메시지 가중치로 존중하는 지식 그래프의 다섯 가지 관계는 안에 고유한 자리를 갖지 못합니다. 트랜스포머는 그것들을 자신의 헤드 안으로 학습해 넣거나 입력을 통해 받아들여야 합니다. 어텐션은 주어진 구조로부터의 자유를 사들이는 대신 구조의 안내를 포기하며, V부의 신경-기호적 질문은 그 안내의 일부를 어떻게 다시 사들일 것인가입니다.
아직 풀리지 않은 부분
bob의 출력 행을 다시 한 번 보겠습니다: 네 개의 숫자 , 즉 13개 값 벡터의 볼록 결합(convex combination)입니다. 평균이란 손실이 있는 요약입니다. 만으로는 어떤 개체가 얼마만큼의 비율로 기여했는지, 그리고 가장 날카로운 지점인, 어떤 역할로 기여했는지를 복원할 수 없습니다. 값들이 구별해서 간직할 가치가 있는 두 사실, 즉 bob이 carol을 지도한다는 것과 bob이 p1을 저술했다는 것을 실어 나른다고 해 봅시다. 하나의 벡터로 섞이고 나면, 그 평균은 carol의 흔적, p1의 흔적, 지도함과 저술함의 흔적을 간직하지만, carol을 지도함에, p1을 저술함에 묶어 주는 것은 그 반대가 아니라 아무것도 남기지 못합니다. 어텐션은 관련성을 아름답게 결정한 다음, 관련 있는 모든 것을 개의 좌표를 가진 모든 실수 벡터의 집합인 의 한 점으로 문질러 버립니다. 헤드를 쌓으면 그 얼룩은 필러(filler)를 역할(role)과 짝짓는 일 없이 그저 곱절이 됩니다. 이것이 바로 벡터 표현에 대한 결속 문제(binding problem)이며, 어텐션의 형식 체계 그 자체 안에는 답이 없습니다: 이 메커니즘은 관련성의 셈법은 가지고 있지만 구조의 셈법은 가지고 있지 않습니다. 답이 있다면 그것은 어떤 모습일까요? 역할과 필러를 (되돌릴 수 있게) 결속(bind)하는 연산과, 여러 개의 결속된 쌍을 (분리 가능하게) 하나의 벡터 안에 중첩(superpose)하는 연산을 갖춘, 벡터 위의 하나의 대수일 것입니다. 그래서 단 하나의 벡터가 "지도한다→carol, 저술했다→p1"을 담을 수 있고, 올바른 역할로 질의하면 올바른 필러를 되돌려 줄 수 있어야 합니다. 그런 대수가 깔끔한 구성물이 아니라 잡음 섞인 학습된 표현 안에서도 대규모로 신뢰할 수 있게 만들어질 수 있는지는 정말로 열려 있는 문제입니다. 그 깔끔한 구성물을 만드는 것이 다음 장의 임무입니다.
왜 중요한가
신경-기호(neuro-symbolic) 프로그램에게 이 장은 하중을 떠받치는 벽입니다. 4권의 통합은 처음부터 끝까지 미분 가능한 메커니즘을 필요로 합니다. 그래야 논리적 구조가 1권의 경사 하강법으로 훈련될 수 있기 때문입니다. 어텐션은 메모리에서 무언가를 조회하는 원초적 행위인 검색(retrieval)이, 극한에서 정확성을 잃지 않으면서도 미분 가능하게 만들어질 수 있다는 증거입니다. 앞으로 가지고 갈 두 개의 커밋된 숫자는 (야코비안이 실재하고 정확하므로 관련성은 학습 가능합니다)와 코사인 (극한이 실재하고 정확하므로 관련성은 조회가 될 수 있습니다)입니다. 이 둘 사이에 모든 트랜스포머 기반 추론기의 설계 공간이 놓여 있습니다: 올바른 사실을 조회할 만큼 날카로우면서도, 계속 학습할 만큼 부드러워야 합니다. 17장이 쿼리를 목표로, 키를 규칙의 머리로, 소프트맥스를 등급이 매겨진 치환으로 읽어 낼 때, 어텐션은 소프트 단일화가 되어 있을 것이고, 약국의 서랍 벽은 어느새 조용히 규칙집으로 바뀌어 있을 것입니다. 그것이 바로 이 권의 기하학에서 4권의 미분 가능한 논리로 이어지는 다리이며, 이토록 작은 메커니즘이 이토록 철저한 유도를 받은 이유입니다.
핵심 용어
- 쿼리 / 키 / 값(Query / key / value): 벡터로 완화된 사전의 세 가지 역할입니다: 내가 찾는 것, 각 슬롯이 무엇에 관한 것인지, 각 슬롯이 무엇을 담고 있는지.
- 어텐션 가중치(Attention weights, 행렬 ): 스케일링된 쿼리-키 내적에 행별로 소프트맥스를 취한 것입니다. 번째 행은 개체 의 관련성 판단을 표현하는 확률분포입니다.
- 스케일드 닷프로덕트 어텐션(Scaled dot-product attention): , 이며, 출력은 값들의 관련성 가중 평균입니다.
- √d_k 스케일링(√d_k scaling): 로짓을 로 나누는 것으로, 독립인 단위분산 성분에 대해 라는 사실로 정당화됩니다. 어떤 키 폭에서도 로짓의 분산을 1로 유지해 줍니다.
- 소프트맥스 야코비안(Softmax Jacobian): 이며, 몫의 법칙으로 유도됩니다. 소프트맥스가 포화될수록 사라지며, 관련성을 얼려 버립니다.
- 포화(Saturation): 분산이 큰 로짓에 의해 도달하는, 소프트맥스가 거의 원-핫에 가까워지는 영역으로, 최대 확률이 1에 가까워지고 그래디언트가 0에 가까워집니다.
- 조회(사전 조회) 극한(Retrieval (dictionary-lookup) limit): 정규직교 키와 날카로운 로짓이 있으면 어텐션은 정확히 하나의 저장 값을 돌려줍니다. 미분 가능한 연산의 극한으로서의 정확한 기호적 조회입니다.
- 다중 헤드 어텐션(Multi-head attention): 여러 개의 어텐션 헤드가 학습된 부분공간 위에서 병렬로 실행되고, 이어 붙여져 뒤섞입니다. 여러 관련성 패턴을 동시에 담아내는 용량으로, 헤드마다 하나의 관계를 맡습니다.
- 내용 주소 메모리(Content-addressed memory): 주소를 따라가거나 고정된 엣지를 따르는 대신 내용을 매칭하여 읽는 메모리입니다. 어텐션을 미분 가능한 메모리 구조와 이어 주는 독법입니다.
- 결속 문제(Binding problem): 가중 평균이 어떤 필러가 어떤 역할과 짝을 이루는지를 기록하지 못하는 무능력입니다. 이 장이 다음 장에 넘겨주는 열린 질문입니다.
이 다음으로 이어지는 것
어텐션은 우리에게 미분 가능한 어디를 볼 것인가를 주었지만, 무엇이 무엇과 짝을 이루는지를 저장할 방법은 남겨 주지 않았습니다. 다음 장인 벡터 기호 구조는 그 빠져 있는 대수를 직접 만들어 냅니다: 역할과 필러를 짝짓는 결속 연산, 여러 개의 결속된 쌍을 하나의 벡터 안에 담는 중첩 연산, 그리고 올바른 필러를 다시 꺼내는 비결속(unbinding) 연산을 갖춘 고차원 벡터이며, 이 전체 방식의 정확도는 여기서 우리에게 를 주었던 것과 같은 종류의 분산 논증으로 보장된 다음 측정됩니다.
짝이 되는 코드: examples/neural/attention_demo.py는 하나의 시드 생성기로부터 전체 파이프라인, 분산 및 포화 연구, 야코비안 검증, 조회 극한, 다중 헤드 형태 추적을 모두 구현합니다. examples/neural/에서 python3 attention_demo.py를 실행하면 이 장의 모든 숫자를 재현할 수 있습니다. 승인 하니스 examples/neural/validate.py는 이 권의 평결의 일부로서 그 역량 단언(행 합, 1e-6 야코비안 기준, 분산 비율, 0.99 조회 코사인, 다중 헤드 형태들)을 다시 실행합니다.