이미지 로딩 중...

Embeddings 개념 완벽 이해 가이드 - 슬라이드 1/9
A

AI Generated

2025. 11. 16. · 9 Views

Embeddings 개념 완벽 이해 가이드

텍스트를 숫자로 변환하는 Embeddings의 개념부터 실전 활용까지 초급 개발자를 위한 완벽 가이드입니다. AI와 머신러닝의 핵심 기술인 임베딩을 쉽게 이해하고 실무에 적용해보세요.


목차

  1. Embeddings_기본_개념
  2. 벡터_유사도_계산
  3. 시맨틱_검색_구현
  4. 벡터_데이터베이스_활용
  5. RAG_시스템_구축
  6. 다국어_임베딩_활용
  7. 임베딩_차원_축소
  8. 임베딩_모델_비교

1. Embeddings_기본_개념

시작하며

여러분이 AI 챗봇을 만들거나 검색 기능을 개발할 때 이런 고민을 해본 적 있나요? "컴퓨터는 어떻게 '사과'와 '과일'이 비슷한 단어라는 걸 알까?" 또는 "왜 똑같은 단어를 검색해도 관련된 다른 내용까지 찾아주는 걸까?" 이런 마법 같은 일은 실제로는 Embeddings라는 기술 덕분에 가능합니다.

컴퓨터는 원래 텍스트를 이해하지 못하고 오직 숫자만 처리할 수 있어요. 그래서 우리는 단어나 문장을 '의미'를 담은 숫자 배열로 변환해야 합니다.

바로 이럴 때 필요한 것이 Embeddings입니다. 마치 여러분이 친구들의 성격을 "외향적 70%, 친절함 90%, 유머 80%"처럼 숫자로 표현하는 것과 비슷해요.

이렇게 하면 비슷한 친구들을 쉽게 찾을 수 있겠죠?

개요

간단히 말해서, Embeddings는 단어나 문장을 수백 개의 숫자로 이루어진 벡터(배열)로 변환하는 기술입니다. 예를 들어 "사과"라는 단어를 [0.2, 0.8, 0.5, ...] 같은 숫자 배열로 바꾸는 거예요.

왜 이 개념이 필요한지 실무 관점에서 설명하면, 검색 엔진, 추천 시스템, 챗봇 등 거의 모든 AI 서비스가 Embeddings를 사용합니다. 예를 들어, 여러분이 "파이썬 배우기"를 검색하면 "Python 학습"이라는 다른 표현의 글도 찾아주는 것이 바로 Embeddings 덕분입니다.

전통적인 방법과 비교하면, 기존에는 정확히 같은 단어만 매칭했다면, 이제는 의미가 비슷한 모든 내용을 찾아낼 수 있습니다. "자동차"를 검색하면 "차량", "승용차" 같은 유사어도 함께 찾아주는 거죠.

Embeddings의 핵심 특징은 첫째, 의미적 유사성을 수치화한다는 점입니다. "왕"과 "여왕"은 비슷한 벡터 값을 가져요.

둘째, 수학적 연산이 가능합니다. "왕 - 남자 + 여자 = 여왕" 같은 재미있는 계산도 할 수 있어요.

이러한 특징들이 AI가 언어를 '이해'하는 것처럼 보이게 만드는 핵심 원리입니다.

코드 예제

# OpenAI의 임베딩 API를 사용한 기본 예제
from openai import OpenAI

# OpenAI 클라이언트 초기화
client = OpenAI(api_key="your-api-key")

# 텍스트를 임베딩 벡터로 변환
response = client.embeddings.create(
    model="text-embedding-3-small",  # 임베딩 모델 선택
    input="Embeddings는 텍스트를 벡터로 변환합니다"  # 변환할 텍스트
)

# 결과 확인 - 1536개의 숫자로 이루어진 벡터
embedding_vector = response.data[0].embedding
print(f"벡터 길이: {len(embedding_vector)}")
print(f"처음 5개 값: {embedding_vector[:5]}")

설명

이것이 하는 일: 위 코드는 한국어 문장을 1536개의 숫자로 이루어진 벡터로 변환합니다. 이 숫자들은 무작위가 아니라 문장의 '의미'를 수학적으로 표현한 것입니다.

첫 번째로, OpenAI 클라이언트를 초기화하는 부분입니다. 이는 마치 여러분이 번역기 앱을 켜는 것과 같아요.

API 키는 여러분이 서비스를 사용할 권한이 있다는 것을 증명하는 열쇠입니다. 실제로는 환경변수에서 안전하게 불러와야 합니다.

두 번째로, embeddings.create() 함수가 실행되면서 실제 변환이 일어납니다. "text-embedding-3-small" 모델은 OpenAI가 학습시킨 AI 모델로, 수십억 개의 텍스트를 학습해서 단어와 문장의 의미를 벡터로 표현하는 방법을 알고 있습니다.

여러분의 텍스트가 이 모델을 거쳐가면 몇 초 만에 벡터로 변환됩니다. 세 번째로, 반환된 response 객체에서 실제 벡터 값을 추출합니다.

embedding_vector는 [0.234, -0.123, 0.567, ...] 같은 형태의 리스트로, 각 숫자는 텍스트의 다양한 의미적 특성을 나타냅니다. 어떤 숫자는 "기술 관련성"을, 어떤 숫자는 "감정의 긍정/부정"을 나타낼 수 있어요.

여러분이 이 코드를 사용하면 어떤 텍스트든 수치화할 수 있고, 이 벡터들을 비교해서 유사도를 계산할 수 있습니다. 실무에서는 이를 활용해 유사 문서 찾기, 의미 기반 검색, 텍스트 분류 등 다양한 작업을 수행합니다.

무엇보다 정확히 같은 단어가 아니어도 의미가 비슷하면 찾아낼 수 있다는 것이 가장 큰 장점입니다.

실전 팁

💡 API 키는 절대 코드에 직접 넣지 마세요. os.environ.get('OPENAI_API_KEY')로 환경변수에서 불러오거나 .env 파일을 사용하세요. 실수로 GitHub에 올리면 요금 폭탄을 맞을 수 있습니다.

💡 임베딩은 한 번 생성하면 재사용하세요. 같은 텍스트를 매번 변환하면 비용과 시간이 낭비됩니다. 데이터베이스나 파일에 저장해두고 필요할 때 불러오세요.

💡 긴 텍스트는 청크(덩어리)로 나누세요. OpenAI 모델은 보통 8,000 토큰(대략 6,000단어) 제한이 있습니다. 긴 문서는 문단이나 섹션 단위로 나눠서 각각 임베딩하세요.

💡 모델 버전에 따라 벡터 차원이 다릅니다. text-embedding-3-small은 1536차원, text-embedding-3-large는 3072차원입니다. 더 큰 모델이 정확하지만 비용과 저장 공간도 더 듭니다.

💡 같은 모델을 일관되게 사용하세요. 서로 다른 모델의 벡터는 비교할 수 없습니다. 프로젝트 시작할 때 모델을 정하고 계속 그 모델을 사용해야 합니다.


2. 벡터_유사도_계산

시작하며

여러분이 유튜브 추천 시스템을 만든다고 상상해보세요. 사용자가 "파이썬 튜토리얼" 영상을 봤다면, 다음에는 어떤 영상을 추천해야 할까요?

"자바 튜토리얼"? "파이썬 게임 만들기"?

"요리 레시피"? 이런 문제는 실제 추천 시스템에서 핵심적인 부분입니다.

