メインコンテンツまでスキップ

BM25 Function

BM25 function は、生テキストを sparse vector に変換し、語彙的関連性に基づいてドキュメントをスコアリングすることで、フルテキスト検索を可能にします。用語ベースのマッチングと頻度を考慮した重み付けを適用し、クエリ語に近く一致するテキストドキュメントの効率的な検索をサポートします。

ローカルなテキスト function として、BM25 function は Zilliz Cloud 内で実行され、モデル推論や外部連携を必要としません。これにより、テキストベースの検索シナリオに対して、決定論的で透明性のある検索メカニズムを提供します。

BM25 の仕組み

BM25 アルゴリズムは、フルテキスト検索で広く使用されている用語ベースの関連性スコアリングアルゴリズムです。Zilliz Cloud では、BM25 はテキストを用語重み表現に変換し、分散 sparse index を使用して上位 K 件のドキュメントを取得する sparse retrieval パイプラインとして実装されています。

全体のワークフローは、ドキュメント取り込みクエリテキスト処理 という 2 つの対称的なパスで構成されており、どちらも同じテキスト解析ロジックを共有します。

ドキュメント取り込み: テキストから sparse 表現へ

ドキュメントが挿入されると、その生テキストはまず analyzer によって処理され、個々の用語へとトークン化されます。

たとえば、次のドキュメント:

plaintext
"We are loving Milvus!"

は、次の用語に解析されます:

plaintext
["we", "love", "milvus"]

その後、各ドキュメントは term frequency(TF)表現として表されます。これは、各用語がドキュメント内に何回出現するかを記録するものです。たとえば次のようになります:

plaintext
{
"we": 1,
"love": 1,
"milvus": 1
}

同時に、Zilliz Cloud は次のような corpus レベルの統計も更新します:

  • 各用語の document frequency(DF)

  • 平均ドキュメント長

  • 各用語をそれを含むドキュメントにマッピングする posting list

ドキュメントの TF 表現は sparse embeddings に挿入され、用語の posting はスケーラブルな検索のためにノード間で分割されます。

クエリテキスト処理: IDF 重み付けを適用

テキストベースのクエリが発行されると、ドキュメント取り込み時に使用された同じ analyzerで処理され、用語分割の一貫性が確保されます。

たとえば、次のクエリ:

plaintext
"who loves Milvus?"

は、次のように解析されます:

plaintext
["who", "love", "milvus"]

各クエリ用語について、Zilliz Cloud は corpus 統計からその inverse document frequency(IDF)を参照します。IDF は、その用語がデータセット全体でどれだけ情報量を持つかを反映します。出現頻度の低い用語ほど高い重みが与えられ、一般的な用語ほど低い重みが与えられます。

概念的には、これにより次のような IDF 重み付きクエリ用語のセットが生成されます:

plaintext
{
"who": 0.1,
"love": 0.5,
"milvus": 1.2
}

BM25 スコアリングと top K 検索

BM25 は、一致したクエリ用語に基づいて関連性スコアを計算することでドキュメントをランキングします。スコアリングは 用語レベル で実行され、その後 ドキュメントレベル で集計されます。

用語レベルのスコアリング

ドキュメント内に出現する各クエリ用語について、BM25 は次の用語レベルスコアを計算します:

plaintext
term_score =
IDF(term) ×
TF_boost(term, document, k1) ×
length_normalization(document, b)

ここで:

  • IDF(term) は、その用語が collection 内でどれだけ希少かを表します

  • TF_boost(…, k1) は用語頻度に応じて増加しますが、頻度が大きくなるにつれて飽和します

  • length_normalization(…, b) はドキュメント長に基づいてスコアを調整します

ドキュメントレベルのスコアリングと Top-K 検索

最終的なドキュメントスコアは、一致したすべてのクエリ用語に対する用語レベルスコアの合計です:

plaintext
document_score =
sum of term_score over all matched query terms

ドキュメントは最終スコアでランキングされ、スコアの高い上位 K 件のドキュメントが返されます。

開始前に

BM25 function を使用する前に、語彙ベースのフルテキスト検索をサポートできるよう collection schema を計画してください:

  • 生コンテンツ用のテキストフィールド

    collection には、生テキストを保存するための VARCHAR フィールドを含める必要があります。このフィールドは、フルテキスト検索のために処理されるテキストのソースです。

  • テキストフィールド用の analyzer

    テキストフィールドでは analyzer を有効化する必要があります。analyzer は、BM25 function によって語彙的関連性が計算される前に、テキストをどのようにトークン化し正規化するかを定義します。

    デフォルトでは、Zilliz Cloud は空白と句読点に基づいてテキストをトークン化する組み込み analyzer を提供します。アプリケーションでカスタムのトークン化または正規化動作が必要な場合は、カスタム analyzer を定義できます。詳細は Choose the Right Analyzer for Your Use Case を参照してください。

  • BM25 出力用の sparse vector

    collection には、BM25 function によって生成される sparse 表現を保存するための SPARSE_FLOAT_VECTOR フィールドを含める必要があります。このフィールドは、フルテキスト検索時の index 作成と検索に使用されます。

