검색 조합 심화
RAG에서 검색 결과는 답변 품질을 거의 직접 결정합니다. LLM이 아무리 좋아도 엉뚱한 문서를 context로 받으면 답변도 흔들립니다.
Hybrid Search는 후보를 넓게 찾는 역할을 하고, Reranker는 그 후보들 중 질문에 더 맞는 문서를 위로 올리는 역할을 합니다.
RAG 검색 파이프라인
1단계
질문 입력
사용자가 자연어로 질문합니다. 질문 안에는 키워드와 의도가 함께 들어 있습니다.
2단계
Hybrid Retrieval
BM25와 vector search가 각각 후보 문서를 가져옵니다.
3단계
Reranking
후보 문서와 질문을 다시 비교해 관련도가 높은 chunk를 앞으로 보냅니다.
4단계
Generation
상단 후보 문서를 context로 넣어 답변을 생성합니다.
왜 Reranker가 따로 필요한가
Retriever는 빠르게 넓게 찾는다
초기 검색은 많은 문서를 대상으로 돌아야 하므로 속도가 중요합니다. 그래서 대략 관련 있는 후보를 빠르게 모읍니다.
Reranker는 느리지만 더 꼼꼼하다
Reranker는 후보 수가 줄어든 뒤 질문과 문서를 더 자세히 비교합니다. 보통 전체 문서 대신 상위 후보에만 적용합니다.
RAG에서는 상위 몇 개가 중요하다
최종 prompt에 들어가는 문서 수는 제한됩니다. 상위 3개, 5개 안에 맞는 근거가 들어가야 답변 품질이 좋아집니다.
비용과 지연 시간이 늘어난다
Reranker는 추가 계산입니다. 품질이 좋아질 수 있지만 응답 시간이 늘 수 있어 후보 개수를 제한해야 합니다.
RAG에서 각 단계가 막는 문제
| 문제 | Hybrid Search | Reranker |
|---|---|---|
| 정확한 키워드를 놓침 | BM25가 보완 | 직접 해결보다는 후보 정렬에 기여 |
| 표현이 다른 문서를 놓침 | Vector Search가 보완 | 후보 중 질문에 맞는 문서 재정렬 |
| 후보가 너무 많음 | 넓게 가져오는 단계 | 최종 context에 넣을 후보를 좁힘 |
| 답변 근거가 약함 | 관련 문서 후보 확보 | 근거로 쓸 문서의 우선순위 개선 |
작은 실험: 후보 검색과 재정렬 분리하기
아래 코드는 1차 검색 점수와 reranker 점수를 분리합니다. Retriever가 넓게 가져온 후보의 순서가 reranker 이후 어떻게 달라지는지 보는 실험입니다.
docs = [
{"id": 1, "text": "RAG는 검색된 문서를 context로 넣어 답변한다."},
{"id": 2, "text": "Reranker는 검색 후보를 질문 관련도 기준으로 다시 정렬한다."},
{"id": 3, "text": "Vector DB는 embedding vector를 저장하고 검색한다."},
{"id": 4, "text": "Hybrid Search는 BM25와 vector search를 함께 사용한다."},
]
query = "RAG에서 hybrid search와 reranker를 왜 같이 쓰나요?"
def rough_retriever_score(doc):
query_terms = set(query.lower().replace("?", "").split())
doc_terms = set(doc["text"].lower().replace(".", "").split())
return len(query_terms & doc_terms)
def reranker_score(doc):
score = rough_retriever_score(doc)
if "Reranker" in doc["text"]:
score += 3
if "Hybrid Search" in doc["text"]:
score += 3
if "context" in doc["text"]:
score += 1
return score
candidates = sorted(docs, key=rough_retriever_score, reverse=True)[:4]
reranked = sorted(candidates, key=reranker_score, reverse=True)
print("retriever order")
for d in candidates:
print(d["id"], rough_retriever_score(d), d["text"])
print()
print("reranker order")
for d in reranked:
print(d["id"], reranker_score(d), d["text"])
context = reranked[:2]
print()
print("context used by generation")
for d in context:
print("-", d["text"])
관찰할 것
- retriever order와 reranker order가 같은지 비교합니다.
- Reranker와 Hybrid Search가 들어간 문서가 상단 후보로 올라오는지 봅니다.
- 최종 context에 들어가는 문서 수를 제한하는 이유를 확인합니다.
예상 출력의 모양
retriever order
4 1 Hybrid Search는 BM25와 vector search를 함께 사용한다.
1 0 RAG는 검색된 문서를 context로 넣어 답변한다.
reranker order
4 4 Hybrid Search는 BM25와 vector search를 함께 사용한다.
2 3 Reranker는 검색 후보를 질문 관련도 기준으로 다시 정렬한다.
context used by generation
- Hybrid Search는 BM25와 vector search를 함께 사용한다.
- Reranker는 검색 후보를 질문 관련도 기준으로 다시 정렬한다.
적용할 때의 기준
- 검색 후보가 충분히 넓지 않으면 reranker를 붙여도 좋은 문서가 후보 안에 없습니다.
- 후보는 괜찮은데 최종 context에 엉뚱한 문서가 들어가면 reranker가 효과를 냅니다.
- 질문마다 rerank할 후보 수를 무작정 늘리면 비용과 지연 시간이 커집니다.
- RAG 평가는 최종 답변만 보지 말고 검색된 chunk가 맞는지도 따로 봐야 합니다.
기억할 것
- Hybrid Search는 후보를 넓게 잡는 장치입니다.
- Reranker는 후보의 순서를 더 정교하게 바꾸는 장치입니다.
- RAG에서는 검색 결과의 상위 몇 개가 답변의 근거가 됩니다.