次の方法で共有


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

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

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

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

  • クエリの実行前 (事前フィルター処理と呼ばれます)。
  • クエリの実行後 (事後フィルター処理と呼ばれます)。
  • グローバルで上位 k 件の結果が特定された後 (厳密な事後フィルター処理 (プレビュー) と呼ばれます)。

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

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

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

Azure AI Search では、概最近隣 (ANN) 検索に階層ナビゲーション可能 Small World (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 を使用して完全な検索を実行することを検討してください。

プリフィルターの図。

比較表

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

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

Important

このセクションは事前フィルター処理と事後フィルター処理に適用されます (厳密な事後フィルター処理には適用されません)。

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

  • 小 (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 つのフィルター可能な数値フィールドを用意しました。 次のインデックスは、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 件より少ない結果が返される場合があります)。

    • フィルター処理の対象が過度に選択的でない場合。

    • インデックスが事前フィルター処理のパフォーマンスでは処理できないようなサイズの場合。

詳細

  • 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% を選択したフィルター条件を意味します。