회사 내부 문서를 기반으로 질문에 답하는 챗봇을 만들어야 했을 때 처음으로 RAG라는 개념을 접했습니다. ChatGPT에 그냥 물어보면 학습 데이터에 없는 내용은 모르거나 틀린 답을 내놓았습니다. 그렇다고 수천 페이지짜리 문서를 통째로 프롬프트에 넣을 수도 없었습니다. 그래서 알아보다 보니 RAG(Retrieval-Augmented Generation)는 이 문제를 해결하는 방법이었습니다. 질문과 관련된 문서만 골라서 LLM에 넘겨주는 구조입니다.
RAG가 어떤 수학적 원리로 관련 문서를 찾아내는지, 벡터 데이터베이스가 왜 필요한지, 그리고 유사도 검색이 실제로 어떻게 작동하는지 확인해보겠습니다.

LLM의 한계 — 왜 최신 정보를 모르는가
ChatGPT나 Claude 같은 LLM은 특정 시점까지의 데이터로 학습됩니다. 학습 이후에 생긴 일은 모릅니다. 2024년 선거 결과, 어제 발표된 신제품, 회사 내부 문서 — 이런 정보는 LLM이 답할 수 없습니다. 그렇다고 새 정보가 생길 때마다 LLM을 다시 학습시키는 건 비용이 너무 큽니다. GPT-4 한 번 학습에 수십억 원이 들기 때문입니다.
LLM의 구조적 한계:
학습 데이터 컷오프: 2024년 4월 (예시)
→ 그 이후 정보는 모름
해결 방법 비교:
① Fine-tuning: 새 데이터로 재학습
비용: 매우 높음 / 시간: 수일~수주
문제: 새 정보 생길 때마다 반복해야 함
② 프롬프트에 직접 넣기 (In-context Learning)
비용: 낮음
문제: 컨텍스트 윈도우 한계 (수천~수만 토큰)
문서가 많으면 전부 넣을 수 없음
③ RAG (Retrieval-Augmented Generation)
비용: 낮음 / 실시간 업데이트 가능
방법: 질문과 관련된 문서만 골라서 LLM에 전달
→ 컨텍스트 윈도우 효율적 사용
→ 최신 정보 반영 가능
RAG는 LLM을 바꾸지 않고 외부 지식을 주입하는 방법입니다. 도서관 사서에게 비유하면 이렇습니다. 질문이 들어오면 사서(검색 시스템)가 관련 책(문서)을 찾아오고, LLM은 그 책을 읽고 답변을 만듭니다. LLM이 모든 걸 외울 필요 없이, 필요할 때 찾아보는 구조입니다.
RAG의 구조 — 검색과 생성을 연결하는 방법
RAG는 크게 두 단계로 나뉩니다. 문서를 미리 처리해서 저장하는 인덱싱 단계와, 질문이 들어왔을 때 관련 문서를 찾아 답변을 생성하는 쿼리 단계입니다. 처음에 이 구조를 봤을 때 검색 엔진이랑 뭐가 다른가 싶었는데, 핵심 차이는 키워드 매칭이 아니라 의미 기반 검색이라는 점이었습니다.
RAG 전체 파이프라인:
── 인덱싱 단계 (오프라인, 미리 처리) ──────────────
문서 수집 (PDF, Word, 웹페이지 등)
↓
청킹 (Chunking): 문서를 적절한 크기로 분할
↓
임베딩 모델: 각 청크 → 벡터 변환
↓
벡터 DB 저장 (Pinecone, Chroma, Weaviate 등)
── 쿼리 단계 (온라인, 실시간) ──────────────────────
사용자 질문 입력
↓
질문 임베딩: 질문 → 벡터 변환
↓
유사도 검색: 벡터 DB에서 가장 가까운 청크 k개 검색
↓
프롬프트 구성: [검색된 문서] + [질문]
↓
LLM 생성: 문서 기반으로 답변 생성
↓
최종 답변 출력
핵심은 임베딩과 유사도 검색입니다. 임베딩 글에서 다룬 것처럼 텍스트를 벡터 공간에 매핑하면, 의미가 비슷한 텍스트는 벡터 공간에서 가까운 위치에 놓입니다. "강아지"와 "개"는 단어가 달라도 벡터 거리가 가깝습니다. 이 원리로 키워드가 일치하지 않아도 의미가 비슷한 문서를 찾아낼 수 있습니다.

