개발은 재밌어야 한다
article thumbnail
Published 2026. 11. 1. 22:49
AI 공부하기 1-3. 임베딩 AI
반응형

임베딩이란?

텍스트는 토크나이저를 거쳐 토큰ID(정수)로 바뀐다. 그런데 ID는 번호일 뿐이라 번호 사이에 의미 관계가 없다.

모델은 이 ID를 그대로 계산하지 않고, 먼저 벡터(실수 배열)로 바꾼다. 이 변환이 임베딩이다.

 

토큰 ID -> (임베딩 테이블 조회) -> 벡터 -> 모델 계산

  • 임베딩 테이블: 어휘 크기 x 차원 크기의 표. GPT-2는 50,257 x 768
  • 토큰 ID는 이 표의 행 번호다. 4881번 토큰의 벡터는 4881번 행이다.
  • 표의 값은 사람이 정하지 않는다. 모델 학습 중에 파라미터로 함께 학습된다.

엑셀 시트로 보면 이해가 쉽다. 행은 토큰의 종류, 열은 숫자 개수고, 한 행이 곧 하나의 벡터다. 아래는 토큰 5종류, 숫자 3개짜리 미니표다.

EX)

행 번호 (토큰 ID) 벡터
0 [0.2, -0.5, 0.9]
1 [0.7, 0.1, -0.3]
2 [-0.4, 0.8, 0.5]
3 [0.0, 0.3, 0.3]
4 [0.9, 0.9, -0.1]

 

  • ID 2를 조회하면 2번 행 [-0.4, 0.8, 0.5] 가 나온다. 계산이 아니라 행을 읽어오는 것이다.
  • GPT-2 의 실제 표는 이것이 행 5행X3열이 아니라 50,257행 X 768열일 뿐

이해 포인트

- 백엔드 관점으로는 ID로 벡터를 조회하는 Map<Integer, float[768]> 와 같은 구조다. 값을 학습으로 채운다는 점이 다르다.
- 토큰 ID는 상품코드와 같다. 코드 숫자의 크기에는 의미가 없고 구분용 이름표일 뿐. 이름표는 의미 있는 계산을 할 수 없어서, 계산에 쓸 수 있는 숫자 묶음(벡터)으로 바꾸는 단계가 필요하다.
- 실제 표는 50,257 X 768 = 38,597,376칸이다. 사람이 채울 수 없고 학습이 채운다.
- 토크나이저가 "문자열 -> ID"라면, 임베딩은 "ID -> 벡터"다. 두 단계가 이어져 모델의 입구가 된다.

 

왜 번호가 아니라 벡터인가?

번호는 크기 비교 외에 정보가 없다. 벡터는 좌표라서 방향과 거리를 잴 수 있다.

  • 지도의 좌표와 같다. 도시 이름만으로는 두 도시가 가까운지 알 수 없지만, 위도와 경도가 있으면 거리를 계산 할 수 있다. 임베딩은 이 좌표가 2개가 아니라 768개다.

학습 결과 비슷한 문맥에서 쓰이는 토큰은 가까운 벡터를 갖는 경향이 있다. 가까운 정도는 보통 코사인 유사도로 잰다.

  • 코사인 유사도: 두 벡터가 같은 방향을 향하는 정도. 범위는 -1 ~ 1 이다.
  • 1에 가까울수록 방향이 비슷하고, 0 근처면 방향 관계가 거의 없다.
  • 길이는 무시하고 방향만 비교한다.

EX)

단어 좌표
France (0.9, 0.8)
Paris (0.8, 0.9)
apple (-0.5, 0.2)

 

  • France와 Paris의 코사인 유사도는 약 0.99다. 두 좌표가 거의 같은 방향이다.
  • France와 apple은 약 -0.45다. 방향이 어긋난다.
  • France를 2배로 늘린 (1.8, 1.6) 과 France의 유사도는 1이다. 길이가 달라도 방향이 같으면 1이다.
  • 실제 계산은 2차원이 아니라 768차원에서 하는 것 뿐이다,.

이해포인트

