次の方法で共有


Azure AI 検索でベクトル クエリにフィルターを追加する

フィルター式を含むベクトル クエリ要求を定義して、包含条件または除外条件をクエリに追加できます。 この記事では、次のことについて説明します:

この記事では、図示するために REST を使用しています。 他の言語のコード サンプルの場合、ベクトル クエリを含むエンドツーエンドのソリューションについては、azure-search-vector-samples GitHub リポジトリを参照してください。

Azure portal で Search エクスプローラーを使用して、ベクトル コンテンツのクエリを実行することもできます。 JSON ビューを使用する場合は、フィルターを追加し、フィルター モードを指定できます。

ベクトル クエリでのフィルター処理のしくみ

フィルターは filterable の非ベクトル フィールド (文字列フィールドまたは数値) に適用され、フィルター条件に基づいて検索ドキュメントを含めたり除外したりします。 ベクトル フィールド自体はフィルター処理できませんが、同じインデックス内の他のフィールドにフィルターを適用して、ドキュメント (ここにもベクトル フィールドが含まれる) を含めたり除外したりできます。

フィルターは、vectorFilterMode パラメーターに基づいてクエリ実行の前または後に適用されます。

フィルターを定義する

フィルターによって、ベクトル クエリのスコープが決まります。 フィルターは、インデックスで filterable という属性が付いている非ベクトルの文字列と数値のフィールドに対して設定されて、それを反復処理しますが、フィルターの目的によって、ベクトル クエリが "何" に対して実行されるかが決まります (検索可能スペース全体、または検索結果の内容)。

テキストまたは数値を含むソース フィールドがない場合は、LastModified プロパティや CreatedBy プロパティなどのドキュメント メタデータを確認します。これは、メタデータ フィルターで役立つ可能性があります。

2024-07-01 は、この API の安定バージョンです。 次の特徴があります。

  • vectorFilterMode による事前フィルター処理 (既定)、または事後フィルター処理モード
  • filter は条件を提供します。

次の例では、クエリ文字列「フルテキスト検索をサポートする Azure サービス」をベクトルで表現しています。 クエリは contentVector フィールドを対象にしています。 実際のベクトルには 1536 個の埋め込みがあるため、この例では読みやすくするためにトリミングされています。

フィルター条件は、検索エンジンがベクトル クエリを実行する前に、フィルター可能なテキスト フィールド (この例では category) に適用されます。

POST https://{{search-service-name}}.search.windows.net/indexes/{{index-name}}/docs/search?api-version=2024-07-01
Content-Type: application/json
api-key: {{admin-api-key}}
{
    "count": true,
    "select": "title, content, category",
    "filter": "category eq 'Databases'",
    "vectorFilterMode": "preFilter",
    "vectorQueries": [
        {
            "kind": "vector",
            "vector": [
                -0.009154141,
                0.018708462,
                . . . 
                -0.02178128,
                -0.00086512347
            ],
            "exhaustive": true,
            "fields": "contentVector",
            "k": 5
        }
    ]
}

vectorFilterMode を設定する

vectorFilterMode クエリ パラメーターは、ベクトル クエリの実行前または実行後にフィルターを適用するかどうかを決定します。

事前フィルター モードを使用する

事前フィルターでは、クエリ実行前にフィルターが適用され、ベクトル検索アルゴリズムで類似コンテンツを見つける検索対象領域を狭めます。

ベクトル クエリでは、preFilter が既定です。

プリフィルターの図。

事後フィルター モードを使用する

事後フィルターでは、クエリ実行後にフィルターが適用され、検索結果を絞り込みます。

フィルター後のダイアグラム。

ベクトル フィルター モードのベンチマーク テスト

どちらのフィルター モードのパフォーマンスが他方より適切であるかの条件を理解するために、大中小のインデックスに対して一連のテストを実行し、クエリの出力を評価しました。

  • 小 (10 万件のドキュメント、2.5 GB のインデックス、1536 個のディメンション)
  • 中 (100 万件のドキュメント、25 GB のインデックス、1536 個のディメンション)
  • 大 (10 億件のドキュメント、1.9 TB のインデックス、96 個のディメンション)

