[Search] ES Relevance Scoring: BM25, TF-IDF, function_score
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. 많이 등장할수록 ↑ |
| k1 | TF saturation. 기본 1.2 |
| b | length 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 = 0 | TF 무시 (term 한 번 = 여러 번 동일) |
k1 = 1.2 (기본) | 균형 |
k1 크게 | TF 가산 강조 |
b = 0 | length 정규화 없음 |
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_mode | function 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 | 3× |
| description | (기본 1) | 1× |
| tags | ^2 | 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
- score 절대값 의존 = shard / 인덱스 마다 다름. 순위만 신뢰.
- boost 남발 = 의도 분산. 적게 시작.
should만으로 ranking 시도 = AND/OR/NOT 조합 + score 가 핵심.- TF-IDF 시대 사고 = ES 7+ 는 BM25 가 기본. saturation 이해.
관련 위키
- elasticsearch-query
- elasticsearch-sort
- elasticsearch-vector-search (hybrid + RRF)
이 글의 용어 (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…
💬 댓글