단순히 같은 단어가 있는지만 보면 정확한 추천을 할 수 없어요. "파이썬 튜토리얼"과 "Python 강의"는 단어는 다르지만 의미는 거의 같고, "파이썬 요리법"은 "파이썬"이라는 단어가 있지만 전혀 다른 내용이죠.

바로 이럴 때 필요한 것이 벡터 유사도 계산입니다. 두 텍스트의 임베딩 벡터 사이의 각도나 거리를 계산해서 얼마나 의미가 비슷한지 수치로 알아낼 수 있습니다.

개요

간단히 말해서, 벡터 유사도는 두 임베딩 벡터가 얼마나 비슷한지를 0에서 1 사이의 숫자로 표현한 것입니다. 1에 가까울수록 매우 유사하고, 0에 가까울수록 관련이 없습니다.

왜 이 개념이 필요한지 실무 관점에서 설명하면, 검색 엔진에서 가장 관련성 높은 결과를 찾거나, 중복 문서를 탐지하거나, 사용자에게 맞춤 콘텐츠를 추천할 때 필수적입니다. 예를 들어, 고객 문의를 자동으로 분류하는 시스템에서 "환불 요청"과 "돈 돌려받고 싶어요"가 같은 의도라는 걸 파악하려면 유사도 계산이 필요합니다.

전통적인 방법과 비교하면, 기존에는 단어 개수나 정확한 일치만 세었다면, 이제는 의미적 유사성을 정확하게 측정할 수 있습니다. "자동차 수리"와 "차량 정비"는 단어는 완전히 다르지만 높은 유사도 점수를 받게 됩니다.

가장 많이 사용하는 방법은 코사인 유사도(Cosine Similarity)입니다. 이는 두 벡터 사이의 각도를 측정하는 방식으로, 벡터의 크기가 아닌 방향만 비교합니다.

유클리드 거리(Euclidean Distance)도 사용되는데, 이는 두 점 사이의 직선 거리를 측정합니다. 코사인 유사도가 일반적으로 더 안정적이고 해석하기 쉬워서 실무에서 선호됩니다.

코드 예제

import numpy as np
from openai import OpenAI

client = OpenAI(api_key="your-api-key")

# 두 문장의 임베딩 생성
texts = [
    "파이썬은 프로그래밍 언어입니다",
    "Python은 코딩 언어입니다"  # 의미는 같지만 다른 표현
]

# 임베딩 벡터 생성
embeddings = []
for text in texts:
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    embeddings.append(response.data[0].embedding)

# 코사인 유사도 계산 함수
def cosine_similarity(vec1, vec2):
    dot_product = np.dot(vec1, vec2)  # 내적 계산
    norm1 = np.linalg.norm(vec1)  # 첫 번째 벡터 크기
    norm2 = np.linalg.norm(vec2)  # 두 번째 벡터 크기
    return dot_product / (norm1 * norm2)  # 코사인 값

# 유사도 계산 및 출력
similarity = cosine_similarity(embeddings[0], embeddings[1])
print(f"유사도: {similarity:.4f}")  # 0.95 정도의 높은 값이 나옴

설명

이것이 하는 일: 위 코드는 의미는 같지만 표현이 다른 두 문장의 유사도를 계산합니다. "파이썬"과 "Python", "프로그래밍 언어"와 "코딩 언어"는 표기는 다르지만 같은 의미라는 것을 수치로 증명합니다.

첫 번째로, 두 문장을 각각 임베딩 벡터로 변환합니다. for 루프를 사용해서 각 텍스트를 API에 보내고 1536차원의 벡터를 받아옵니다.

이 벡터들은 각 문장의 의미를 수학적으로 인코딩한 것입니다. embeddings 리스트에는 두 개의 벡터가 저장됩니다.

두 번째로, cosine_similarity 함수를 정의합니다. 이 함수의 핵심은 수학 공식 cos(θ) = (A·B) / (|A||B|) 입니다.

np.dot()으로 내적을 계산하고, np.linalg.norm()으로 각 벡터의 크기(길이)를 구합니다. 내적을 두 벡터 크기의 곱으로 나누면 -1에서 1 사이의 코사인 값이 나오는데, 임베딩에서는 보통 0에서 1 사이 값이 나옵니다.

세 번째로, 실제 유사도를 계산하고 출력합니다. 위 예제에서는 0.95 정도의 매우 높은 값이 나올 것입니다.

이는 두 문장이 95% 정도 의미가 같다는 뜻이에요. 만약 "파이썬은 프로그래밍 언어입니다"와 "사과는 맛있는 과일입니다"를 비교하면 0.3 이하의 낮은 값이 나올 겁니다.

여러분이 이 코드를 사용하면 검색 엔진에서 사용자 쿼리와 가장 유사한 문서를 찾거나, 챗봇에서 사용자 질문과 가장 비슷한 FAQ를 찾을 수 있습니다. 실무에서는 수천, 수만 개의 문서 중에서 상위 10개를 추출하는 식으로 활용합니다.

임계값(threshold)을 설정해서 유사도가 0.8 이상인 것만 "관련 있음"으로 분류하는 것도 일반적입니다.

실전 팁

💡 NumPy를 사용하면 계산이 빠릅니다. 순수 Python으로 반복문 돌리는 것보다 100배 이상 빠르니 꼭 NumPy나 다른 벡터 연산 라이브러리를 사용하세요.

💡 유사도 0.8 이상이면 "매우 유사", 0.6~0.8이면 "관련 있음", 0.6 이하면 "다른 주제"로 대략적인 기준을 잡을 수 있습니다. 하지만 실제로는 여러분의 데이터로 테스트해서 최적의 임계값을 찾아야 합니다.

💡 대량의 벡터를 비교할 때는 FAISS나 Pinecone 같은 벡터 데이터베이스를 사용하세요. 일반 반복문으로 1만 개 벡터를 비교하면 너무 느립니다.

💡 정규화된 벡터를 사용하면 코사인 유사도 계산이 더 빠릅니다. 벡터를 미리 단위 벡터로 만들어두면 내적만 계산하면 되기 때문입니다.

💡 다국어 비교도 가능합니다. OpenAI 임베딩 모델은 100개 이상 언어를 지원하므로 "Hello"와 "안녕하세요"의 유사도도 계산할 수 있습니다.


3. 시맨틱_검색_구현

시작하며

여러분이 회사 내부 문서 검색 시스템을 만든다고 생각해보세요. 직원이 "휴가 신청 방법"을 검색했는데, 문서에는 "연차 사용 절차"라고 적혀 있다면 기존 검색으로는 못 찾겠죠?

정확한 단어가 일치하지 않으니까요. 이런 문제는 키워드 기반 검색의 한계입니다.

사용자는 자신만의 표현으로 검색하고, 문서 작성자는 다른 용어를 사용할 수 있어요. "비용 청구"와 "경비 처리", "버그 수정"과 "오류 해결"은 의미는 같지만 단어는 다릅니다.

바로 이럴 때 필요한 것이 시맨틱 검색(Semantic Search)입니다. 단어가 아닌 의미를 기반으로 검색하여, 사용자가 원하는 정보를 더 정확하게 찾아줍니다.

구글도 이제 이 방식을 사용하고 있습니다.

개요

간단히 말해서, 시맨틱 검색은 검색어와 문서를 모두 임베딩으로 변환한 뒤, 벡터 유사도로 가장 관련성 높은 결과를 찾는 방식입니다. 의미를 이해하는 검색이라고 할 수 있어요.

왜 이 개념이 필요한지 실무 관점에서 설명하면, 고객 지원, 문서 관리, 이커머스 상품 검색 등 거의 모든 검색 기능에서 사용자 만족도를 크게 높일 수 있습니다. 예를 들어, 법률 회사에서 "계약 해지 조건"을 검색하면 "계약 종료 요건", "합의 해제 방법" 같은 다양한 표현의 문서를 모두 찾아줍니다.

