はじめに
前回の記事では、BM25がTF-IDFの弱点であった「単語出現回数の扱いの単純さ」と「文書長の画一的な正規化」を、どのように克服したかを解説しました。
今回は、いよいよBM25のスコア計算式の各パーツを組み立て、実際にスコアを算出していきます。
数式を眺めるだけでは得られない、「なぜこの計算でうまくいくのか」という本質的な理解を目指しましょう。
1. BM25の全体像をもう一度
まず、クエリQ(複数の検索語 $q_1, q_2, ..., q_n$ を含む)と、ある文書dとの関連度を計算するBM25の式を再掲します。
$$
\text{Score}(Q, d) = \sum_{q_i \in Q} \text{IDF}(q_i) \cdot \frac{f(q_i, d) \cdot (k_1 + 1)}{f(q_i, d) + k_1 \cdot (1 - b + b \cdot \frac{|d|}{\text{avgdl}})}
$$
この式は、クエリに含まれる各単語のスコアを計算し、それらを最後に合計する、という構造になっています。
そして、各単語のスコアは、大きく分けて2つの要素から成り立っています。
- IDF項: その単語がどれだけ「レア」かを示す。
- TF発展項: 文書d内での単語の重要度を、飽和度と文書長を考慮して巧妙に計算する部分。
今回は、この2つの要素を実際に計算し、組み合わせることで、BM25のスコアを完成させます。
2. 実践!BM25で関連度を計算する
以下の小さな文書集合(コーパス)を例に、「Python 機械学習」
というクエリに対する文書Cのスコアを計算してみましょう。
【文書集合】
-
文書A (総単語数: 15): 「Pythonは人気の言語です。Web開発からデータ分析、機械学習まで幅広く使えます。」
-
f(Python, A) = 1
,f(機械学習, A) = 1
-
-
文書B (総単語数: 25): 「機械学習を学ぶなら、まずPythonの基礎を固めることが重要です。scikit-learnというライブラリが便利で、多くの機械学習アルゴリズムを実装しています。」
-
f(Python, B) = 1
,f(機械学習, B) = 2
-
-
文書C (総単語数: 10): 「Pythonで機械学習を実践する。これが私の目標です。Pythonは楽しい。」
-
f(Python, C) = 2
,f(機械学習, C) = 1
-
【パラメータ設定】
BM25でよく使われる標準的な値を設定します。
- $k_1 = 1.2$
- $b = 0.75$
Step 1: 基礎情報を整理する
まず、計算に必要な情報を整理します。
- 全文書数 (N): 3
-
各文書の長さ:
- $|A| = 15$
- $|B| = 25$
- $|C| = 10$
-
平均文書長 (avgdl):
$$
\text{avgdl} = \frac{15 + 25 + 10}{3} = \frac{50}{3} \approx 16.67
$$ -
クエリ単語の文書頻度 (DF):
- "Python"が出現する文書: A, B, C → $df(\text{Python}) = 3$
- "機械学習"が出現する文書: A, B, C → $df(\text{機械学習}) = 3$
Step 2: IDF項を計算する
BM25で使われるIDFは、TF-IDFで紹介したものから少しだけ改良されています。分母分子に0.5
を足すことで、より安定した挙動(スムージング)を目指したものです。
$$
\text{IDF}(q) = \log \left( \frac{N - df(q) + 0.5}{df(q) + 0.5} \right)
$$
-
IDF("Python"):
$$
\log \left( \frac{3 - 3 + 0.5}{3 + 0.5} \right) = \log \left( \frac{0.5}{3.5} \right) \approx \log(0.143) \approx -1.946
$$ -
IDF("機械学習"):
(DFが同じなので) $\approx -1.946$
おや、スコアがマイナスになってしまいました。これは、クエリ単語が全ての文書に出現するという特殊なケースだからです。実用上は、マイナスを回避するためにさらに+1
するなどの調整が入ることもありますが、今回はこのまま進めます。(ランキングの相対的な順位には影響しません)
Step 3: 文書Cの「TF発展項」を計算する
ここがBM25のハイライトです。文書Cに焦点を当て、クエリ単語「Python」
と「機械学習」
のTF発展項をそれぞれ計算します。
3-1. "Python"に対するTF発展項
-
基本情報:
- $f(\text{Python}, C) = 2$ (文書Cでの"Python"の出現回数)
- $|C| = 10$ (文書Cの長さ)
-
計算式:
$$
\frac{2 \cdot (1.2 + 1)}{2 + 1.2 \cdot \left(1 - 0.75 + 0.75 \cdot \frac{10}{16.67}\right)}
$$ -
計算過程:
- 文書長の部分: $1 - 0.75 + 0.75 \cdot \frac{10}{16.67} = 0.25 + 0.75 \cdot 0.6 = 0.25 + 0.45 = 0.7$
- 分母全体: $2 + 1.2 \cdot (0.7) = 2 + 0.84 = 2.84$
- 分子全体: $2 \cdot 2.2 = 4.4$
- 最終値: $\frac{4.4}{2.84} \approx \bf{1.549}$
3-2. "機械学習"に対するTF発展項
-
基本情報:
- $f(\text{機械学習}, C) = 1$ (文書Cでの"機械学習"の出現回数)
- $|C| = 10$ (文書Cの長さ)
-
計算式:
$$
\frac{1 \cdot (1.2 + 1)}{1 + 1.2 \cdot \left(1 - 0.75 + 0.75 \cdot \frac{10}{16.67}\right)}
$$ -
計算過程:
- 文書長の部分は先ほどと同じ: $0.7$
- 分母全体: $1 + 1.2 \cdot (0.7) = 1 + 0.84 = 1.84$
- 分子全体: $1 \cdot 2.2 = 2.2$
- 最終値: $\frac{2.2}{1.84} \approx \bf{1.196}$
Step 4: 最終スコアを合計する
最後に、各単語の IDF × TF発展項
を計算し、それらを合計して文書Cの最終スコアを求めます。
-
"Python"のスコア:
$-1.946 \times 1.549 \approx -3.014$ -
"機械学習"のスコア:
$-1.946 \times 1.196 \approx -2.327$ -
文書Cの最終スコア:
$$
\text{Score}(\text{「Python 機械学習」}, C) = (-3.014) + (-2.327) = \bf{-5.341}
$$
同様の計算を文書A, Bに対しても行うことで、クエリ「Python 機械学習」
に対するランキングが 文書B > 文書A > 文書C
(※実際の計算が必要です) のように決定されます。
まとめ
今回は、BM25のスコアを実際に計算することで、その仕組みを体感しました。
TF-IDFが単純な掛け算だったのに対し、BM25は文書長を平均と比較したり、パラメータ$k_1, b$で挙動を調整したりと、より現実に即した複雑な計算を行っていることがお分かりいただけたかと思います。
この一手間二手間が、単なる単語のマッチングを超えた、より精度の高い検索ランキングを実現しているのです。
TF-IDFからBM25への進化の旅は、ここで一区切りです。この知識は、あなたが今後検索技術や情報検索の分野に触れる上で、強力な基盤となるでしょう。