テキスト一致

Milvusのテキストマッチ機能は、特定の用語に基づいて文書を正確に検索することを可能にします。この機能は主に、特定の条件を満たすためのフィルタリング検索に使用され、スカラーフィルタリングを組み合わせてクエリ結果を絞り込むことができます。これにより、スカラー基準を満たすベクトル内での類似度検索が可能になります。

TEXT_MATCH は分析済みの用語と完全に一致するものを検索しますが、TEXT_MATCH_FUZZY はクエリトークンとインデックス化されたトークンの間にわずかな編集距離がある場合でも許容します。どちらもブール演算によるフィルタリング操作であり、一致したドキュメントの関連性を評価するものではありません。クエリ用語の語義や重要度に基づいて最も関連性の高いドキュメントを取得したい場合は、全文検索の使用をお勧めします。

概要

Milvusは、基盤となる逆引きインデックスおよび用語ベースのテキスト検索を実現するためにTantivyを統合しています。各テキスト入力に対して、Milvusは以下の手順に従ってインデックスを作成します。

  1. アナライザー:アナライザーは、入力テキストを個々の単語(トークン)に分割し、必要に応じてフィルタを適用して処理します。これにより、Milvusはこれらのトークンに基づいてインデックスを構築できます。

  2. インデックス作成:テキスト分析の後、Milvusは各一意のトークンを、それを含むドキュメントにマッピングする逆インデックスを作成します。

ユーザーがテキスト検索を行うと、逆引きインデックスが使用され、その用語を含むすべてのドキュメントが迅速に検索されます。これは、ドキュメントを1つずつスキャンするよりもはるかに高速です。

Keyword Match キーワード検索

テキスト一致を有効にする

テキスト一致は、一致機能が有効になっている文字列フィールドで動作します。このページの例では VARCHARを使用しており、これはすべてのクライアント SDK でサポートされています。Milvus 3.0.x では、 TEXT フィールドも、Storage V3が有効になっている場合にテキストマッチをサポートします。どちらのフィールドタイプの場合も、enable_analyzerenable_match の両方をTrue に設定し、必要に応じてコレクションスキーマを定義する際にアナライザーを設定してください。

enable_analyzerenable_match

特定のVARCHAR フィールドでテキスト検索を有効にするには、フィールドスキーマを定義する際に、enable_analyzerenable_match の両方のパラメータをTrue に設定します。これにより、Milvusはテキストをトークン化し、指定されたフィールドの逆引きインデックスを作成するよう指示され、高速かつ効率的なテキスト検索が可能になります。

from pymilvus import MilvusClient, DataType

schema = MilvusClient.create_schema(enable_dynamic_field=False)
schema.add_field(
    field_name="id",
    datatype=DataType.INT64,
    is_primary=True,
    auto_id=True
)
schema.add_field(
    field_name='text', 
    datatype=DataType.VARCHAR, 
    max_length=1000, 
    enable_analyzer=True, # Whether to enable text analysis for this field
    enable_match=True # Whether to enable text match
)
schema.add_field(
    field_name="embeddings",
    datatype=DataType.FLOAT_VECTOR,
    dim=5
)
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()
        .enableDynamicField(false)
        .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)
        .enableMatch(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("embeddings")
        .dataType(DataType.FloatVector)
        .dimension(5)
        .build());
import "github.com/milvus-io/milvus/client/v2/entity"

schema := entity.NewSchema().WithDynamicFieldEnabled(false)
schema.WithField(entity.NewField().
    WithName("id").
    WithDataType(entity.FieldTypeInt64).
    WithIsPrimaryKey(true).
    WithIsAutoID(true),
).WithField(entity.NewField().
    WithName("text").
    WithDataType(entity.FieldTypeVarChar).
    WithEnableAnalyzer(true).
    WithEnableMatch(true).
    WithMaxLength(1000),
).WithField(entity.NewField().
    WithName("embeddings").
    WithDataType(entity.FieldTypeFloatVector).
    WithDim(5),
)
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: "embeddings",
    data_type: DataType.FloatVector,
    dim: 5,
  },
];
export schema='{
        "autoId": true,
        "enabledDynamicField": false,
        "fields": [
            {
                "fieldName": "id",
                "dataType": "Int64",
                "isPrimary": true
            },
            {
                "fieldName": "text",
                "dataType": "VarChar",
                "elementTypeParams": {
                    "max_length": 1000,
                    "enable_analyzer": true,
                    "enable_match": true
                }
            },
            {
                "fieldName": "embeddings",
                "dataType": "FloatVector",
                "elementTypeParams": {
                    "dim": "5"
                }
            }
        ]
    }'