임베딩 — 텍스트를 벡터로 변환하는 원리
임베딩은 텍스트를 고차원 벡터로 변환하는 과정입니다. 임베딩 글에서 Word2Vec을 다뤘는데, RAG에서는 문장 전체를 하나의 벡터로 표현하는 문장 임베딩을 씁니다. 같은 의미의 문장은 벡터 공간에서 가깝고, 다른 의미의 문장은 멀리 떨어집니다.
문장 임베딩 예시 (실제는 1536차원, 여기선 3차원으로 단순화):
문장 벡터 (3차원)
────────────────────────────────────────────────
"강아지는 귀엽다" [0.8, 0.6, 0.1]
"개는 사랑스럽다" [0.7, 0.7, 0.1] ← 의미 유사 → 가까움
"고양이는 독립적이다" [0.2, 0.8, 0.3]
"딥러닝 학습률 설정" [0.1, 0.1, 0.9] ← 의미 다름 → 멀음
청킹 전략 (Chunking):
문서를 어떻게 자르느냐에 따라 검색 품질이 달라짐
고정 크기 청킹:
→ 500 토큰씩 자름
→ 문장 중간에서 잘릴 수 있음
문장 단위 청킹:
→ 문장 경계에서 자름
→ 의미 보존 우수
슬라이딩 윈도우:
→ 청크 간 50토큰 겹침 (overlap)
→ 경계 부분 정보 손실 방지
→ 실무에서 가장 많이 씀
청킹 크기가 너무 작으면 문맥이 부족하고, 너무 크면 관련 없는 내용이 섞여서 LLM이 혼란스러워합니다. 실무에서는 보통 256~512 토큰에 50 토큰 겹침을 기본값으로 시작해서 조정합니다. 트랜스포머 글에서 다룬 컨텍스트 윈도우 개념이 청킹 전략과 직접 연결되는 지점입니다.
유사도 검색 — 실제 숫자로 계산해 보기
질문 벡터와 문서 벡터 사이의 유사도를 측정하는 방법은 여러 가지입니다. RAG에서 가장 많이 쓰이는 건 코사인 유사도입니다. 협업 필터링 글에서 다룬 바로 그 공식입니다.
유사도 측정 방법 비교:
코사인 유사도 (Cosine Similarity):
cos(θ) = (A·B) / (||A|| × ||B||)
→ 벡터 방향의 유사성 측정
→ 크기(길이) 무관 → 문서 길이 차이 영향 없음
→ RAG에서 가장 많이 사용
내적 (Dot Product):
A·B = Σ aᵢbᵢ
→ 임베딩이 정규화된 경우 코사인 유사도와 동일
→ 계산 빠름
유클리드 거리 (L2 Distance):
d = √Σ(aᵢ - bᵢ)²
→ 벡터 공간에서 실제 거리
→ 값이 작을수록 유사
실제 계산 예시 (3차원):
질문: "강아지 훈련 방법" → q = [0.75, 0.65, 0.1]
문서 A: "강아지 훈련 팁" → a = [0.8, 0.6, 0.1]
문서 B: "고양이 습성" → b = [0.2, 0.8, 0.3]
cos(q, a) = (0.75×0.8 + 0.65×0.6 + 0.1×0.1)
/ (√(0.75²+0.65²+0.1²) × √(0.8²+0.6²+0.1²))
= (0.60 + 0.39 + 0.01) / (0.994 × 1.0)
= 1.00 / 0.994 ≈ 0.996 ← 매우 유사
cos(q, b) = (0.75×0.2 + 0.65×0.8 + 0.1×0.3)
/ (0.994 × √(0.04+0.64+0.09))
= (0.15 + 0.52 + 0.03) / (0.994 × 0.860)
= 0.70 / 0.855 ≈ 0.819 ← 덜 유사
→ 문서 A가 질문과 더 유사 → RAG가 문서 A를 선택
유사도 점수가 높은 상위 k개 문서를 선택해서 LLM에 넘깁니다. k=3이면 가장 유사한 문서 3개를 프롬프트에 포함합니다. k가 클수록 더 많은 정보를 주지만 컨텍스트 윈도우를 많이 씁니다. 실무에서는 k=3~5가 기본값입니다.
벡터 데이터베이스 — 왜 일반 DB로는 안 되는가
유사도 검색을 구현할 때 처음엔 그냥 NumPy로 모든 벡터와 거리를 계산하면 되지 않나 싶었습니다. 문서가 100개면 가능합니다. 문제는 실제 서비스에서는 문서가 수백만 개가 되는 순간부터입니다.
브루트 포스(완전 탐색) vs 벡터 DB 비교:
브루트 포스:
문서 100만 개 × 1536차원 벡터
→ 질문 1개 처리 시 100만 번 유사도 계산
→ 검색 시간: 수 초~수십 초
→ 실시간 서비스 불가
벡터 데이터베이스 (FAISS, Pinecone, Chroma 등):
→ 인덱싱 구조로 검색 공간 미리 압축
→ 100만 개 중 관련 있을 법한 후보만 검색
→ 검색 시간: 수 밀리초
→ 정확도 약간 희생 (근사 최근접 이웃, ANN)
주요 벡터 DB 비교:
FAISS : Meta 오픈소스, 로컬 사용, 빠름
Chroma : 오픈소스, 로컬/클라우드, 간편
Pinecone : 클라우드 서비스, 관리형, 실무 많이 사용
Weaviate : 오픈소스, 하이브리드 검색 지원
Qdrant : 오픈소스, 고성능, 필터링 강력
벡터 DB는 일반 관계형 DB(MySQL 등)와 근본적으로 다릅니다. 일반 DB는 정확한 값을 찾는 데 최적화되어 있습니다. "나이가 30인 사람"처럼 정확한 조건으로 검색합니다. 벡터 DB는 "이 벡터와 가장 비슷한 벡터"를 찾는 근사 검색에 최적화되어 있습니다. PCA 글에서 다룬 차원 축소 개념이 벡터 인덱싱에서도 쓰입니다. 고차원 벡터를 저차원으로 압축해서 검색 속도를 높이는 방식입니다.
HNSW — 벡터 검색을 빠르게 만드는 알고리즘
벡터 DB 내부에서 실제로 검색을 빠르게 만드는 핵심 알고리즘이 HNSW(Hierarchical Navigable Small World)입니다. 처음엔 이름이 어렵게 느껴졌는데, 구조 자체는 직관적입니다. 그래프 이론 글에서 다룬 소셜 네트워크 개념이 여기서 그대로 쓰입니다.
HNSW 구조:
계층적 그래프 구조 (위로 갈수록 노드 수 적음):
Layer 2 (최상위): ●────────────●
(멀리 떨어진 노드만 연결)
Layer 1 (중간): ●──● ●──●──●
(중간 거리 노드 연결)
Layer 0 (최하위): ●─●─●─●─●─●─●─●
(모든 노드, 가까운 노드 연결)
검색 과정:
1. 최상위 레이어에서 시작 (넓은 범위 탐색)
2. 질문 벡터와 가장 가까운 노드로 이동
3. 아래 레이어로 내려가며 점점 세밀하게 탐색
4. 최하위 레이어에서 최종 k개 선택
시간복잡도:
브루트 포스: O(n) → 문서 수에 비례
HNSW: O(log n) → 문서 수 로그에 비례
문서 100만 개 기준:
브루트 포스: 100만 번 계산
HNSW: 약 20번 계산 (log₂ 1,000,000 ≈ 20)
HNSW는 정확한 최근접 이웃을 찾는 게 아니라 근삿값을 찾습니다. 정확도를 약간 희생하는 대신 속도를 log n 수준으로 낮춥니다. 실무에서 정확도 손실은 보통 1~5% 수준이라 체감하기 어렵습니다. 검색 속도가 수십 배 빨라지는 것에 비하면 감수할 만한 트레이드오프입니다.

