self-attention

Trasnformer Self-Attention 쉽게 이해하기: 다른 단어를 얼마나 참고할까?

Transformer를 공부하다 보면 반드시 등장하는 개념이 바로 Self-Attention입니다.

처음 보면 Query, Key, Value, Softmax 같은 용어 때문에 어렵게 느껴질 수 있지만, 핵심 아이디어는 생각보다 단순합니다.

문장 속 각 단어가 다른 단어를 얼마나 참고해야 하는지 점수로 계산하는 것

이번 글에서는 다음 문장을 예시로 Self-Attention을 이해해보겠습니다.

나는 오늘 사과를 먹었다

문장을 단어 단위로 나누면 다음과 같습니다.

나는 / 오늘 / 사과를 / 먹었다

1. Self-Attention이란?

사람은 문장을 읽을 때 각 단어를 독립적으로 이해하지 않습니다.

예를 들어,

나는 오늘 사과를 먹었다

에서 먹었다라는 단어를 보면 자연스럽게 다음과 같은 생각을 합니다.

무엇을 먹었지?
→ 사과를

즉, 먹었다라는 단어의 의미를 이해하기 위해 사과를이라는 단어를 함께 참고합니다.

Self-Attention도 비슷합니다.

각 단어가 문장 속 다른 단어들을 보고,

이 단어가 나에게 얼마나 중요한가?

를 계산합니다.


2. Attention Matrix로 표현하기

Self-Attention 결과는 보통 다음과 같은 행렬로 표현할 수 있습니다.

transformer self-attention 설명
Query ↓/Key →나는오늘사과를먹었다
나는0.550.200.100.15
오늘0.200.500.100.20
사과를0.050.100.650.20
먹었다0.100.100.350.45

여기서 중요한 것은 행(Row)열(Column) 입니다.

행(Row) = Query
현재 보고 있는 단어

열(Column) = Key
현재 단어가 참고하는 대상

예를 들어 먹었다 행을 보면 다음과 같습니다.

나는     0.10
오늘     0.10
사과를   0.35
먹었다   0.45

즉,

먹었다 → 사과를 : 0.35
먹었다 → 먹었다 : 0.45

입니다.

먹었다가 자기 자신을 가장 많이 참고하면서도, 사과를도 상당히 많이 참고하고 있다는 뜻입니다.

이렇게 하면 모델은

먹었다 → 무엇을 먹었다?
       → 사과를 먹었다

라는 문맥 관계를 학습할 수 있습니다.


3. 대각선 값은 왜 1이 아닐까?

Self-Attention 행렬을 처음 보면 이런 의문이 생길 수 있습니다.

자기 자신을 보는 값이면
대각선은 1이어야 하는 것 아닌가?

하지만 그렇지 않습니다.

Self-Attention에서 대각선은 단순히

자기 자신을 얼마나 참고하는가

를 의미합니다.

예를 들어 다음과 같은 값이 있을 수 있습니다.

사과를 → 사과를 = 0.65

0.65라고 해서 이상한 것이 아닙니다.

오히려 Self-Attention에서는 다른 단어와의 관계도 같이 고려해야 하기 때문에 대각선이 반드시 1일 필요가 없습니다.

만약 다음과 같다면 어떨까요?

사과를 → 나는    0
사과를 → 오늘    0
사과를 → 사과를  1
사과를 → 먹었다  0

이 경우 사과를은 자기 자신만 보고 다른 단어는 전혀 참고하지 않습니다.

그렇다면 문맥을 이해하는 Self-Attention의 장점이 크게 줄어들게 됩니다.


4. 대신 각 행의 합은 1이 된다

Self-Attention에서 더 중요한 특징은 다음입니다.

Softmax를 적용한 뒤에는 각 행의 합이 1이 됩니다.

예를 들어 먹었다 행을 보겠습니다.

0.10 + 0.10 + 0.35 + 0.45

계산하면

1.00

입니다.

즉,

나는      10%
오늘      10%
사과를    35%
먹었다    45%

처럼 전체 관심도를 100%로 나눠 갖는다고 생각하면 이해하기 쉽습니다.


