Transformer 모델을 공부하다 보면 가장 먼저 만나게 되는 개념이 바로 Embedding과 Positional Encoding입니다.
Transformer 핵심은 주위의 단어가 현재 단어의 의미를 결정해준다는 의미입니다. 즉 attention mechanism을 사용합니다.
많은 사람들이 처음 Transformer를 접할 때 다음과 같은 의문을 가집니다.
“Transformer는 RNN처럼 순서대로 데이터를 처리하지 않는데, 문장의 순서를 어떻게 알 수 있을까?”
그 답은 바로 Positional Encoding입니다.
Transformer의 입력 과정은 크게 다음 순서로 진행됩니다.
문장
↓
Tokenization
↓
Vocabulary 변환
↓
Embedding
↓
Positional Encoding 추가
↓
Transformer Encoder 입력
이번 글에서는 PyTorch 코드를 이용하여 Transformer의 입력 데이터가 어떻게 만들어지는지 단계별로 살펴보겠습니다.

위의 이미지는 Transformer 모델의 구조이고 이번 블로그 내용에서는 붉은색으로 표시한 부분을 살펴보겠습니다.
1. Vocabulary와 Token 변환
컴퓨터는 자연어 문장을 직접 이해할 수 없습니다.
따라서 먼저 단어를 숫자로 변환해야 합니다.
예를 들어 Vocabulary를 다음과 같이 정의할 수 있습니다.
vocab = {
"[PAD]": 0,
"나는": 1,
"오늘": 2,
"학교에": 3,
"간다": 4,
"도서관에": 5,
"학교에서": 6,
"공부한다": 7,
"집에": 8
}
각 단어는 하나의 고유한 숫자를 가지게 됩니다.
예:
나는 → 1
오늘 → 2
학교에 → 3
간다 → 4
따라서 문장
나는 오늘 학교에 간다
는 다음과 같이 변환됩니다.
[1, 2, 3, 4]
2. PAD 토큰이 필요한 이유
실제 자연어 데이터에서는 문장 길이가 모두 다릅니다.
예를 들어:
나는 오늘 학교에 간다
나는 오늘 도서관에 간다
오늘 나는 학교에서 공부한다
나는 집에 간다
문장 길이는 각각
4
4
4
3
처럼 서로 다릅니다.
하지만 Transformer는 Batch 단위로 연산하기 때문에 같은 Batch 안에서는 입력 크기가 동일해야 합니다.
그래서 길이가 부족한 문장 뒤에는 특별한 토큰을 추가합니다.
바로
[PAD]
입니다.
이번 예제에서는 길이를 4로 맞추기 위해:
나는 집에 간다
를
나는 집에 간다 [PAD]
로 변환합니다.
즉,
[1, 8, 4, 0]
이 됩니다.
여기서
0 = [PAD]
입니다.
3. Embedding Layer란?
Transformer는 숫자만 입력받을 수 있지만, 단순한 숫자 자체에는 의미가 없습니다.
예를 들어:
나는 = 1
오늘 = 2
학교에 = 3
이라고 해도 숫자 1, 2, 3 사이의 관계는 없습니다.
그래서 단어를 의미 있는 벡터 공간으로 변환합니다.
이 과정을 Embedding이라고 합니다.
예를 들어:
나는
↓
[-0.7741, -5.1812, 0.7894, -3.0945, 0.8573, 0.7548]
처럼 여러 차원의 벡터로 변환됩니다.
PyTorch에서는 nn.Embedding을 사용합니다.
self.lut = nn.Embedding(
vocab_size,
d_model
)
여기서:
vocab_size- 전체 단어 개수
d_model- Embedding 벡터 차원
입니다.
4. Embedding Scaling (sqrt(d_model))
Transformer 논문에서는 Embedding 결과에 다음 값을 곱합니다.
코드:
return self.lut(x) * math.sqrt(self.d_model)
예를 들어:
d_model = 6
이면
sqrt(6) ≈ 2.45
를 곱합니다.
왜 필요할까요?
Embedding과 Positional Encoding은 서로 더해집니다.
따라서 두 값의 크기(scale)를 어느 정도 맞춰주기 위해 사용합니다.
5. Transformer에서 Position 정보가 필요한 이유
RNN은 순서대로 데이터를 처리합니다.
예:
나는
↓
오늘
↓
학교에
↓
간다
따라서 자연스럽게 순서 정보를 알 수 있습니다.
하지만 Transformer는 모든 단어를 동시에 처리합니다.
즉:
나는 오늘 학교에 간다
와
간다 학교에 오늘 나는
을 단순히 보면 같은 단어 집합입니다.
그래서 Transformer에는 별도로 위치 정보를 넣어줘야 합니다.
이것이 바로 Positional Encoding입니다.
6. Positional Encoding 구현
Transformer는 위치 정보를 다음 공식으로 계산합니다.
짝수 차원:
홀수 차원:
코드에서는:
pe[:,0::2] = torch.sin(position * div_term)
pe[:,1::2] = torch.cos(position * div_term)
으로 구현합니다.
즉:
0번째 차원 → sin
1번째 차원 → cos
2번째 차원 → sin
3번째 차원 → cos
...
형태입니다.
7. register_buffer()는 무엇인가?
Positional Encoding은 학습해야 하는 값이 아닙니다.
Embedding처럼 업데이트되는 Parameter가 아니라 고정된 값입니다.
그래서:
self.register_buffer("pe", pe)
를 사용합니다.
Buffer의 특징:
| 구분 | Parameter | Buffer |
|---|---|---|
| 학습 여부 | O | X |
| GPU 이동 | O | O |
| Model 저장 | O | O |
즉 Positional Encoding은 모델과 함께 저장되지만 학습되지는 않습니다.
8. Embedding과 Positional Encoding 결합
Transformer Encoder에 들어가는 최종 입력은:
입니다.
코드:
return x + self.pe[:, :x.size(1)]
입니다.
예를 들면:
Embedding:
[단어 의미 정보]
Positional Encoding:
[단어 위치 정보]
=
Transformer 입력이 됩니다.
9. 전체 코드
import torch
import torch.nn as nn
import math
torch.manual_seed(0)
vocab = {
"[PAD]":0,
"나는":1,
"오늘":2,
"학교에":3,
"간다":4,
"도서관에":5,
"학교에서":6,
"공부한다":7,
"집에":8
}
x = torch.LongTensor([
[1,2,3,4],
[1,2,5,4],
[2,1,6,7],
[1,8,4,0]
])
class Embeddings(nn.Module):
def __init__(self, d_model, vocab_size):
super().__init__()
# look up table
self.lut = nn.Embedding(
vocab_size,
d_model
)
self.d_model = d_model
def forward(self,x):
return self.lut(x) * math.sqrt(self.d_model)
d_model = 6
embedding_layer = Embeddings(
d_model,
len(vocab)
)
embedding = embedding_layer(x)
print("embedding:\n", embedding)
print("embedding shape:\n", embedding.shape)
class PositionalEncoding(nn.Module):
def __init__(self,d_model,max_len=4):
super().__init__()
pe = torch.zeros(max_len,d_model)
position = torch.arange(
0,max_len
).unsqueeze(1).float()
div_term = torch.exp(
torch.arange(0,d_model,2).float()
*
(-math.log(10000.0)/d_model)
)
pe[:,0::2] = torch.sin(
position * div_term
)
pe[:,1::2] = torch.cos(
position * div_term
)
pe = pe.unsqueeze(0)
print("pe: \n", pe)
print("pe shape: \n", pe.shape)
self.register_buffer(
"pe",
pe
)
def forward(self,x):
return x + self.pe[:,:x.size(1)]
pe = PositionalEncoding(d_model)
output = pe(embedding)
print("output:\n", output)
print("output shape\n",output.shape)
실행 결과:



- 3번째 구절의 경우 같은 단어지만 다르게 임베딩이 된것으로 나타납니다.

10. 정리
이번 예제에서는 Transformer 입력 데이터가 만들어지는 과정을 살펴보았습니다.
전체 흐름은 다음과 같습니다.
문장
↓
Vocabulary 변환
나는 오늘 학교에 간다
↓
[1,2,3,4]
↓
Embedding
↓
단어 의미 벡터 생성
↓
Positional Encoding 추가
↓
위치 정보 포함
↓
Transformer Encoder 입력
핵심 개념을 정리하면:
| 구성 요소 | 역할 |
|---|---|
| Vocabulary | 단어를 숫자로 변환 |
| PAD | 문장 길이를 맞추기 위한 빈 공간 |
| Embedding | 단어 의미 표현 |
| sqrt(d_model) | Embedding scale 조정 |
| Positional Encoding | 단어 위치 정보 추가 |
| register_buffer | 학습하지 않는 값 저장 |
Transformer는 Attention만으로 문장을 이해하기 때문에 단어의 의미 정보(Embedding)와 순서 정보(Positional Encoding)를 함께 제공해야 합니다.
이 두 가지가 결합되어야 Transformer는 문장의 의미와 위치 관계를 모두 학습할 수 있습니다.
기타 참조:
논문 참조 링크:

