본문으로 건너뛰기
김신건의 로그

[Search] ES Relevance Scoring: BM25, TF-IDF, function_score

· 수정 · 📖 약 1분 · 416자/단어 #elasticsearch #bm25 #scoring #relevance #ranking
BM25, Okapi BM25, TF-IDF, relevance score, _score, k1, b parameter, function_score, rescore

정의

Relevance Score (_score) = 쿼리와 문서의 매칭 강도. ES 7+ 의 기본은 BM25 (TF-IDF 의 발전형).

BM25 공식

score(D, Q) = Σ IDF(qi) · ((f(qi, D) · (k1 + 1)) / (f(qi, D) + k1 · (1 - b + b · |D|/avgdl)))

qi      = 쿼리 term i
f(qi,D) = D 에서 term qi 의 frequency
|D|     = 문서 길이 (token 수)
avgdl   = 평균 문서 길이
k1, b   = 튜닝 파라미터
부분의미
IDF (qi)희귀할수록 점수 ↑
f (qi, D)term frequency. 많이 등장할수록 ↑
k1TF saturation. 기본 1.2
blength normalization. 기본 0.75

TF-IDF 와의 차이

flowchart LR
    TFIDF["TF-IDF<br/>TF × IDF<br/>(선형 증가)"] --> Issue1[term 100번 = 점수 100배]
    BM25["BM25<br/>TF saturated<br/>(점진 포화)"] --> Better[term 많이 등장해도 *상한선*]

TF 가 무한 늘면 점수도 무한. BM25 는 적절히 늘다 saturate. 검색 직관에 더 가깝다.

TF 별 BM25 score (k1 = 1.2)
BM25 는 TF 가 크면 *saturate*. TF-IDF 는 *선형 증가*.

k1, b 튜닝

의미
k1 = 0TF 무시 (term 한 번 = 여러 번 동일)
k1 = 1.2 (기본)균형
k1 크게TF 가산 강조
b = 0length 정규화 없음
b = 0.75 (기본)균형
b = 1완전 length 정규화
PUT /products
{
  "settings": {
    "index": {
      "similarity": {
        "custom_bm25": { "type": "BM25", "k1": 1.5, "b": 0.5 }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": { "type": "text", "similarity": "custom_bm25" }
    }
  }
}

explain API

GET /products/_search?explain=true
{ "query": { "match": { "title": "rabbit hat" } } }
"_explanation": {
  "value": 2.85,
  "description": "sum of:",
  "details": [
    { "value": 1.34, "description": "weight(title:rabbit in 0) ..." },
    { "value": 1.51, "description": "weight(title:hat in 0) ..." }
  ]
}

왜 이 점수 인지 디버깅. Kibana 의 Profile UI 가 더 시각적.

function_score (커스텀 boost)

자세한 건 elasticsearch-query 의 function_score.

{
  "query": {
    "function_score": {
      "query": { "match": { "title": "keyboard" } },
      "functions": [
        { "field_value_factor": { "field": "rating", "modifier": "log1p", "factor": 1.2 } },
        { "gauss": { "created_at": { "origin": "now", "scale": "30d", "decay": 0.5 } } }
      ],
      "score_mode": "sum",
      "boost_mode": "multiply"
    }
  }
}
score_mode함수들의 결합
multiply (기본)
sum
avg평균
max, min, first
boost_modefunction vs query 결합
multiply (기본)function × query
sum, replace, avg, max, min

Rescore (top-K 재정렬)

{
  "query": { "match": { "title": "rabbit hat" } },
  "rescore": {
    "window_size": 50,
    "query": {
      "rescore_query": {
        "match_phrase": { "title": { "query": "rabbit hat", "slop": 1 } }
      },
      "query_weight": 0.7,
      "rescore_query_weight": 1.3
    }
  }
}

값싼 쿼리로 top-K비싼 쿼리로 top-K 만 재정렬. 경제적 ranking.

boost 의 영향

{
  "multi_match": {
    "query": "rabbit",
    "fields": ["title^3", "description", "tags^2"]
  }
}
필드boost점수
title^3
description(기본 1)
tags^2

제목 매칭이 본문 매칭보다 3배 가치.

Score normalization 함정

shard 1: doc A score = 5.0
shard 2: doc B score = 4.5

→ 합칠 때 *상대 비교* OK
→ *절대값* 은 *shard 간 다르다*. 비교 불가

CAUTION

0~1 정규화 불가. 상대 순위만 의미. DFS_QUERY_THEN_FETCH (정확 IDF) 옵션 가능하나 비쌈.

흔한 함정

WARNING

  1. score 절대값 의존 = shard / 인덱스 마다 다름. 순위만 신뢰.
  2. boost 남발 = 의도 분산. 적게 시작.
  3. should 만으로 ranking 시도 = AND/OR/NOT 조합 + score 가 핵심.
  4. TF-IDF 시대 사고 = ES 7+ 는 BM25 가 기본. saturation 이해.

관련 위키

이 글의 용어 (3개)
[Search] ES Query: must / should / filter / must_notsearch
정의 ES 의 Query DSL = JSON 기반 표현력 풍부한 쿼리 언어. bool query + leaf query 의 조합. bool query: 4가지 절 | 절 | AN…
[Search] ES Sort: relevance, field, script, geosearch
정의 ES 의 기본 정렬 = relevance score (BM25) 내림차순. 명시적 sort 로 변경 가능. docvalues 로 메모리 효율적 정렬. 5가지 정렬 1. Fi…
[Search] ES Vector Search: kNN, ELSER, RAGsearch
정의 ES 의 벡터 검색 = 임베딩 기반 의미 검색. kNN (Approximate Nearest Neighbor) 으로 수억 벡터 안에서 가까운 K개 찾기. 2026 시점의 E…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기