전통적인 키워드 검색과 비교하면, 기존에는 "iPhone 13"을 검색하면 정확히 그 단어가 있는 문서만 찾았다면, 시맨틱 검색은 "애플 아이폰 13", "Apple iPhone 13", "아이폰13" 등 다양한 표현을 모두 찾아냅니다. 오타나 띄어쓰기 차이도 어느 정도 극복할 수 있어요.

시맨틱 검색의 핵심 과정은 세 단계입니다. 첫째, 모든 문서를 사전에 임베딩으로 변환해서 저장합니다.

둘째, 사용자가 검색어를 입력하면 실시간으로 임베딩으로 변환합니다. 셋째, 검색어 벡터와 모든 문서 벡터의 유사도를 계산해서 상위 결과를 반환합니다.

이 과정이 1초 이내에 일어나야 좋은 사용자 경험을 제공할 수 있습니다.

코드 예제

from openai import OpenAI
import numpy as np

client = OpenAI(api_key="your-api-key")

# 검색 대상 문서들
documents = [
    "파이썬으로 웹 스크래핑하는 방법",
    "자바스크립트 비동기 프로그래밍 가이드",
    "파이썬 데이터 크롤링 튜토리얼",
    "리액트 컴포넌트 설계 패턴"
]

# 모든 문서를 임베딩으로 변환 (사전 작업)
doc_embeddings = []
for doc in documents:
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=doc
    )
    doc_embeddings.append(response.data[0].embedding)

# 검색 함수
def semantic_search(query, top_k=2):
    # 검색어를 임베딩으로 변환
    query_response = client.embeddings.create(
        model="text-embedding-3-small",
        input=query
    )
    query_embedding = query_response.data[0].embedding

    # 모든 문서와 유사도 계산
    similarities = []
    for i, doc_emb in enumerate(doc_embeddings):
        similarity = np.dot(query_embedding, doc_emb) / (
            np.linalg.norm(query_embedding) * np.linalg.norm(doc_emb)
        )
        similarities.append((i, similarity))

    # 유사도 높은 순으로 정렬
    similarities.sort(key=lambda x: x[1], reverse=True)

    # 상위 k개 결과 반환
    results = []
    for i in range(min(top_k, len(similarities))):
        idx, score = similarities[i]
        results.append({
            'document': documents[idx],
            'score': score
        })
    return results

# 검색 실행
query = "웹에서 데이터 수집하기"
results = semantic_search(query, top_k=2)

for i, result in enumerate(results):
    print(f"{i+1}. [{result['score']:.3f}] {result['document']}")

설명

이것이 하는 일: 위 코드는 "웹에서 데이터 수집하기"라는 검색어로 가장 관련성 높은 문서 2개를 찾습니다. "스크래핑"과 "크롤링"이라는 다른 단어를 사용했지만 의미가 유사한 문서를 정확히 찾아냅니다.

첫 번째로, 모든 문서를 사전에 임베딩으로 변환합니다. 이 과정은 실제 서비스에서는 데이터베이스에 저장해두고 재사용합니다.

문서가 새로 추가되거나 수정될 때만 다시 임베딩을 생성하면 되므로, 매번 할 필요가 없어요. 이렇게 하면 검색 속도가 훨씬 빨라집니다.

두 번째로, semantic_search 함수가 실행되면 사용자의 검색어를 실시간으로 임베딩으로 변환합니다. "웹에서 데이터 수집하기"가 1536차원의 벡터가 되는 거죠.

그 다음 for 루프를 돌면서 이 검색어 벡터와 모든 문서 벡터의 코사인 유사도를 계산합니다. (인덱스, 유사도) 튜플을 리스트에 저장합니다.

세 번째로, similarities.sort()로 유사도가 높은 순서대로 정렬합니다. reverse=True로 내림차순 정렬하여 가장 관련성 높은 문서가 맨 앞에 오도록 합니다.

그리고 top_k개만큼만 잘라서 결과로 반환합니다. 이때 문서 내용과 유사도 점수를 함께 반환해서 사용자가 얼마나 관련성이 있는지 알 수 있게 합니다.

여러분이 이 코드를 사용하면 고객 지원 시스템에서 FAQ를 자동으로 찾아주거나, 사내 위키에서 관련 문서를 추천하거나, 이커머스에서 상품을 검색하는 기능을 만들 수 있습니다. 실무에서는 문서가 수만 개 이상일 수 있으므로 벡터 데이터베이스(Pinecone, Weaviate, Qdrant)를 사용해서 빠른 검색을 구현합니다.

또한 하이브리드 검색으로 키워드 검색과 시맨틱 검색을 결합하면 더 좋은 결과를 얻을 수 있습니다.

실전 팁

💡 문서를 청크(chunk)로 나누세요. 긴 문서는 섹션이나 문단 단위로 쪼개서 각각 임베딩하면 검색 정확도가 올라갑니다. 500-1000 토큰 정도가 적당합니다.

💡 메타데이터를 함께 저장하세요. 문서 제목, 작성일, 카테고리 등을 저장해두면 필터링과 결합할 수 있습니다. 예를 들어 "2023년 이후 파이썬 관련 문서"처럼 조건을 걸 수 있어요.

💡 유사도 임계값을 설정하세요. 0.5 이하의 낮은 점수는 "검색 결과 없음"으로 처리하는 게 사용자 경험에 더 좋습니다. 관련 없는 결과를 보여주는 것보다 낫습니다.

💡 캐싱을 활용하세요. 자주 검색되는 쿼리는 결과를 캐시해두면 API 비용과 응답 시간을 크게 줄일 수 있습니다. Redis나 메모리 캐시를 사용하세요.

💡 재순위(re-ranking)를 고려하세요. 임베딩으로 상위 50개를 뽑은 뒤, 더 정교한 모델로 다시 순위를 매기면 정확도가 더 올라갑니다.


4. 벡터_데이터베이스_활용

시작하며

여러분이 백만 개의 고객 리뷰를 검색하는 서비스를 만든다고 상상해보세요. 앞에서 배운 방법대로 for 루프로 100만 번 유사도를 계산하면 몇 분이 걸릴 겁니다.

사용자는 절대 그렇게 기다려주지 않아요. 이런 문제는 규모가 커질수록 심각해집니다.

문서가 10개일 때는 괜찮지만, 1만 개, 10만 개, 100만 개가 되면 일반적인 방법으로는 실시간 검색이 불가능합니다. 메모리도 부족하고 계산 시간도 너무 오래 걸립니다.

바로 이럴 때 필요한 것이 벡터 데이터베이스입니다. 수백만 개의 벡터를 효율적으로 저장하고, 밀리초 단위로 유사한 벡터를 찾아주는 전문 데이터베이스입니다.

Pinecone, Weaviate, ChromaDB 같은 서비스들이 이를 제공합니다.

개요

간단히 말해서, 벡터 데이터베이스는 임베딩 벡터를 저장하고 빠르게 검색하기 위해 최적화된 특수한 데이터베이스입니다. 일반 데이터베이스가 텍스트나 숫자를 다룬다면, 이것은 고차원 벡터를 다룹니다.

왜 이 개념이 필요한지 실무 관점에서 설명하면, AI 서비스의 성능과 비용을 결정하는 핵심 인프라입니다. ChatGPT, Notion AI, GitHub Copilot 같은 서비스들도 모두 벡터 데이터베이스를 사용합니다.

