기본 콘텐츠로 건너뛰기

하이브리드 검색 만들기: BM25+벡터를 RRF로 합치고 재정렬까지 직접 돌려 봤다 [RAG 검색 이해하기 4편]

3편 마지막에 "둘을 합치는 게 하이브리드 검색"이라고 적었다. 지금 내 서비스도 하이브리드 검색에 재정렬(reranker)을 붙여서 쓰고 있다. 이번 편은 합치면 무엇이 좋아지고 무엇이 그대로인지 하나씩 비교해 본다.

그래서 1~3편에서 쓴 문서를 전부 모아 14개로 만들고, 같은 질문을 키워드, 벡터, 하이브리드, 재정렬까지 한 번에 돌려 봤다. 결론부터 말하면 하이브리드가 항상 이기지는 않았다. ㅡ,.ㅡ; (1편 · 2편 · 3편)

점수가 아니라 순위로 합친다: RRF

하이브리드 검색은 키워드 검색과 벡터 검색을 둘 다 돌린 뒤 결과를 하나로 합친다. 문제는 점수의 단위가 다르다는 것이다. 이번 실험에서 같은 1위 문서가 키워드(BM25)로는 4.40점, 벡터로는 0.901점이었다. 그냥 더하면 키워드 점수가 다 이긴다.

그래서 많이 쓰는 방법이 1편에서 소개한 RRF(Reciprocal Rank Fusion)다. 점수는 버리고 순위만 쓴다. 각 검색에서 1 ÷ (60 + 순위)를 구해 더한다. 코드로는 몇 줄이다.

from collections import defaultdict

def rrf(*rank_lists, k=60):
    """각 검색 결과의 '순위'만 보고 합친다. 점수 단위가 달라도 상관없다."""
    score = defaultdict(float)
    for ranks in rank_lists:                 # ranks = [문서번호, 문서번호, ...] (1등부터)
        for rank, doc in enumerate(ranks, 1):
            score[doc] += 1 / (k + rank)     # 1등은 1/61, 2등은 1/62 ...
    return sorted(score, key=lambda d: -score[d])

keyword_ranks = bm25_search(question)        # 2편의 키워드 검색
vector_ranks  = vector_search(question)      # 3편의 벡터 검색
hybrid_ranks  = rrf(keyword_ranks, vector_ranks)

점수를 0~1로 맞춘 뒤 가중치를 줘서 더하는 방법도 있다. 다만 벡터 점수가 3편에서 본 것처럼 0.8 근처에 몰려 있어서, 맞추는 방식에 따라 결과가 많이 흔들린다. 순위만 보는 RRF가 손이 덜 간다. Elasticsearch와 OpenSearch의 최근 버전에도 RRF 기능이 들어 있다(버전과 라이선스에 따라 다르니 확인 필요).

실험 1: 질문 5개를 네 가지 방법으로

질문마다 "정답 문서가 몇 위에 나왔는지"만 정리했다. 재정렬은 하이브리드 상위 5개를 다시 줄 세운 결과다.

정답 문서가 몇 위에 나왔나: 키워드, 벡터, 하이브리드, 재정렬 비교
질문 (정답 문서)키워드벡터하이브리드(RRF)+ 재정렬
GPU 서버는 최대 며칠까지 쓸 수 있어? (제3장 사용 시간)1위1위1위1위
회사 돈으로 쓴 택시비 돌려받는 법 (출장비 정산 규정)없음1위3위1위
머신러닝 (기계학습 교육 문서도 나와야 함)없음2위3위1위
개인정보가 있는 데이터를 올려도 돼? (제4장 데이터)1위1위1위-
TD-2024-0137 (장애 대응 절차)1위1위1위-

'최대 며칠', '개인정보', '문서번호'는 모두 잘 찾았다. 그런데 표를 보면 이상한 줄이 두 개 있다. 벡터 검색은 1·2위로 찾았는데, 하이브리드로 합치니 3위로 밀렸다.