5. Self-Attention은 어떻게 계산될까?

실제 Transformer에서는 단어 임베딩으로부터 세 가지 벡터를 만듭니다.

Query
Key
Value

보통 다음과 같이 표현합니다.

Q = XWQ
K = XWK
V = XWV

여기서 X는 단어 임베딩이고,

WQ
WK
WV

는 학습되는 가중치입니다.

Attention Score는 다음과 같이 계산합니다.

Q × Kᵀ

그리고 값이 너무 커지는 것을 방지하기 위해 √dk로 나눕니다.

QKᵀ / √dk

그다음 Softmax를 적용합니다.

Attention = Softmax(QKᵀ / √dk)

마지막으로 Value와 곱합니다.

Output = Attention × V

6. PyTorch로 Self-Attention 직접 계산해보기

이번에는 실제 숫자를 사용해 Self-Attention을 계산해보겠습니다.

먼저 4개의 단어가 있다고 가정하겠습니다.

import torch
import torch.nn.functional as F
import math

words = ["나는", "오늘", "사과를", "먹었다"]

각 단어가 3차원 임베딩을 가진다고 가정하겠습니다.

X = torch.tensor([
    [1.0, 0.0, 1.0],  # 나는
    [0.0, 1.0, 1.0],  # 오늘
    [1.0, 1.0, 0.0],  # 사과를
    [1.0, 1.0, 1.0]   # 먹었다
])

print(X)
print(X.shape)

출력 형태는 다음과 같습니다.

torch.Size([4, 3])

즉,

4 = 단어 개수
3 = embedding 차원

입니다.


7. Query와 Key 만들기

설명을 쉽게 하기 위해 이번 예제에서는 별도의 가중치 행렬을 사용하지 않고,

Q = X
K = X
V = X

라고 가정하겠습니다.

Q = X
K = X
V = X

실제 Transformer에서는 각각 서로 다른 가중치가 곱해집니다.

Q = XWQ
K = XWK
V = XWV

8. Q와 K를 곱하기

Attention Score는 다음처럼 계산합니다.

scores = Q @ K.T

전체 코드는 다음과 같습니다.

scores = Q @ K.T

print(scores)

K.T는 Key 행렬을 transpose한 것입니다.

Shape을 보면,

Q   : [4, 3]
K.T : [3, 4]

이므로 결과는

[4, 4]

가 됩니다.

즉,

4개의 단어 × 4개의 단어

관계가 만들어집니다.


9. √dk로 나누기

Transformer의 Scaled Dot-Product Attention에서는 다음처럼 계산합니다.

d_k = Q.size(-1)

scaled_scores = scores / math.sqrt(d_k)

여기서 d_k는 Query와 Key의 차원입니다.

현재는

d_k = 3

입니다.


10. Softmax 적용하기

이제 Softmax를 적용합니다.

attention = F.softmax(scaled_scores, dim=-1)

print(attention)

여기서

dim=-1

은 각 행을 기준으로 Softmax를 적용한다는 뜻입니다.

즉, 각 Query가 모든 Key에 대해 가지는 Attention을 계산합니다.


11. 각 행의 합 확인하기

Softmax를 적용했기 때문에 각 행의 합은 1이 됩니다.

print(attention.sum(dim=-1))

출력은 다음과 비슷합니다.

tensor([
    1.0000,
    1.0000,
    1.0000,
    1.0000
])

이 부분이 매우 중요합니다.

대각선 = 1

이 아니라,

각 행 전체의 합 = 1

입니다.


12. 단어별 Attention 출력하기

조금 더 보기 쉽게 출력해보겠습니다.

for i, query_word in enumerate(words):
    print(f"\nQuery: {query_word}")

    for j, key_word in enumerate(words):
        print(
            f"{query_word} -> {key_word}: "
            f"{attention[i, j].item():.4f}"
        )

결과는 다음과 같은 형태가 됩니다.

Query: 나는

나는 -> 나는: 0.xxxx
나는 -> 오늘: 0.xxxx
나는 -> 사과를: 0.xxxx
나는 -> 먹었다: 0.xxxx

