2장. Transformer 입력 표현
위치 정보가 필요한 이유
카드 게임에서 손에 든 카드 석 장을 탁자에 엎어 섞었다고 해 봅시다. 어떤 카드가 있었는지는 알지만, 어떤 순서로 들고 있었는지는 알 수 없습니다. 1장의 RNN 은 문장을 한 줄씩 차례로 읽었으므로 순서를 저절로 알았습니다. 그런데 Transformer 의 Self-Attention 은 모든 토큰을 한꺼번에 봅니다. 그대로 두면 "dog bites man(개가 사람을 문다)" 과 "man bites dog(사람이 개를 문다)" 이 같은 카드 묶음으로 보입니다.
그림 왼쪽은 위치 정보가 없을 때입니다. 두 문장에서 순서를 떼어 내면 dog, bites, man 의 같은 카드 묶음만 남아 두 문장의 차이를 알 수 없습니다. 오른쪽은 카드마다 1, 2, 3 위치 번호를 붙인 모습입니다. 같은 dog 라도 1번 자리와 3번 자리는 다르게 읽힙니다.

Transformer 는 번호를 따로 적지 않고, 임베딩 벡터에 위치마다 다른 벡터(Positional Encoding)를 더합니다. 그림의 세 줄이 차례로 임베딩, 위치 벡터, 둘을 더한 결과입니다. 더하기라서 모양은 (토큰 수, d_model) 그대로입니다.
위치 정보가 있을 때와 없을 때
| 경우 | dog bites man | man bites dog | 모델이 보는 것 |
|---|---|---|---|
| 위치 정보 없음 | {dog, bites, man} | {dog, bites, man} | 같은 카드 묶음 |
| 위치 정보 더함 | dog@1, bites@2, man@3 | man@1, bites@2, dog@3 | 서로 다른 입력 |
짧은 예시
import numpy as np
# 손으로 만든 임베딩 표: 0 = dog, 1 = bites, 2 = man (d_model 2)
emb = np.array([[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]])
a = emb[[0, 1, 2]] # dog bites man
b = emb[[2, 1, 0]] # man bites dog
def same_bundle(x, y):
# 행의 순서를 무시하고 같은 행들이 들어 있는지 본다(카드를 섞어 놓고 비교)
return sorted(map(tuple, x.round(3))) == sorted(map(tuple, y.round(3)))
print(same_bundle(a, b)) # 결과: True 순서만 다른 같은 카드 묶음
# 위치 0, 1, 2 에 다른 벡터를 준다: [sin(위치), cos(위치)]
pos = np.arange(3)
pe = np.stack([np.sin(pos), np.cos(pos)], axis=1)
print(pe.round(3))
# 결과:
# [[ 0. 1. ]
# [ 0.841 0.54 ]
# [ 0.909 -0.416]]
print(same_bundle(a + pe, b + pe)) # 결과: False 위치를 더하니 다른 묶음
print((a + pe)[0].round(3), (b + pe)[2].round(3)) # 결과: [1. 1.] [ 1.909 -0.416] 같은 dog, 다른 자리이 코드가 뜻하는 것
카드를 섞어 놓고 비교하는 함수 same_bundle 로 두 문장을 견줘 본 코드입니다. 위치를 더하기 전에는 True, 즉 같은 묶음입니다. 위치 벡터를 더한 뒤에는 False 가 됩니다. 마지막 줄은 같은 dog 라도 0번 자리에서는 [1, 0] + [0, 1] = [1, 1], 2번 자리에서는 [1, 0] + [0.909, -0.416] = [1.909, -0.416] 이 된다는 뜻입니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
emb[[2, 1, 0]] | 순서를 바꾼 문장의 임베딩 | 카드 순서 바꾸기 |
same_bundle(a, b) | 순서를 무시하고 비교한다 | 섞어 놓고 견주기 |
pe = [sin, cos] | 위치마다 다른 벡터를 만든다 | 번호표 만들기 |
a + pe | 임베딩에 위치 벡터를 더한다 | 카드에 번호표 붙이기 |
주의할 점
- 모든 위치에 같은 벡터를 더하면 순서를 구분할 수 없습니다. 위치마다 다른 값이어야 같은 토큰이라도 첫 번째와 다섯 번째 자리를 다르게 나타냅니다.
- 이 예시는 원리를 보이려고 d_model 을 2로 줄였습니다. 실제로는 수백 차원이라 위치 무늬와 뜻 정보가 서로 덜 부딪힙니다.