예를 들어, RAG(Retrieval Augmented Generation) 시스템에서 관련 문서를 찾을 때 벡터 DB 없이는 실용적인 서비스를 만들 수 없습니다. 전통적인 SQL 데이터베이스와 비교하면, 기존 DB는 "정확히 일치"하거나 "보다 크거나 작은" 검색만 가능했다면, 벡터 DB는 "가장 유사한" 검색을 초고속으로 수행합니다.

PostgreSQL에도 pgvector 확장이 있지만, 전문 벡터 DB가 훨씬 빠르고 확장성이 좋습니다. 벡터 데이터베이스의 핵심 기술은 ANN(Approximate Nearest Neighbor) 알고리즘입니다.

모든 벡터를 다 비교하지 않고, 인덱스를 활용해서 "대략적으로 가장 가까운" 벡터를 빠르게 찾습니다. HNSW, IVF 같은 알고리즘이 사용되는데, 약간의 정확도를 포기하는 대신 속도를 수백 배 빠르게 만듭니다.

실무에서는 95% 정확도로도 충분하기 때문에 이런 트레이드오프가 합리적입니다.

코드 예제

from openai import OpenAI
import chromadb
from chromadb.utils import embedding_functions

# ChromaDB 클라이언트 초기화 (로컬 저장)
chroma_client = chromadb.Client()

# OpenAI 임베딩 함수 설정
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
    api_key="your-api-key",
    model_name="text-embedding-3-small"
)

# 컬렉션 생성 (테이블 같은 개념)
collection = chroma_client.create_collection(
    name="my_documents",
    embedding_function=openai_ef
)

# 문서 추가 (자동으로 임베딩 생성)
documents = [
    "파이썬으로 머신러닝 모델 학습하기",
    "자바스크립트 Promise 완벽 가이드",
    "파이썬 딥러닝 신경망 구현",
    "타입스크립트 타입 시스템 이해하기"
]

collection.add(
    documents=documents,
    ids=[f"doc{i}" for i in range(len(documents))],
    metadatas=[{"category": "tutorial"} for _ in documents]
)

# 시맨틱 검색 실행
results = collection.query(
    query_texts=["AI 모델 만드는 법"],
    n_results=2
)

# 결과 출력
for i, (doc, distance) in enumerate(zip(
    results['documents'][0],
    results['distances'][0]
)):
    print(f"{i+1}. [거리: {distance:.3f}] {doc}")

설명

이것이 하는 일: 위 코드는 ChromaDB를 사용해서 문서를 저장하고 "AI 모델 만드는 법"이라는 쿼리로 가장 관련성 높은 문서 2개를 찾습니다. 모든 임베딩 생성과 검색이 자동으로 처리됩니다.

첫 번째로, ChromaDB 클라이언트와 OpenAI 임베딩 함수를 초기화합니다. openai_ef는 ChromaDB가 자동으로 OpenAI API를 호출해서 임베딩을 생성하도록 설정하는 부분입니다.

여러분이 직접 임베딩을 생성할 필요 없이 텍스트만 넘기면 내부에서 알아서 처리해줍니다. 이렇게 하면 코드가 훨씬 간결해지고 실수할 여지가 줄어듭니다.

두 번째로, create_collection으로 문서를 저장할 컬렉션을 만듭니다. 이는 SQL의 테이블과 비슷한 개념이에요.

collection.add()로 문서를 추가할 때 세 가지를 넘깁니다. documents는 실제 텍스트 내용, ids는 각 문서의 고유 식별자, metadatas는 추가 정보입니다.

메타데이터로 카테고리, 작성일, 저자 등을 저장해두면 나중에 필터링할 때 유용합니다. 세 번째로, collection.query()로 검색을 실행합니다.

query_texts에 검색어를 넘기면 자동으로 임베딩으로 변환되고, 가장 유사한 문서를 찾아서 반환합니다. n_results=2로 상위 2개만 요청했습니다.

결과는 딕셔너리 형태로 오는데, documents에는 원본 텍스트가, distances에는 벡터 간 거리가 들어있습니다. 거리가 작을수록 더 유사합니다(코사인 유사도와는 반대).

여러분이 이 코드를 사용하면 수십만 개의 문서를 다루는 실제 프로덕션 서비스를 만들 수 있습니다. ChromaDB는 로컬에서 간단히 사용할 수 있고, Pinecone이나 Weaviate는 클라우드에서 확장성 있게 운영할 수 있습니다.

실무에서는 문서 업데이트, 삭제, 필터링 기능도 함께 사용하며, 여러 컬렉션을 만들어서 데이터를 분리 관리합니다. 예를 들어 사용자별로 컬렉션을 나누거나, 언어별로 분리하는 식입니다.

실전 팁

💡 적절한 벡터 DB를 선택하세요. ChromaDB는 프로토타입과 중소 규모에, Pinecone은 관리형 클라우드 서비스로 운영 부담 없이, Weaviate는 온프레미스와 고급 기능이 필요할 때 좋습니다.

💡 배치 처리로 삽입하세요. 문서를 하나씩 추가하지 말고 100~1000개씩 묶어서 추가하면 속도가 10배 이상 빨라집니다. collection.add()는 리스트를 받을 수 있습니다.

💡 메타데이터 필터링을 활용하세요. where={"category": "tutorial", "year": {"$gte": 2023}} 같은 조건으로 검색 범위를 좁히면 정확도와 속도가 모두 올라갑니다.

💡 인덱스 설정을 최적화하세요. HNSW의 ef_construction, M 파라미터를 조정해서 속도와 정확도의 균형을 맞추세요. 기본값으로 시작해서 실제 데이터로 테스트하며 튜닝하세요.

💡 비용을 모니터링하세요. 벡터 저장과 검색은 비용이 들 수 있습니다. 불필요한 데이터는 주기적으로 삭제하고, 캐싱을 활용해서 같은 검색을 반복하지 않도록 하세요.


5. RAG_시스템_구축

시작하며

여러분이 회사의 방대한 기술 문서를 학습한 AI 챗봇을 만든다고 생각해보세요. ChatGPT에 직접 물어보면 일반적인 답변만 하고, 여러분 회사만의 특수한 정보는 모릅니다.

그렇다고 GPT를 처음부터 학습시키기에는 비용이 수억 원이 들어요. 이런 문제는 많은 기업이 겪는 고민입니다.

최신 정보나 사내 정보는 LLM이 모르기 때문에, 잘못된 정보(hallucination)를 생성할 수 있습니다. "우리 회사 휴가 정책이 뭐지?"라고 물으면 지어낸 답변을 할 수도 있죠.

바로 이럴 때 필요한 것이 RAG(Retrieval Augmented Generation) 시스템입니다. 사용자 질문에 관련된 문서를 먼저 찾아서(Retrieval), 그것을 참고해서 답변을 생성하는(Generation) 방식입니다.

마치 시험 볼 때 교과서를 펼쳐놓고 보는 것과 같아요.

개요

간단히 말해서, RAG는 벡터 검색으로 관련 문서를 찾은 뒤, 그 문서를 컨텍스트로 제공해서 LLM이 정확한 답변을 생성하도록 하는 시스템입니다. 검색과 생성을 결합한 하이브리드 AI 시스템이에요.

왜 이 개념이 필요한지 실무 관점에서 설명하면, 가장 비용 효율적으로 맞춤형 AI를 만드는 방법이기 때문입니다. 파인튜닝보다 100배 저렴하고, 문서만 업데이트하면 즉시 최신 정보를 반영할 수 있습니다.

