본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 25. · 3 Views
Reranking 기법으로 RAG 검색 정확도 10배 높이기
RAG 시스템의 검색 정확도를 획기적으로 개선하는 Reranking 기법을 배웁니다. Cross-Encoder, Cohere Rerank API, LLM-based Reranking 등 실무에서 바로 적용할 수 있는 다양한 방법을 실제 코드와 함께 소개합니다.
목차
- Reranking의 필요성
- Cross-Encoder 활용
- Cohere Rerank API
- LLM-based Reranking
- 실습: Reranker 통합
- 실습: 검색 정확도 향상 측정
1. Reranking의 필요성
스타트업에서 AI 챗봇을 개발하는 김개발 씨는 최근 골치 아픈 문제에 직면했습니다. RAG 시스템을 구축했는데, 사용자들이 "답변이 엉뚱하다"는 피드백을 계속 보내는 것입니다.
벡터 검색으로 관련 문서를 찾아내는데 왜 이런 일이 생길까요?
Reranking은 초기 검색 결과를 다시 정렬하여 가장 관련성 높은 결과를 상위에 배치하는 기법입니다. 마치 도서관에서 사서가 검색 결과를 한 번 더 확인하고 가장 적합한 책을 맨 위에 놓는 것과 같습니다.
벡터 검색의 한계를 보완하여 RAG 시스템의 정확도를 극적으로 향상시킬 수 있습니다.
다음 코드를 살펴봅시다.
# 기본 벡터 검색과 Reranking 비교
from sentence_transformers import CrossEncoder
# 초기 벡터 검색 결과 (관련도 순서가 완벽하지 않음)
search_results = [
{"text": "Python은 객체지향 언어입니다.", "score": 0.72},
{"text": "Django는 Python 웹 프레임워크입니다.", "score": 0.68},
{"text": "FastAPI로 REST API를 만들 수 있습니다.", "score": 0.71}
]
query = "Python 웹 개발 프레임워크 추천해줘"
# Reranker 모델 초기화
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
# Reranking 수행 - 쿼리와 각 문서의 관련성을 정확히 평가
pairs = [[query, doc["text"]] for doc in search_results]
rerank_scores = reranker.predict(pairs)
# 점수로 재정렬
for i, score in enumerate(rerank_scores):
search_results[i]["rerank_score"] = score
reranked = sorted(search_results, key=lambda x: x["rerank_score"], reverse=True)
print(f"최적 답변: {reranked[0]['text']}")
김개발 씨는 회사의 사내 문서 검색 AI 챗봇을 개발했습니다. 수천 개의 문서를 벡터 데이터베이스에 저장하고, 사용자 질문이 들어오면 유사도 검색으로 관련 문서를 찾아 답변을 생성하는 시스템입니다.
테스트 때는 잘 작동하는 것 같았는데, 실제 사용자들의 반응은 싸늘했습니다. "Python 웹 개발 프레임워크 추천해줘"라고 물었더니 "Python은 객체지향 언어입니다"라는 영 관계없는 답변이 나왔다는 것입니다.
김개발 씨는 당황했습니다. 분명히 데이터베이스에는 Django, FastAPI에 대한 좋은 문서들이 있는데 말이죠.
선배 개발자 박시니어 씨가 김개발 씨의 화면을 들여다봅니다. "아, 벡터 검색만 사용했구나.
이럴 때 필요한 게 바로 Reranking이야." 벡터 검색의 근본적인 한계를 이해해야 합니다. 벡터 검색은 쿼리와 문서를 각각 임베딩 벡터로 변환한 후 코사인 유사도로 비교합니다.
이 방식은 빠르고 효율적이지만 치명적인 약점이 있습니다. 쿼리와 문서를 독립적으로 임베딩하기 때문에 둘 사이의 상호작용을 고려하지 못한다는 것입니다.
마치 두 사람의 궁합을 각자의 프로필만 보고 판단하는 것과 같습니다. 실제로 만나서 대화를 나눠봐야 궁합을 제대로 알 수 있듯이, 쿼리와 문서도 함께 분석해야 진짜 관련성을 파악할 수 있습니다.
그래서 등장한 것이 Reranking 기법입니다. Reranking은 2단계 검색 전략입니다.
1단계에서는 빠른 벡터 검색으로 후보군을 추려냅니다. 보통 수백만 개 문서 중에서 상위 100개 정도를 선택합니다.
2단계에서는 이 후보군을 더 정교한 모델로 재평가합니다. 쿼리와 각 문서를 함께 분석하여 진짜 관련성을 측정하는 것입니다.
이렇게 하면 정확도가 극적으로 향상됩니다. 실제로 많은 연구에서 Reranking을 적용하면 검색 정확도가 30%에서 50%까지 향상된다는 결과가 나왔습니다.
위의 코드를 자세히 살펴보겠습니다. 먼저 벡터 검색으로 3개의 후보 문서를 찾았습니다.
점수를 보면 0.72, 0.68, 0.71로 비슷비슷합니다. 이 상태에서는 어느 것이 진짜 관련성이 높은지 확신하기 어렵습니다.
그래서 CrossEncoder 모델을 불러와 Reranking을 수행합니다. CrossEncoder는 쿼리와 문서를 하나의 입력으로 받아서 관련성 점수를 계산하는 모델입니다.
각 문서를 독립적으로 평가하는 것이 아니라, 쿼리와의 상호작용을 직접 분석합니다. 결과를 보면 "Django는 Python 웹 프레임워크입니다"가 가장 높은 점수를 받았습니다.
원래 벡터 검색에서는 2위였지만, Reranking 후에는 1위로 올라온 것입니다. 이제 사용자는 질문에 딱 맞는 답변을 받을 수 있습니다.
실무에서는 어떻게 활용할까요? 고객 상담 챗봇을 예로 들어봅시다.
수만 건의 FAQ 문서가 있고, 고객이 "환불은 어떻게 하나요?"라고 질문합니다. 벡터 검색만 사용하면 "환불 정책", "환불 절차", "환불 불가 상품" 같은 문서들이 비슷한 점수로 나옵니다.
하지만 Reranking을 적용하면 고객의 질문 의도를 정확히 파악해서 "환불 절차" 문서를 최상위에 배치할 수 있습니다. 네이버, 카카오 같은 대기업의 검색 시스템도 이런 2단계 접근법을 사용합니다.
1단계에서 빠르게 후보를 추리고, 2단계에서 정교하게 순위를 매기는 것입니다. 주의할 점도 있습니다.
Reranking 모델은 벡터 검색보다 훨씬 느립니다. CrossEncoder는 쿼리와 각 문서를 함께 처리하기 때문에 계산량이 많습니다.
따라서 모든 문서에 Reranking을 적용하면 안 되고, 1단계에서 추린 소수의 후보에만 적용해야 합니다. 또한 Reranking 모델의 크기도 고려해야 합니다.
큰 모델일수록 정확하지만 느립니다. 서비스의 응답 시간 요구사항에 맞춰 적절한 모델을 선택해야 합니다.
박시니어 씨의 조언을 들은 김개발 씨는 바로 Reranking을 적용했습니다. 다음 날 사용자 만족도 조사를 해보니 답변 정확도에 대한 긍정 평가가 60%에서 85%로 올랐습니다.
Reranking은 RAG 시스템의 정확도를 높이는 가장 효과적인 방법 중 하나입니다. 약간의 지연 시간을 감수하고라도 정확한 답변을 제공하고 싶다면 반드시 적용해야 할 기법입니다.
실전 팁
💡 - 1단계 검색에서는 100~200개 후보를 추린 후 Reranking 적용
- 응답 시간이 중요하면 경량 모델(MiniLM) 사용, 정확도가 중요하면 대형 모델 사용
- Reranking 결과를 캐싱하면 같은 쿼리에 대해 속도 개선 가능
2. Cross-Encoder 활용
Reranking의 필요성을 깨달은 김개발 씨는 어떤 모델을 사용해야 할지 고민에 빠졌습니다. 박시니어 씨는 "일단 Cross-Encoder부터 시작해봐"라고 조언합니다.
Bi-Encoder와 Cross-Encoder는 무엇이 다를까요?
Cross-Encoder는 쿼리와 문서를 하나의 입력으로 함께 처리하여 관련성 점수를 계산하는 모델입니다. Bi-Encoder가 두 텍스트를 독립적으로 임베딩하는 것과 달리, Cross-Encoder는 두 텍스트 간의 상호작용을 직접 학습합니다.
정확도가 높지만 속도가 느려서 Reranking 단계에 적합합니다.
다음 코드를 살펴봅시다.
# Cross-Encoder로 정교한 Reranking 구현
from sentence_transformers import CrossEncoder
import numpy as np
# 여러 Cross-Encoder 모델 중 선택 가능
model_name = 'cross-encoder/ms-marco-MiniLM-L-12-v2' # 균형잡힌 선택
# model_name = 'cross-encoder/ms-marco-TinyBERT-L-2-v2' # 빠른 속도
# model_name = 'cross-encoder/ms-marco-electra-base' # 높은 정확도
reranker = CrossEncoder(model_name)
query = "FastAPI에서 비동기 처리 방법"
candidates = [
"FastAPI는 async/await를 지원하는 현대적인 Python 프레임워크입니다.",
"Python의 asyncio 라이브러리로 비동기 프로그래밍을 할 수 있습니다.",
"Django는 ASGI를 통해 비동기를 지원합니다.",
"Flask는 동기 프레임워크이지만 Celery로 비동기 작업을 처리할 수 있습니다."
]
# 배치 처리로 효율성 향상
query_doc_pairs = [[query, doc] for doc in candidates]
scores = reranker.predict(query_doc_pairs)
# 점수와 함께 정렬
ranked_results = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
for rank, (doc, score) in enumerate(ranked_results, 1):
print(f"{rank}위 (점수: {score:.3f}): {doc[:50]}...")
김개발 씨는 Reranking에 대해 공부하다가 헷갈리는 용어들을 발견했습니다. Bi-Encoder, Cross-Encoder, Dual Encoder...
도대체 뭐가 뭔지 알 수가 없습니다. 박시니어 씨에게 물어봤습니다.
"선배, Bi-Encoder랑 Cross-Encoder가 뭐가 다른 건가요?" 박시니어 씨는 화이트보드에 그림을 그리기 시작합니다. 먼저 Bi-Encoder의 동작 방식을 이해해야 합니다.
Bi-Encoder는 이름 그대로 두 개의 인코더를 사용합니다. 정확히는 하나의 인코더를 두 번 사용하는 것이죠.
쿼리를 임베딩하고, 문서를 임베딩하고, 두 벡터의 유사도를 계산합니다. 마치 두 사람의 프로필을 각각 분석한 후 점수를 매기는 것과 같습니다.
이 방식의 장점은 속도입니다. 모든 문서를 미리 임베딩해두면, 쿼리가 들어왔을 때 쿼리만 임베딩하고 유사도 계산만 하면 됩니다.
수백만 개 문서에서도 밀리초 단위로 검색이 가능합니다. 하지만 치명적인 단점이 있습니다.
쿼리와 문서가 독립적으로 처리되기 때문에 둘 사이의 복잡한 관계를 포착하지 못한다는 것입니다. 반면 Cross-Encoder는 완전히 다른 접근법을 사용합니다.
Cross-Encoder는 쿼리와 문서를 [CLS] 쿼리 [SEP] 문서 [SEP] 형태로 이어붙여서 하나의 입력으로 만듭니다. 그리고 트랜스포머 모델이 이 전체 시퀀스를 한 번에 처리합니다.
이 과정에서 쿼리의 각 단어와 문서의 각 단어가 어텐션 메커니즘을 통해 상호작용합니다. 마치 두 사람을 실제로 만나게 해서 대화를 나누게 한 후 궁합을 평가하는 것과 같습니다.
훨씬 정확하지만, 모든 조합에 대해 일일이 만남을 주선해야 하므로 느립니다. 위의 코드를 단계별로 살펴보겠습니다.
먼저 사용할 Cross-Encoder 모델을 선택합니다. Sentence-Transformers 라이브러리에서는 여러 가지 사전 학습된 모델을 제공합니다.
MiniLM 계열은 속도와 정확도의 균형이 좋고, TinyBERT는 가장 빠르며, ELECTRA-base는 가장 정확합니다. 쿼리와 각 후보 문서를 페어로 만듭니다.
이것이 핵심입니다. 각 페어가 모델에 함께 입력되어 상호작용을 학습합니다.
predict() 메서드를 호출하면 각 페어에 대한 관련성 점수가 나옵니다. 이 점수는 보통 -10에서 10 사이의 값이며, 높을수록 관련성이 높습니다.
결과를 보면 "FastAPI는 async/await를 지원하는..."이 1위를 차지합니다. 쿼리가 "FastAPI에서 비동기 처리 방법"이었으니 정확히 맞는 답입니다.
Bi-Encoder만 사용했다면 "asyncio 라이브러리" 문서와 점수가 비슷하게 나왔을 수도 있습니다. 실무에서 Cross-Encoder를 효과적으로 사용하는 방법을 알아봅시다.
이커머스 사이트의 상품 검색을 예로 들겠습니다. 사용자가 "여름에 입기 좋은 면 티셔츠"라고 검색합니다.
1단계 벡터 검색에서 100개 상품을 추립니다. 이 중에는 "면 티셔츠", "여름 셔츠", "반팔 티셔츠" 등 다양한 상품이 섞여 있습니다.
Cross-Encoder로 Reranking하면 쿼리의 의도를 정확히 파악합니다. "여름"과 "면"이라는 두 조건을 모두 만족하는 상품을 최상위로 올립니다.
단순히 키워드가 많이 겹치는 것이 아니라, 의미적으로 가장 관련성이 높은 상품을 찾는 것입니다. 실제로 아마존, 쿠팡 같은 검색 시스템도 이런 2단계 랭킹을 사용한다고 알려져 있습니다.
주의할 점이 있습니다. Cross-Encoder는 느립니다.
GPU가 없는 환경에서 100개 문서를 Reranking하는 데 1~2초가 걸릴 수 있습니다. 따라서 반드시 1단계 검색으로 후보를 줄인 후에 사용해야 합니다.
전체 데이터베이스에 직접 적용하면 안 됩니다. 또한 배치 처리를 활용하면 속도를 개선할 수 있습니다.
한 번에 하나씩 처리하는 것보다 여러 개를 배치로 묶어서 처리하면 GPU를 효율적으로 사용할 수 있습니다. 김개발 씨는 Cross-Encoder를 적용한 후 검색 품질이 눈에 띄게 좋아진 것을 확인했습니다.
사용자들도 "이제 원하는 답을 바로 찾을 수 있다"는 긍정적인 피드백을 보내왔습니다. Cross-Encoder는 Reranking의 표준 도구입니다.
정확도가 중요한 검색 시스템이라면 반드시 고려해야 할 기법입니다.
실전 팁
💡 - 1단계 검색에서 50~200개 후보를 추린 후 Cross-Encoder 적용
- GPU 사용 가능하면 큰 모델, CPU만 있으면 TinyBERT 같은 경량 모델 사용
- 배치 크기를 늘리면 처리 속도 향상 (GPU 메모리 허용 범위 내에서)
3. Cohere Rerank API
Cross-Encoder를 적용해서 좋은 성과를 낸 김개발 씨는 새로운 문제에 부딪혔습니다. 서비스 사용자가 늘어나면서 자체 서버의 GPU 비용이 만만치 않은 것입니다.
박시니어 씨가 "API 서비스를 고려해봐"라고 조언합니다. Cohere Rerank API가 뭘까요?
Cohere Rerank API는 Cohere에서 제공하는 클라우드 기반 Reranking 서비스입니다. 자체 모델을 관리하지 않고도 강력한 Reranking 기능을 API 호출로 간단히 사용할 수 있습니다.
다국어 지원, 대규모 처리, 최신 모델 자동 업데이트 등의 장점이 있습니다.
다음 코드를 살펴봅시다.
# Cohere Rerank API로 간편한 Reranking
import cohere
# Cohere API 클라이언트 초기화
co = cohere.Client('your-api-key-here')
query = "RAG 시스템에서 정확도를 높이는 방법"
documents = [
"벡터 데이터베이스는 임베딩을 효율적으로 저장합니다.",
"Reranking은 검색 결과의 순위를 재조정하여 정확도를 높입니다.",
"LLM은 자연어를 이해하고 생성할 수 있습니다.",
"청킹 전략은 문서를 적절한 크기로 나누는 방법입니다.",
"프롬프트 엔지니어링으로 LLM 성능을 개선할 수 있습니다."
]
# Rerank API 호출 - 매우 간단함
results = co.rerank(
model='rerank-multilingual-v3.0', # 다국어 모델
query=query,
documents=documents,
top_n=3, # 상위 3개만 반환
return_documents=True # 문서 내용도 함께 반환
)
# 결과 출력
for idx, result in enumerate(results.results, 1):
print(f"{idx}위 (관련도: {result.relevance_score:.3f})")
print(f"문서: {result.document.text}\n")
김개발 씨의 챗봇 서비스가 인기를 끌면서 트래픽이 급증했습니다. 처음에는 자체 서버에 Cross-Encoder 모델을 올려서 운영했는데, 이제는 GPU 서버 비용이 매달 수백만 원씩 나옵니다.
더 큰 문제는 피크 시간대에 응답이 느려진다는 것입니다. 박시니어 씨는 "이럴 때는 Managed 서비스를 고려해봐야 해"라고 조언합니다.
"Cohere 같은 회사에서 Rerank API를 제공하거든. 인프라 관리 부담 없이 쓸 수 있어." Cohere Rerank API의 핵심 장점을 알아봅시다.
첫째, 인프라 관리가 필요 없습니다. 자체 서버에 모델을 올리려면 GPU 인스턴스를 관리하고, 모델을 로드하고, 스케일링을 고민해야 합니다.
하지만 API 서비스는 단순히 HTTP 요청을 보내면 됩니다. 서버 다운타임, 모델 업데이트, 성능 최적화는 모두 Cohere가 알아서 처리합니다.
둘째, 최신 모델을 자동으로 사용할 수 있습니다. Cohere는 지속적으로 모델을 개선합니다.
API를 사용하면 코드 변경 없이 자동으로 개선된 모델의 혜택을 받습니다. 셋째, 다국어 지원이 탁월합니다.
rerank-multilingual-v3.0 모델은 100개 이상의 언어를 지원합니다. 한국어, 영어, 일본어, 중국어 등 어떤 언어든 높은 품질의 Reranking을 제공합니다.
위의 코드는 놀라울 정도로 간단합니다. 먼저 Cohere 클라이언트를 초기화합니다.
API 키만 있으면 됩니다. 무료 티어도 제공되므로 테스트해보기 쉽습니다.
co.rerank() 메서드 하나로 모든 것이 해결됩니다. 모델 이름, 쿼리, 문서 리스트를 전달하면 Reranking된 결과가 돌아옵니다.
top_n 파라미터로 상위 몇 개만 받을지 지정할 수 있습니다. 결과를 보면 "Reranking은 검색 결과의 순위를..."가 1위를 차지합니다.
쿼리가 "RAG 시스템에서 정확도를 높이는 방법"이었으니 정확한 답입니다. 자체 Cross-Encoder 모델과 비교해도 품질이 뛰어납니다.
실무에서 어떻게 활용할까요? 글로벌 서비스를 운영하는 스타트업을 예로 들어봅시다.
한국, 일본, 미국 사용자가 모두 사용하는 지식베이스 검색 서비스입니다. 각 언어별로 별도의 Reranking 모델을 관리하면 운영 부담이 큽니다.
Cohere의 다국어 모델 하나면 모든 언어를 커버할 수 있습니다. 한국어 쿼리든 영어 쿼리든 똑같은 API 호출로 처리됩니다.
언어 감지나 별도 라우팅 로직이 필요 없습니다. 또한 트래픽 변동이 큰 서비스에 적합합니다.
평소에는 트래픽이 적다가 특정 시간대에 폭증하는 경우, 자체 서버는 비효율적입니다. API 서비스는 사용한 만큼만 비용을 내므로 경제적입니다.
주의할 점도 있습니다. 첫째, API 호출 비용을 계산해야 합니다.
Cohere는 1,000회 검색당 약 2달러를 부과합니다. 트래픽이 매우 많으면 자체 서버보다 비쌀 수 있습니다.
비용 분석을 먼저 해보세요. 둘째, 네트워크 지연이 발생합니다.
자체 모델은 로컬에서 실행되지만, API는 외부 서버와 통신해야 합니다. 보통 100~300ms 정도 추가 지연이 발생합니다.
응답 시간이 매우 중요한 서비스라면 고려해야 합니다. 셋째, 데이터 프라이버시를 검토해야 합니다.
문서 내용이 Cohere 서버로 전송됩니다. 민감한 정보가 포함된 경우 문제가 될 수 있습니다.
개인정보나 기업 기밀이 포함된다면 자체 모델을 사용하는 것이 안전합니다. 김개발 씨는 비용을 계산해봤습니다.
월 100만 건 쿼리 기준으로 Cohere API는 약 200만 원, 자체 GPU 서버는 약 300만 원이 나왔습니다. 게다가 운영 부담까지 고려하면 API가 훨씬 유리했습니다.
바로 마이그레이션을 시작했습니다. Cohere Rerank API는 빠르게 고품질 Reranking을 도입하고 싶을 때 훌륭한 선택입니다.
특히 다국어 서비스나 트래픽 변동이 큰 서비스에 적합합니다.
실전 팁
💡 - 무료 티어로 먼저 테스트해보고 품질과 속도 확인
- 비용 분석 필수: API 비용 vs 자체 서버 비용 비교
- 민감한 데이터는 자체 모델, 일반 데이터는 API 사용 고려
4. LLM-based Reranking
Cohere API로 안정적인 서비스를 운영하던 김개발 씨는 새로운 요구사항을 받았습니다. "검색 결과에 대한 설명도 함께 보여줄 수 있나요?" 단순히 점수만 매기는 것이 아니라 왜 이 문서가 관련성이 높은지 설명해야 한다는 것입니다.
박시니어 씨가 말합니다. "그럼 이제 LLM-based Reranking을 써볼 때가 됐네."
LLM-based Reranking은 GPT-4, Claude 같은 대형 언어 모델을 Reranker로 활용하는 기법입니다. 단순히 관련성 점수만 제공하는 것이 아니라, 판단 근거를 자연어로 설명할 수 있습니다.
복잡한 쿼리나 도메인 특화 지식이 필요한 경우 특히 효과적입니다.
다음 코드를 살펴봅시다.
# LLM으로 설명 가능한 Reranking 구현
from openai import OpenAI
import json
client = OpenAI(api_key='your-api-key')
query = "Python에서 메모리 누수를 디버깅하는 방법"
documents = [
"Python의 gc 모듈로 가비지 컬렉션을 제어할 수 있습니다.",
"memory_profiler 라이브러리로 메모리 사용량을 프로파일링합니다.",
"tracemalloc 모듈은 메모리 할당을 추적합니다.",
"PyCharm의 디버거는 중단점과 변수 조사를 지원합니다."
]
# LLM에게 Reranking과 설명 요청
prompt = f"""다음 쿼리와 문서들을 평가해주세요.
쿼리: {query}
문서들:
{json.dumps(documents, ensure_ascii=False, indent=2)}
각 문서의 관련성을 0-10점으로 평가하고, 그 이유를 설명해주세요.
JSON 형식으로 반환: [{{"index": 0, "score": 9, "reason": "이유..."}}]"""
response = client.chat.completions.create(
model='gpt-4-turbo',
messages=[{'role': 'user', 'content': prompt}],
response_format={'type': 'json_object'}
)
# 결과 파싱 및 정렬
results = json.loads(response.choices[0].message.content)
ranked = sorted(results, key=lambda x: x['score'], reverse=True)
for rank, item in enumerate(ranked, 1):
print(f"{rank}위 (점수: {item['score']}/10)")
print(f"문서: {documents[item['index']]}")
print(f"이유: {item['reason']}\n")
김개발 씨는 기술 문서 검색 서비스를 개선하고 있었습니다. 개발자 사용자들이 요청한 것은 단순히 관련 문서를 찾는 것뿐만 아니라 "왜 이 문서가 내 질문과 관련이 있는지" 알고 싶다는 것이었습니다.
특히 복잡한 기술적 질문일수록 이런 설명이 중요했습니다. 박시니어 씨는 "Cross-Encoder는 점수만 주지, 설명은 못 해.
이럴 때는 LLM을 Reranker로 쓰는 거야"라고 알려줍니다. LLM-based Reranking의 핵심 아이디어를 이해해봅시다.
기존 Reranking 모델들은 판별 모델입니다. 입력을 받아서 점수라는 단일 숫자를 출력합니다.
내부적으로 어떤 추론을 했는지 알 수 없습니다. 블랙박스처럼 작동하는 것입니다.
반면 LLM은 생성 모델입니다. 자연어를 이해하고 생성할 수 있습니다.
따라서 "이 문서가 관련성이 높은 이유는 메모리 누수 디버깅에 직접 사용할 수 있는 도구를 소개하기 때문입니다"처럼 설명을 생성할 수 있습니다. 마치 사람이 문서를 읽고 판단하는 것과 비슷합니다.
단순히 "좋다/나쁘다"가 아니라 "왜 좋은지" 논리적으로 설명할 수 있는 것입니다. 위의 코드를 단계별로 살펴보겠습니다.
프롬프트에서 LLM에게 명확한 작업을 지시합니다. 쿼리와 문서들을 제공하고, 각 문서를 평가하되 이유도 함께 설명하라고 요청합니다.
JSON 형식으로 결과를 받으면 파싱하기 쉽습니다. response_format={'type': 'json_object'}를 지정하면 GPT-4가 반드시 유효한 JSON을 반환합니다.
파싱 오류를 방지할 수 있습니다. 결과를 보면 "tracemalloc 모듈은 메모리 할당을 추적합니다"가 높은 점수를 받습니다.
이유를 보면 "메모리 누수를 디버깅하려면 어디서 메모리가 할당되는지 추적해야 하므로 tracemalloc이 가장 직접적인 도구입니다"라고 설명합니다. 사용자는 이 설명을 읽고 왜 이 문서가 추천되었는지 이해할 수 있습니다.
실무에서 LLM-based Reranking이 빛을 발하는 경우를 알아봅시다. 의료 분야 검색을 예로 들겠습니다.
의사가 "당뇨병 환자에게 안전한 진통제"를 검색합니다. 일반 Reranking 모델은 "당뇨병"과 "진통제" 키워드가 들어간 문서에 높은 점수를 줍니다.
하지만 LLM은 도메인 지식을 활용합니다. "아세트아미노펜은 신장 기능에 영향을 주지 않아 당뇨병 환자에게 안전합니다"라는 문서를 찾아내고, "당뇨병 환자는 신장 합병증 위험이 있으므로 신장에 부담을 주지 않는 진통제가 적합합니다.
따라서 이 문서가 가장 관련성이 높습니다"라고 설명합니다. 이런 추론 능력은 Cross-Encoder가 따라올 수 없는 영역입니다.
또 다른 사례로 법률 문서 검색이 있습니다. 변호사가 복잡한 판례를 찾을 때, LLM은 여러 법 조항과 판례의 관계를 이해하고 가장 관련성 높은 문서를 찾아낼 수 있습니다.
주의할 점이 있습니다. 첫째, 비용이 매우 높습니다.
GPT-4로 100개 문서를 Reranking하면 수천 개의 토큰을 처리하게 됩니다. 호출 한 번에 수십 원에서 수백 원이 듭니다.
Cross-Encoder나 Cohere API에 비해 10배 이상 비쌀 수 있습니다. 둘째, 속도가 느립니다.
LLM은 추론에 시간이 걸립니다. GPT-4로 응답을 받는 데 2~5초가 걸릴 수 있습니다.
실시간 검색 서비스에는 부적합할 수 있습니다. 셋째, 일관성이 떨어질 수 있습니다.
LLM은 확률적 모델이라 같은 입력에도 약간씩 다른 결과를 낼 수 있습니다. Temperature를 0으로 설정하면 개선되지만 완벽하지는 않습니다.
따라서 LLM-based Reranking은 선택적으로 사용해야 합니다. 모든 쿼리에 적용하는 것이 아니라, 복잡하거나 중요한 쿼리에만 적용하는 하이브리드 전략이 효과적입니다.
김개발 씨는 이렇게 구현했습니다. 일반 쿼리는 Cohere API로 처리하고, 사용자가 "상세 설명 보기" 버튼을 누르면 LLM-based Reranking을 수행합니다.
비용을 절감하면서도 필요한 경우 깊이 있는 설명을 제공할 수 있었습니다. LLM-based Reranking은 설명 가능성과 복잡한 추론이 필요한 특수한 경우에 강력한 도구입니다.
비용과 속도를 감안하여 전략적으로 사용하세요.
실전 팁
💡 - 모든 쿼리에 적용하지 말고 복잡한 쿼리나 중요한 쿼리에만 선택적 적용
- 프롬프트에 예시를 포함하면 LLM의 평가 품질 향상
- JSON mode 사용으로 파싱 오류 방지
5. 실습: Reranker 통합
다양한 Reranking 기법을 배운 김개발 씨는 실제 RAG 시스템에 통합하는 단계에 들어섰습니다. "벡터 검색 → Reranking → LLM 생성"의 전체 파이프라인을 어떻게 구축할까요?
박시니어 씨가 "전체 흐름을 보여줄게"라며 코드를 작성하기 시작합니다.
Reranker 통합은 벡터 검색과 Reranking을 결합하여 완전한 RAG 파이프라인을 구축하는 과정입니다. 검색 → Reranking → 컨텍스트 구성 → LLM 생성의 각 단계를 효율적으로 연결하여 정확도 높은 AI 애플리케이션을 만들 수 있습니다.
다음 코드를 살펴봅시다.
# 완전한 RAG 파이프라인 with Reranking
from sentence_transformers import SentenceTransformer, CrossEncoder
import faiss
import numpy as np
# 1단계: 벡터 검색 준비
encoder = SentenceTransformer('all-MiniLM-L6-v2')
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
# 문서 데이터베이스 (실제로는 수천~수만 개)
documents = [
"FastAPI는 Python 웹 프레임워크입니다.",
"FastAPI는 async/await를 지원합니다.",
"Pydantic으로 데이터 검증을 합니다.",
"uvicorn은 ASGI 서버입니다.",
"FastAPI는 자동으로 API 문서를 생성합니다.",
# ... 수천 개의 문서
]
# 벡터 인덱스 구축
doc_embeddings = encoder.encode(documents, convert_to_numpy=True)
index = faiss.IndexFlatL2(doc_embeddings.shape[1])
index.add(doc_embeddings)
# 2단계: 검색 함수
def search_with_reranking(query, top_k=100, rerank_top_n=5):
# Step 1: 빠른 벡터 검색으로 후보 추출
query_embedding = encoder.encode([query], convert_to_numpy=True)
distances, indices = index.search(query_embedding, top_k)
candidates = [documents[idx] for idx in indices[0]]
# Step 2: Cross-Encoder로 정교한 Reranking
pairs = [[query, doc] for doc in candidates]
scores = reranker.predict(pairs)
# Step 3: 재정렬 및 상위 N개 반환
ranked_results = sorted(zip(candidates, scores),
key=lambda x: x[1], reverse=True)
return ranked_results[:rerank_top_n]
# 3단계: 실제 검색 수행
query = "FastAPI에서 비동기 처리 방법"
results = search_with_reranking(query, top_k=100, rerank_top_n=3)
print(f"쿼리: {query}\n")
for rank, (doc, score) in enumerate(results, 1):
print(f"{rank}위 (관련도: {score:.3f}): {doc}")
# 4단계: LLM에 전달할 컨텍스트 구성
context = "\n\n".join([doc for doc, _ in results])
print(f"\n[LLM에 전달될 컨텍스트]\n{context}")
김개발 씨는 지금까지 배운 내용을 정리하고 있었습니다. 벡터 검색, Cross-Encoder, Cohere API, LLM Reranking...
각각은 이해했지만 이것들을 어떻게 조합해야 할지 막막했습니다. 박시니어 씨가 말합니다.
"각 기법은 도구일 뿐이야. 중요한 건 이것들을 어떻게 조합하느냐지.
전체 파이프라인을 설계하는 게 핵심이야." 완전한 RAG 파이프라인의 구조를 이해해봅시다. 1단계: 벡터 검색이 첫 관문입니다.
전체 문서 데이터베이스에서 빠르게 후보를 추립니다. 수백만 개 문서 중에서 상위 100~200개를 선택합니다.
이 단계는 속도가 핵심입니다. FAISS, Pinecone, Weaviate 같은 벡터 데이터베이스를 사용하면 밀리초 단위로 검색할 수 있습니다.
2단계: Reranking이 품질을 결정합니다. 1단계에서 추린 100~200개 후보를 정교하게 재평가합니다.
Cross-Encoder나 Cohere API를 사용하여 쿼리와 각 문서의 진짜 관련성을 측정합니다. 이 단계는 정확도가 핵심입니다.
3단계: 컨텍스트 구성에서 LLM에 전달할 정보를 준비합니다. Reranking 결과 상위 3~5개 문서를 선택하여 하나의 컨텍스트로 합칩니다.
LLM의 컨텍스트 윈도우 제한을 고려하여 적절한 개수를 선택해야 합니다. 4단계: LLM 생성에서 최종 답변을 만듭니다.
구성된 컨텍스트와 사용자 쿼리를 LLM에 전달하여 자연스러운 답변을 생성합니다. 위의 코드를 자세히 분석해봅시다.
먼저 벡터 인코더와 Reranker를 초기화합니다. 인코더는 가볍고 빠른 모델을 사용하고, Reranker는 정확도가 중요하므로 성능 좋은 모델을 선택합니다.
문서들을 임베딩하여 FAISS 인덱스에 저장합니다. 이 작업은 한 번만 수행하면 됩니다.
실제 서비스에서는 서버 시작 시 미리 로드해둡니다. search_with_reranking() 함수가 핵심입니다.
이 함수 하나가 전체 검색 파이프라인을 담당합니다. 먼저 FAISS로 빠르게 100개 후보를 찾고, Cross-Encoder로 정교하게 재평가한 후, 상위 5개를 반환합니다.
결과를 보면 "FastAPI는 async/await를 지원합니다"가 1위를 차지합니다. 쿼리가 "FastAPI에서 비동기 처리 방법"이었으니 정확한 답입니다.
단순 벡터 검색만 했다면 "FastAPI는 Python 웹 프레임워크입니다" 같은 일반적인 문서가 상위에 올라왔을 수도 있습니다. 실무에서 이 파이프라인을 최적화하는 방법을 알아봅시다.
캐싱 전략이 중요합니다. 같은 쿼리가 자주 들어오는 경우 매번 Reranking을 수행하면 비효율적입니다.
Redis 같은 캐시에 결과를 저장하면 응답 속도를 크게 개선할 수 있습니다. 배치 처리도 효과적입니다.
여러 사용자의 쿼리를 모아서 한 번에 처리하면 GPU를 효율적으로 활용할 수 있습니다. 특히 Cross-Encoder는 배치 크기가 클수록 throughput이 향상됩니다.
하이브리드 전략을 고려해봅시다. 간단한 쿼리는 벡터 검색만 사용하고, 복잡한 쿼리만 Reranking을 적용하는 것입니다.
쿼리 복잡도를 분석하는 간단한 휴리스틱(예: 단어 수, 특수 키워드 포함 여부)으로 분기할 수 있습니다. 모니터링도 필수입니다.
각 단계의 지연 시간, 검색 품질 지표(MRR, NDCG 등), 비용을 추적해야 합니다. 병목 지점을 발견하면 해당 단계를 최적화할 수 있습니다.
주의할 점이 있습니다. top_k와 rerank_top_n의 균형을 잘 맞춰야 합니다.
top_k가 너무 작으면 관련 문서를 놓칠 수 있고, 너무 크면 Reranking 시간이 길어집니다. 보통 top_k=100200, rerank_top_n=310이 적절합니다.
문서 청킹 전략도 중요합니다. 긴 문서를 어떻게 나눌지에 따라 검색 품질이 달라집니다.
너무 작게 나누면 맥락이 손실되고, 너무 크게 나누면 관련 없는 정보가 섞입니다. 보통 300~500 토큰 단위로 나누되, 문단이나 섹션 경계를 고려합니다.
김개발 씨는 이 파이프라인을 적용한 후 사용자 만족도가 크게 향상된 것을 확인했습니다. 답변 정확도는 65%에서 88%로 올랐고, 사용자들의 긍정적인 피드백이 쏟아졌습니다.
완전한 RAG 파이프라인 구축은 각 단계를 이해하고 효율적으로 조합하는 것입니다. 속도와 정확도의 균형을 찾는 것이 핵심입니다.
실전 팁
💡 - 1단계는 속도, 2단계는 정확도에 집중
- 캐싱으로 반복 쿼리 최적화
- top_k=100
200, rerank_top_n=310이 일반적으로 효과적
6. 실습: 검색 정확도 향상 측정
Reranking을 적용한 김개발 씨는 "정말 효과가 있나?"라는 질문을 받았습니다. 체감으로는 좋아진 것 같은데, 이것을 객관적으로 증명해야 합니다.
박시니어 씨가 "검색 품질을 정량적으로 측정해야 해"라고 조언합니다. 어떻게 측정할까요?
검색 정확도 측정은 Reranking의 효과를 객관적으로 평가하는 과정입니다. MRR, NDCG, Recall 같은 지표를 사용하여 Reranking 전후를 비교합니다.
테스트 데이터셋을 구축하고 자동화된 평가 파이프라인을 만들어 지속적으로 개선할 수 있습니다.
다음 코드를 살펴봅시다.
# 검색 정확도 측정 - MRR과 NDCG 계산
import numpy as np
from typing import List, Tuple
# 평가 지표 함수들
def mean_reciprocal_rank(results: List[List[Tuple[str, bool]]]) -> float:
"""MRR: 첫 번째 정답이 나타나는 순위의 역수 평균"""
mrr = 0.0
for result in results:
for rank, (doc, is_relevant) in enumerate(result, 1):
if is_relevant:
mrr += 1.0 / rank
break # 첫 번째 정답만 고려
return mrr / len(results)
def ndcg_at_k(results: List[List[Tuple[str, bool]]], k: int = 5) -> float:
"""NDCG@k: 순위를 고려한 정규화된 이득"""
def dcg(relevances):
return sum(rel / np.log2(idx + 2) for idx, rel in enumerate(relevances[:k]))
ndcg_scores = []
for result in results:
relevances = [1 if is_rel else 0 for _, is_rel in result]
ideal_relevances = sorted(relevances, reverse=True)
dcg_score = dcg(relevances)
idcg_score = dcg(ideal_relevances)
ndcg_scores.append(dcg_score / idcg_score if idcg_score > 0 else 0)
return np.mean(ndcg_scores)
# 테스트 데이터 (실제로는 수백 개 필요)
test_queries = [
{
"query": "FastAPI 비동기 처리",
"results_before_rerank": [
("FastAPI는 웹 프레임워크입니다", False),
("FastAPI는 async/await를 지원합니다", True),
("Python은 인터프리터 언어입니다", False)
],
"results_after_rerank": [
("FastAPI는 async/await를 지원합니다", True),
("FastAPI는 웹 프레임워크입니다", False),
("Python은 인터프리터 언어입니다", False)
]
},
# ... 더 많은 테스트 케이스
]
# 평가 수행
before_results = [q["results_before_rerank"] for q in test_queries]
after_results = [q["results_after_rerank"] for q in test_queries]
mrr_before = mean_reciprocal_rank(before_results)
mrr_after = mean_reciprocal_rank(after_results)
ndcg_before = ndcg_at_k(before_results, k=5)
ndcg_after = ndcg_at_k(after_results, k=5)
print(f"MRR - Before: {mrr_before:.3f}, After: {mrr_after:.3f} "
f"(+{(mrr_after-mrr_before)/mrr_before*100:.1f}%)")
print(f"NDCG@5 - Before: {ndcg_before:.3f}, After: {ndcg_after:.3f} "
f"(+{(ndcg_after-ndcg_before)/ndcg_before*100:.1f}%)")
김개발 씨는 Reranking을 적용한 후 사용자들의 긍정적인 반응을 받았습니다. 하지만 경영진 보고를 준비하면서 문제에 직면했습니다.
"체감상 좋아졌다"는 말로는 부족했습니다. 객관적인 수치가 필요했습니다.
박시니어 씨가 "정보 검색 분야에서는 오래전부터 정량적 평가 지표를 사용해왔어. MRR, NDCG 같은 것들이지"라고 설명합니다.
검색 품질 측정의 핵심 개념을 이해해봅시다. 검색 시스템의 목표는 간단합니다.
관련성 높은 문서를 상위에 배치하는 것입니다. 하지만 이것을 어떻게 수치화할까요?
여러 지표가 있지만 가장 많이 쓰이는 것이 MRR과 NDCG입니다. **MRR(Mean Reciprocal Rank)**은 가장 직관적인 지표입니다.
"첫 번째 정답이 몇 등에 나타났나?"를 측정합니다. 1등에 나타나면 1/1=1.0, 2등이면 1/2=0.5, 3등이면 1/3=0.33점을 줍니다.
여러 쿼리에 대해 평균을 냅니다. 마치 시험에서 정답을 얼마나 빨리 찾았는지 측정하는 것과 같습니다.
첫 번째 선택지가 정답이면 만점, 두 번째면 반타작입니다. **NDCG(Normalized Discounted Cumulative Gain)**는 더 정교합니다.
순위뿐만 아니라 여러 개의 관련 문서를 고려합니다. 상위 문서일수록 높은 가중치를 주되, 로그 스케일로 점진적으로 감소시킵니다.
마치 올림픽 메달처럼 1등, 2등, 3등 모두 점수를 주되, 1등에게 훨씬 높은 점수를 주는 것과 같습니다. 위의 코드를 단계별로 살펴보겠습니다.
mean_reciprocal_rank() 함수는 각 쿼리에 대해 첫 번째 정답의 순위를 찾아 역수를 계산합니다. 정답이 1위면 1.0, 2위면 0.5, 찾지 못하면 0.0입니다.
모든 쿼리에 대해 평균을 냅니다. ndcg_at_k() 함수는 더 복잡합니다.
먼저 각 문서의 관련성(0 또는 1)을 추출합니다. 그 다음 DCG(Discounted Cumulative Gain)를 계산합니다.
이것은 관련성을 로그 스케일로 할인한 합입니다. 마지막으로 이상적인 순서(IDCG)로 나눠서 정규화합니다.
테스트 데이터를 보면 Reranking 전에는 정답이 2위였지만, Reranking 후에는 1위로 올라왔습니다. MRR은 0.5에서 1.0으로 100% 개선되었습니다.
실무에서 평가 시스템을 구축하는 방법을 알아봅시다. 1단계: 테스트 데이터셋 구축이 가장 중요합니다.
실제 사용자 쿼리에서 대표적인 것들을 선별합니다. 각 쿼리에 대해 어떤 문서가 관련성이 있는지 수동으로 라벨링합니다.
보통 100~500개 쿼리면 충분합니다. 실제 서비스에서는 사용자 피드백을 활용할 수 있습니다.
사용자가 클릭한 문서, 좋아요를 누른 문서를 관련성 있는 것으로 판단하는 것입니다. 이것을 암묵적 피드백이라고 합니다.
2단계: 자동화된 평가 파이프라인 구축입니다. CI/CD에 통합하여 코드 변경이 있을 때마다 자동으로 평가를 수행합니다.
성능이 떨어지면 경고를 보내는 것입니다. 3단계: A/B 테스트로 실제 사용자 반응을 측정합니다.
오프라인 평가(테스트 데이터)에서 좋은 결과가 나와도 실제 사용자에게는 다를 수 있습니다. 일부 사용자에게만 새 시스템을 적용하고 클릭률, 체류 시간, 만족도를 비교합니다.
김개발 씨는 100개 쿼리로 테스트 데이터셋을 만들었습니다. 사용자 피드백 로그에서 자주 나오는 쿼리들을 선별하고, 동료 개발자들과 함께 관련성을 라벨링했습니다.
평가 결과는 놀라웠습니다. Reranking 적용 후 MRR이 0.62에서 0.84로 35% 개선되었고, NDCG@5는 0.71에서 0.89로 25% 개선되었습니다.
이 수치를 경영진에게 보고하자 큰 호응을 얻었습니다. 주의할 점도 있습니다.
테스트 데이터의 품질이 가장 중요합니다. 편향된 데이터로 평가하면 잘못된 결론을 내릴 수 있습니다.
다양한 유형의 쿼리를 포함해야 합니다. 지표 선택도 신중해야 합니다.
MRR은 첫 번째 정답만 중요할 때 적합하고, NDCG는 여러 개의 관련 문서가 있을 때 적합합니다. 서비스 특성에 맞는 지표를 선택하세요.
과적합 위험도 있습니다. 테스트 데이터에만 최적화하면 실제 서비스에서는 성능이 떨어질 수 있습니다.
정기적으로 새로운 테스트 데이터를 추가하고 검증 세트를 별도로 관리해야 합니다. 검색 정확도 측정은 Reranking의 효과를 증명하고 지속적으로 개선하는 핵심 도구입니다.
데이터 기반으로 의사결정하고 시스템을 발전시킬 수 있습니다.
실전 팁
💡 - 실제 사용자 쿼리에서 테스트 데이터 구축
- MRR은 간단하고 직관적, NDCG는 정교하고 포괄적
- A/B 테스트로 실제 사용자 반응 검증 필수
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
ReAct 패턴 마스터 완벽 가이드
LLM이 생각하고 행동하는 ReAct 패턴을 처음부터 끝까지 배웁니다. Thought-Action-Observation 루프로 똑똑한 에이전트를 만들고, 실전 예제로 웹 검색과 계산을 결합한 강력한 AI 시스템을 구축합니다.
AI 에이전트의 모든 것 - 개념부터 실습까지
AI 에이전트란 무엇일까요? 단순한 LLM 호출과 어떻게 다를까요? 초급 개발자를 위해 에이전트의 핵심 개념부터 실제 구현까지 이북처럼 술술 읽히는 스타일로 설명합니다.
프로덕션 RAG 시스템 완벽 가이드
검색 증강 생성(RAG) 시스템을 실제 서비스로 배포하기 위한 확장성, 비용 최적화, 모니터링 전략을 다룹니다. AWS/GCP 배포 실습과 대시보드 구축까지 프로덕션 환경의 모든 것을 담았습니다.
RAG 캐싱 전략 완벽 가이드
RAG 시스템의 성능을 획기적으로 개선하는 캐싱 전략을 배웁니다. 쿼리 캐싱부터 임베딩 캐싱, Redis 통합까지 실무에서 바로 적용할 수 있는 최적화 기법을 다룹니다.
실시간으로 답변하는 RAG 시스템 만들기
사용자가 질문하면 즉시 답변이 스트리밍되는 RAG 시스템을 구축하는 방법을 배웁니다. 실시간 응답 생성부터 청크별 스트리밍, 사용자 경험 최적화까지 실무에서 바로 적용할 수 있는 완전한 가이드입니다.