트랜스포머·PEFT 2일 과정3장 · Attention과 Encoder5 / 18← 이전목차다음 →MetaContents

3장. Attention과 Encoder

Scaled Dot-Product Attention: 행렬 크기로 흐름 따라가기

반장 선거 개표를 떠올려 봅니다. 먼저 후보마다 표를 셉니다(점수). 점수가 너무 크면 눈금을 줄입니다. 그다음 득표를 비율(합 100%)로 바꾸고, 그 비율만큼 후보들의 공약을 섞어 학급 계획을 세웁니다. Attention 의 식 softmax(QKᵀ / √d_k) V 가 이 차례를 그대로 따릅니다. "비교하기 → 중요한 곳 고르기 → 정보 섞기"입니다.

Scaled Dot-Product Attention 의 MatMul, Scale, Mask, SoftMax, MatMul 차례와 이를 h 개 겹친 Multi-Head Attention 을 나란히 놓은 원 논문 그림
비교, 눈금, 가리기, 비율, 섞기의 다섯 칸을 이은 흐름도

원 논문 그림의 왼쪽이 이 절의 식입니다. 아래에서 위로 읽습니다. Q 와 K 가 첫 MatMul(행렬곱)로 들어가 점수가 되고, Scale 에서 √d_k 로 나뉘고, Mask(opt.)는 필요할 때만 씁니다(4장의 Decoder). SoftMax 로 비율이 되고, 마지막 MatMul 에서 V 와 곱해집니다. 오른쪽 그림은 다음 절의 Multi-Head 입니다.

다섯 단계

단계하는 일식개표로 치면
1Q 와 K 를 비교QKᵀ후보마다 표 세기
2점수 조정÷ √d_k눈금 줄이기
3Mask 적용보면 안 되는 칸을 가림(4장)무효표 빼기
4softmax행마다 합 1 인 비율득표율
5V 를 섞음× V득표율만큼 공약 섞기

행렬 크기로 따라가기

토큰 표현 X (10, 512) 에 W_Q, W_K, W_V (512, 64) 를 곱해 Q, K, V (10, 64) 를 만들고, QKᵀ (10, 10), softmax, V 합산으로 head 출력 (10, 64) 를 내는 강사자료 그림

강사자료 그림은 원 논문 base 모델의 숫자(d_model 512, head 8개, head 하나의 d_k 64)를 씁니다. 낱말 10개는 shape 를 따라가기 위한 예시입니다. 왼쪽 X 는 (10, 512) 입니다. 여기에 (512, 64) 표를 곱하면 Q, K, V 는 모두 (10, 64) 입니다. 오른쪽 상자에서 QKᵀ 는 (10, 64) × (64, 10) = (10, 10) 입니다. 낱말 10개가 서로 매긴 점수표입니다. softmax 뒤에도 (10, 10) 이고, V 를 곱하면 (10, 10) × (10, 64) = (10, 64) 로 head 출력이 됩니다. 아래 띠는 8개 head 를 합치는 다음 절의 내용입니다.

계산왼쪽 shape오른쪽 shape결과 shape뜻
Q = XW_Q(10, 512)(512, 64)(10, 64)낱말마다 검색어
QKᵀ(10, 64)(64, 10)(10, 10)모든 낱말 쌍의 점수
softmax(10, 10)-(10, 10)행마다 합 1
가중치 × V(10, 10)(10, 64)(10, 64)낱말마다 새 벡터

짧은 예시

Python
import numpy as np

def softmax(x):
    # 가장 큰 값을 빼고 exp 를 해도 비율은 같다(숫자가 너무 커지는 것을 막는다)
    e = np.exp(x - x.max(axis=-1, keepdims=True))
    # 행마다 합으로 나눈다 → 행 합이 1
    return e / e.sum(axis=-1, keepdims=True)

# 낱말 2개, d_k = 4 (설명을 위해 정한 값)
Q = np.array([[1., 0., 1., 0.],
              [0., 1., 0., 1.]])
K = np.array([[1., 0., 1., 0.],
              [1., 1., 1., 1.]])
V = np.array([[10., 0.],
              [0., 10.]])

scores = Q @ K.T                   # (2, 4) @ (4, 2) → (2, 2)
print(scores)                      # 결과: [[2. 2.] [0. 2.]]
weights = softmax(scores / np.sqrt(4))   # √4 = 2 로 나눈 뒤 비율로
print(weights.round(2))            # 결과: [[0.5 0.5] [0.27 0.73]]
print((weights @ V).round(1))      # 결과: [[5. 5.] [2.7 7.3]]

이 코드가 뜻하는 것

낱말 두 개가 서로 표를 주고(점수), 득표율을 내고(softmax), 득표율만큼 상대의 공약(V)을 섞는 개표 과정입니다. 여기서 표 세기가 Q @ K.T, 득표율이 weights, 섞은 공약이 weights @ V 입니다.

줄하는 일쉽게 말하면
Q @ K.T모든 낱말 쌍의 내적후보마다 표 세기
scores / np.sqrt(4)점수를 2 로 나눈다눈금 줄이기
softmax(...)행마다 합 1 인 비율df.div(df.sum(axis=1), axis=0) 과 같은 꼴
weights @ V비율대로 V 를 섞는다득표율만큼 공약 섞기

둘째 낱말의 줄을 손으로 따라가 봅니다. 점수는 0 과 2, 2 로 나누면 0 과 1 입니다. e^0 = 1, e^1 ≈ 2.72 이고 합은 3.72 이므로 비율은 1 ÷ 3.72 ≈ 0.27, 2.72 ÷ 3.72 ≈ 0.73 입니다. 섞은 결과는 0.27×[10, 0] + 0.73×[0, 10] = [2.7, 7.3] 입니다.

주의할 점

  • K.T 를 빼먹고 Q @ K 를 쓰면 shape 가 맞지 않아 오류가 나거나(낱말 수와 d_k 가 다를 때), 우연히 shape 가 맞으면 틀린 값이 조용히 나옵니다.
  • softmax 는 행(axis=-1, 마지막 축)마다 합니다. 열 방향으로 하면 "한 낱말이 다른 낱말들을 얼마나 참고하는가"가 아니게 됩니다.
← 이전 절3장 · Query·Key·Value다음 절 →3장 · √d_k 로 나누는 까닭