オプション:アナライザーの設定

キーワードマッチングのパフォーマンスと精度は、選択したアナライザーによって異なります。アナライザーは言語やテキスト構造に応じて最適化されているため、適切なアナライザーを選択することで、特定のユースケースにおける検索結果に大きな影響を与える可能性があります。

デフォルトでは、Milvusはstandard アナライザーを使用します。このアナライザーは、空白や句読点に基づいてテキストをトークン化し、40文字を超えるトークンを削除し、テキストを小文字に変換します。このデフォルト設定を適用するために追加のパラメーターは必要ありません。詳細については、「Standard」を参照してください。

別のアナライザーが必要な場合は、analyzer_params パラメーターを使用して設定できます。たとえば、英語のテキストを処理するためにenglish アナライザーを適用するには、次のようにします。

analyzer_params = {
    "type": "english"
}
schema.add_field(
    field_name='text',
    datatype=DataType.VARCHAR,
    max_length=200,
    enable_analyzer=True,
    analyzer_params = analyzer_params,
    enable_match = True,
)
Map<String, Object> analyzerParams = new HashMap<>();
analyzerParams.put("type", "english");
schema.addField(AddFieldReq.builder()
        .fieldName("text")
        .dataType(DataType.VarChar)
        .maxLength(200)
        .enableAnalyzer(true)
        .analyzerParams(analyzerParams)
        .enableMatch(true)
        .build());
analyzerParams := map[string]any{"type": "english"}
schema.WithField(entity.NewField().
    WithName("text").
    WithDataType(entity.FieldTypeVarChar).
    WithEnableAnalyzer(true).
    WithEnableMatch(true).
    WithAnalyzerParams(analyzerParams).
    WithMaxLength(200),
)
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,
    analyzer_params: { type: 'english' },
  },
  {
    name: "embeddings",
    data_type: DataType.FloatVector,
    dim: 5,
  },
];
export schema='{
        "autoId": true,
        "enabledDynamicField": false,
        "fields": [
            {
                "fieldName": "id",
                "dataType": "Int64",
                "isPrimary": true
            },
            {
                "fieldName": "text",
                "dataType": "VarChar",
                "elementTypeParams": {
                    "max_length": 200,
                    "enable_analyzer": true,
                    "enable_match": true,
                    "analyzer_params": {"type": "english"}
                }
            },
            {
                "fieldName": "embeddings",
                "dataType": "FloatVector",
                "elementTypeParams": {
                    "dim": "5"
                }
            }
        ]
    }'

Milvus では、さまざまな言語やシナリオに適したその他のアナライザーも多数提供されています。詳細については、「アナライザーの概要」を参照してください。

テキストマッチの使用

コレクションスキーマ内のVARCHAR またはTEXT フィールドでテキストマッチを有効にすると、TEXT_MATCH 式を使用してテキストマッチを実行できます。

TEXT_MATCH 式の構文

TEXT_MATCH 式は、検索対象のフィールドと検索語を指定するために使用されます。その構文は次のとおりです。

TEXT_MATCH(field_name, text)
  • field_name: 検索対象となる、一致処理が有効なVARCHAR またはTEXT フィールドの名前。

  • text: 検索対象の用語。複数の用語は、スペース、または言語や設定されたアナライザーに応じた適切な区切り文字で区切ることができます。

デフォルトでは、TEXT_MATCH「OR」のマッチングロジックを使用します。つまり、指定された用語のいずれかを含むドキュメントが返されます。たとえば、text フィールド内で「machine 」または「deep 」という用語を含むドキュメントを検索するには、次の式を使用します:

filter = "TEXT_MATCH(text, 'machine deep')"
String filter = "TEXT_MATCH(text, 'machine deep')";
filter := "TEXT_MATCH(text, 'machine deep')"
const filter = "TEXT_MATCH(text, 'machine deep')";
export filter="\"TEXT_MATCH(text, 'machine deep')\""