각 행이 현재 단어인 Query이고,

각 열이 참고 대상인 Key입니다.


13. 최종 Self-Attention Output 계산하기

Attention Score를 계산하는 것만으로 끝나는 것은 아닙니다.

최종적으로 Attention Matrix와 Value를 곱합니다.

output = attention @ V

print(output)

즉,

Output = Attention × Value

입니다.

쉽게 생각하면 다음과 같습니다.

기존 먹었다 벡터에,

나는 정보
오늘 정보
사과를 정보
먹었다 정보

를 Attention 비율만큼 섞어서 새로운 먹었다 벡터를 만드는 것입니다.

이 과정을 통해 단어는 혼자 존재하는 것이 아니라 문맥 정보를 포함한 벡터로 변하게 됩니다.


14. 전체 코드

지금까지의 코드를 한 번에 정리하면 다음과 같습니다.

import torch
import torch.nn.functional as F
import math

words = ["나는", "오늘", "사과를", "먹었다"]

# 단어 Embedding이라고 가정
X = torch.tensor([ [1.0, 0.0, 1.0], # 나는
                   [0.0, 1.0, 1.0], # 오늘
                   [1.0, 1.0, 0.0], # 사과를
                   [1.0, 1.0, 1.0]  # 먹었다
                 ])

# 설명을 위해 Q, K, V를 동일하게 사용
Q = X
K = X
V = X

# 1. Attention Score
scores = Q @ K.T
print("Raw Attention Score")
print(scores)

# 2. Scaling
d_k = Q.size(-1)
print("Q size: ", Q.size() )
print("K size: ", K.size())
print("d_k: ", d_k)
scaled_scores = scores / math.sqrt(d_k)
print("\nScaled Attention Score")
print(scaled_scores)

# 3. Softmax
attention = F.softmax( scaled_scores, dim=-1 )
print("\nAttention Matrix")
print(attention)

# 4. 각 행의 합 확인
print("\nRow Sum")
print(attention.sum(dim=-1))

# 5. 단어별 Attention 확인
for i, query_word in enumerate(words):
    print(f"\nQuery: {query_word}")

    for j, key_word in enumerate(words):
        print(
            f"{query_word} -> {key_word}: "
            f"{attention[i, j].item():.4f}"
        )

# 6. 최종 Self-Attention Output
output = attention @ V
print("\nSelf-Attention Output")
print(output)

실행 결과:


15. 핵심만 다시 정리하기

Self-Attention을 이해할 때 다음 네 가지를 기억하면 됩니다.

1. 행은 Query이다

현재 보고 있는 단어

입니다.


2. 열은 Key이다

현재 단어가 참고하는 대상

입니다.


3. 대각선이 1일 필요는 없다

대각선은 단순히

자기 자신에게 주는 Attention

을 의미합니다.

따라서

0.4
0.5
0.7

등 어떤 값도 나올 수 있습니다.


4. Softmax 이후 각 행의 합은 1이다

예를 들어,

먹었다
↓
나는      0.10
오늘      0.10
사과를    0.35
먹었다    0.45

라면,

0.10 + 0.10 + 0.35 + 0.45
= 1.00

입니다.


마무리

Self-Attention의 핵심은 복잡한 수식 자체가 아닙니다.

결국 모델이 하는 일은 다음과 같습니다.

현재 단어를 이해하기 위해
문장 속 다른 단어를 얼마나 참고할 것인가?

이를 숫자로 계산하는 것이 Self-Attention입니다.

특히 다음 관계를 기억하면 Self-Attention Matrix를 훨씬 쉽게 읽을 수 있습니다.

Row = Query
Column = Key

Query → Key
현재 단어 → 참고할 단어

그리고 가장 중요한 점은,

대각선 = 1

이 아니라

각 행의 합 = 1

이라는 것입니다.

이 개념을 이해하면 이후 배우게 되는

Q, K, V
Scaled Dot-Product Attention
Multi-Head Attention
Causal Mask
Transformer

도 훨씬 쉽게 이해할 수 있습니다.

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다