이 글은 Hugging Face 모델을 활용한 문서 임베딩의 전체 과정을 정리한 글입니다.
tokenizer, chunking, truncation, padding, CLS 임베딩까지 헷갈리기 쉬운 개념들을 하나하나 설명합니다.
문서를 임베딩해 벡터 DB에 저장하기 위한 전체 과정은 다음과 같은 흐름으로 진행됩니다:
Tokenizer는 문장을 숫자 토큰 ID 리스트로 바꿔주는 도구입니다.
원-핫 벡터는 아니며, 각 단어/서브워드는 고유한 숫자 ID로 변환됩니다.
사용되는 vocab(사전)은 모델마다 다릅니다.
tokenizer("I love meat")
# → [101, 1045, 2293, 3111, 102]
Chunking은 긴 문서를 모델이 처리 가능한 길이로 쪼개는 작업입니다.
보통 토큰 수를 기준으로 쪼개며, 512 토큰 이하로 유지합니다.
중요한 문맥 손실을 방지하기 위해 슬라이딩 윈도우 방식으로 겹치게 자르기도 합니다.
chunk1: 토큰 0 ~ 511
chunk2: 토큰 412 ~ 923 (chunk_overlap = 100)
각 chunk는 문장 2~5개 정도로 구성되며, 이 하나의 chunk가 임베딩 단위가 됩니다.
토큰화 과정에서 짧은 chunk는 padding으로 0을 채우고,
너무 긴 chunk는 max_length까지만 남기고 나머지는 잘라냅니다.
tokenizer(
chunk,
padding=True,
truncation=True,
max_length=512,
return_tensors="pt"
)
padding=True: 짧은 입력의 뒷부분을 0으로 채움truncation=True: 긴 입력은 자르고 나머지 버림max_length=512: 대부분의 BERT 모델은 512 토큰까지 입력 가능결과적으로 모든 입력은 길이가 딱 512 토큰으로 맞춰집니다.
토크나이징된 chunk를 모델에 넣으면, 각 토큰의 출력 벡터가 나옵니다.
그 중 첫 번째 토큰([CLS])의 벡터만 추출해서 해당 chunk 전체의 의미를 대표하는 임베딩으로 사용합니다.
with torch.no_grad():
output = model(**inputs)
embeddings = output.last_hidden_state[:, 0, :]
output.last_hidden_state: 각 토큰의 벡터들 ([batch, seq_len, hidden])[:, 0, :]: 각 chunk의 첫 번째 토큰([CLS]) 벡터만 추출즉, chunk 하나당 하나의 벡터가 생성됩니다.
추출된 텐서를 numpy 배열로 바꾸고, 리스트로 변환하여 DB나 파일로 저장합니다.
vectors = embeddings.cpu().numpy().tolist()
from transformers import AutoTokenizer, AutoModel
import torch
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
model = AutoModel.from_pretrained("bert-base-uncased")
chunks = [
"삼겹살은 맛있다. 비 오는 날에 더 맛있다.",
"불판에 구우면 재미도 있고 소주와 잘 어울린다."
]
inputs = tokenizer(
chunks,
return_tensors="pt",
padding=True,
truncation=True,
max_length=512
)
with torch.no_grad():
output = model(**inputs)
embeddings = output.last_hidden_state[:, 0, :] # CLS 벡터 추출
vectors = embeddings.cpu().numpy().tolist()
| 개념 | 설명 |
|---|---|
| Tokenizer | 문장을 숫자 토큰 ID로 변환 |
| Chunk | 여러 문장을 모아 만든 덩어리 (임베딩 단위) |
| Padding | 짧은 입력은 0으로 채움 |
| Truncation | 긴 입력은 잘라냄 |
| CLS 벡터 | chunk 전체 의미를 요약한 벡터 |
| 임베딩 단위 | 항상 청크 하나당 벡터 하나 |
HuggingFace 기반 문서 임베딩에서 중요한 건
항상 chunk 단위로 임베딩하고,
CLS 토큰 벡터를 통해 그 chunk 전체의 의미를 벡터로 뽑아낸다는 것입니다.