文書を検索して並べるとき、素朴には問いの語がいくつ含まれるかで数えます。ところがこの方式には、はっきりした弱点があります。
あらゆる語を少しずつ含む長い総則を1件混ぜたところ、5問中4問でその総則が1位になりました。BM25に変えると2問まで下がります。
文書13件と問い5件で実際に計算しました。語の一致だけでは総則が4問で1位、BM25では2問に下がります。
BM25が何を直しているのかを、実際に計算して確かめました。用意したのは社内規程を模した文書13件で、うち1件は「あらゆる語を少しずつ含む長い総則」です。
検索は本当に実行しています。文書も問いも正解もコードに書いてあり、そこを変えれば結果も変わります。
// 語の一致だけで数える素朴な方式
function overlap(qs) {
const q = new Set(bigrams(qs));
return tokenized.map((d, i) => ({ i, s: d.filter((w) => q.has(w)).length }))
.sort((a, b) => b.s - a.s);
}
// BM25。珍しい語を重く、同じ語の繰り返しは頭打ち、長さで割る
const idf = Math.log(1 + (N - n + 0.5) / (n + 0.5));
s += idf * ((f * (k1 + 1)) / (f + k1 * (1 - b + (b * d.length) / avgdl)));
問い 語の一致だけ BM25
宿泊費の上限は 上位: 12,1,5 上位: 1,5,12
日帰りの日当 上位: 3,9,12 上位: 3,9,12
前払の申請期限 上位: 12,4,6 上位: 12,4,6
備品の購入 上位: 12,7,0 上位: 12,7,0
報告書の提出期限 上位: 12,9,11 上位: 11,9,12
上位3件の適合率 語の一致だけ: 46.7% BM25: 46.7%
長い総則(12番)が1位になった問い
語の一致だけ: 4 / 5
BM25: 2 / 5
b の値 総則が1位 上位3件の適合率 意味
0 3 / 5 46.7% 長さを無視
0.3 3 / 5 46.7% やや補正
0.75 2 / 5 46.7% 標準的な補正
1 2 / 5 46.7% 長さで強く割る
12番が総則です。語の一致だけで数えると、5問中4問でこれが1位になりました。どの問いにも答えていないのに、です。
理由は数え方にあります。語がいくつ含まれるかで数えると、語数の多い文書ほど有利になります。
総則は「宿泊費の上限」も「前払の申請」も「備品の購入」も列挙しています。それぞれ1回ずつしか触れていませんが、触れている語の種類だけは多いのです。
BM25に変えると4問が2問になりました。「宿泊費の上限は」と「報告書の提出期限」では、本来の正解が1位に上がっています。
効いているのは、珍しい語を重く数える部分と、文書の長さで割る部分です。総則は長いぶん割られ、専用の条文は短いぶん残ります。
下の表は、長さで割る度合いを変えたものです。割らない設定では3問、標準的な設定では2問まで下がりました。
Elasticsearchの説明でも、この値は文書の長さが語の出現回数をどの程度打ち消すかを制御するとされています。表の動きは、その説明のとおりです。
公平に書いておくと、上位3件の適合率はどちらも46.7%で変わりませんでした。総則が1位から3位に下がっただけで、上位3件には残っています。
つまりこの規模では、順位は改善しても中身は入れ替わりませんでした。指標の選び方で結論が変わる例にもなっています。指標の違いはnDCGの記事で扱っています。
長い総則が上位を占めるのは、数え方が長さを見ていないから。
b Controls to what degree document length normalizes tf values.原文Elasticsearch Reference「Similarity settings」 この内容の有効期限2027-02-18
語の出現回数と珍しさ、そして文書の長さの3つから点を付けます。多くの検索製品で既定になっています。
BM25は、問いの語がどれだけ含まれるかを、3つの観点で補正しながら点にする計算です。多くの検索製品で既定の方式になっています。
4番目は含まれません。語がどの順で並んでいるかは考えないので、意味の近さを捉える仕組みとは別物です。
Elasticsearchの説明では、BM25が既定の方式で、出現回数の補正が組み込まれており、名前のような短い項目でうまく働くとされています。
つまり調整しなくても、そこそこの結果が出る方式です。前の節で見たとおり、語の一致だけで数える方式より明らかに素直に並びます。
調整できる値は2つです。同じ文書では、1つは語の出現回数が頭打ちになる度合い、もう1つは文書の長さで割る度合いだと案内されています。
前の節で動かしたのは後者です。長い総則の扱いが、この値で変わりました。
実装してみて感じたのは、BM25の設定より、日本語をどう語に区切るかのほうが結果を左右するということでした。今回は2文字ずつ区切る素朴な方法を使っています。区切り方が変われば、同じ計算でも順位が変わります。文字を揃える処理は日本語正規化の記事で扱っています。
BM25 similarity ( default ) TF/IDF based similarity that has built-in tf normalization and is supposed to work better for short fields (like names).原文Elasticsearch Reference「Similarity settings」 この内容の有効期限2027-02-18
まず既定のまま測ります。動かすのは、長い文書が上位を占めるといった具体的な症状が出てからです。
BM25には調整できる値がありますが、最初から動かす必要はありません。既定でそこそこ働く方式として設計されています。
動かす判断は、症状から入ります。長い文書ばかり上位に来るなら、長さで割る度合いを上げる。前の節の表が、その動きを示しています。
同じ語を大量に繰り返した文書が上位に来るなら、もう1つの値のほうです。Elasticsearchでも、語の出現回数を頭打ちにする度合いを制御すると説明されています。
つまみを動かす前に、問いと正解の組を用意してください。前の節でも、これがなければ「4問から2問に減った」という比較ができません。
測り方はnDCGの記事で扱っています。指標を複数並べて見るのが実際的です。
語が一致しなければ、BM25は当てられません。「宿泊費」と「ホテル代」のように、違う語で同じことを指す場合は0点になります。
ここは別の仕組みで補います。意味の近さで探す方式と組み合わせる形は、LlamaIndexの記事で扱った文書の分け方とも関わってきます。
既定で動かし、症状が出てからつまみに手を付ける。
k1 Controls non-linear term frequency normalization (saturation).原文Elasticsearch Reference「Similarity settings」 この内容の有効期限2027-02-18
同じ課題を持つ会社にとって、動いている設定は「作る時間」を買えるということです。ServiceDockは自作のワークフローやテンプレートを出品できるマーケットプレイスです。手数料や出品の流れは出品者向けページにまとまっています。
出品の仕組みを見る