예를 들어, 법률 회사에서 수천 건의 판례를 학습한 AI 변호사 도우미를 만들 때 RAG를 사용하면 몇 주 만에 구축 가능합니다. 전통적인 방법과 비교하면, 기존에는 LLM을 직접 파인튜닝하거나 프롬프트에 모든 정보를 넣었다면, RAG는 필요한 정보만 동적으로 가져옵니다.

1000페이지 문서에서 관련된 3페이지만 찾아서 제공하므로 토큰 비용도 절약되고 답변 품질도 높아집니다. RAG의 핵심 워크플로우는 5단계입니다.

첫째, 모든 지식 문서를 청크로 나누고 임베딩으로 변환해서 벡터 DB에 저장합니다(사전 작업). 둘째, 사용자가 질문을 입력합니다.

셋째, 질문을 임베딩으로 변환해서 가장 관련성 높은 청크 3~5개를 검색합니다. 넷째, 검색된 문서를 프롬프트에 컨텍스트로 추가합니다.

다섯째, LLM이 컨텍스트를 참고해서 답변을 생성합니다.

코드 예제

from openai import OpenAI
import chromadb
from chromadb.utils import embedding_functions

# 초기화
client = OpenAI(api_key="your-api-key")
chroma_client = chromadb.Client()
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
    api_key="your-api-key",
    model_name="text-embedding-3-small"
)

# 지식베이스 구축
collection = chroma_client.create_collection(
    name="knowledge_base",
    embedding_function=openai_ef
)

# 회사 문서 추가 (예시)
knowledge = [
    "우리 회사의 연차는 입사 1년 후 15일이 부여됩니다.",
    "경조사 휴가는 결혼 5일, 출산 10일입니다.",
    "재택근무는 주 2회까지 가능하며 사전 승인이 필요합니다."
]

collection.add(
    documents=knowledge,
    ids=[f"kb{i}" for i in range(len(knowledge))]
)

# RAG 질의응답 함수
def rag_query(question):
    # 1. 관련 문서 검색
    search_results = collection.query(
        query_texts=[question],
        n_results=2
    )

    # 2. 컨텍스트 구성
    context = "\n".join(search_results['documents'][0])

    # 3. LLM에게 질문 (컨텍스트 포함)
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": "당신은 회사 규정을 안내하는 도우미입니다. 제공된 문서를 기반으로만 답변하세요."
            },
            {
                "role": "user",
                "content": f"문서:\n{context}\n\n질문: {question}"
            }
        ]
    )

    return response.choices[0].message.content

# 사용 예시
answer = rag_query("입사한 지 1년 됐는데 휴가 며칠 쓸 수 있어?")
print(answer)

설명

이것이 하는 일: 위 코드는 사용자가 "휴가 며칠 쓸 수 있어?"라고 물으면, 관련 회사 규정을 자동으로 찾아서 그것을 근거로 정확한 답변을 생성합니다. LLM이 지어내는 게 아니라 실제 문서에 기반합니다.

첫 번째로, 지식베이스를 구축하는 사전 작업입니다. collection.add()로 회사의 모든 규정, 매뉴얼, FAQ 등을 추가합니다.

실제로는 수백~수천 개의 문서가 들어갈 것입니다. 이 작업은 한 번만 하면 되고, 문서가 업데이트될 때만 다시 추가하면 됩니다.

PDF, Markdown, Word 파일을 파싱해서 텍스트로 변환한 뒤 청크로 나눠서 저장하는 것이 일반적입니다. 두 번째로, rag_query 함수가 실행되면 먼저 collection.query()로 질문과 관련된 문서를 검색합니다.

"휴가"라는 키워드가 없어도 의미적으로 "연차", "경조사 휴가" 같은 관련 문서를 찾아냅니다. n_results=2로 상위 2개 문서를 가져옵니다.

검색된 문서들을 \n으로 연결해서 하나의 컨텍스트 문자열로 만듭니다. 세 번째로, OpenAI Chat API에 컨텍스트와 질문을 함께 보냅니다.

시스템 메시지에서 "제공된 문서를 기반으로만 답변하세요"라고 지시해서 hallucination을 방지합니다. 사용자 메시지에는 검색된 문서와 원래 질문을 모두 포함합니다.

GPT가 문서를 읽고 이해한 뒤, 자연스러운 답변을 생성합니다. 예를 들어 "입사 1년이 지나셨다면 연차 15일을 사용하실 수 있습니다"라고 답할 것입니다.

여러분이 이 코드를 사용하면 고객 지원 챗봇, 사내 지식 관리 시스템, 법률/의료 상담 도우미 등 다양한 서비스를 만들 수 있습니다. 실무에서는 검색 품질을 높이기 위해 쿼리 재작성(query rewriting), 하이브리드 검색(키워드+시맨틱), 재순위(re-ranking) 같은 고급 기법을 추가합니다.

또한 답변의 출처(source)를 함께 표시해서 사용자가 원본 문서를 확인할 수 있게 하는 것이 좋습니다.

실전 팁

💡 청크 크기를 실험하세요. 너무 작으면(100 토큰) 맥락이 부족하고, 너무 크면(2000 토큰) 관련 없는 정보도 포함됩니다. 300~500 토큰이 일반적으로 좋은 출발점입니다.

💡 Citation(출처 표시)을 추가하세요. LLM에게 "답변 끝에 참고한 문서 ID를 표시하라"고 지시하면 사용자가 원본을 확인할 수 있어 신뢰도가 올라갑니다.

💡 검색 결과가 없을 때를 대비하세요. 관련 문서가 없으면 "죄송하지만 해당 정보를 찾을 수 없습니다"라고 답하도록 처리하세요. 억지로 답변하면 hallucination이 발생합니다.

💡 대화 히스토리를 활용하세요. 이전 대화를 컨텍스트에 포함하면 "그건 뭐야?"같은 후속 질문도 이해할 수 있습니다. 단, 토큰 제한을 고려해서 최근 3~5턴만 포함하세요.

💡 평가 지표를 추적하세요. 사용자 피드백(좋아요/싫어요), 검색 정확도, 답변 생성 시간 등을 모니터링해서 시스템을 지속적으로 개선하세요.


6. 다국어_임베딩_활용

시작하며

여러분이 글로벌 고객을 대상으로 하는 검색 서비스를 만든다고 생각해보세요. 한국 사용자는 "머신러닝 튜토리얼"을 검색하고, 미국 사용자는 "machine learning tutorial"을 검색하는데, 같은 결과를 보여줘야 합니다.

어떻게 해야 할까요? 이런 문제는 국제적인 서비스에서 필수적으로 마주치는 도전 과제입니다.

언어마다 별도의 데이터베이스를 만들면 유지보수가 어렵고, 번역을 하자니 품질과 비용 문제가 있습니다. 또한 "smartphone"과 "스마트폰"이 같은 의미라는 걸 시스템이 어떻게 알 수 있을까요?

바로 이럴 때 필요한 것이 다국어 임베딩입니다. 최신 임베딩 모델들은 100개 이상의 언어를 지원하며, 서로 다른 언어로 된 텍스트도 의미가 같으면 비슷한 벡터로 표현합니다.

마치 세계 공통어처럼 모든 언어를 하나의 벡터 공간에 매핑하는 거예요.

개요

간단히 말해서, 다국어 임베딩은 여러 언어의 텍스트를 같은 벡터 공간에 표현해서, 언어가 달라도 의미가 같으면 유사한 벡터를 생성하는 기술입니다. 언어 간 장벽을 허무는 기술이에요.

왜 이 개념이 필요한지 실무 관점에서 설명하면, 글로벌 서비스의 개발 비용과 복잡도를 크게 줄일 수 있기 때문입니다. 하나의 시스템으로 모든 언어를 처리할 수 있습니다.