참고로 3편에서 벡터 검색이 놓쳤던 '최대 며칠' 질문을 이번에는 벡터도 1위로 찾았다(0.901). 문서 내용은 같고, 달라진 건 조각마다 앞에 "GPU 서버 사용 규정"이라는 문서 제목을 붙인 것 하나다. 3편에서 청킹 요령으로 적은 "조각마다 문서 제목을 붙인다"가 실제로 효과가 있었다.

하이브리드가 오히려 망친 질문

'택시비' 질문에서 키워드 검색은 엉뚱하게 GPU 서버 규정 제3장(1.57)과 제2장(1.16)을 올렸다. 질문의 '쓴'과 규정의 '쓸 수 있고', '쓰려는'이 형태소 분석으로 모두 '쓰다'가 되면서 겹친 거다. 출장비 문서와는 겹치는 단어가 하나도 없으니 키워드 쪽 순위에 아예 없다.

RRF는 어느 쪽이 맞는지 모른다. 두 순위를 공평하게 더할 뿐이다. 키워드 쪽 1·2위였던 GPU 규정은 벡터 쪽에서도 중간쯤 순위를 받아 점수가 두 번 더해졌고, 벡터 쪽에서만 1위이고 키워드 쪽에는 아예 없던 출장비 문서는 한 번만 더해졌다. 그래서 출장비 문서가 졌다.

1위2위3위
키워드규정 제3장 (1.57)규정 제2장 (1.16)-
벡터출장비 정산 규정 (0.832)GPU서버 신청 (0.797)딥러닝 설치 (0.792)
RRF규정 제3장 (0.0313)규정 제2장 (0.0308)출장비 정산 규정 (0.0164)

'머신러닝'도 같다. 키워드 쪽에서 '머신러닝'이 '머신'과 '러닝'으로 쪼개지면서 '딥러닝' 문서가 2위로 올라왔고(2편에서 본 그 문제), 그 바람에 벡터 쪽 2위였던 기계학습 교육 문서가 3위로 밀렸다.

즉 하이브리드는 키워드 검색의 잡음까지 같이 섞는다. 그래서 키워드 쪽을 다듬는 일이 여전히 중요하다. 2편의 사용자 사전·동의어 사전, 검색에 의미 없는 품사('쓰다', '하다' 같은 동사) 빼기, 그리고 다음에 볼 재정렬이 그 역할을 한다.

재정렬(reranker): 질문과 문서를 같이 읽고 다시 줄 세운다

벡터 검색은 질문과 문서를 따로 숫자로 바꾼 뒤 비교한다. 빠르지만 거칠다. 재정렬 모델은 질문과 문서를 한 쌍으로 같이 읽고 "이 문서가 이 질문의 답인가"를 점수로 낸다. 훨씬 정확하지만 느려서, 하이브리드로 추린 상위 몇 개에만 쓴다. 이번에는 한국어를 지원하는 bge-reranker-v2-m3로 상위 5개를 다시 줄 세웠다.

from sentence_transformers import CrossEncoder

reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")   # 한국어 지원, 처음에 약 2.3GB

candidates = hybrid_ranks[:5]                         # 하이브리드 상위 5개만
scores = reranker.predict([(question, texts[i]) for i in candidates])
reranked = [i for _, i in sorted(zip(scores, candidates), reverse=True)]
질문재정렬 1위점수2위 점수5개 처리 시간
GPU 서버는 최대 며칠까지 쓸 수 있어?규정 제3장 사용 시간0.9940.020514 ms
회사 돈으로 쓴 택시비 돌려받는 법출장비 정산 규정0.0010.000540 ms
머신러닝기계학습 기초 교육 과정 안내0.6480.025307 ms

하이브리드가 3위로 밀어냈던 두 문서가 모두 1위로 돌아왔다. 점수 모양도 벡터와 완전히 다르다. 벡터는 1위 0.901, 2위 0.867처럼 다 비슷했는데, 재정렬은 0.994와 0.020처럼 확실하게 갈린다.

