Elasticsearch 相关性算分详解:TF-IDF 与 BM25 算法

157 阅读3分钟

一、相关性算分基础

Elasticsearch 使用相关性评分(relevance scoring)来确定文档与查询的匹配程度。评分越高,文档越相关。在 Elasticsearch 的发展历程中,主要使用过两种评分算法:

算法Elasticsearch 版本特点
TF-IDF5.x 之前经典的词频-逆文档频率算法
BM255.x 及以后更现代的改进算法,成为默认

二、TF-IDF 算法详解

1. TF-IDF 组成要素

TF-IDF = TF (词频) × IDF (逆文档频率) × 字段长度归一化

TF (Term Frequency) :

  • 词项在当前文档中出现的频率
  • 计算公式:tf(t in d) = √frequency

IDF (Inverse Document Frequency) :

  • 词项在所有文档中的稀有程度
  • 计算公式:idf(t) = 1 + log(numDocs / (docFreq + 1))

字段长度归一化(Field-length norm) :

  • 字段越短,权重越高
  • 计算公式:norm(d) = 1 / √numTerms

2. TF-IDF 实际示例

假设有以下3个文档:

文档1: "苹果 苹果 香蕉"
文档2: "苹果 橙子"
文档3: "苹果 香蕉 葡萄"

查询:"苹果 香蕉"

TF计算:

  • 文档1中"苹果"的TF: √2 ≈ 1.414
  • 文档1中"香蕉"的TF: √1 = 1

IDF计算:

  • "苹果"的IDF: 1 + log(3/3) = 1
  • "香蕉"的IDF: 1 + log(3/2) ≈ 1.405

字段长度归一化:

  • 文档1有3个词项: 1/√3 ≈ 0.577

文档1最终评分:

score = (TF("苹果") * IDF("苹果") + TF("香蕉") * IDF("香蕉")) * norm
      = (1.414*1 + 1*1.405) * 0.577 ≈ 1.627

三、BM25 算法详解

1. BM25 改进点

BM25 (Best Match 25) 是 TF-IDF 的改进版本,主要优化:

  • 词频饱和度控制:避免高频词过度影响
  • 字段长度归一化更灵活:通过参数调节

2. BM25 公式

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

参数说明:

  • k1: 控制词频饱和度 (默认1.2)
  • b: 控制字段长度影响 (默认0.75)
  • |D|: 当前文档字段长度
  • avgdl: 平均字段长度

3. BM25 实际示例

使用相同文档集:

参数设置:

  • k1 = 1.2
  • b = 0.75
  • avgdl = (3+2+3)/3 ≈ 2.667

文档1评分计算:

f("苹果",D1)=2, f("香蕉",D1)=1
|D1|=3

TF("苹果")部分: (2*(1.2+1))/(2+1.2*(1-0.75+0.75*3/2.667)) ≈ 2.2/2.81 ≈ 0.782
TF("香蕉")部分: (1*(1.2+1))/(1+1.2*(1-0.75+0.75*3/2.667)) ≈ 2.2/1.81 ≈ 1.215

IDF("苹果")=1, IDF("香蕉")≈1.405

score = 1*0.782 + 1.405*1.215 ≈ 0.782 + 1.707 ≈ 2.489

四、Elasticsearch 实际操作

1. 查看评分详情

GET /your_index/_search
{
  "query": {
    "match": {
      "content": "苹果 香蕉"
    }
  },
  "explain": true  // 显示评分细节
}

2. 比较 TF-IDF 和 BM25

设置相似度算法:

PUT /your_index
{
  "settings": {
    "index": {
      "similarity": {
        "my_tfidf": {  // 自定义TF-IDF
          "type": "classic" 
        },
        "my_bm25": {   // 自定义BM25
          "type": "BM25",
          "k1": 1.2,
          "b": 0.75
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "similarity": "my_bm25"  // 指定字段使用BM25
      }
    }
  }
}

3. 实际查询示例

BM25查询 (默认):

GET /fruits/_search
{
  "query": {
    "match": {
      "name": "苹果 香蕉"
    }
  }
}

TF-IDF查询:

PUT /fruits_tfidf
{
  "settings": {
    "index": {
      "similarity": {
        "default": {
          "type": "classic"
        }
      }
    }
  }
}

// 然后执行相同查询

五、算法对比分析

特性TF-IDFBM25
词频处理线性增长有上限的饱和曲线
字段长度影响严格按长度反比可调节(b参数)
默认使用版本≤5.x≥5.x
长文档处理对长文档不利对长文档更公平
高频词影响可能过度影响抑制高频词影响

六、业务场景建议

  1. 使用BM25的场景

    • 通用搜索场景
    • 文档长度差异大的情况
    • 需要抑制高频词影响的场景
  2. 使用TF-IDF的场景

    • 需要与旧系统保持一致性
    • 特定领域已知TF-IDF效果更好
    • 需要更简单直接的评分模型

七、进阶调优

1. BM25参数调优

PUT /your_index
{
  "settings": {
    "index": {
      "similarity": {
        "custom_bm25": {
          "type": "BM25",
          "k1": 1.5,  // 增大k1增强词频影响
          "b": 0.6    // 减小b降低长度惩罚
        }
      }
    }
  }
}

2. 字段级别配置

PUT /your_index/_mapping
{
  "properties": {
    "title": {
      "type": "text",
      "similarity": "custom_bm25",
      "norms": false  // 禁用长度归一化
    }
  }
}

3. 组合查询权重


GET /_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "title": {
              "query": "苹果",
              "boost": 2.0  // 提高权重
            }
          }
        },
        {
          "match": {
            "content": "香蕉"
          }
        }
      ]
    }
  }
}

通过理解这些评分算法的工作原理,您可以更好地优化Elasticsearch搜索相关性,满足不同业务场景的需求。