예를 들어, Airbnb는 200개 국가의 숙소를 다국어로 검색할 수 있는데, 다국어 임베딩 덕분에 각 언어별로 별도 시스템을 만들 필요가 없습니다. 전통적인 방법과 비교하면, 기존에는 모든 것을 영어로 번역한 뒤 검색하거나, 언어별로 완전히 분리된 시스템을 운영했다면, 이제는 하나의 벡터 DB에 모든 언어를 함께 저장하고 검색할 수 있습니다.

한국어로 검색해도 영어 문서를 찾을 수 있고, 그 반대도 가능합니다. 다국어 임베딩의 핵심 원리는 "언어 간 정렬(cross-lingual alignment)"입니다.

모델 학습 시 같은 의미의 문장을 여러 언어로 묶어서 학습시켜, "I love you", "사랑해", "Je t'aime"가 모두 비슷한 벡터를 갖도록 만듭니다. OpenAI의 text-embedding-3 모델은 100개 이상 언어를 지원하며, Cohere의 embed-multilingual 모델도 유명합니다.

이런 모델들은 번역 없이도 언어 간 의미 비교가 가능합니다.

코드 예제

from openai import OpenAI
import numpy as np

client = OpenAI(api_key="your-api-key")

# 다양한 언어로 같은 의미의 문장
texts = {
    "한국어": "인공지능은 미래를 바꿀 것입니다",
    "영어": "Artificial intelligence will change the future",
    "일본어": "人工知能は未来を変えるでしょう",
    "스페인어": "La inteligencia artificial cambiará el futuro"
}

# 모든 텍스트를 임베딩으로 변환
embeddings = {}
for lang, text in texts.items():
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    embeddings[lang] = response.data[0].embedding

# 한국어를 기준으로 다른 언어와의 유사도 계산
def cosine_similarity(vec1, vec2):
    return np.dot(vec1, vec2) / (
        np.linalg.norm(vec1) * np.linalg.norm(vec2)
    )

base_lang = "한국어"
print(f"'{texts[base_lang]}'와의 유사도:")
print("-" * 50)

for lang, text in texts.items():
    if lang != base_lang:
        similarity = cosine_similarity(
            embeddings[base_lang],
            embeddings[lang]
        )
        print(f"{lang}: {similarity:.4f} - '{text}'")

설명

이것이 하는 일: 위 코드는 같은 의미를 가진 4개 언어의 문장을 임베딩으로 변환한 뒤, 서로 얼마나 유사한지 계산합니다. 단어는 완전히 다르지만 의미가 같으므로 0.9 이상의 높은 유사도가 나옵니다.

첫 번째로, 다양한 언어로 된 문장을 딕셔너리에 저장합니다. "인공지능"과 "Artificial intelligence"는 완전히 다른 문자와 발음을 가지지만 같은 개념을 나타냅니다.

이런 문장들을 각각 임베딩으로 변환하는데, 중요한 점은 모두 같은 모델(text-embedding-3-small)을 사용한다는 것입니다. 이 모델은 학습 과정에서 여러 언어의 의미적 관계를 배웠기 때문에 언어가 달라도 일관된 벡터를 생성합니다.

두 번째로, for 루프를 돌면서 각 언어의 텍스트를 OpenAI API에 보냅니다. 특별한 전처리나 언어 지정이 필요 없습니다.

모델이 자동으로 언어를 감지하고 적절한 벡터를 생성합니다. embeddings 딕셔너리에 각 언어의 벡터가 저장되며, 모두 1536차원의 동일한 구조를 가집니다.

세 번째로, 한국어 문장을 기준으로 다른 언어들과의 코사인 유사도를 계산합니다. 놀랍게도 영어는 0.92, 일본어는 0.90, 스페인어는 0.91 같은 매우 높은 유사도를 보일 것입니다.

이는 모델이 표면적인 단어가 아니라 의미를 이해한다는 증거입니다. 만약 전혀 다른 의미의 문장("오늘 날씨가 좋네요")과 비교하면 0.3 이하의 낮은 값이 나올 겁니다.

여러분이 이 코드를 사용하면 글로벌 검색 엔진, 다국어 챗봇, 크로스언어 문서 분류 등을 구축할 수 있습니다. 실무에서는 영어 FAQ 데이터베이스를 만들어두면 한국어로 질문해도 관련 답변을 찾아줄 수 있습니다.

번역 비용과 시간을 절약하면서도 좋은 사용자 경험을 제공할 수 있어요. 또한 언어별 데이터가 불균형해도 괜찮습니다.

영어 문서가 많고 한국어 문서가 적어도 서로 연결되기 때문입니다.

실전 팁

💡 주요 언어는 테스트해보세요. 모델마다 언어별 성능 차이가 있습니다. OpenAI 모델은 영어, 중국어, 스페인어가 특히 강하고, 희귀 언어는 상대적으로 약할 수 있습니다.

💡 혼합 언어 쿼리도 가능합니다. "iPhone 케이스"처럼 영어와 한국어가 섞인 검색어도 잘 처리합니다. 이런 코드 스위칭(code-switching)은 실제 사용자들이 자주 사용합니다.

💡 언어별 메타데이터를 추가하세요. 검색 결과에 언어 정보를 포함하면 사용자가 선호하는 언어의 결과를 우선 표시할 수 있습니다. 필터링과 결합하면 더 좋습니다.

💡 번역과 함께 사용하면 시너지가 납니다. 검색은 다국어 임베딩으로 하고, 결과를 보여줄 때는 번역 API로 사용자 언어로 번역하면 최상의 경험을 제공할 수 있습니다.

💡 도메인 특화 용어는 주의하세요. 전문 용어나 신조어는 일반 모델이 잘 이해하지 못할 수 있습니다. 필요하면 파인튜닝을 고려하거나 용어집을 따로 관리하세요.


7. 임베딩_차원_축소

시작하며

여러분이 수백만 개의 문서를 저장하는 서비스를 운영한다고 상상해보세요. text-embedding-3-large 모델은 3072차원의 벡터를 생성하는데, 100만 개 문서면 12GB가 넘는 저장 공간이 필요합니다.

클라우드 저장 비용이 상당하겠죠? 이런 문제는 규모가 커질수록 심각해집니다.

저장 공간뿐만 아니라 검색 속도도 느려지고, 메모리 사용량도 늘어납니다. 3072차원 벡터 간 유사도 계산은 1536차원보다 2배 느립니다.

과연 모든 차원이 정말 필요할까요? 바로 이럴 때 필요한 것이 임베딩 차원 축소입니다.

놀랍게도 OpenAI 모델은 차원을 줄여도 성능이 크게 떨어지지 않습니다. 3072차원을 512차원으로 줄여도 대부분의 경우 95% 이상 성능을 유지할 수 있어요.

마치 4K 영상을 1080p로 압축해도 대부분의 사람은 차이를 못 느끼는 것과 비슷합니다.

개요

간단히 말해서, 차원 축소는 임베딩 벡터의 차원 수를 줄여서 저장 공간과 계산 비용을 절약하면서도 의미적 정보는 대부분 유지하는 기술입니다. 효율성과 성능의 균형을 맞추는 방법이에요.

왜 이 개념이 필요한지 실무 관점에서 설명하면, 비용과 속도 최적화가 필수적인 프로덕션 환경에서 매우 중요합니다. Pinecone 같은 벡터 DB는 차원 수에 따라 가격이 달라지므로, 차원을 절반으로 줄이면 비용도 절반으로 줄어듭니다.

