logo

Rank Fusion

Rank Fusion

  • BM25 등 sparse 검색의 장점
    • 별도의 모델 및 훈련 과정이 필요 없음
    • 검색 속도가 빠름
    • 학습시키지 않은 분야(out-of-domain)에서 성능이 높음
  • 벡터 검색 등 dense 검색의 장점
    • 동의어 등을 비롯한 단어/문장의 의미 반영
  • Rank Fusion: 서로 다른 검색 결과 목록에서 등수를 결합

Rank fusion

  • Reciprocal Rank Fusion: 등수의 역수 합계 (보통 𝑘 = 60)
  • Inverse Square Rank: log(목록의 개수) × 등수 제곱의 역수 합계
  • Rank Biased Centroids: 𝜙^등수 합계 (0 < 𝜙 < 1)

Benham & Culpepper (2017) Risk-reward trade-offs in rank fusion

성능 실험

  • 단순 BM25로 검색하는 것과 비교할 때, Rank Fusion을 사용하는 것이 성능이 더 좋을 때(Win)도 있고, 나쁠 때(Loss)도 있음
  • ISR, logISR을 제외하면 대체로 비슷하지만 RRF가 성능이 좀 더 좋음

SQL 문

--  (1) 벡터 검색 결과
WITH vector_result AS (
SELECT
  id,
  title,
  title_embedding <=> CAST(:query_emb AS vector) AS score,
  ROW_NUMBER() OVER (
    ORDER BY title_embedding <=> CAST(:query_emb AS vector)
  ) AS rank_vec
FROM wikipages
LIMIT 100
),
-- (2) 키워드 검색 결과
text_result AS (
SELECT
  id,
  title,
  title_nouns <@> :query AS score,
ROW_NUMBER() OVER (
ORDER BY title_nouns <@> :query
) AS rank_text
FROM wikipages
LIMIT 100
),
-- (3) 검색 결과 합치기
merged AS (
SELECT
  COALESCE(v.id, t.id) AS id,
  COALESCE(v.title, t.title) AS title,
  v.rank_vec,
  t.rank_text
FROM vector_result v
FULL OUTER JOIN text_result t ON v.id = t.id
)
-- (4) RRF 계산
SELECT
  id,
  title,
  rank_vec,
  rank_text,
  (
  COALESCE(1.0 / (60 + rank_vec), 0.0) +
  COALESCE(1.0 / (60 + rank_text), 0.0)
  ) AS rrf_score
FROM merged
ORDER BY rrf_score DESC
LIMIT 10;

Common Table Expression (CTE)

  • WITH는 복잡한 SQL을 여러 단계로 나누어 임시 테이블처럼 사용할 수 있게
WITH temp AS (
SELECT ...
)
SELECT * FROM temp;
  • 현재 쿼리에서는 벡터 검색 결과, 키워드 검색 결과, 합친 결과를 나타내는데 사용
WITH
vector_result AS (...),
text_result AS (...),
merged AS (...)

ROW_NUMBER() OVER (ORDER BY ...)

  • ROW_NUMBER(): 윈도우 함수. 순서대로 행 번호를 붙임
  • OVER는 SQL의 윈도우 함수의 적용 방식을 지정
  • OVER에서 사용하는 표현:
    • PARTITION BY: 집단으로 나누어 적용(GROUP BY와 비슷하지만 행의 수는 원래대로 유지)
    • ORDER BY: 정렬 순서대로 적용

COALESCE

  • NULL이 아닌 첫 번째 값을 반환
  • 검색 결과에서 순위에 없을 경우 id나 title이 누락되어 NULL이 될 수 있음
  • 순위에 있는 결괏값을 사용
COALESCE(v.id, t.id)

SQL 문 실행

# 1. 검색할 질문 설정
search_query = "컴퓨터"
# 2. 질문을 임베딩 벡터로 변환 (기존에 로드한 sbert 모델 사용)
query_embedding = sbert.encode(search_query).tolist()
query_embedding_json = json.dumps(query_embedding)
# 3. 쿼리
rrf_search_query = sqlalchemy.text(f"""여기에 SQL 문을 넣으세요""")
# 4. 결과 출력
pd.read_sql(
rrf_search_query, engine,
params={"query_emb": query_embedding_json, "query": search_query})
Previous
임베딩을 이용한 검색