什么是BM25算法?

1. 基础定义

BM25 是一种常用的文本相关性排序算法,广泛应用于搜索引擎和信息检索系统。它能根据关键词和文档的匹配程度,为每个文档打分并排序。

通俗理解: BM25 就像"关键词找文章"的打分裁判,分数越高,内容越相关。

2. 生活类比

3. BM25的核心思想与公式

核心思想: 关键词在文档中出现得越多,文档越短,且关键词越少见,分数越高。

公式:
\[ \text{score}(D, Q) = \sum_{q \in Q} \frac{IDF(q) \cdot f(q, D) \cdot (k_1 + 1)}{f(q, D) + k_1 \cdot (1 - b + b \cdot \frac{|D|}{\text{avgdl}})} \]
其中:
- \(f(q, D)\):关键词q在文档D中出现的次数
- \(|D|\):文档D的长度
- \(\text{avgdl}\):所有文档的平均长度
- \(k_1, b\):调节参数,常用 \(k_1=1.5, b=0.75\)
- \(IDF(q)\):关键词q的逆文档频率,越少见分值越高
- ∑表示对查询Q 中的每一个词q,都计算一遍后面的分数,然后把所有词的分数加起来,得到最终的相关性分数

1. 公式整体结构

2. 公式各部分含义

3. 公式的核心思想

4. 生活类比

5. 公式分子和分母的作用

6. 总结

BM25 公式的本质就是:
"关键词出现得多、文档短、关键词稀有" → 分数高,排序靠前。

4. 主要应用场景

5. BM25相关性排序

输入关键词,系统会用BM25算法为下方文档打分并排序:

文档:

小结: BM25 是文本检索领域的"经典算法",与向量检索结合后,能让AI搜索既懂"关键词"又懂"语义"。
// 定义一个包含若干中文文档的数组
const docs = [
  '人工智能正在改变世界。',
  '机器学习和深度学习是人工智能的重要分支。',
  '猫和狗是常见的宠物。',
  'AI可以帮助医生诊断疾病。',
  '篮球是一项受欢迎的运动。',
  '人工智能与大数据密不可分。',
  '天气预报依赖于大量数据分析。'
];

// 分词函数:将文本按中文、字母、数字和空格分割成词语
function tokenize(text) {
  // 先去除除中文、字母、数字、空格以外的字符,再按空格分割,最后过滤掉空字符串
  return text.replace(/[^\u4e00-\u9fa5a-zA-Z0-9 ]/g, '').split(/\s+/).filter(Boolean);
}

// 计算IDF(逆文档频率):衡量关键词在所有文档中的稀有程度
function computeIDF(term, docs) {
  // 统计包含该关键词的文档数
  let df = 0;
  for (const doc of docs) {
    // 如果该文档分词后包含该关键词,则df加1
    if (tokenize(doc).includes(term)) df++;
  }
  // 按照BM25的IDF公式计算并返回
  return Math.log((docs.length - df + 0.5) / (df + 0.5) + 1);
}

// BM25打分函数:计算某个文档与查询关键词的相关性得分
function bm25Score(query, doc, docs, k1 = 1.5, b = 0.75) {
  // 对查询进行分词
  const terms = tokenize(query);
  // 对文档进行分词
  const docTerms = tokenize(doc);
  // 当前文档的长度
  const docLen = docTerms.length;
  // 所有文档的平均长度
  const avgdl = docs.reduce((sum, d) => sum + tokenize(d).length, 0) / docs.length;
  // 初始化得分
  let score = 0;
  // 遍历每个查询词
  for (const q of terms) {
    // 统计该查询词在当前文档中出现的次数
    const fq = docTerms.filter(t => t === q).length;
    // 如果该词在文档中未出现,跳过
    if (fq === 0) continue;
    // 计算该词的IDF
    const idf = computeIDF(q, docs);
    // 按照BM25公式累加得分
    score += idf * fq * (k1 + 1) / (fq + k1 * (1 - b + b * docLen / avgdl));
  }
  // 返回最终得分
  return score;
}

// 展示排序结果的函数
function showResults() {
  // 获取用户输入的查询关键词,并去除首尾空格
  const query = document.getElementById('query-input').value.trim();
  // 获取用于显示结果的DOM元素
  const resultList = document.getElementById('result-list');
  // 如果没有输入关键词,提示用户输入
  if (!query) {
    resultList.innerHTML = '请输入关键词';
    return;
  }
  // 对每个文档计算BM25得分,并过滤掉得分为0的文档
  const scored = docs.map((doc, idx) => ({
    doc,
    score: bm25Score(query, doc, docs)
  }))
  // 按得分从高到低排序
  scored.sort((a, b) => b.score - a.score);
  // 如果没有相关内容,提示用户
  if (scored.length === 0) {
    resultList.innerHTML = '没有找到相关内容';
    return;
  }
  // 将排序后的结果渲染到页面
  resultList.innerHTML = scored.map(item => `
    
${item.doc} 分数:${item.score.toFixed(3)}
`).join(''); } // 给"相关性排序"按钮绑定点击事件,点击时执行showResults函数 document.getElementById('search-btn').onclick = showResults;