Domain-tuned Reranker — Cohere 후퇴 이후
앞선 세 편에서 Hybrid retriever와 Cohere rerank를 세웠다. 그런데 프로젝트에서 어느 시점 이런 관측이 나왔다.
Cohere rerank를 붙였더니 오히려 지표가 떨어지는 케이스가 있다.
이번 글은 그 이유와 그 이후의 방향 — domain-tuned reranker — 를 정리한다. Cross-encoder 원리부터 BGE-reranker fine-tuning의 접근까지.
Cross-encoder vs Bi-encoder
먼저 용어. Reranker의 두 계열을 먼저 짚고 간다.
Bi-encoder (dense retriever)
query와 document를 각각 임베딩한다.
그 임베딩들 사이의 유사도(cosine 등)로 후보를 뽑는다.
Dense retriever가 여기 속한다. 장점은 미리 임베딩을 다 만들어 놓을 수 있어서 검색이 빠르다는 것.
저장 시: document → embedding → 벡터 DB
검색 시: query → embedding → nearest neighbor 검색
Cross-encoder (reranker)
query와 document를 하나의 입력으로 붙여서 넣고
"이 pair가 얼마나 관련 있는가"를 직접 스코어링한다.
Cohere rerank, BGE-reranker 같은 게 여기 속한다. Bi-encoder와 달리 미리 계산해 둘 수 없다. 매 query마다 query+document pair를 모델에 넣어야 한다.
검색 시: query + doc_1 → cross-encoder → score_1
query + doc_2 → cross-encoder → score_2
...
느리다. 그래서 top-k(예: 20~30)로 좁혀놓고 그 안에서만 돌린다. 그 대신 query와 document를 동시에 attention하기 때문에 훨씬 정확하다.
Cohere rerank가 유효한 이유는 이 정확도 때문이다. 잘 되면 Hybrid top-20을 top-5로 정확히 좁혀준다.
Cohere rerank 후퇴 관측
프로젝트 evaluation set에서 Cohere rerank 도입 전후를 비교했다.
baseline (Hybrid) +Cohere rerank
Recall@5 0.71 0.68
MRR 0.62 0.58
faithfulness 0.919 0.911
세 지표 모두 소폭 떨어졌다. Cohere rerank는 성능이 검증된 상용 API인데 왜 이런 결과가 나왔나. 원인을 하나씩 봤다.
원인 1: 도메인 미스매치
Cohere rerank는 general-domain으로 학습된 cross-encoder다. "관련성"의 감각이 일반 검색(웹, 위키, 뉴스)에 맞춰져 있다.
프로젝트 도메인은 한국 상장사 공시·재무·뉴스다. 이 도메인의 "관련성"은 좀 다르다.
질의: "A사 3분기 매출은?"
일반 관련성: 매출 얘기가 나오면 어디든 관련 있음
도메인 관련성:
- "3분기 사업보고서" → 매우 관련
- "2분기 사업보고서" → 관련은 있지만 시점이 다름
- "동종업계 매출 뉴스" → 회사가 다름, 관련 낮음
- "A사 CEO 인터뷰" → 매출 언급 있어도 근거로는 약함
Cohere는 시점(분기)과 회사(ticker)의 세밀한 차이를 도메인만큼 못 잡는다. 그래서 "관련은 있지만 근거로 못 쓰는" 문서를 위로 끌어올리는 경우가 생긴다.
원인 2: 언어
Cohere rerank는 다국어 지원이지만, 한국어 성능은 영어보다 여전히 낮다. 도메인이 한국어 재무 텍스트라 이 격차가 실제로 드러났다.
원인 3: 짧은 컨텍스트에서의 rerank 이득이 적다
이건 프로젝트 특성. 상류 노이즈 컷(rag-quality/01) 이후 top-20이 이미 상당히 좋은 상태였다. Rerank가 개선할 여지가 줄어든 상태.
방향: domain-tuned reranker
Cohere rerank를 그냥 빼는 건 답이 아니다. Cross-encoder의 정확도 이득 자체는 여전히 크다. 문제는 general-domain이라는 것.
방향은 도메인 특화 cross-encoder를 자체로 얹는 것이다. 후보는 몇 개 있다.
1) BGE-reranker-v2-m3 → 다국어 지원, 오픈소스, 로컬 실행
2) bge-reranker-v2-gemma → 성능 더 좋음, 무거움
3) mxbai-rerank-large-v1 → 영어 우세
BGE-reranker-v2-m3를 baseline으로 잡고, 도메인 pair 데이터로 소량 fine-tune 하는 방향이 실무적으로 가장 무난하다.
BGE reranker 로컬 실행
로컬 실행 자체는 어렵지 않다.
from FlagEmbedding import FlagReranker
reranker = FlagReranker("BAAI/bge-reranker-v2-m3", use_fp16=True)
pairs = [(query, doc) for doc in candidates]
scores = reranker.compute_score(pairs)GPU가 있으면 batch로 돌린다. CPU에서도 top-20 정도는 sub-second로 가능하다.
Baseline(fine-tune 없이) BGE-reranker-v2-m3만 넣어봐도 프로젝트 evaluation에서는 Cohere보다 살짝 나은 결과가 나왔다.
+Cohere +BGE-v2-m3 (no tune)
Recall@5 0.68 0.72
MRR 0.58 0.63
여전히 baseline(rerank 없음, 0.71) 대비 큰 차이는 아니다. 도메인 튜닝을 해야 진짜 이득이 나온다.
도메인 fine-tune 접근
Fine-tune에 필요한 건 (query, positive_doc, negative_doc) triplet이다.
(query, positive_doc, negative_doc)
↓
loss = max(0, score(q, neg) - score(q, pos) + margin)
Triplet 구성이 실제 fine-tune의 절반이다.
Positive pair는 어디서 오나
Evaluation set이 있으면 그게 답이다.
질의 라벨: "A사 3분기 매출은?"
정답 문서: "[정기]A사 3분기 사업보고서"
이 쌍이 그대로 (query, positive_doc)이 된다.
Evaluation set이 없으면 사용자 로그에서 클릭·인용 기록으로 만들 수 있다. 아니면 LLM을 써서 문서를 보고 "이 문서에 답할 수 있는 질문"을 생성시켜 (query, doc) 쌍을 만드는 방법도 있다.
Hard-negative mining
Negative는 쉽다 — 아무거나 잡아 넣어도 negative다. 그런데 그러면 학습이 잘 안 된다. 어려운 negative여야 학습이 된다.
Hard negative의 정의는 이렇다.
"query와 관련은 있어 보이지만, 실제로는 정답이 아닌 문서"
프로젝트 도메인에서 하드 negative는 뻔한 곳에 있다.
질의: "A사 3분기 매출"
positive: [정기]A사 3분기 사업보고서
hard negatives:
- [정기]A사 2분기 사업보고서 (같은 회사, 시점 다름)
- [정기]B사 3분기 사업보고서 (같은 시점, 회사 다름)
- [기재정정]A사 3분기 사업보고서 (같은 문서의 정정본)
- A사 3분기 IR 자료 (같은 시점, 문서 유형 다름)
앞선 rag-quality/01에서 blacklist로 잘랐던 문서 유형들이 여기서 다시 등장한다. blacklist는 "애초에 인덱스에 안 넣는" 방식이었고, hard-negative mining은 "인덱스엔 있되 rerank가 아래로 밀어내게 학습시키는" 방식이다. 둘은 대체가 아니라 보완 관계다.
소량 fine-tune으로 어디까지
BGE reranker fine-tune은 데이터가 많지 않아도 도메인 이득이 크다. 경험상 이 정도 규모면 유의미하다.
1,000 triplet → 도메인 감각 형성 시작
5,000 triplet → 안정적인 도메인 우세
10,000+ → 마진 수렴, ROI 감소
프로젝트에서는 evaluation set(300 라벨)과 사용자 로그를 조합해서 2,000 triplet을 만드는 방향으로 접근할 예정이다.
Rerank를 언제 안 쓰나
여기서 하나 짚어야 할 것. Rerank가 항상 좋은 건 아니다. 다음 조건에서는 rerank를 빼는 것도 정답이다.
1) Retriever가 이미 top-5 안에서 정답을 잘 뽑음
→ rerank가 개선할 여지 없음
2) Latency 예산이 빡빡함
→ cross-encoder 호출이 부담
3) 도메인 특화 rerank 데이터가 없고 general model이 안 맞음
→ Cohere 후퇴 관측처럼 오히려 손해
프로젝트에서 Cohere 대신 baseline으로 잠깐 돌아간 이유가 이거다. "완벽하지 않은 rerank는 안 쓰는 게 낫다"는 판단.
시리즈 위치
이 편이 rag-retriever 시리즈의 4번째다.
01: Dense·Sparse·Hybrid
02: Reranker + Metadata Filter
03: 검색 품질과 평가 체계
04 (여기): Domain-tuned Reranker ← rerank의 심화판
02에서 소개했던 Cohere rerank가 "왜 도메인에서 후퇴할 수 있는지", 그리고 그 이후의 방향이 이 편이다.
이 편의 다음 iteration은 실제 fine-tune 결과 리포트가 될 예정이다. 2,000 triplet으로 BGE-reranker-v2-m3를 튜닝하고, evaluation set에서 Recall@5·MRR·faithfulness가 어떻게 움직였는지 실측 리포트로.