全文検索
全文検索は、テキストデータセット内の特定の語句を含む文書を検索し、関連性に基づいて結果をランク付けする機能です。この機能は、正確な用語を見落とす可能性のあるセマンティック検索の制限を克服し、最も正確で文脈に関連した結果を確実に受け取れるようにします。さらに、生のテキスト入力を受け付けることでベクトル検索を簡素化し、ベクトル埋め込みを手動で生成することなく、テキストデータをスパース埋め込みに自動的に変換します。
関連性のスコアリングにBM25アルゴリズムを使用するこの機能は、特定の検索用語に密接に一致する文書を優先的に検索する、検索拡張世代(RAG)シナリオで特に有用です。
全文検索とセマンティックベースの密なベクトル検索を統合することで、検索結果の精度と関連性を高めることができます。詳しくは、ハイブリッド検索をご参照ください。
BM25の実装
Milvusは、情報検索システムで広く採用されているスコアリング機能であるBM25関連性アルゴリズムを搭載した全文検索を提供し、Milvusはこれを検索ワークフローに統合することで、正確で関連性のランク付けされたテキスト結果を提供します。
Milvusの全文検索は以下のようなワークフローで行われます:
生のテキスト入力:テキスト文書を挿入するか、プレーンテキストを使用してクエリを提供します。
テキスト分析:Milvusはアナライザーを使用して、テキストをインデックス化および検索可能な意味のある用語に処理します。
BM25関数処理:組み込み関数が、これらの用語をBM25スコアリングに最適化されたスパースベクトル表現に変換します。
コレクションストア:Milvusは、検索とランキングを高速化するために、結果のスパース埋め込みをコレクションに保存します。
BM25関連性スコアリング:検索時に、MilvusはBM25スコアリング関数を適用して文書の関連性を計算し、クエリ用語に最もマッチするランク付けされた結果を返します。
全文検索
全文検索を使用するには、主に以下の手順に従ってください:
コレクションを作成します:必要なフィールドを設定し、生テキストをスパース埋め込みに変換する BM25 関数を定義します。
データを挿入します:生テキスト文書をコレクションに取り込む。
検索を実行する:自然言語のクエリテキストを使用して、BM25 の関連性に基づいてランク付けされた結果を取得します。
BM25 全文検索用のコレクションの作成
BM25 を使用した全文検索を有効にするには、必要なフィールドを持つコレクションを準備し、スパース・ベク ターを生成する BM25 関数を定義し、インデックスを構成してから、コレクションを作成する必要があります。
スキーマフィールドの定義
コレクションスキーマには、少なくとも3つの必須フィールドを含める必要があります:
プライマリ・フィールド:コレクション内の各エンティティを一意に識別する。
テキストフィールド(
VARCHAR):生のテキスト文書を格納する。Milvus が BM25 関連性ランキングのためにテキストを処理できるように、enable_analyzer=Trueを設定する必要があります。デフォルトでは、Milvusはテキスト分析にアナライザを使用します。standardアナライザを使用します。別のアナライザを設定するには、アナライザの概要を参照してください。スパースベクトルフィールド(
SPARSE_FLOAT_VECTOR):BM25関数によって自動的に生成されたスパース埋め込みを格納する。
from pymilvus import MilvusClient, DataType, Function, FunctionType
client = MilvusClient(
uri="http://localhost:19530",
token="root:Milvus"
)
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
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
CreateCollectionReq.CollectionSchema schema = CreateCollectionReq.CollectionSchema.builder()
.build();
schema.addField(AddFieldReq.builder()
.fieldName("id")
.dataType(DataType.Int64)
.isPrimaryKey(true)
.autoID(true)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("text")
.dataType(DataType.VarChar)
.maxLength(1000)
.enableAnalyzer(true)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("sparse")
.dataType(DataType.SparseFloatVector)
.build());
import (
"context"
"fmt"
"github.com/milvus-io/milvus/client/v2/column"
"github.com/milvus-io/milvus/client/v2/entity"
"github.com/milvus-io/milvus/client/v2/index"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
milvusAddr := "localhost:19530"
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
Address: milvusAddr,
})
if err != nil {
fmt.Println(err.Error())
// handle error
}
defer client.Close(ctx)
schema := entity.NewSchema()
schema.WithField(entity.NewField().
WithName("id").
WithDataType(entity.FieldTypeInt64).
WithIsPrimaryKey(true).
WithIsAutoID(true),
).WithField(entity.NewField().
WithName("text").
WithDataType(entity.FieldTypeVarChar).
WithEnableAnalyzer(true).
WithMaxLength(1000),
).WithField(entity.NewField().
WithName("sparse").
WithDataType(entity.FieldTypeSparseVector),
)
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";
const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});
const schema = [
{
name: "id",
data_type: DataType.Int64,
is_primary_key: true,
},
{
name: "text",
data_type: "VarChar",
enable_analyzer: true,
enable_match: true,
max_length: 1000,
},
{
name: "sparse",
data_type: DataType.SparseFloatVector,
},
];
console.log(res.results)
export schema='{
"autoId": true,
"enabledDynamicField": false,
"fields": [
{
"fieldName": "id",
"dataType": "Int64",
"isPrimary": true
},
{
"fieldName": "text",
"dataType": "VarChar",
"elementTypeParams": {
"max_length": 1000,
"enable_analyzer": true
}
},
{
"fieldName": "sparse",
"dataType": "SparseFloatVector"
}
]
}'
先のコンフィグでは
id: は主キーとして機能し、auto_id=Trueで自動的に生成されます。text: は、全文検索操作のための生のテキストデータを格納します。データ型はVARCHARでなければなりません。VARCHARはテキスト保存用のmilvus文字列データ型です。sparse: 全文検索操作のために内部で生成されたスパース埋め込みを格納するために予約されたベクトルフィールド。データ型はSPARSE_FLOAT_VECTORでなければならない。
BM25関数の定義
BM25関数は、トークン化されたテキストをBM25スコアリングをサポートするスパースベクトルに変換します。
関数を定義し、スキーマに追加します:
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)
import io.milvus.common.clientenum.FunctionType;
import io.milvus.v2.service.collection.request.CreateCollectionReq.Function;
import java.util.*;
schema.addFunction(Function.builder()
.functionType(FunctionType.BM25)
.name("text_bm25_emb")
.inputFieldNames(Collections.singletonList("text"))
.outputFieldNames(Collections.singletonList("sparse"))
.build());
function := entity.NewFunction().
WithName("text_bm25_emb").
WithInputFields("text").
WithOutputFields("sparse").
WithType(entity.FunctionTypeBM25)
schema.WithFunction(function)
const functions = [
{
name: 'text_bm25_emb',
description: 'bm25 function',
type: FunctionType.BM25,
input_field_names: ['text'],
output_field_names: ['sparse'],
params: {},
},
];
export schema='{
"autoId": true,
"enabledDynamicField": false,
"fields": [
{
"fieldName": "id",
"dataType": "Int64",
"isPrimary": true
},
{
"fieldName": "text",
"dataType": "VarChar",
"elementTypeParams": {
"max_length": 1000,
"enable_analyzer": true
}
},
{
"fieldName": "sparse",
"dataType": "SparseFloatVector"
}
],
"functions": [
{
"name": "text_bm25_emb",
"type": "BM25",
"inputFieldNames": ["text"],
"outputFieldNames": ["sparse"],
"params": {}
}
]
}'
パラメータ |
説明 |
|---|---|
|
関数の名前。この関数は、 |
|
テキストからスパース・ベクトルへの変換を必要とする |
|
内部生成されたスパース・ベクトルが格納されるフィールドの名前。 |
|
使用する関数の型。 |
複数のフィールド(VARCHAR )で BM25 処理が必要な場合は、フィールドごとに 1 つの BM25 関数を定義し、それぞれ固有の名前と出力フィールドを持つ。
インデックスの構成
必要なフィールドと組み込み関数でスキーマを定義したら、コレクションのインデックスを設定します。
index_params = client.prepare_index_params()
index_params.add_index(
field_name="sparse",
index_type="SPARSE_INVERTED_INDEX",
metric_type="BM25",
params={
"inverted_index_algo": "DAAT_MAXSCORE",
"bm25_k1": 1.2,
"bm25_b": 0.75
}
)
import io.milvus.v2.common.IndexParam;
Map<String,Object> params = new HashMap<>();
params.put("inverted_index_algo", "DAAT_MAXSCORE");
params.put("bm25_k1", 1.2);
params.put("bm25_b", 0.75);
List<IndexParam> indexes = new ArrayList<>();
indexes.add(IndexParam.builder()
.fieldName("sparse")
.indexType(IndexParam.IndexType.AUTOINDEX)
.metricType(IndexParam.MetricType.BM25)
.extraParams(params)
.build());
indexOption := milvusclient.NewCreateIndexOption("my_collection", "sparse",
index.NewAutoIndex(entity.MetricType(entity.BM25)))
.WithExtraParam("inverted_index_algo", "DAAT_MAXSCORE")
.WithExtraParam("bm25_k1", 1.2)
.WithExtraParam("bm25_b", 0.75)
const index_params = [
{
field_name: "sparse",
metric_type: "BM25",
index_type: "SPARSE_INVERTED_INDEX",
params: {
"inverted_index_algo": "DAAT_MAXSCORE",
"bm25_k1": 1.2,
"bm25_b": 0.75
}
},
];
export indexParams='[
{
"fieldName": "sparse",
"metricType": "BM25",
"indexType": "AUTOINDEX",
"params":{
"inverted_index_algo": "DAAT_MAXSCORE",
"bm25_k1": 1.2,
"bm25_b": 0.75
}
}
]'
パラメータ |
説明 |
|---|---|
|
インデックスを作成するベクターフィールドの名前。全文検索の場合、これは生成されたスパース・ベクトルを格納するフィールドでなければなりません。この例では、値を |
|
作成するインデックスのタイプ |
|
全文検索機能を使用する場合は、このパラメータの値を |
|
インデックス固有の追加パラメータの辞書。 |
|
インデックスの構築とクエリに使用されるアルゴリズム。有効な値:
|
|
用語頻度の飽和度を制御する。値が高いほど、文書ランキングにおける用語頻度の重要度が増す。値の範囲:[1.2, 2.0]. |
|
文書の長さを正規化する程度を制御する。一般的に0から1の間の値が使用され、一般的なデフォルト値は0.75程度です。1の値は長さの正規化を行わないことを意味し、0の値は完全な正規化を意味する。 |
コレクションの作成
定義したスキーマとインデックス・パラメータを使用して、コレクションを作成する。
client.create_collection(
collection_name='my_collection',
schema=schema,
index_params=index_params
)
import io.milvus.v2.service.collection.request.CreateCollectionReq;
CreateCollectionReq requestCreate = CreateCollectionReq.builder()
.collectionName("my_collection")
.collectionSchema(schema)
.indexParams(indexes)
.build();
client.createCollection(requestCreate);
err = client.CreateCollection(ctx,
milvusclient.NewCreateCollectionOption("my_collection", schema).
WithIndexOptions(indexOption))
if err != nil {
fmt.Println(err.Error())
// handle error
}
await client.create_collection(
collection_name: 'my_collection',
schema: schema,
index_params: index_params,
functions: functions
);
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
\"collectionName\": \"my_collection\",
\"schema\": $schema,
\"indexParams\": $indexParams
}"
テキストデータの挿入
コレクションとインデックスを設定したら、テキストデータを挿入する準備ができた。このプロセスでは、生のテキストを提供するだけでよい。先ほど定義した組み込み関数が、各テキスト・エントリに対応するスパース・ベクトルを自動的に生成します。
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.'},
])
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.milvus.v2.service.vector.request.InsertReq;
Gson gson = new Gson();
List<JsonObject> rows = Arrays.asList(
gson.fromJson("{\"text\": \"information retrieval is a field of study.\"}", JsonObject.class),
gson.fromJson("{\"text\": \"information retrieval focuses on finding relevant information in large datasets.\"}", JsonObject.class),
gson.fromJson("{\"text\": \"data mining and information retrieval overlap in research.\"}", JsonObject.class)
);
client.insert(InsertReq.builder()
.collectionName("my_collection")
.data(rows)
.build());
// go
await client.insert({
collection_name: 'my_collection',
data: [
{'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.'},
]);
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/insert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
"data": [
{"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."}
],
"collectionName": "my_collection"
}'
全文検索の実行
データをコレクションに挿入したら、生テキストクエリを使用して全文検索を実行できます。Milvusは自動的にクエリをスパースベクトルに変換し、マッチした検索結果をBM25アルゴリズムを使ってランク付けし、トップK(limit)の結果を返します。
テキスト・ハイライターを設定することで、検索結果でマッチした用語をハイライトすることができます。詳細はテキスト・ハイライターを参照。
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,
)
print(res)
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.EmbeddedText;
import io.milvus.v2.service.vector.response.SearchResp;
Map<String,Object> searchParams = new HashMap<>();
SearchResp searchResp = client.search(SearchReq.builder()
.collectionName("my_collection")
.data(Collections.singletonList(new EmbeddedText("whats the focus of information retrieval?")))
.annsField("sparse")
.topK(3)
.searchParams(searchParams)
.outputFields(Collections.singletonList("text"))
.build());
annSearchParams := index.NewCustomAnnParam()
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"my_collection", // collectionName
3, // limit
[]entity.Vector{entity.Text("whats the focus of information retrieval?")},
).WithConsistencyLevel(entity.ClStrong).
WithANNSField("sparse").
WithAnnParam(annSearchParams).
WithOutputFields("text"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
for _, resultSet := range resultSets {
fmt.Println("IDs: ", resultSet.IDs.FieldData().GetScalars())
fmt.Println("Scores: ", resultSet.Scores)
fmt.Println("text: ", resultSet.GetColumn("text").FieldData().GetScalars())
}
await client.search(
collection_name: 'my_collection',
data: ['whats the focus of information retrieval?'],
anns_field: 'sparse',
output_fields: ['text'],
limit: 3,
)
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--data-raw '{
"collectionName": "my_collection",
"data": [
"whats the focus of information retrieval?"
],
"annsField": "sparse",
"limit": 3,
"outputFields": [
"text"
],
"searchParams":{
"params":{}
}
}'
パラメータ |
説明 |
|---|---|
|
検索パラメータを含む辞書。 |
|
検索時に無視する重要度の低い用語の割合。詳細はスパース・ベクターを参照。 |
|
自然言語による生のクエリテキスト。MilvusはBM25関数を使用して、テキストクエリを自動的にスパースベクトルに変換します。 |
|
内部で生成されたスパースベクトルを含むフィールド名。 |
|
検索結果に返すフィールド名のリスト。BM25 が生成した埋め込みを含むスパース・ベクトル・フィールド以外のすべてのフィールドをサポートします。一般的な出力フィールドには、主キー・フィールド(例: |
|
返されるトップマッチの最大数。 |
よくある質問
BM25 関数で生成されたスパース・ベクトルを全文検索で出力したりアクセスしたりできますか?
いいえ。BM25関数で生成されたスパース・ベクトルは、全文検索で直接アクセスしたり出力したりすることはできません。詳細は以下の通りです:
BM25関数は、ランキングと検索のために内部でスパースベクトルを生成します。
これらのベクトルはスパースフィールドに格納されますが、全文検索に含めることはできません。
output_fields出力できるのは、元のテキストフィールドとメタデータ(
idやtextなど)のみです。
例
# ❌ This throws an error - you cannot output the sparse field
client.search(
collection_name='my_collection',
data=['query text'],
anns_field='sparse',
output_fields=['text', 'sparse'] # 'sparse' causes an error
limit=3,
search_params=search_params
)
# ✅ This works - output text fields only
client.search(
collection_name='my_collection',
data=['query text'],
anns_field='sparse',
output_fields=['text']
limit=3,
search_params=search_params
)
スパース・ベクトル・フィールドにアクセスできないのに、なぜ定義する必要があるのですか?
スパース・ベクター・フィールドは、ユーザーが直接操作しないデータベース・インデックスと同様に、内部検索インデックスとして機能します。
設計上の理由
懸念の分離:ユーザはテキスト(入出力)を扱い、milvusはベクトル(内部処理)を扱う。
パフォーマンス事前に計算された疎なベクトルにより、クエリ中にBM25のランキングを高速に行うことができる。
ユーザーエクスペリエンス複雑なベクトル操作を抽象化し、シンプルなテキストインターフェイスを実現
ベクトル・アクセスが必要な場合:
全文検索の代わりに手動でスパースベクトル操作を使用
カスタムスパースベクトルワークフロー用に個別のコレクションを作成できます。
詳細については、スパースベクトルを参照してください。