'택시비' 줄도 눈여겨볼 만하다. 출장비 문서를 1위로 올리긴 했는데 점수가 0.001이다. 이 문서는 제목 한 줄뿐이고 택시비 이야기는 없으니, "이 문서로는 답할 수 없다"는 판단에 가깝다. 벡터 검색은 관계없는 문서에도 0.79를 줬다. 재정렬 점수는 이렇게 낮게 나오니 "관련 문서 없음"을 거르는 기준으로 쓸 수 있을지도 모른다. 정말 그런지는 5편에서 확인한다.

대신 비용이 있다. 모델이 약 2.3GB이고 처음 불러오는 데 53초가 걸렸다. CPU에서 문서 5개를 다시 줄 세우는 데 0.3~0.5초가 들었다. 그래서 전체 문서가 아니라 하이브리드 상위 5~20개에만 쓴다. GPU 서버에 올리면 훨씬 빨라진다.

R1·R2와 문서번호는 검색이 아니라 필터로 푼다

3편에서 '최신 버전'을 물으면 벡터 검색은 R1과 R2에 똑같이 0.899를 줬다. 키워드 검색도 3.82로 똑같았다. 하이브리드로 합쳐도 R1이 1위였다. 검색 방법을 아무리 섞어도 "R2가 더 새것"이라는 정보는 문서 글자 안에 없다.

그래서 문서마다 문서번호, 버전, 날짜를 메타데이터로 따로 달아 두고, 검색 결과를 걸렀다.

import re

# ① 질문에 문서번호가 있으면 그 문서만 남긴다
m = re.search(r"TD-\d{4}-\d{4}", question)
if m:
    hybrid_ranks = [i for i in hybrid_ranks if docs[i].get("doc_no") == m.group()]

# ② 같은 문서번호는 가장 높은 버전만 남긴다
latest = {}
for i, d in enumerate(docs):
    if "doc_no" in d and d["ver"] > latest.get(d["doc_no"], (0, -1))[0]:
        latest[d["doc_no"]] = (d["ver"], i)
keep = {i for _, i in latest.values()} | {i for i, d in enumerate(docs) if "doc_no" not in d}
hybrid_ranks = [i for i in hybrid_ranks if i in keep]
질문필터 전 (RRF)필터 후
GPU 서버 운영 매뉴얼 최신 버전1위 R1, 2위 R21위 R2 (R1은 빠짐)
TD-2024-0137 장애 나면 어떻게 해?1위 0137, 2위 0131 R2, 3위 2025-00120137 하나만

실제 서비스라면 Elasticsearch의 filter 절에 문서번호(term)를 걸고, 색인할 때 "최신 버전인지"를 필드로 달아 두면 된다. 우리 문서처럼 R1, R2로 버전이 올라가는 문서라면 이 필드가 없으면 같은 내용이 계속 중복으로 나온다.

참고로 이번에 코드를 고친 곳이 하나 있다. 형태소 분석기 Kiwi는 '2024-0137' 같은 일련번호를 W_SERIAL이라는 별도 품사로 분류하는데, 2편 코드는 이 품사를 버리고 있었다. 그래서 2편 코드로 문서번호를 검색하면 'TD' 두 글자만 맞춰서 찾게 된다. 남길 품사에 W_SERIAL을 넣고 나서야 문서번호가 제대로 맞았다.

정리: 이번 실험으로 본 하이브리드 검색의 순서

이번 실험으로 정리한 하이브리드 검색 순서 6단계
  1. 질문에 문서번호 같은 정확한 값이 있으면 먼저 필터로 범위를 좁힌다.
  2. 키워드 검색과 벡터 검색을 같이 돌린다.
  3. RRF로 순위를 합친다.
  4. 최신 버전만 남기는 식으로 메타데이터 필터를 건다.
  5. 상위 5~20개만 재정렬한다. 점수가 너무 낮으면 "관련 문서 없음" 후보로 본다(기준점은 5편에서).
  6. 남은 조각을 생성형 AI에게 근거로 넘긴다.

직접 돌려 보니 하이브리드는 "합치면 끝"이 아니었다. 합치는 순간 양쪽의 잡음도 같이 들어온다. 그 잡음을 걸러 주는 게 재정렬과 필터였다. 그런데 오늘처럼 질문 5개를 눈으로 보는 걸로는 정말 좋아졌는지 말하기 어렵다. 그래서 다음 편은 검색 품질을 숫자로 재는 방법이다.

