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

メモ

strictPostFilter は現在パブリック プレビュー段階です。 このプレビューは、サービス レベル アグリーメントなしで提供され、運用環境のワークロードには推奨されません。 特定の機能がサポートされていないか、機能が制限されている可能性があります。 詳細については、「Microsoft Azure プレビューの使用条件を参照してください。

prefilter および postfilter は、 最新の安定した REST API バージョンで一般提供されています。

Azure AI 検索では、filter 式を使用して、包含または除外の条件を vector クエリに追加できます。 フィルターを適用するフィルター モードを指定することもできます。

  • クエリの実行前 ( プリフィルター処理と呼ばれます)。
  • クエリの実行後( 事後フィルター処理と呼ばれます)。
  • グローバルトップk の結果が特定された後、この処理は 厳密なポストフィルタリング (プレビュー) として知られています。

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

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

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

Azure AI 検索では、近似最近傍 (ANN) 検索に階層的ナビゲーションが可能なスモールワールド (HNSW) アルゴリズムを使用し、HNSWグラフを複数のシャードにわたって格納します。 各シャードには、インデックス全体の一部が含まれています。

フィルターは、文字列または数値 filterable非ベクトル フィールドに適用され、フィルター条件に基づいて検索ドキュメントを含めるか除外します。 ベクター フィールド自体はフィルター処理できませんが、同じインデックス内の他のフィールドでフィルターを使用して、ベクター検索と見なされるドキュメントを絞り込むことができます。 インデックスに適切なテキストフィールドや数値フィールドがない場合は、 LastModifiedCreatedBy プロパティなど、フィルター処理に役立つ可能性のあるドキュメント メタデータを確認します。

vectorFilterMode パラメーターは、検索の段階でフィルター操作が適用される場所を制御します。これは、結果を項目のサブセット (カテゴリ、タグ、その他の属性など) にフィルター処理する方法に影響し、待機時間、再現率、スループットに影響します。 次の 3 つのモードがあります。

  • preFilter は、各シャードで HNSW トラバーサル にフィルターを適用します。 このモードでは、再現率は最大化されますが、より多くのグラフを走査できるため、高度に選択的なフィルターの CPU と待機時間が増加します。

  • postFilter は、各シャードで HNSW トラバーサルとフィルター処理を個別に実行し、シャード レベルで結果を交差させ、各シャードの上位 k をグローバル トップ kに集計します。 このモードでは、選択性の高いフィルターまたは小さな k 値に対して偽の否定を作成できます。

  • strictPostFilter(プレビュー) フィルターを適用するkに、フィルター処理されていないグローバル トップ を検索します。 このモードでは、選択性の高いフィルターと小さな k 値に対して偽陰性が返されるリスクが最も高くなります。

これらのモードの詳細については、「 フィルター モードの設定」を参照してください。

フィルターを定義する

フィルターはベクター クエリのスコープを決定し、 Documents - Search Post (REST API) を使用して定義されます。 プレビュー機能を使用する場合を除き、最新の安定バージョンの Search Service REST API を 使用して要求を作成します。

この REST API は次の機能を提供します。

  • 条件指定用の filter
  • vectorFilterMode ベクター クエリ中にフィルターを適用するタイミングを指定します。 サポートされているモードについては、「 フィルター モードの設定」を参照してください。
POST https://{search-endpoint}/indexes/{index-name}/docs/search?api-version={api-version}
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,
                . . . // Trimmed for readability
                -0.02178128,
                -0.00086512347
            ],
            "fields": "contentVector",
            "k": 50
        }
    ]
}

この例では、ベクター埋め込みによって contentVector フィールドが対象となり、フィルター条件がフィルター可能なテキスト フィールドである categoryに適用されます。 preFilter モードが使用されるため、検索エンジンがクエリを実行する前にフィルターが適用されるため、ベクター検索中にDatabases カテゴリ内のドキュメントのみが考慮されます。

フィルター モードを設定する

vectorFilterMode パラメーターは、ベクター クエリの実行に対してフィルターを適用するタイミングと方法を決定します。 次のモードを使用できます。

  • preFilter (推奨)
  • postFilter
  • strictPostFilter (プレビュー)

メモ

preFilter は、2023 年 10 月 15 日以降に作成されたインデックスの既定値です。 この日付より前に作成されたインデックスの場合、 postFilter が既定値です。 preFilterやその他の高度なベクター機能 (ベクター圧縮など) を使用するには、インデックスを再作成する必要があります。

互換性をテストする場合は、"vectorFilterMode": "preFilter" REST API バージョン以降で2023-10-01-previewを使用してベクター クエリを送信します。 クエリが失敗した場合、インデックスは preFilterをサポートしません。

事前フィルター処理では、クエリの実行前にフィルターが適用され、ベクター検索アルゴリズムの候補セットが減ります。 その後、上位k 結果がこのフィルター処理されたセットから選択されます。

ベクター クエリでは、 preFilter が既定のモードです。これは、待機時間よりも再現率と品質が優先されるためです。