- "의미가 비슷하다"는 사람이 정의한 것이 아니다. 비슷한 문맥에 등장하는 토큰이 비슷한 위치로 모이는 학습 결과다.
- 벡터의 768개 값은 칸마다 사람이 붙인 의미(무게, 색 같은 속성)가 없다. 의미는 값 하나가 아니라 768개 전체의 조합에 있다.
- 이 개념이 RAG의 의미 기반 검색으로 이어진다.

 

임베딩의 두 가지 의미

구분 입력 용도
토큰 임베딩 (이번 글) 토큰 하나 LLM 내부, 모델의 첫 단계
문장/문서 임베딩 문장, 문서 전체 검색, 유사도 비교, RAG

 

  • RAG에서 쓰는 것은 문장/문서 임베딩이다.
  • 토큰 임베딩이 재료 하나의 성분표라면, 문장/문서 임베딩은 완성된 요리 한 접시의 맛 프로필에 가깝다.

 

실행 예제

transformers, torch가 설치되어 있어야 한다. 없으면 먼저 설치한다.

py -3.12 -m pip install transformers torch

 

notepad embed_demo.py

 

 

embed_demo.py

import torch
import torch.nn.functional as F
from transformers import AutoTokenizer, AutoModelForCausalLM

tok = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2")
model.eval()

W = model.get_input_embeddings().weight.detach()   # 임베딩 테이블 [어휘 크기, 차원]

# 1) 임베딩 테이블의 크기
total = sum(p.numel() for p in model.parameters())
print("[1] 임베딩 테이블 shape:", tuple(W.shape))
print("[1] 임베딩 파라미터 수:", W.numel())
print("[1] 모델 전체 파라미터 수:", total)
print(f"[1] 전체 중 비율: {W.numel() / total:.1%}")

# 2) 토큰 ID -> 벡터 (조회)
tid = tok.encode(" France")[0]
vec = W[tid]
print("[2] ' France'의 ID:", tid)
print("[2] 벡터 길이:", vec.shape[0])
print("[2] 앞 8개 값:", [round(x, 4) for x in vec[:8].tolist()])

# 3) 코사인 유사도 비교
def vec_of(word):
    ids = tok.encode(word)
    if len(ids) != 1:
        return None                      # 한 토큰이 아니면 비교 불가
    return W[ids[0]]

for w in [" Paris", " Germany", " French", " apple", " the"]:
    v = vec_of(w)
    if v is None:
        print(f"[3] {w!r}: 토큰 {len(tok.encode(w))}개로 쪼개져서 건너뜀")
        continue
    sim = F.cosine_similarity(vec, v, dim=0).item()
    print(f"[3] ' France' vs {w!r}: {sim:.4f}")

# 4) 어휘 전체에서 ' France'와 가장 가까운 토큰
sims = F.cosine_similarity(W, vec.unsqueeze(0), dim=1)
top = torch.topk(sims, 11)               # 자기 자신 포함 11개
for s, i in zip(top.values, top.indices):
    print(f"[4] {tok.decode(i.item())!r}: {s.item():.4f}")

 

실행

py -3.12 embed_demo.py

 

한글이나 특수문자가 깨지면 'chcp 65001'을 먼저 입력한 뒤 다시 실행해야 한다.

 

 

코드 이해 

  • get_input_embeddings().weight: 모델 안의 임베딩 테이블. [50257, 768] 모양의 표다.
  • W[tid]: ID로 행 하나를 읽는다. 이것이 임베딩 조회다.
  • cosine_similarity(vec, v, dim=0): 벡터 두 개의 방향이 얼마나 같은지 계산한다.
  • cosine_similarity(W, vec.unsqueeze(0), dim=1): 어휘 전체를 한 번에 비교한다.
  • 한 토큰이 아니면 건너뛰는 이유: 토크나이저가 단어를 여러 조각으로 쪼갤 수 있기 때문이다.

 

결과 해석

[1] 테이블 크기

  • 임베딩 테이블 shape는 (50257, 768) 이고, 파라미터는 38,597,376개다. 모델 전체 124,439,808개의 31.0%다.
  • 50,257 X 768 = 38,597,376 이다. 실행 결과와 일치한다.
  • 모델 파라미터의 31%가 토큰 ID를 벡터로 바꾸는 표 하나에 들어 있다.