また、論理演算子を使用して複数のTEXT_MATCH 式を組み合わせ、AND検索を行うこともできます。

  • text 」フィールドに「machine 」と「deep 」の両方が含まれるドキュメントを検索するには、次の式を使用します。

    filter = "TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'deep')"
    
    String filter = "TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'deep')";
    
    filter := "TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'deep')"
    
    const filter = "TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'deep')"
    
    export filter="\"TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'deep')\""
    
  • text フィールドに「machine 」と「learning 」の両方が含まれているが、「deep 」は含まれていないドキュメントを検索するには、次の式を使用します:

    filter = "not TEXT_MATCH(text, 'deep') and TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'learning')"
    
    String filter = "not TEXT_MATCH(text, 'deep') and TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'learning')";
    
    filter := "not TEXT_MATCH(text, 'deep') and TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'learning')"
    
    const filter = "not TEXT_MATCH(text, 'deep') and TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'learning')";
    
    export filter="\"not TEXT_MATCH(text, 'deep') and TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'learning')\""
    

TEXT_MATCH_FUZZY 式構文Compatible with Milvus 3.0.0+

TEXT_MATCH_FUZZY を使用すると、クエリトークンとインデックス化されたトークン間のスペル違いを許容できます。Milvus はフィールドのアナライザーを使用してクエリテキストを解析し、結果として得られた各トークンにファジーマッチングを適用します。クエリによって複数のトークンが生成された場合、いずれかのトークンが設定された編集距離を満たせば、その式はエンティティと一致します。

構文は次のとおりです:

TEXT_MATCH_FUZZY(field_name, text, max_edit_distance = 1)
  • field_name: 検索対象となる、マッチングが有効なVARCHAR またはTEXT フィールドの名前。

  • text: 解析を行い、インデックス化されたトークンとの照合を行うクエリテキスト。

  • max_edit_distance: 各クエリトークンに対して許容される最大編集距離。オプション名は必ず「max_edit_distance 」とし、その値は「0 」、「1 」、または「2 」のいずれかでなければなりません。「0 」を指定すると、トークンの完全一致が行われ、「TEXT_MATCH 」と同等になります。

たとえば、次の式は、machne から1文字以内の編集範囲にあるトークン(machine を含む)に一致します。

filter = "TEXT_MATCH_FUZZY(text, 'machne', max_edit_distance = 1)"
String filter = "TEXT_MATCH_FUZZY(text, 'machne', max_edit_distance = 1)";
filter := "TEXT_MATCH_FUZZY(text, 'machne', max_edit_distance = 1)"
const filter = "TEXT_MATCH_FUZZY(text, 'machne', max_edit_distance = 1)";
export filter="\"TEXT_MATCH_FUZZY(text, 'machne', max_edit_distance = 1)\""

TEXT_MATCH_FUZZY はフィルタ式構文の一部であるため、クライアント SDK には専用のファジーマッチメソッドは必要ありません。検索やクエリ操作において、TEXT_MATCH に対して使用されるのと同じfilter パラメータを通じて、この式を渡してください。

テキスト一致による検索

テキストマッチは、ベクトル類似度検索と組み合わせて使用することで、検索範囲を絞り込み、検索パフォーマンスを向上させることができます。ベクトル類似度検索の前にテキストマッチを使用してコレクションをフィルタリングすることで、検索対象となるドキュメントの数を減らし、クエリ処理時間を短縮できます。

この例では、filter 式によって、指定された用語「keyword1 」または「keyword2 」に一致するドキュメントのみが検索結果に含められるようにフィルタリングされます。その後、このフィルタリングされたドキュメントのサブセットに対してベクトル類似度検索が実行されます。

テキストハイライターを設定することで、検索結果内で一致した用語を強調表示できます。詳細については、「テキストハイライター」を参照してください。

# Match entities with `keyword1` or `keyword2`
filter = "TEXT_MATCH(text, 'keyword1 keyword2')"

# Assuming 'embeddings' is the vector field and 'text' is the VARCHAR field
result = client.search(
    collection_name="my_collection", # Your collection name
    anns_field="embeddings", # Vector field name
    data=[query_vector], # Query vector
    filter=filter,
    search_params={"params": {"nprobe": 10}},
    limit=10, # Max. number of results to return
    output_fields=["id", "text"] # Fields to return
)
String filter = "TEXT_MATCH(text, 'keyword1 keyword2')";

SearchResp searchResp = client.search(SearchReq.builder()
        .collectionName("my_collection")
        .annsField("embeddings")
        .data(Collections.singletonList(queryVector)))
        .filter(filter)
        .topK(10)
        .outputFields(Arrays.asList("id", "text"))
        .build());