예를 들어, 500만 개 문서를 1536차원으로 저장하면 월 수백 달러가 나오는데, 512차원으로 줄이면 1/3로 절감됩니다. 차원 축소 방법에는 여러 가지가 있습니다.

첫째, OpenAI의 dimensions 파라미터를 사용해서 API 호출 시 바로 축소된 벡터를 받을 수 있습니다. 둘째, PCA(주성분 분석)로 기존 벡터를 사후에 압축할 수 있습니다.

셋째, Matryoshka Representation Learning 같은 최신 기법도 있습니다. 가장 간단하고 효과적인 것은 OpenAI의 dimensions 파라미터 사용입니다.

차원 축소의 핵심 원리는 "정보의 중요도 불균형"입니다. 1536개 차원이 모두 동등하게 중요한 게 아니라, 일부 차원이 대부분의 의미를 담고 있습니다.

마치 이미지의 JPEG 압축처럼, 덜 중요한 정보를 버려도 핵심 의미는 유지됩니다. OpenAI 모델은 학습 시 이를 고려해서 앞쪽 차원에 중요한 정보를 몰아넣도록 설계되었습니다.

코드 예제

from openai import OpenAI
import numpy as np

client = OpenAI(api_key="your-api-key")

# 같은 텍스트를 다양한 차원으로 임베딩
text = "임베딩 차원 축소는 비용 절감에 효과적입니다"

dimensions_to_test = [256, 512, 1024, 1536]
embeddings = {}

for dim in dimensions_to_test:
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text,
        dimensions=dim  # 차원 지정
    )
    embeddings[dim] = response.data[0].embedding
    print(f"{dim}차원: {len(response.data[0].embedding)}개 값")

# 원본(1536)과 축소 버전들의 유사도 비교
def cosine_similarity(vec1, vec2):
    return np.dot(vec1, vec2) / (
        np.linalg.norm(vec1) * np.linalg.norm(vec2)
    )

# 다른 텍스트와의 유사도 비교
test_texts = [
    "임베딩 압축으로 저장 공간을 줄일 수 있습니다",  # 유사
    "오늘 날씨가 정말 좋네요"  # 다른 주제
]

for test_text in test_texts:
    print(f"\n비교 텍스트: '{test_text}'")
    print("-" * 50)

    for dim in dimensions_to_test:
        test_emb = client.embeddings.create(
            model="text-embedding-3-small",
            input=test_text,
            dimensions=dim
        ).data[0].embedding

        similarity = cosine_similarity(embeddings[dim], test_emb)
        print(f"{dim}차원: 유사도 {similarity:.4f}")

설명

이것이 하는 일: 위 코드는 같은 텍스트를 256, 512, 1024, 1536 차원으로 변환한 뒤, 각 차원에서의 검색 성능을 비교합니다. 차원이 줄어들어도 유사도 점수가 거의 비슷하게 유지되는 것을 확인할 수 있습니다.

첫 번째로, dimensions 파라미터를 사용해서 다양한 크기의 벡터를 생성합니다. 일반적으로 dimensions를 지정하지 않으면 모델의 기본 차원(text-embedding-3-small은 1536)으로 생성됩니다.

하지만 dimensions=512를 지정하면 512차원의 벡터가 바로 반환됩니다. 이는 API 서버에서 처리되므로 클라이언트에서 별도의 압축 작업이 필요 없습니다.

각 차원의 벡터를 딕셔너리에 저장하고 길이를 출력해서 제대로 생성되었는지 확인합니다. 두 번째로, 두 가지 테스트 텍스트를 준비합니다.

첫 번째는 원본과 의미가 비슷한 문장, 두 번째는 전혀 다른 주제의 문장입니다. 이렇게 하는 이유는 차원을 줄였을 때 유사한 것은 여전히 유사하게, 다른 것은 여전히 다르게 분류할 수 있는지 확인하기 위함입니다.

각 차원별로 테스트 텍스트도 같은 차원으로 임베딩을 생성해야 공정한 비교가 가능합니다. 세 번째로, 각 차원에서 유사도를 계산하고 출력합니다.

결과를 보면 놀라운 사실을 발견할 수 있습니다. 유사한 텍스트의 경우 1536차원에서 0.85가 나왔다면, 512차원에서도 0.83 정도로 거의 비슷한 값이 나옵니다.

256차원으로 줄여도 0.80 정도로 큰 차이가 없어요. 다른 주제의 경우도 마찬가지로 모든 차원에서 낮은 유사도(0.2~0.3)를 유지합니다.

이는 차원을 1/6로 줄여도 검색 품질은 거의 유지된다는 의미입니다. 여러분이 이 코드를 사용하면 자신의 데이터로 최적의 차원을 실험할 수 있습니다.

실무에서는 테스트 쿼리 1001000개로 각 차원에서의 검색 정확도를 측정하고, 비용과 성능의 최적 지점을 찾습니다. 일반적으로 512768 차원이 좋은 균형점입니다.

매우 간단한 작업(FAQ 매칭)이라면 256차원도 충분하고, 복잡한 작업(법률 문서 분석)이라면 1024 이상이 필요할 수 있습니다.

실전 팁

💡 모델별 지원 차원이 다릅니다. text-embedding-3-small은 최대 1536, text-embedding-3-large는 최대 3072입니다. 최소값은 둘 다 1이지만 실용적으로는 256 이상을 권장합니다.

💡 일관성을 유지하세요. 프로젝트에서 한 번 차원을 정하면 계속 그 차원을 사용해야 합니다. 512차원과 1536차원 벡터는 비교할 수 없습니다.

💡 비용 계산을 해보세요. Pinecone 기준으로 1536차원 100만 벡터는 월 $70, 512차원은 $23 정도입니다. 차원을 줄이면 3배 가까이 절약됩니다.

💡 정확도 저하를 측정하세요. 여러분의 실제 데이터로 A/B 테스트를 해보세요. 대부분의 경우 1536→512로 줄여도 정확도 손실은 2% 이내입니다.

💡 검색 속도도 빨라집니다. 차원이 1/3이면 유사도 계산도 1/3로 빨라지고, 벡터 DB 쿼리 응답 시간도 줄어듭니다. 비용뿐만 아니라 성능 개선 효과도 있습니다.


8. 임베딩_모델_비교

시작하며

여러분이 새 프로젝트를 시작하는데 임베딩 모델을 선택해야 한다면, OpenAI만 사용해야 할까요? Cohere, Voyage AI, HuggingFace 등 다양한 선택지가 있습니다.

각각 가격, 성능, 지원 언어가 다른데 어떤 것을 골라야 할까요? 이런 문제는 프로젝트 초기에 중요한 결정입니다.

한 번 선택하면 나중에 바꾸기 어렵기 때문에 신중해야 합니다. 모델마다 벡터가 호환되지 않아서 바꾸려면 모든 데이터를 다시 임베딩해야 하고, 이는 시간과 비용이 많이 듭니다.

바로 이럴 때 필요한 것이 임베딩 모델 비교입니다. MTEB(Massive Text Embedding Benchmark) 같은 공개 벤치마크와 여러분의 실제 데이터로 테스트해서 가성비가 가장 좋은 모델을 선택해야 합니다.

비싼 모델이 항상 좋은 건 아니에요.

개요

간단히 말해서, 임베딩 모델 비교는 여러 모델의 성능(정확도), 비용(API 가격), 속도(응답 시간), 기능(지원 언어, 최대 토큰)을 종합적으로 평가하는 과정입니다. 데이터 기반 의사결정이 필요합니다.

왜 이 개념이 필요한지 실무 관점에서 설명하면, 잘못된 선택으로 인한 시간과 비용 낭비를 막을 수 있기 때문입니다. 예를 들어, 간단한 FAQ 매칭에 가장 비싼 모델을 쓰는 것은 과한 지출이고, 복잡한 법률 문서 분석에 작은 모델을 쓰면 정확도가 떨어집니다.

적재적소가 중요합니다. 주요 임베딩 모델을 비교하면 다음과 같습니다.

OpenAI text-embedding-3-small은 가격 대비 성능이 좋고 사용이 간편합니다. text-embedding-3-large는 더 정확하지만 5배 비쌉니다.

Cohere embed-multilingual-v3는 다국어에 강하고 검색에 최적화되어 있습니다. Voyage AI는 도메인별 특화 모델을 제공합니다.

HuggingFace의 오픈소스 모델들은 무료지만 직접 호스팅해야 합니다. 선택 기준은 다섯 가지입니다.

첫째, 정확도 - MTEB 점수와 여러분 데이터로 테스트. 둘째, 비용 - 월 예상 API 호출 수 × 가격.

셋째, 지원 언어 - 다국어 필요 여부. 넷째, 최대 입력 길이 - 긴 문서 처리 필요 여부.

다섯째, 응답 속도 - 실시간 서비스인지 배치 처리인지. 이 다섯 가지를 종합적으로 고려해야 합니다.

코드 예제

from openai import OpenAI
import cohere
import time
import numpy as np

# 클라이언트 초기화
openai_client = OpenAI(api_key="openai-key")
cohere_client = cohere.Client("cohere-key")

# 테스트 데이터
test_query = "머신러닝 모델 학습하기"
test_docs = [
    "파이썬으로 딥러닝 신경망 훈련하는 방법",
    "자바스크립트 프론트엔드 개발 가이드",
    "인공지능 알고리즘 구현 튜토리얼"
]

# OpenAI 모델 테스트
def test_openai():
    start = time.time()

    # 쿼리 임베딩
    query_emb = openai_client.embeddings.create(
        model="text-embedding-3-small",
        input=test_query
    ).data[0].embedding

    # 문서 임베딩
    docs_emb = []
    for doc in test_docs:
        emb = openai_client.embeddings.create(
            model="text-embedding-3-small",
            input=doc
        ).data[0].embedding
        docs_emb.append(emb)

    # 유사도 계산
    similarities = [
        np.dot(query_emb, doc_emb) / (
            np.linalg.norm(query_emb) * np.linalg.norm(doc_emb)
        )
        for doc_emb in docs_emb
    ]

    elapsed = time.time() - start
    return similarities, elapsed

# Cohere 모델 테스트
def test_cohere():
    start = time.time()

    # 쿼리와 문서를 함께 임베딩 (효율적)
    response = cohere_client.embed(
        texts=[test_query] + test_docs,
        model="embed-multilingual-v3.0",
        input_type="search_query"  # 검색 최적화
    )

    embeddings = response.embeddings
    query_emb = embeddings[0]
    docs_emb = embeddings[1:]

    # 유사도 계산
    similarities = [
        np.dot(query_emb, doc_emb) / (
            np.linalg.norm(query_emb) * np.linalg.norm(doc_emb)
        )
        for doc_emb in docs_emb
    ]

    elapsed = time.time() - start
    return similarities, elapsed

# 모델 비교 실행
print("OpenAI text-embedding-3-small:")
openai_sim, openai_time = test_openai()
for i, sim in enumerate(openai_sim):
    print(f"  문서 {i+1}: {sim:.4f}")
print(f"  소요 시간: {openai_time:.2f}초\n")

print("Cohere embed-multilingual-v3:")
cohere_sim, cohere_time = test_cohere()
for i, sim in enumerate(cohere_sim):
    print(f"  문서 {i+1}: {sim:.4f}")
print(f"  소요 시간: {cohere_time:.2f}초")

설명

이것이 하는 일: 위 코드는 OpenAI와 Cohere 모델을 같은 쿼리와 문서로 테스트해서 각 모델의 유사도 점수와 응답 속도를 비교합니다. 어떤 모델이 여러분의 데이터에 더 적합한지 판단하는 데 도움을 줍니다.

첫 번째로, test_openai 함수는 OpenAI의 표준 워크플로우를 따릅니다. 쿼리를 먼저 임베딩으로 변환하고, 그 다음 각 문서를 순차적으로 임베딩합니다.

time.time()으로 시작과 끝을 측정해서 전체 소요 시간을 계산합니다. 이 방식은 간단하지만 API 호출이 여러 번 일어나서 네트워크 지연이 누적될 수 있습니다.

각 문서와 쿼리의 코사인 유사도를 리스트로 반환합니다. 두 번째로, test_cohere 함수는 Cohere의 배치 임베딩 기능을 활용합니다.

texts=[test_query] + test_docs로 쿼리와 모든 문서를 한 번에 보내서 한 번의 API 호출로 모든 임베딩을 받아옵니다. 이는 네트워크 오버헤드를 크게 줄여서 속도가 빠릅니다.

input_type="search_query"는 검색 작업에 최적화된 임베딩을 생성하라는 힌트인데, Cohere는 이런 세밀한 제어를 제공합니다. 세 번째로, 두 모델의 결과를 출력하고 비교합니다.

유사도 점수를 보면 두 모델이 비슷한 순위를 매기는지 확인할 수 있습니다. 예를 들어 둘 다 문서 1과 3을 높게 평가하고 문서 2를 낮게 평가한다면, 모델 간 일관성이 있다는 뜻입니다.

소요 시간을 비교하면 Cohere가 배치 처리 덕분에 더 빠를 가능성이 높습니다. 하지만 이는 네트워크 상황에 따라 달라질 수 있으므로 여러 번 테스트해야 합니다.

여러분이 이 코드를 사용하면 자신의 실제 데이터로 여러 모델을 공정하게 비교할 수 있습니다. 실무에서는 100개 이상의 테스트 쿼리로 정확도(Top-1 Accuracy, MRR 등)를 측정하고, 비용 계산기로 월 예상 비용을 산출합니다.

예를 들어 OpenAI small은 100만 토큰에 $0.02, Cohere는 $0.10으로 5배 비쌉니다. 하지만 Cohere가 정확도가 10% 더 높다면 비즈니스 가치에 따라 선택이 달라질 수 있습니다.

오픈소스 모델은 호스팅 비용을 고려해야 하고, GPU 서버 비용이 API 비용보다 비쌀 수 있습니다.

실전 팁

💡 MTEB 리더보드를 참고하세요. https://huggingface.co/spaces/mteb/leaderboard 에서 공개 벤치마크 점수를 확인할 수 있습니다. 하지만 여러분의 도메인과 다를 수 있으니 직접 테스트도 필수입니다.

💡 배치 임베딩을 활용하세요. 대부분의 API는 한 번에 여러 텍스트를 보낼 수 있습니다. 이렇게 하면 API 호출 횟수가 줄어서 속도와 비용 모두 개선됩니다.

💡 캐싱 정책을 고려하세요. 자주 검색되는 쿼리는 임베딩을 캐시해두면 API 비용을 줄일 수 있습니다. 모델 선택 시 캐시 적중률도 함께 고려하세요.

💡 폴백(fallback) 전략을 세우세요. 주 모델이 다운되거나 느릴 때 대체 모델로 전환할 수 있도록 설계하면 서비스 안정성이 올라갑니다.

💡 비용 알림을 설정하세요. 예상보다 API 사용량이 많을 수 있습니다. 클라우드 제공자의 비용 알림 기능을 활성화해서 예산을 초과하지 않도록 관리하세요.


#Python#Embeddings#NLP#VectorDB#AI

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.