임베딩의 원리와 텍스트, 이미지, 멀티모달 임베딩 모델을 비교하고, 유사도 메트릭의 수학적 배경과 차원의 저주, 임베딩 모델 선택 가이드를 다룹니다.
**임베딩(Embedding)**은 텍스트, 이미지, 오디오 등 비정형 데이터를 고정 길이의 수치 벡터로 변환한 결과물입니다. 핵심 아이디어는 의미적으로 유사한 데이터는 벡터 공간에서 가까이 위치한다는 것입니다.
예를 들어, "고양이가 소파에서 잠을 잔다"와 "소파 위에서 자는 고양이"는 단어 구성이 다르지만 의미가 같으므로, 두 문장의 임베딩 벡터는 벡터 공간에서 매우 가까이 위치합니다.
from openai import OpenAI
client = OpenAI()
# 두 문장의 임베딩 생성
texts = [
"고양이가 소파에서 잠을 잔다",
"소파 위에서 자는 고양이"
]
response = client.embeddings.create(
model="text-embedding-3-small",
input=texts
)
vec_a = response.data[0].embedding # 1536차원 벡터
vec_b = response.data[1].embedding # 1536차원 벡터
# 코사인 유사도: 0.95+ (매우 유사)임베딩 벡터의 각 차원은 단독으로는 의미가 없습니다. 모든 차원이 결합되어 의미를 표현합니다. 이를 **분산 표현(Distributed Representation)**이라 합니다.
텍스트 임베딩 모델은 가장 널리 사용되는 유형입니다. 주요 모델들을 비교해 보겠습니다.
| 모델 | 차원 | 최대 토큰 | 특징 |
|---|---|---|---|
| text-embedding-3-large | 3072 | 8191 | 최고 성능, 차원 축소 가능 |
| text-embedding-3-small | 1536 | 8191 | 비용 효율적, 대부분의 용도에 충분 |
| text-embedding-ada-002 | 1536 | 8191 | 레거시, 호환성 유지 |
OpenAI의 3세대 임베딩 모델은 Matryoshka Representation Learning 기법을 적용하여, 전체 차원의 앞부분만 사용해도 유의미한 성능을 유지합니다. 예를 들어 text-embedding-3-large의 3072차원 중 1024차원만 사용해도 상당한 검색 품질을 확보할 수 있습니다.
상용 API에 의존하지 않으려면 오픈소스 모델을 고려할 수 있습니다.
from sentence_transformers import SentenceTransformer
# 한국어 지원 모델
model = SentenceTransformer("intfloat/multilingual-e5-large")
sentences = [
"query: 벡터 데이터베이스란 무엇인가",
"passage: 벡터 데이터베이스는 고차원 벡터를 저장하고 검색하는 시스템입니다"
]
embeddings = model.encode(sentences)
print(f"벡터 차원: {embeddings.shape[1]}") # 1024주요 오픈소스 모델로는 E5, BGE, GTE 시리즈가 있으며, MTEB(Massive Text Embedding Benchmark) 리더보드에서 성능을 비교할 수 있습니다.
한국어 임베딩 품질이 중요하다면 다국어(multilingual) 모델을 사용하거나, 한국어 데이터로 파인튜닝된 모델을 선택하는 것이 좋습니다. 영어 전용 모델은 한국어에서 성능이 크게 떨어집니다.
이미지를 벡터로 변환하는 모델은 시각적 유사성을 기반으로 작동합니다. 대표적인 모델로 **CLIP(Contrastive Language-Image Pre-training)**이 있습니다.
CLIP은 텍스트와 이미지를 동일한 벡터 공간에 매핑하는 것이 특징입니다. "고양이 사진"이라는 텍스트와 실제 고양이 사진의 임베딩이 가까운 위치에 놓이므로, 텍스트로 이미지를 검색하거나 이미지로 유사 이미지를 검색하는 것이 가능합니다.
from transformers import CLIPModel, CLIPProcessor
from PIL import Image
model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-large-patch14")
# 이미지 임베딩
image = Image.open("cat.jpg")
inputs = processor(images=image, return_tensors="pt")
image_embedding = model.get_image_features(**inputs) # 768차원
# 텍스트 임베딩 (같은 벡터 공간)
inputs = processor(text="a photo of a cat", return_tensors="pt")
text_embedding = model.get_text_features(**inputs) # 768차원**멀티모달 임베딩(Multimodal Embedding)**은 텍스트, 이미지, 오디오 등 서로 다른 모달리티의 데이터를 하나의 통합된 벡터 공간에 매핑합니다. Google의 Vertex AI Multimodal Embedding이나 OpenAI의 CLIP 계열 모델이 대표적입니다.
멀티모달 임베딩의 핵심 가치는 **크로스모달 검색(Cross-modal Search)**입니다. 텍스트로 이미지를 검색하거나, 이미지로 관련 텍스트를 찾는 것이 자연스럽게 가능해집니다. 이커머스의 "이 사진과 비슷한 상품 찾기" 기능이 대표적인 활용 사례입니다.
두 벡터 A, B의 코사인 유사도는 다음과 같이 정의됩니다.
벡터 A와 B의 내적을 각 벡터의 L2 노름의 곱으로 나눈 값입니다. 결과 범위는 -1에서 1이며, 1이면 완전히 같은 방향, 0이면 직교(무관), -1이면 완전히 반대 방향입니다.
특징: 벡터의 크기(magnitude)에 영향받지 않습니다. 문서 길이가 다른 텍스트 임베딩 비교에 적합합니다.
두 벡터 사이의 직선 거리입니다. 각 차원의 차이를 제곱하여 합한 뒤 제곱근을 취합니다. 값이 0에 가까울수록 유사합니다.
특징: 벡터의 크기에 영향받습니다. 같은 분포에서 생성된 벡터 비교에 적합합니다.
각 차원의 곱을 모두 합한 값입니다. 값이 클수록 유사합니다.
특징: 정규화된 벡터(L2 노름이 1)에서는 코사인 유사도와 동일한 순위를 반환합니다. 계산량이 가장 적어서 성능 최적화 시 선호됩니다.
import numpy as np
def compare_metrics(a: np.ndarray, b: np.ndarray) -> dict:
cosine = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
euclidean = np.linalg.norm(a - b)
dot_product = np.dot(a, b)
return {
"cosine_similarity": cosine,
"euclidean_distance": euclidean,
"dot_product": dot_product
}
# 정규화된 벡터에서는 코사인과 내적 순위가 동일
a = np.array([0.6, 0.8])
a_normalized = a / np.linalg.norm(a)
b = np.array([0.5, 0.5])
b_normalized = b / np.linalg.norm(b)
print(compare_metrics(a_normalized, b_normalized))대부분의 벡터 데이터베이스는 내부적으로 **거리(distance)**를 사용합니다. 코사인 유사도를 선택하면 1 - cosine_similarity로 변환하여 거리로 처리합니다. 이 점을 인지하지 못하면 결과 해석에서 혼동이 발생할 수 있습니다.
| 상황 | 권장 메트릭 |
|---|---|
| 텍스트 임베딩 (길이 다양) | 코사인 유사도 |
| 정규화된 임베딩 | 내적 (성능 최적) |
| 이미지 특징 벡터 | 유클리드 거리 |
| 추천 시스템 | 내적 (스코어 크기가 의미 있는 경우) |
**차원의 저주(Curse of Dimensionality)**는 고차원 공간에서 발생하는 반직관적인 현상입니다. 차원이 높아질수록 모든 점 사이의 거리가 비슷해지는 경향이 있습니다.
예를 들어, 2차원 공간에서는 "가까운 점"과 "먼 점"의 거리 차이가 명확합니다. 하지만 1000차원 공간에서는 가장 가까운 점과 가장 먼 점의 거리 비율이 1에 수렴합니다. 이는 유사도 검색의 **구별력(discriminability)**이 떨어진다는 의미입니다.
import numpy as np
def distance_ratio(dim: int, n_points: int = 1000) -> float:
"""고차원에서 최근접/최원점 거리 비율 측정"""
points = np.random.randn(n_points, dim)
query = np.random.randn(dim)
distances = np.linalg.norm(points - query, axis=1)
return distances.min() / distances.max()
# 차원이 높아질수록 비율이 1에 가까워짐
for dim in [2, 10, 100, 1000]:
ratio = distance_ratio(dim)
print(f"차원 {dim:>5d}: min/max 거리 비율 = {ratio:.4f}")실용적 해결 방법은 다음과 같습니다.
프로젝트에 적합한 임베딩 모델을 선택할 때 고려해야 할 요소들을 정리합니다.
임베딩 모델을 바꾸면 기존 벡터를 모두 다시 생성해야 합니다. 초기에 신중하게 선택하되, A/B 테스트로 실제 검색 품질을 검증하는 것이 중요합니다.
이번 장에서는 벡터 임베딩의 원리와 주요 모델 유형, 유사도 메트릭의 수학적 배경을 살펴보았습니다. 텍스트, 이미지, 멀티모달 각 영역에서 임베딩 모델의 발전이 빠르게 이루어지고 있으며, 프로젝트의 요구사항에 맞는 모델을 선택하는 것이 벡터 검색 품질의 출발점입니다.
다음 장에서는 가장 널리 사용되는 ANN 인덱싱 알고리즘인 **HNSW(Hierarchical Navigable Small World)**를 심층 분석합니다. 그래프 기반 검색의 원리부터 핵심 파라미터 튜닝까지 상세히 다루겠습니다.
이 글이 도움이 되셨나요?
HNSW 알고리즘의 원리를 NSW 그래프에서부터 다층 구조까지 단계별로 분석하고, 핵심 파라미터 튜닝과 성능 특성, 적합한 사용 시나리오를 다룹니다.
벡터 데이터베이스가 등장한 배경과 전통 데이터베이스와의 차이점, 유사도 검색 원리, ANN 알고리즘의 필요성, 그리고 주요 벡터 데이터베이스 생태계를 개괄합니다.
IVF 클러스터링 기반 검색과 Product Quantization의 원리를 분석하고, IVF+PQ 조합의 대규모 데이터셋 최적화 전략과 메모리-정확도 트레이드오프를 다룹니다.