이상 하이브리드 검색 만들기 끝!

댓글

이 블로그의 인기 게시물

싱가포르 여행 필수 준비물: G타입 변환 어댑터 (돼지코 전기 플러그)

조만간 우리 아이들이 싱가포르로 여행을 간다. 핸드폰 충전은 물론이고 다른 전기 제품 사용 때문에 어댑터가 꼭 필요하다. 해외여행 갈 때 어댑터 하나 잘못 챙겨가면 충전도 못 하고 전기 사용도 못 해서 낭패 보는 경우가 많다. 10년 전에 내가 싱가포르에 출장 갈 일이 있었는데 그때는 멀티 어댑터를 누군가에게 빌려갔었다. 이번 기회에 아이들 여행 준비물 중에 하나인 변환 플러그 검색하면서 알게 된 싱가포르 사용하는 전압과 플러그 타입, 어떤 어댑터를 챙겨야 하는지를 공유하려 한다. 싱가포르 여행을 계획하는 사람들을게 도움이 되겠지?  내 자식들 챙겨주는 마음으로 꼼꼼하게 설명해 줄 테니, 싱가포르 여행 준비물 리스트에 어댑터 관련 정보가 필요한 사람들은 꼭 참고했으면 한다. 1. 싱가포르 전기 환경 (전압과 플러그 타입) 해외여행 시 현지 전기 환경을 파악하는 것은 필수적인 여행 준비물 점검 사항이다. 싱가포르는 한국과 다른 전압 및 플러그 타입을 사용한다. 1.1 싱가포르 전압 (Voltage) 싱가포르는 230V(볼트) 전압을 사용한다. 한국은 220V를 사용하므로 전압 자체는 큰 차이가 없다. 대부분의 최신 전자기기(휴대폰 충전기, 노트북 충전기 등)는 100~240V의 프리볼트(Free Voltage)를 지원하므로, 전압 변환기(변압기)는 별도로 필요하지 않다. 1.2 싱가포르 플러그 타입 (Plug Type) 및 사용 국가 싱가포르는 Type G 플러그를 사용한다. 이는 영국식 3핀(세 발) 플러그 형태로, 한국(Type C, F)과는 모양이 완전히 다르다. 따라서 한국에서 사용하는 전자기기를 싱가포르에서 사용하려면 반드시 Type G 형태의 어댑터가 필요하다. Type G 플러그를 사용하는 주요 국가   아시아: 싱가포르, 홍콩, 말레이시아, 아랍에미리트(UAE) 등 유럽: 영국, 아일랜드, 몰타, 키프로스 등 기타: 사우디아라비아, 카리브해 일부 국가 등 이러한 국가들을 여행할 때도 Type G 어댑터 하나만 있으면 전기 ...

제주도 탐나는전 결제 사용 후기 (실물카드 미소지로 사용 실패)