[2] 테이블 크기

  • 'France' 의 ID는 4881 이고, 벡터 길이는 768이다. 앞 8개 값은 [-0.0694, 0.0489, 0.1624, -0.0735, 0.0406, 0.0608, -0.2263, 0.0003]이다.
  • 값 하나하나 해석 할 수 없다. 의미는 값 하나가 아니라 벡터 전체의 위치에 있다.

[3] 코사인 유사도

비교 대상 코사인 유사도
'Paris' 0.6344
'Germany' 0.6960
'French' 0.7119
'apple' 0.2382
'the' 0.2138

 

  • 'France' 와 가장 비슷하게 나온 것은 'French' (0.7119) 이고, 가장 낮은 것은 'the' (0.2138)다.
  • Paris, Germany, French 는 0.63 ~ 0.71 이고, 관련 없는 apple, the 는 0.21~0.24다. 의미가 가까운 토큰일수록 유사도가 높다.
  • 관련 없는 토큰도 0이 아니라. 0.2 수준이 나왔다. 유사도는 절대값보다 상대 순위로 본다.
  • 수도인 'Paris' (0.6344) 는 'Germany' (0.6960), 'French' (0.7119) 보다 낮다.

[4] 가장 가까운 토큰

순위 토큰 코사인 유사도
1 ‘ France’ (자기 자신) 1.0000
2 ‘France’ 0.8291
3 ‘ French’ 0.7119
4 ‘ Germany’ 0.6960
5 ‘ Spain’ 0.6757
6 ‘ Italy’ 0.6755
7 ‘French’ 0.6640
8 ‘ Europe’ 0.6610
9 ‘ Belgium’ 0.6447
10 ‘Germany’ 0.6364
11 ‘ Paris’ 0.6344

 

  • 어휘 50,257개 중 상위 11개다. 1등은 자기 자신이고, 나머지 10개는 모두 나라, 국적, 유럽, 프랑스와 관련있는 토큰이다.
  • 2위는 공백 없는 'France' (0.8291) 다. 공백 유무에 따라 서로 다른 토큰인데도 벡터는 가깝다. 'French', 'Germany' 도 공백 없는 형태로 함께 올라와 있다.
  • 'Paris' 는 11개 중 마지막이다.
  • France는 수도(Paris) 보다 같은 범주(나라, 국적)의 단어와 더 가깝게 놓여 있다.

토큰 임베딩은 문맥을 모른다

중요!

  • 토큰 임베딩은 문맥과 무관한다. 같은 토큰이면 항상 같은 벡터다.
  • 국어사전에서 "배"를 찾으면 항상 같은 설명이 나오는 것과 같다. 문장 속에서 어떤 뜻인지는 사전이 아니라 문장을 읽어야 정해진다.
  • "bank"가 은행인지 강둑인지는 이 단계에서 구분하지 못한다.
  • 문맥에 따라 벡터가 바뀌는 과정은 Transformer 층의 Attention에서 일어난다.
  • 순서 정보도 임베딩 테이블만으로는 알 수 없다. GPT-2는 위치 정보를 별도로 더한다.

서비스를 만들 떄 중요한 점

1. 벡터 저장 크기를 제한 할 수 있다. 768차원 float32 벡터 하나는 768 × 4 = 3,072바이트(약 3KB)다. 100만 개를 저장하면 약 3GB다. 차원이 2배면 저장 크기도 2배다. 차원은 임베딩 모델마다 다르다.

2. 임베딩 모델을 바꾸면 기존 벡터를 쓸 수 없다. 벡터는 모델 기준으로만 비교할 수 있어서, 모델을 교체하면 전체를 다시 임베딩해야한다.

3. 검색 방식이 달라진다. 키워드 검색(LIKE, 역색인)은 단어가 일치해야 찾고, 벡터 검색은 의미가 가까운 것을 찾는다. RAG의 출발점이다.

반응형

'AI' 카테고리의 다른 글

AI 공부하기 1-2. 토큰과 토크나이저  (1) 2026.10.30
AI 공부하기 1-1.LLM이란 무엇인가  (0) 2026.10.29
profile

개발은 재밌어야 한다

@ghyeong

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!