小と中のワークロードでは、1 個のパーティションと 1 個のレプリカで構成される Standard 2 (S2) のサービスを使用しました。 大のワークロードでは、12 個のパーティションと 1 個のレプリカで構成される Standard 3 (S3) のサービスを使用しました。

インデックスには同一の構成、1 つのキー フィールド、1 つのテキスト フィールド、1 つのフィルター可能な数値フィールドを用意しました。 次のインデックスは、2023-11-03 構文を使って定義されています。

def get_index_schema(self, index_name, dimensions):
    return {
        "name": index_name,
        "fields": [
            {"name": "id", "type": "Edm.String", "key": True, "searchable": True},
            {"name": "content_vector", "type": "Collection(Edm.Single)", "dimensions": dimensions,
              "searchable": True, "retrievable": True, "filterable": False, "facetable": False, "sortable": False,
              "vectorSearchProfile": "defaulthnsw"},
            {"name": "text", "type": "Edm.String", "searchable": True, "filterable": False, "retrievable": True,
              "sortable": False, "facetable": False},
            {"name": "score", "type": "Edm.Double", "searchable": False, "filterable": True,
              "retrievable": True, "sortable": True, "facetable": True}
        ],
      "vectorSearch": {
        "algorithms": [
            {
              "name": "defaulthnsw",
              "kind": "hnsw",
              "hnswParameters": { "metric": "euclidean" }
            }
          ],
          "profiles": [
            {
              "name": "defaulthnsw",
              "algorithm": "defaulthnsw"
            }
        ]
      }
    }

クエリでは、事前フィルターと事後フィルターの両方に同一のフィルターを使用しました。 パフォーマンスの差異がフィルターの複雑さによるものではなく、フィルター モードに起因するように、単純なフィルターを使用しました。

出力は、1 秒あたりのクエリ数 (QPS) で測定しました。

重要なポイント

  • 事前フィルターでは、ほぼ毎回、事後フィルターより時間がかかりましたが、小インデックスでのパフォーマンスではほぼ同等の速さでした。

  • 大きいデータセットでの事前フィルターは、極端に遅れをとっていました。

  • 事前フィルターの方がほぼ毎回速度が遅いのに既定である理由 事前フィルターでは、インデックスに存在する場合、k の結果が返されることが保証されます。この場合、偏りによって速度よりもリコールと精度が優先されます。

  • 事後フィルターは次のお客様に適してします:

    • 選択よりも速度を優先する場合 (事後フィルターでは k より少ない結果が返されます)
    • フィルター処理の対象が過度に選択的でない場合
    • インデックスが事前フィルターでは処理できないようなサイズの場合

詳細

  • 1536 個のディメンションにおいて 10 万個のベクトルで構成されるデータセットの場合:

    • データセットの 30% 以上をフィルター処理する場合、事前フィルターと事後フィルターで違いはありません。
    • データセットの 0.1% 以下をフィルター処理する場合、事前フィルターの方が事後フィルターよりも約 50% 遅くなります。
  • 1536 個のディメンションにおいて 100 万個のベクトルで構成されるデータセットの場合:

    • データセットの 30% 以上をフィルター処理する場合、事前フィルターの方が約 30% 遅くなります。
    • データセットの 2% 以下をフィルター処理する場合、事前フィルターの方が約 7 倍遅くなります。
  • 96 個のディメンションにおいて 10 億個のベクトルで構成されるデータセットの場合:

    • データセットの 5% 以上をフィルター処理する場合、事前フィルターの方が約 50% 遅くなります。
    • データセットの 10% 以下をフィルター処理する場合、事前フィルターの方が約 7 倍遅くなります。

次のグラフでは、事前フィルターの QPS を事後フィルターの QPS で割って計算した事前フィルターの相対 QPS を示しています。

相対 QPS の小、中、および大のインデックスの QPS パフォーマンスを示すグラフ。

縦軸は、事後フィルターの QPS に対する事前フィルターの QPS です。 たとえば、0.0 の値は事前フィルターの方が 100% 遅いことを、縦軸の 0.5 は事前フィルターの方が 50% 遅いことを、1.0 は事前フィルターと事後フィルターが同等であることを意味しています。

横軸は、フィルター処理のレート、またはフィルターを適用した後に候補となるドキュメントの割合を表しています。 たとえば、1.00% は検索コーパスの 1% がフィルター処理の条件で選択されたことを意味しています。