제주도 여행을 앞두고 지역화폐인 '탐나는전'을 신청했다. 평소 성남사랑상품권을 잘 쓰고 있어서 제주도에서도 비슷하게 편리하게 쓸 수 있을 거라 생각했거든. 그런데... 결론부터 말하면 한 번도 써보지 못하고 환불하게 됐다. 탐나는전 신청과 설정 제주 출발 전에 미리 탐나는전을 신청했어. 신청 후 1주일 이내에 집에 도착.. 안내 브로셔를 보니 꽤 체계적으로 되어있더라고. 캐시백 혜택부터 시작해서 카드 등록, 사용법까지 자세히 나와있었어. 특히 소득공제 혜택도 있고, 한도상향 완료라는 문구도 보이니 더욱 기대가 됐어. 성남사랑상품권처럼 잘 활용할 수 있을 거라 생각했거든. 카드 등록 카드 등록을 진행했어. 인터페이스도 깔끔하고 사용하기 편해 보였어. "탐나는전 사용 가능한 카드를 신청 또는 등록해주세요!"라는 안내 문구를 보며 설레었지. 카드 정보를 직접 입력하거나 바코드로 인식할 수 있게 되어있어서 편리했어. 성남사랑상품권 앱과 비슷한 느낌이었거든. 난 바코드로 한큐에 등록.. 출발 전날인 6월 24일에 10만원을 충전했어. 탐나는전 선불카드로 등록도 완료하고, 제주도에서 맛있는 것도 사고 기념품도 살 생각에 들떠있었지. 그런데... 현실은? 제주도에 도착해서 탐나는전을 써보려고 했는데, 큰 문제가 있었어. 성남사랑상품권처럼 QR코드나 바코드로 간편하게 결제할 수 있을 거라 생각하고 실물카드를 안 가져갔거든. 그런데 제주도 매장들은 대부분 실물카드만 받더라고! QR코드나 바코드 결제를 지원하는 곳이 거의 없어서 당황했어. 성남에서는 거의 모든 곳에서 앱으로 간편결제가 되니까 당연히 제주도에서도 될 거라 생각했는데... 완전 오산이었어 😢 결국 환불... 여행 내내 한 번도 써보지 못하고, 마지막 날인 6월 29일에 결국 10만원 전액 환불했어. 충전 수수료나 환불 수수료 같은 건 없어서 다행이었지만, 뭔가 허무한 기분이었어. 후기와 조언 탐나는전 자체는 나쁘지 않은 시스템인 것 같아. 소득공제 혜택도 있고, 앱도 사용하기 편해...

미국 주식 투자 환전 수수료 비교 (증권사 환율우대 100% 찾는 법)

나는 달러 환테크와 미국 주식 투자를 병행하고 있다. 단순히 달러를 사고파는 것을 넘어, 환전한 달러로 바로 미국 주식을 매매할 수 있어야 하므로 증권사 환전 서비스를 이용하는 것은 필수다. 혹시 미국 주식을 아직도 한번도 매매 못한 사람을 위해 일반적인 프로세스는 아래와 같다. 이때 가장 중요한 것이 바로 '환전수수료' , 즉 환율우대 혜택이다. 1%, 0.1%의 수수료 차이가 투자 수익률에 큰 영향을 미치기 때문이다. 오늘은 미국 주식 투자를 위한 증권사별 환전 서비스를  비교하고, 환율우대 혜택을 가장 많이 받을 수 있는 곳이 어디인지 직접 정리해 본다.  1. 들어가기 전: 환전수수료와 환율우대란? 우리가 달러를 살 때, 기준이 되는 '매매기준율'에 증권사가 수수료를 붙여서 판매한다. 이 수수료를 '환전 스프레드' 라고 하며, 보통 1달러당 5원~10원 정도다. '환율우대' 란 이 환전 스프레드를 깎아주는 것을 의미한다. 예를 들어, 환율우대 95% 는 내가 내야 할 환전수수료의 95%를 할인해준다는 뜻이다. 당연히 우대율이 높을수록 우리에게 유리하며, 환율우대 100%는 환전수수료가 0원 이라는 의미다. 2. 증권사별 환전 우대율 및 이용 시간 비교 (우대율 높은 순) ※ 중요: 아래 정보는 신규 또는 휴면 고객 대상 이벤트 기준으로, 2025년 8월 말 현재 시점으로 재확인한 내용이다. 이벤트 기간이나 조건은 언제든 변경될 수 있으니, 거래 전 반드시 해당 증권사에 직접 확인해야 한다. 참고로 난 한투로 미국 주식을 하고 있는데 90% 우대를 받고 있다. 아직은 작은 금액이라 무시하고 있지만 억단위로 넘어가면 위 증권사 계좌로 넘어갈 계획이다. 3. 서비스별 심층 분석 및 투자 전략 3.1 조건부 최강자: NH투자증권 (나무증권)  NH투자증권은 '원화주문' 이라는 독특한 서비스를 통해 차별점을 가진다. 핵심 장점: 24시간 100% 자동 환율우대: 미국 주식 시장이 열리...