filter := "TEXT_MATCH(text, 'keyword1 keyword2')"

resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
    "my_collection", // collectionName
    10,               // limit
    []entity.Vector{entity.FloatVector(queryVector)},
).WithANNSField("embeddings").
    WithFilter(filter).
    WithOutputFields("id", "text"))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
// Match entities with `keyword1` or `keyword2`
const filter = "TEXT_MATCH(text, 'keyword1 keyword2')";

// Assuming 'embeddings' is the vector field and 'text' is the VARCHAR field
const result = await client.search(
    collection_name: "my_collection", // Your collection name
    anns_field: "embeddings", // Vector field name
    data: [query_vector], // Query vector
    filter: filter,
    params: {"nprobe": 10},
    limit: 10, // Max. number of results to return
    output_fields: ["id", "text"] //Fields to return
);
export filter="\"TEXT_MATCH(text, 'keyword1 keyword2')\""

export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
    "collectionName": "my_collection",
    "annsField": "embeddings",
    "data": [[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104]],
    "filter": '"$filter"',
    "searchParams": {
        "params": {
            "nprobe": 10
        }
    },
    "limit": 10,
    "outputFields": ["text","id"]
}'

テキスト一致によるクエリ

テキスト一致は、クエリ操作におけるスカラーフィルタリングにも使用できます。query() メソッドのexpr パラメータにTEXT_MATCH 式を指定することで、指定された用語に一致するドキュメントを取得できます。

以下の例では、text フィールドに「keyword1 」と「keyword2 」の両方の用語が含まれるドキュメントを取得します。

# Match entities with both `keyword1` and `keyword2`
filter = "TEXT_MATCH(text, 'keyword1') and TEXT_MATCH(text, 'keyword2')"

result = client.query(
    collection_name="my_collection",
    filter=filter, 
    output_fields=["id", "text"]
)
String filter = "TEXT_MATCH(text, 'keyword1') and TEXT_MATCH(text, 'keyword2')";

QueryResp queryResp = client.query(QueryReq.builder()
        .collectionName("my_collection")
        .filter(filter)
        .outputFields(Arrays.asList("id", "text"))
        .build()
);
filter = "TEXT_MATCH(text, 'keyword1') and TEXT_MATCH(text, 'keyword2')"
resultSet, err := client.Query(ctx, milvusclient.NewQueryOption("my_collection").
    WithFilter(filter).
    WithOutputFields("id", "text"))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}

// Match entities with both `keyword1` and `keyword2`
const filter = "TEXT_MATCH(text, 'keyword1') and TEXT_MATCH(text, 'keyword2')";

const result = await client.query(
    collection_name: "my_collection",
    filter: filter, 
    output_fields: ["id", "text"]
)
export filter="\"TEXT_MATCH(text, 'keyword1') and TEXT_MATCH(text, 'keyword2')\""

export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/query" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
    "collectionName": "my_collection",
    "filter": '"$filter"',
    "outputFields": ["id", "text"]
}'

考慮事項

  • フィールドで用語マッチングを有効にすると、逆引きインデックスが作成され、ストレージリソースを消費します。この機能を有効にする際は、テキストのサイズ、一意のトークン数、および使用するアナライザーによってストレージへの影響が異なるため、その点を考慮してください。

  • スキーマでアナライザーを定義すると、その設定は当該コレクションに対して永続化されます。別のアナライザーの方がニーズに適していると判断した場合は、既存のコレクションを削除し、希望するアナライザー設定で新しいコレクションを作成することを検討してください。

  • filter 式におけるエスケープ規則:

    • 式内で二重引用符または単一引用符で囲まれた文字は、文字列定数として解釈されます。文字列定数にエスケープ文字が含まれる場合、そのエスケープ文字はエスケープシーケンスで表現する必要があります。たとえば、\ を表現するには `\\ `、タブを表現するには `\\t `、改行を表現するには `\t`、\\n を使用します。

    • 文字列定数が一重引用符で囲まれている場合、定数内の「'」は\\' で表し、「"」は" または\\" のいずれかで表すことができます。例:'It\\'s milvus'

    • 文字列定数が二重引用符で囲まれている場合、定数内の二重引用符は `\\" ` と記述し、単一引用符は `' ` または `\\'` のいずれかで記述します。例:"He said \\"Hi\\""