logo

검색 결과의 순위 결합

검색 점수 또는 순위로 결과를 결합하는 일곱 가지 방법을 비교하고, RRF로 키워드 검색과 벡터 검색의 결과를 합친다.

하이브리드 검색과 순위 결합

  • 하이브리드 검색은 키워드 검색과 벡터 검색의 결과를 하나의 목록으로 결합
    • 키워드 검색(BM25)은 질문과 일치하는 단어가 있는 청크에 높은 점수 부여
    • 벡터 검색은 질문과 의미가 가까운 청크에 높은 점수 부여
  • 각 검색기가 매긴 점수 또는 각 목록에서의 순위로 청크별 결합 점수를 계산한 뒤 높은 순으로 정렬

검색 점수를 사용하는 방법

  • 점수 정규화는 검색기마다 다른 점수 척도를 맞추는 과정
    • BM25 점수에는 고정된 상한이 없고, 코사인 유사도의 범위는 −1에서 1
    • 원점수를 그대로 더하면 점수 범위가 큰 검색기의 영향이 커지므로 합산 전에 정규화
xmax​−xmin​x−xmin​​
  • 각 청크가 등장한 결과 목록만 계산에 포함
    • si​는 검색기 i가 해당 청크에 준 정규화 점수
    • m은 해당 청크가 등장한 결과 목록의 수
방법 청크의 결합 점수 해석
CombSUM ∑i​si​ 검색기별 점수의 합계
CombMNZ m∑i​si​ 점수 합계에 목록 수를 곱해 여러 검색기가 함께 찾은 청크에 가중치 부여
  • 정규화한 점수로 청크 A와 B를 비교
    • 청크 A는 키워드 검색에서 0.9점. 벡터 검색 결과에는 미포함
    • 청크 B는 키워드 검색에서 0.5점, 벡터 검색에서 0.4점
  • CombSUM에서는 A와 B 모두 0.9점으로 동점(0.9=0.5+0.4)
  • CombMNZ에서는 A가 1×0.9=0.9점, B가 2×(0.5+0.4)=1.8점으로 두 검색 결과에 모두 등장한 B가 상위

순위를 사용하는 방법

  • 청크가 등장한 각 목록에서 순위를 점수로 바꾼 뒤 합산
    • 원점수 대신 순위만 쓰므로 점수 척도가 달라도 정규화가 필요 없음
    • ri​는 검색기 i의 결과 목록에서 해당 청크의 순위(1위부터 시작)
    • ni​는 그 목록에 포함된 청크 수
방법 청크의 결합 점수 해석
보르다(Borda) ∑i​ni​ni​−ri​+1​ 한 등수 내려갈 때마다 같은 폭으로 점수 감소. 결과가 10개면 1위는 1점, 2위는 0.9점, 10위는 0.1점
RRF (Reciprocal Rank Fusion) ∑i​k+ri​1​ 순위에 상수 k를 더한 값의 역수를 합산. 흔히 쓰는 k=60에서는 1위와 2위가 각각 1/61, 1/62점으로 차이가 작아 여러 목록에 든 청크에 유리
ISR (Inverse Square Rank) m∑i​ri2​1​ 순위 제곱의 역수를 합산한 뒤 목록 수를 곱해 상위권과 중복 등장에 가중치 부여. 한 목록에서 2위의 기여분은 1위의 1/4
logISR log(m)∑i​ri2​1​ 목록 수가 늘어날 때 가중치는 ISR보다 완만하게 증가. 한 목록에만 등장한 청크는 log(1)=0이므로 1위여도 0점
RBC (Rank Biased Centroids) ∑i​(1−ϕ)ϕri​−1 한 등수 내려갈 때마다 직전 점수에 ϕ를 곱해 상위권에 점수 집중. 0<ϕ<1에서 ϕ가 작을수록 점수가 빠르게 감소. ϕ=0.5이면 1위부터 3위까지는 각각 0.5점, 0.25점, 0.125점

출처 — Benham과 Culpepper(2017), 「Risk-Reward Trade-offs in Rank Fusion」 표 1

같은 검색 결과에서 RRF와 ISR 비교

청크 키워드 검색 벡터 검색
A 1위 목록에 없음
B 3위 3위
  • RRF(k=60)에서는 두 검색기가 함께 찾은 B가 더 높은 순위
    • A: 1/(60+1)≈0.0164점
    • B: 1/(60+3)+1/(60+3)≈0.0317점
  • ISR에서는 키워드 검색 1위인 A가 더 높은 순위
    • A: 1×1/12=1점
    • B: 2×(1/32+1/32)=4/9점

검색 순위 합치기

  • 앞 실습의 search_bm25와 search_dense로 같은 질문을 검색
  • search_hybrid는 두 목록의 순위를 RRF 점수로 바꿔 청크별로 합산
def search_hybrid(query, k=3, rrf_k=60):  # 두 검색기의 전체 순위를 RRF로 합침
    fused = {}
    for results in (search_bm25(query, len(chunks)), search_dense(query, len(chunks))):
        for rank, (i, _) in enumerate(results, start=1):
            fused[i] = fused.get(i, 0) + 1 / (rrf_k + rank)
    top = sorted(fused.items(), key=lambda item: -item[1])[:k]
    return [(i, round(score, 4)) for i, score in top]
query = "불량품이면 반품 기한이 더 길어요?"
print("BM25:", search_bm25(query))
print("벡터:", search_dense(query))
print("RRF:", search_hybrid(query))
실행 결과
BM25: [(10, 4.37), (11, 3.04), (13, 2.88)]
벡터: [(13, 0.548), (10, 0.538), (15, 0.52)]
RRF: [(10, 0.0325), (13, 0.0323), (11, 0.0318)]
  • 정답은 불량품의 반품 기한을 담은 10번 청크. 벡터 검색은 반품 후 환불 절차를 다룬 13번을 1위로 올림.
  • 10번은 BM25 1위, 벡터 2위라 1/61+1/62≈0.0325. 13번은 BM25 3위, 벡터 1위라 1/63+1/61≈0.0323. 두 목록에서 모두 높은 10번이 1위.
  • 항상 나아지는 것은 아님. "키캡 뺐는데 환불 돼요?"는 BM25 1위가 정답 12번이지만, 벡터 검색 상위권에 없어 RRF 상위 3개에서 빠짐. query를 바꿔 직접 비교해 볼 것.

퀴즈

문제 1 / 7맞음: 0힌트: 0틀림: 0채점중: 0남음: 7

키워드 검색과 벡터 검색의 점수를 정규화하는 이유는?

  • ○점수의 척도를 맞추기 위해
  • ○검색 결과의 개수를 맞추기 위해
  • ○청크의 순위를 일치시키기 위해

퀴즈를 풀려면 대화형 기능을 불러와야 합니다.