このモードのしくみ

  1. 各シャードで、HNSW トラバーサルにフィルター述語を適用し、k 個の候補が見つかるまでグラフを展開します。

  2. シャードごとに事前フィルター処理されたローカルの上位k 結果を生成します。

  3. フィルター処理された結果をグローバルな最上位k の結果セットに集計します。

このモードの効果

トラバーサルは、特にフィルターが選択されている場合に、フィルター処理された候補を検索するために検索サーフェイスを展開します。 これにより、すべてのシャードにわたって最も類似した上位 k 件の結果が生成されます。 各シャードは、フィルター述語を満たす k 結果を識別します。

事前フィルター処理では、 k 結果がインデックスに存在する場合に返されることを保証します。 高度に選択的なフィルターの場合、これによりグラフのかなりの部分が走査され、計算コストと待機時間が増加し、スループットが低下する可能性があります。 フィルターが選択性が高い (一致するものが非常に少ない) 場合は、 exhaustive: true を使用して完全な検索を実行することを検討してください。

プレフィルターの図。

比較表

モード リコール (フィルター処理された結果) 計算コスト 偽陰性のリスク 使用するタイミング
preFilter 非常に高い 高い (フィルターの選択性と複雑度に伴って増加) リスクなし すべてのシナリオ (特に、呼び戻しが重要な場合 (機密性の高い検索ドメイン)、選択的フィルターを使用する場合、または小規模な kを使用する場合に推奨される既定値。
postFilter 中から高 (フィルター選択性により減少) フィルター処理されないのに似ていますが、フィルターの複雑さが増します 中程度 (シャードあたりの一致を逃す可能性があります) あまり選択的でないフィルターと高レベルのk クエリのためのオプション。
strictPostFilter 最低 (フィルター選択性で最も迅速に減少) フィルター処理されていないものに似ている 最高 (選択的フィルターまたは小さな kの場合は 0 個の結果を返すことができます) フィルター アプリケーションの後でより多くの結果が表示されるファセット検索アプリケーションのオプションは、誤検知のリスクよりもユーザー エクスペリエンスに影響します。 小さな kでは使用しないでください。

事前フィルター処理と後フィルター処理のベンチマーク テスト

重要

このセクションは、厳密なポストフィルター処理ではなく、事前フィルター処理とポストフィルター処理に適用されます。

1 つのフィルター モードのパフォーマンスが他のフィルター モードよりも優れている条件を理解するために、一連のテストを実行して、小、中、および大のインデックスに対してクエリ結果を評価しました。

  • 小 (100,000 ドキュメント、2.5 GB インデックス、1,536 ディメンション)
  • 中 (100 万ドキュメント、25 GB インデックス、1,536 ディメンション)
  • 大規模 (10 億ドキュメント、1.9 TB インデックス、96 ディメンション)

中小規模のワークロードでは、1 つのパーティションと 1 つのレプリカを含む Standard 2 (S2) サービスを使用しました。 大規模なワークロードでは、12 個のパーティションと 1 つのレプリカを含む Standard 3 (S3) サービスを使用しました。

インデックスの構造は同じです。1 つのキー フィールド、1 つのベクター フィールド、1 つのテキスト フィールド、1 つの数値フィルター可能フィールドです。 次のインデックスは、 2023-11-01 構文を使用して定義されます。

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 未満になることがあります)。

    • 過度に選択的ではないフィルターを使用します。

    • 事前フィルター処理のパフォーマンスが許容できない十分なサイズのインデックスを作成します。

詳細

  • 1,536 次元で 100,000 個のベクターを含むデータセットを指定します。

    • データセットの 30% を超えるフィルター処理を行う場合、事前フィルター処理とポストフィルター処理は同等でした。

    • データセットのフィルター処理が 0.1% 未満の場合、事前フィルター処理はポストフィルター処理よりも約 50% 遅くなっていました。

  • 1,536 次元で 100 万個のベクトルを持つデータセットを指定すると、次のようになります。

    • データセットの 30% を超えるフィルター処理を行う場合、事前フィルター処理は約 30% 低速でした。

    • データセットのフィルター処理が 2% 未満の場合、プリフィルター処理の速度は約 7 倍でした。

  • 96 次元で 10 億個のベクトルを持つデータセットを指定します。

    • データセットの 5% を超えるフィルター処理を行った場合、事前フィルター処理は約 50% 低速でした。

    • データセットのフィルター処理が 10% 未満の場合、事前フィルター処理の速度は約 7 倍でした。

次のグラフは、プレフィルター QPS をポストフィルター QPS で除算して計算された、プリフィルター相対 QPS を示しています。

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

縦軸は、ポストフィルター処理と比較したプリフィルター処理の相対的なパフォーマンスを表し、QPS (1 秒あたりのクエリ数) の比率で表されます。 例えば:

  • 0.0の値は、事前フィルター処理がポストフィルター処理よりも 100% 遅い場合を意味します。
  • 0.5の値は、事前フィルター処理が 50% 低速であることを意味します。
  • 1.0の値は、事前フィルター処理と後フィルター処理が同等であることを意味します。

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