LLM 서비스를 개발하면 Embedding 이라는 키워드를 만나게 된다. 자연어 문장을 이해하고 문맥간의 관계를 위해서 필요한 과정인데, 간단하게 생각하면 사람의 말, 자연어를 컴퓨터가 이해할 수 있는 디지털, 숫자 형식 Vector 로 변환하는 것이다.
이 글에서는 Vector, Embedding 에 대한 개념과 자연어를 Embedding 하는 예제, 그리고 Embedding 한 Vector 와 Vector DB 를 사용하여 데이터 저장과 검색하는 것까지 정리해보겠다.
1. Vector, Embedding 개념
- Vector
Vetor 는 수학에서 크기와 방향을 동시에 가지는 물리량을 의미한다. 이는 곧 n 차원 공간의 좌표로 이해할 수 있는데, 아래 예시와 같이 특정 차원의 숫자 배열로 표현된다.
ex) [0.12, -0.91, 0.33, ...]
벡터를 비교하여 서로 간의 거리를 계산할 수 있는데, LLM 에서는 거리 계산을 통해 유사도를 구할 때 사용한다. 유사도를 계산할 때는 주로 cosine similarity 방식으로 계산하는데, 이 방법은 원점을 기준으로 두 벡터의 방향, 각도를 계산하여 두 벡터의 의미가 얼마나 유사한지 계산하는 방식이다.
- Embedding
문장 또는 단어와 같이 자연어의 유사도를 비교하기 위해서는 먼저 벡터로 변환해야 한다. 이렇게 자연어를 벡터로 변환하여 그 의미를 수치화하는 기술이 임베딩이다.
이때 단순히 숫자 배열로 변환하는 것이 아니라 문자가 가지고 있는 정보, 의미 관계, 문맥적 특성 등을 유지하여 수치화한다. 한마디로 자연어의 의미 관계를 기반으로 구성된 다차원 공간의 좌표로 변환되는 것이다.
이러한 의미의 보존을 위해서 특정한 알고리즘이나 미리 문장, 단어 등 언어를 이해할 수 있도록 학습된 임베딩 모델을 사용하여 변환을 수행한다.
2. OpenAI Embedding API practice
OpenAI SDK 의 Embedding 기능을 사용하여 문장의 벡터 임베딩을 구해보겠다.
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
input="Your text string goes here",
model="text-embedding-3-small"
)
print(response)
Embedding API 를 사용하여 `Your text string goes here` 라는 문장을 임베딩 해보았다. 모델은 `text-embedding-3-small` 을 사용했다.
CreateEmbeddingResponse(
data=[
Embedding(
embedding=[0.005130767822265625, 0.0171966552734375, -0.0187225341796875, ... ],
index=0,
object='embedding'
)
],
model='text-embedding-3-small',
object='list',
usage=Usage(
prompt_tokens=5,
total_tokens=5
)
)
코드를 실행하면 위와 같은 결과가 출력된다. Embedding create API 의 response 는 CreateEmbeddingResponse 형식으로 임베딩 벡터를 포함하고 있다.
3. Similarity
임베딩 벡터들을 코사인 유사도 방식을 사용하여 유사도를 계산해보겠다.
먼저 배열간의 연산을 위해 numpy 패키지를 설치한다.
pip install numpy
numpy 를 사용하여 두 벡터 간의 코사인 유사도를 계산하는 코드를 작성했다.
import numpy as np
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
이 함수를 사용하여 여러 단어 들의 유사도를 구해보겠다. apple, banana, yellow, iphone 4가지 단어의 벡터를 구하고 코사인 유사도로 각 단어 간의 관련성을 구해보겠다.
text = [
"apple",
"banana",
"yellow",
"iphone",
]
client = OpenAI()
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
embeddings = [d.embedding for d in response.data]
for i, j in combinations(range(len(text)), 2):
similarity = cosine_similarity(embeddings[i], embeddings[j])
print(f"{text[i]}:{text[j]} > similarity={similarity}")
코드를 실행하면 아래와 같이 결과가 출력된다. apple 을 아이폰 회사 애플로 인지해서인지 apple 과 iphone 이 가장 높은 유사도를 보였고, 그 다음 같은 과일인 apple 과 banana 가 높은 유사도를 보인다.
apple:banana > similarity=0.46358548480432327
apple:yellow > similarity=0.3605400337614089
apple:iphone > similarity=0.5849859334386475
banana:yellow > similarity=0.28280421177123766
banana:iphone > similarity=0.35388335994848097
yellow:iphone > similarity=0.30231458709237263
4. Vector DB
실제 서비스에서 여러 문장들을 저장하고 유사도 기반으로 검색하기 위해서는 정말 많은 단어와 문장들을 저장해야한다. 그런데 일반 DB 에 문자열을 저장하게 되면 문장의 의미를 보존한채 저장되는 것이 아니라 단순 문자로 저장되게 된다. 이를 방지하기 위해서는 벡터 임베딩을 저장해야 하는데, 이때 사용되는 것이 Vector DB 이다.
Vector DB 는 기존 DB 와 달리 비정형 데이터를 임베딩한 임베딩 벡터를 저장한다. 이를 통해서 각 데이터의 의미를 살리고 쿼리를 할 때에도 의미 기반으로 관련성을 조회하여 검색할 수 있다.
마지막 장에서는 Vector DB 에 데이터를 저장하고 쿼리하는 에제를 작성할 것이다. 예제는 chroma db 를 사용하여 작성한다.
- chroma 패키지 설치
먼저 python 에서 chroma 라이브러리를 사용하기 위해 chromadb 패키지를 설치한다.
pip install chromadb
- chroma 클라이언트와 컬렉션 생성
chroma db 를 사용하기 위해서 클라이언트 객체를 먼저 생성한다. 이 글에서는 실제 DB 를 사용하지 않고 테스트를 위해 in-memory 에 데이터를 저장하려 한다. chroma db 에서는 in-memory 클라이언트로 EphemeralClient 클래스를 제공해주는데, 이것을 사용하여 클라이언트 객체를 생성했다.
그 다음 데이터를 저장할 컬렉션을 생성해준다. 컬렉션인 RDB 의 테이블이라고 생각하면 된다. my_collection 이라는 컬렉션을 생성해주는데, 이 때 해당 컬렉션에서 사용할 embedding function 을 정의해줘야 한다.
embeddibng fuction 은 저장하는 문자나 검색에 사용하는 쿼리를 임베딩할 때 사용하는 함수이다. 이를 통해서 해당 컬렉션에 저장되는 문자는 모두 같은 방식으로 임베딩된다.
이 글에서는 OpenAIEmbeddingFunction 을 사용했는데, 이 객체는 내부적오르 OPENAI_API_KEY 환경변수를 로드하여 사용하기 때문에 dotenv 의 load_dotenv 를 호출하여 API KEY 가 저장된 .env 파일의 환경변수를 사용하도록 했다.
from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction
from dotenv import load_dotenv
import chromadb
load_dotenv()
EMBEDDING_MODEL = "text-embedding-3-small"
embedding_function = OpenAIEmbeddingFunction(model_name=EMBEDDING_MODEL)
# in-memory client
client = chromadb.EphemeralClient()
collection = client.create_collection(
name="my_collection",
embedding_function=embedding_function
)
- Document 저장
이제 DB 에 데이터를 저장해보겠다. ChatGPT 로 생성한 문장들을 my_collection 에 저장한다.
이때 각 문장들은 embedding function 을 통해 임베딩되어 저장된다.
# add documents
documents = [
"FastAPI is a modern Python web framework for building APIs with high performance.",
"Kubernetes is used to orchestrate containerized applications at scale.",
"Grilling steak requires high heat and proper seasoning for best flavor.",
"A balanced diet includes vegetables, proteins, and healthy fats.",
"Squats and deadlifts are essential exercises for building lower body strength."
]
ids = [f"doc_{i}" for i in range(len(documents))]
collection.add(
ids=ids,
documents=documents
)
- Document 검색
이제 데이터가 저장된 컬렉션을 대상으로 쿼리를 실행해보겠다. 4가지 서로 다른 질문들을 쿼리하고, 답변으로 문장을 2개씩 출력하도록 헀다. 결과를 확인해서 출력된 답변들이 문맥에 맞는지 보겠다.
# query
queries = [
"How to build scalable backend systems?",
"What should I eat to stay healthy?",
"Best way to cook meat with fire",
"Exercises for strong legs"
]
for query in queries:
result = collection.query(
query_texts=[query],
n_results=2 # query 마다 2개의 답변을 반환
)
print(f"[Query] {query}")
for i, document in enumerate(result["documents"][0]):
print(f" -> Result {i + 1}: {document}")
코드를 수행하면 아래와 같이 결과가 출력된다.
scalable backend system 에 대한 질문에는 Kubernetes 와 FastAPI 같은 백엔드 기술들의 문장이 출력되었다. 건강한 식단 관련해서도 균형잡힌 식단에 대한 답변이 출력되었다. 고기를 잘 굽는 방법에 대해서는 스테이크 굽기에 대한 문장이, 튼튼한 하체를 위한 운동에 대한 질문에는 스쿼트와 데드리프트에 대한 답변이 출력되었다. 이를 통해서 단순 단어 비교가 아닌 문맥, 의미를 기반으로 답변이 출력된 것을 확인할 수 있다.
[Query] How to build scalable backend systems?
-> Result 1: Kubernetes is used to orchestrate containerized applications at scale.
-> Result 2: FastAPI is a modern Python web framework for building APIs with high performance.
[Query] What should I eat to stay healthy?
-> Result 1: A balanced diet includes vegetables, proteins, and healthy fats.
-> Result 2: Squats and deadlifts are essential exercises for building lower body strength.
[Query] Best way to cook meat with fire
-> Result 1: Grilling steak requires high heat and proper seasoning for best flavor.
-> Result 2: A balanced diet includes vegetables, proteins, and healthy fats.
[Query] Exercises for strong legs
-> Result 1: Squats and deadlifts are essential exercises for building lower body strength.
-> Result 2: A balanced diet includes vegetables, proteins, and healthy fats.
[References]
- https://developers.openai.com/api/docs/guides/embeddings
- https://docs.trychroma.com/docs/embeddings/embedding-functions
'AI' 카테고리의 다른 글
| [LLM] LangChain (0) | 2026.03.28 |
|---|---|
| [LLM] RAG (Retrieval-Augmented Generation) (0) | 2026.03.25 |
| [LLM] OpenAI SDK Quickstart (0) | 2026.03.09 |
| [LLM] LLM Provider 와 OpenAI API (0) | 2026.02.11 |