実践で学ぶBM25 - パート1:Elasticsearchの関連性スコアリングにシャードが及ぼす影響
この記事は、類似性ランキング(関連性)に関する3部構成のPractical BM25シリーズの、第1部にあたります。次の投稿へのリンクは下部にあります。
背景
Elasticsearch 5.0では、デフォルトの 類似度アルゴリズム として Okapi BM25 を採用しました。これは、クエリに関連する結果をスコアリングするために使用されるものです。このブログではBM25と他の手法の比較については深く触れませんが、BM25の理論的根拠についての入門情報が必要な場合は、Elastic{ON} 2016のプレゼンテーション 「BM25 Demystified」 をご覧ください。代わりに、利用可能なパラメータやスコアリングに影響を与える要素など、BM25の実践的な使用方法について解説(および解明)していきます。
このブログは主に、テキストドキュメントのスコアリングを行うユーザーを対象としている点にご留意ください。つまり、検索するユーザーの支援に重点を置いています。ログやメトリックのインデキシングを行っており、タイムスタンプのような明示的なメタデータや数値順でソートされた結果を返す場合、このブログは参考程度にしかならない可能性があります。
シャードがスコアリングに与える影響の理解
ご自宅で実際に試していただくことを想定しているため、まず最初に理解しておくべきこととして、シャードが1つ以上ある場合にスコアリングがどのような影響を受けるかについて説明します。Elasticsearchでは、デフォルトでインデックスごとに5つのプライマリシャードが使用されるためです。まずは「people」というインデックスを作成することから始めましょう。ここで指定する設定はデフォルト値(そのため定義する必要はありません)ですが、デモンストレーションのために明示的に記述します。ここでは私の名前(「Shane Connelly」)のバリエーションを使用しますが、ご自宅で試される場合は、お好きな名前に置き換えていただいて構いません。
PUT people
{
"settings": {
"number_of_shards": 5,
"index" : {
"類似性" : {
"default" : {
"type" : "BM25"
}
}
}
}
}
次に、ドキュメントを追加して検索してみましょう。まずは、私の名前を追加します。
PUT /people/_doc/1
{
"title": "Shane"
}
GET /people/_doc/_検索する
{
"query": {
"match": {
"title": "Shane"
}
}
}
この時点で1件のヒットが返され、スコアは0.2876821となります。このスコアがどのように算出されるかについては後ほど詳しく説明しますが、まずは私のフルネームの異なるバリエーションを持つドキュメントをいくつか追加した場合にどうなるかを見てみましょう。
PUT /people/_doc/2
{
"title": "Shane C"
}
PUT /people/_doc/3
{
"title": "Shane Connelly"
}
PUT /people/_doc/4
{
"title": "Shane P Connelly"
}
次に、同じ検索をもう一度実行します:
GET /people/_doc/_検索する
{
"query": {
"match": {
"title": "Shane"
}
}
}
現時点で4件のヒットがあるはずですが、スコアを見ると不思議に思うかもしれません。ドキュメント1と3のスコアはどちらも0.2876821ですが、ドキュメント2のスコアは0.19856805、ドキュメント4のスコアは0.16853254となっています。これは、新しいユーザーがよく戸惑う点です。ドキュメント2と3は非常によく似ており、どちらも2つの単語を持ち、どちらも「shane」に一致していますが、ドキュメント2のスコアははるかに低くなっています。「C」のスコアリングと「Connelly」のスコアリングに何か違いがあるのではないかと思われるかもしれませんが、実際には、ドキュメントがどのシャードに格納されたかに関係しています。
念のため補足すると、Elasticsearchはドキュメントをシャードに分割し、各シャードがデータの一部を保持します。以下を見てみましょう。
GET /_cat/シャード/people?v
これを実行すると、シャード2には2つのドキュメントがあるのに対し、シャード3と4には1つしかドキュメントがないことがわかります(シャード0と1にはまだドキュメントがありません)。つまり、用語「shane」の合計出現回数がこれらのシャード間で異なっており、それが最終的にこのケースでのスコアの違いを生んでいます。デフォルトでは、Elasticsearchはシャード単位でスコアを計算します。
インデックスに少数のドキュメントを読み込み始めたユーザーから、「なぜドキュメントAのスコアがドキュメントBより高い(または低い)のか」という質問を受けることがあります。その回答として、ドキュメント数に対してシャードの比率が比較的高いことが原因で、シャード間でスコアが偏っている場合があります。シャード間でより一貫したスコアを得るには、いくつかの方法があります。
- インデックスに読み込むドキュメントが増えるほど、シャードの用語統計はより正規化されます。十分な数のドキュメントがあれば、各シャードにおける用語統計やスコアリングのわずかな違いは気にならなくなるでしょう。
- シャード数を減らすことで、単語頻度の統計的な偏差を抑えることができます。たとえば、インデックス設定で
number_of_shardsを1に設定した場合、スコアは大きく異なります。ドキュメント1のスコアは0.13245322、ドキュメント2と3のスコアはそれぞれ0.105360515、ドキュメント4のスコアは0.0874691となります。プライマリシャードの数にはいくつかのトレードオフがあり、これについては定量的クラスタサイジングのウェビナーで解説しています。 - リクエストに
?検索する_type=dfs_query_then_fetchを追加できます。これにより、まず分散ターム頻度(DFS = Distributed Frequency 検索する)が収集され、それを使用してスコアが計算されます。実際、これはシャードが1つだけの場合と同じスコアを返します。「検索する_type」パラメーターの有無で結果がどのように異なるかを確認してください:GET /people/_doc/_search?search_type=dfs_query_then_fetch { "query": { "match": { "title": "Shane" } } }これは、number_of_shards=1を設定した場合と同じ結果になります。「では、これでより正確なスコアが得られるのなら、なぜデフォルトで有効になっていないのか?」と疑問に思うかもしれません。その答えは、すべての統計を収集するために処理中に余分な往復が発生するためです。一部のユースケース(スコアリングの精度よりも速度が重要な場合)では、この往復処理は不要です。また、シャード内に十分なデータがあれば、統計値が互いに非常に近くなるため、往復処理も不要になります。十分なデータがある場合、search_type=dfs_query_then_fetchが必要になるのは、主にシャード間のデータ分散が不均一な状態が続く場合のみです。これは、一部のカスタムルーティングを使用している場合などが該当します。
さて、シャーディングがスコアリングにどのような影響を与えるか(そしてそれをどのように調整するか)について理解できました。次に、BM25アルゴリズムを見て、さまざまな変数がどのように作用するかを確認します。
本シリーズの続きはこちら:パート2:BM25のアルゴリズムと変数