これらの schema レベルの考慮事項を整理したら、collection を作成して BM25 function を使用してください。

ステップ 1: BM25 function を持つ collection を作成する

BM25 function を使用するには、collection 作成時にそれを定義する必要があります。この function は collection schema の一部となり、データの挿入時および検索時に自動的に適用されます。

SDK を使用する場合

schema フィールドを定義する

collection schema には、少なくとも次の 3 つの必須フィールドを含める必要があります:

  • Primary field: collection 内の各 entity を一意に識別します。

  • Text field (VARCHAR): 生テキストドキュメントを保存します。Zilliz Cloud が BM25 関連性ランキングのためにテキストを処理できるよう、enable_analyzer=True を設定する必要があります。デフォルトでは、Zilliz Cloud はテキスト解析に standard analyzer を使用します。別の analyzer を設定する場合は、Analyzer Overview を参照してください。

  • Sparse vector field (SPARSE_FLOAT_VECTOR): BM25 function によって自動生成される sparse embeddings を保存します。

python
from pymilvus import MilvusClient, DataType, Function, FunctionType

client = MilvusClient(
uri="YOUR_CLUSTER_ENDPOINT",
token="YOUR_CLUSTER_TOKEN"
)

schema = client.create_schema()

schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, auto_id=True) # Primary field
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=1000, enable_analyzer=True) # Text field
schema.add_field(field_name="sparse", datatype=DataType.SPARSE_FLOAT_VECTOR) # Sparse vector field; no dim required for sparse vectors

BM25 function を定義する

BM25 function は、トークン化されたテキストを BM25 スコアリングをサポートする sparse vector に変換します。

function を定義し、schema に追加します:

python
bm25_function = Function(
name="text_bm25_emb", # Function name
input_field_names=["text"], # Name of the VARCHAR field containing raw text data
output_field_names=["sparse"], # Name of the SPARSE_FLOAT_VECTOR field reserved to store generated embeddings
function_type=FunctionType.BM25, # Set to `BM25`
)

schema.add_function(bm25_function)

index を構成する

必要なフィールドと組み込み function を含む schema を定義したら、collection の index を設定します。このプロセスを簡単にするため、index_type として AUTOINDEX を使用してください。これは、データ構造に基づいて Zilliz Cloud が最適な index type を選択し、設定できるオプションです。

python
index_params = client.prepare_index_params()

index_params.add_index(
field_name="sparse",
index_type="AUTOINDEX",
metric_type="BM25"
)

collection を作成する

次に、定義した schema と index パラメータを使用して collection を作成します。

python
client.create_collection(
collection_name='my_collection',
schema=schema,
index_params=index_params
)

Web コンソールを使用する場合

または、Zilliz Cloud console で BM25 function を使用して collection を作成することもできます。

BM25 function を持つ collection を作成したら、テキストを挿入し、テキストクエリに基づく lexical search を実行できます。

ステップ 2: collection にテキストデータを挿入する

collection と index のセットアップが完了したら、テキストデータを挿入する準備が整います。このプロセスでは、生のテキストのみを提供すれば十分です。先ほど定義した BM25 function が、各テキストエントリに対して sparse vector を自動的に生成します。

python
client.insert('my_collection', [
{'text': 'information retrieval is a field of study.'},
{'text': 'information retrieval focuses on finding relevant information in large datasets.'},
{'text': 'data mining and information retrieval overlap in research.'},
])

ステップ 3: テキストクエリで検索する

collection にデータを挿入したら、生のテキストクエリを使用して全文検索を実行できます。Zilliz Cloud はクエリを自動的に sparse vector に変換し、BM25 アルゴリズムを使用して一致した検索結果をランク付けしたうえで、上位 topK(limit)件の結果を返します。

python
search_params = {
'params': {'level': 10},
}

res = client.search(
collection_name='my_collection',
data=['whats the focus of information retrieval?'],
anns_field='sparse',
output_fields=['text'], # Fields to return in search results; sparse field cannot be output
limit=3,
search_params=search_params
)

print(res)