딥러닝으로 자연어 처리(NLP)를 공부하다 보면 가장 먼저 만나게 되는 레이어 중 하나가 바로 Embedding Layer입니다.
PyTorch에서는 nn.Embedding()을 사용하여 단어, 문자, 토큰(Token) 등의 ID를 학습 가능한 벡터(Vector)로 변환할 수 있습니다.
이번 글에서는 One-Hot Encoding부터 시작하여 Embedding의 개념, 사용 방법, 그리고 사전 학습된 임베딩까지 차근차근 알아보겠습니다.
1. Embedding이 필요한 이유
컴퓨터는 문자열을 직접 처리할 수 없습니다.
예를 들어 다음과 같은 단어가 있다고 가정해 보겠습니다.
사과
바나나
포도
먼저 단어를 숫자로 변환해야 합니다.
사과 → 0
바나나 → 1
포도 → 2
이를 Token ID라고 합니다.
하지만 ID 자체에는 단어의 의미가 포함되어 있지 않습니다.
0
1
2
는 단순한 번호일 뿐이며,
2 > 1 > 0
이라는 수학적 관계가
포도 > 바나나 > 사과
라는 의미를 나타내는 것은 아닙니다.
그렇다면 단어를 어떻게 표현해야 할까요?
초기의 자연어 처리에서는 One-Hot Encoding을 사용했습니다.
2. One-Hot Encoding이란?
One-Hot Encoding은 단어를 벡터로 표현하는 가장 단순한 방법입니다.
예를 들어 단어 사전이 다음과 같다고 가정해 보겠습니다.
사과 → 0
바나나 → 1
포도 → 2
One-Hot Encoding을 적용하면
사과
[1, 0, 0]
바나나
[0, 1, 0]
포도
[0, 0, 1]
처럼 표현됩니다.
즉,
자신의 위치만 1
나머지는 모두 0
인 벡터입니다.
3. One-Hot Encoding의 문제점
문제 1 : 벡터가 너무 커진다
단어가 10,000개라면
[0,0,0,0,0, ... ,1, ... ,0]
처럼 길이가 10,000인 벡터가 필요합니다.
만약
100,000개 단어
가 존재한다면
100,000차원 벡터
가 필요합니다.
메모리 낭비가 매우 심합니다.
문제 2 : 단어 간 관계를 표현할 수 없다
예를 들어
사과
바나나
는 둘 다 과일입니다.
하지만 One-Hot Encoding에서는
사과
[1,0,0]
바나나
[0,1,0]
로 표현됩니다.
두 벡터 사이에는 아무런 의미적 관계가 없습니다.
문제 3 : 모든 단어가 동일한 거리
One-Hot 벡터에서는
사과 ↔ 바나나
사과 ↔ 자동차
가 모두 동일하게 취급됩니다.
즉,
유사도 정보를 표현할 수 없음
이라는 치명적인 문제가 있습니다.
4. Embedding의 역할
Embedding은 이러한 문제를 해결하기 위해 등장했습니다.
예를 들어
사과
[0.12, 0.88, -0.51]
바나나
[0.15, 0.91, -0.48]
자동차
[-0.82, 0.17, 0.63]
처럼 표현될 수 있습니다.
Embedding은 학습 과정에서
비슷한 단어
↓
가까운 벡터
를 가지도록 학습됩니다.
예를 들어
사과 ↔ 바나나
는 가까워지고
사과 ↔ 자동차
는 멀어지게 됩니다.
5. One-Hot Encoding과 Embedding 비교
| 항목 | One-Hot Encoding | Embedding |
|---|---|---|
| 벡터 크기 | 매우 큼 | 작음 |
| 메모리 사용량 | 많음 | 적음 |
| 의미 정보 | 없음 | 있음 |
| 단어 유사도 표현 | 불가능 | 가능 |
| 학습 가능 | 불가능 | 가능 |
| 실제 사용 여부 | 거의 사용 안 함 | 매우 많이 사용 |
현재 딥러닝에서는 One-Hot Encoding을 직접 사용하는 경우는 거의 없으며 대부분 Embedding을 사용합니다.
6. nn.Embedding 기본 문법
nn.Embedding(
num_embeddings,
embedding_dim
)
예를 들어
nn.Embedding(10000, 128)
이라면
10000개의 단어
↓
128차원 벡터
를 의미합니다.
7. 가장 간단한 예제
import torch
from torch import nn
embedding = nn.Embedding(10,3)
x = torch.tensor([1,2,4])
out = embedding(x)
print(out)
print(out.shape)
출력
torch.Size([3,3])
입력된 3개의 ID가 각각 3차원 벡터로 변환됩니다.
8. Embedding의 내부 구조
Embedding은 사실상 하나의 Lookup Table입니다.
embedding = nn.Embedding(5,4)
print(embedding.weight.shape)
출력
torch.Size([5,4])
의미
5개의 단어
↓
각 단어마다 4개의 값
입니다.
만약
x = torch.tensor([2])
라면
실제로는
embedding.weight[2]
를 가져오는 것과 거의 동일합니다.
9. 배치(Batch) 입력 처리
x = torch.tensor([
[1,2,3],
[4,5,6]
])
embedding = nn.Embedding(10,4)
out = embedding(x)
print(out.shape)
출력
torch.Size([2,3,4])
즉,
[배치 크기, 시퀀스 길이]
↓
[배치 크기, 시퀀스 길이, 임베딩 차원]
으로 변환됩니다.
10. 학습 가능한 파라미터
embedding = nn.Embedding(1000,128)
print(
sum(p.numel() for p in embedding.parameters())
)
출력
128000
Embedding도 다른 신경망 가중치처럼 학습 과정에서 업데이트됩니다.
11. nn.Linear와의 차이
nn.Linear
linear = nn.Linear(3,2)
입력
[1.2,3.4,5.6]
↓
출력
[0.8,1.5]
nn.Embedding
입력
[1,2,3]
↓
출력
[
[0.12,0.34],
[0.56,0.78],
[0.91,0.22]
]
즉,
nn.Linear
↓
실수 벡터 변환
nn.Embedding
↓
정수 ID → 벡터 변환
입니다.
12. 사전 학습된(Pretrained) Embedding
모든 Embedding이 랜덤하게 시작되는 것은 아닙니다.
이미 대규모 텍스트로 학습된 벡터를 사용할 수도 있습니다.
대표적인 예는 다음과 같습니다.
- Word2Vec
- GloVe
- FastText
이러한 벡터는 이미 단어 간의 의미 관계를 학습한 상태입니다.
13. Pretrained Embedding 불러오기
import torch
from torch import nn
pretrained_weights = torch.randn(5000,100)
embedding = nn.Embedding.from_pretrained(
pretrained_weights
)
shape 확인
print(embedding.weight.shape)
출력
torch.Size([5000,100])
14. Embedding 고정(Freeze)하기
추가 학습 허용
embedding = nn.Embedding.from_pretrained(
pretrained_weights,
freeze=False
)
고정
embedding = nn.Embedding.from_pretrained(
pretrained_weights,
freeze=True
)
15. Random Embedding vs Pretrained Embedding
| 항목 | Random | Pretrained |
|---|---|---|
| 초기값 | 랜덤 | 학습 완료 |
| 의미 정보 | 없음 | 있음 |
| 데이터 요구량 | 많음 | 적음 |
| 학습 속도 | 느림 | 빠름 |
마무리
nn.Embedding은 단순한 숫자 변환 레이어가 아닙니다.
ID 자체에는 의미 정보가 없기 때문에, 의미를 표현할 수 있는 벡터 공간으로 변환하기 위해 사용됩니다.
또한 One-Hot Encoding의 차원 폭발 문제와 의미 표현의 한계를 해결하며, 현재 자연어 처리와 추천 시스템에서 가장 널리 사용되는 범주형 데이터 표현 방법 중 하나입니다.