RAG 파이프라인 구현하기
LangChain과 Chroma를 이용한 기본 RAG 파이프라인입니다. 실제로 돌려보면 RAG의 전체 흐름이 한눈에 들어옵니다.
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
# 1. 문서 준비
documents = [
"강아지는 사회적 동물로 훈련이 잘 됩니다.",
"고양이는 독립적인 성격을 가지고 있습니다.",
"RAG는 LLM에 외부 지식을 주입하는 방법입니다.",
"벡터 데이터베이스는 유사도 검색에 최적화되어 있습니다.",
]
# 2. 청킹
splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=50
)
chunks = splitter.create_documents(documents)
# 3. 임베딩 + 벡터 DB 저장
embedding_model = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embedding_model
)
# 4. 검색기 설정 (k=2: 상위 2개 문서 검색)
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# 5. RAG 체인 구성
llm = ChatOpenAI(model="gpt-4", temperature=0)
rag_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=retriever
)
# 6. 질문
result = rag_chain.run("RAG가 뭔가요?")
print(result)
# → 벡터 DB에서 RAG 관련 문서 검색 후 LLM이 답변 생성
실제로 돌려보면 같은 질문을 RAG 없이 LLM에 바로 물어봤을 때와 답변 품질이 눈에 띄게 달라집니다. 특히 회사 내부 문서나 최신 정보처럼 LLM이 모르는 내용에 대해 RAG는 정확한 근거를 제시하면서 답합니다. 임베딩, 벡터 공간, 코사인 유사도, 그래프 이론이 하나의 파이프라인 안에서 전부 연결되어 작동하고 있습니다.
'데이터 과학 수학' 카테고리의 다른 글
| 사진 속 고양이를 어떻게 알아보는가, 합성곱 신경망(CNN)과 이미지 인식의 수학 (0) | 2026.05.06 |
|---|---|
| 정답 없이 데이터를 스스로 나누는 법, 군집화(K-means)와 EM 알고리즘의 수학 (0) | 2026.05.05 |
| 머신러닝의 시작, 선형 회귀(Linear Regression)와 최소제곱법·정규방정식의 수학 (0) | 2026.05.04 |
| 스팸 필터는 어떻게 작동하는가, 로지스틱 회귀(Logistic Regression)의 수학과 확률적 분류 (0) | 2026.05.03 |
| 주가와 날씨는 어떻게 예측하는가, 시계열 데이터와 LSTM의 수학적 원리 (0) | 2026.05.02 |