Hinzufügen eines Filters zu einer Vektorabfrage in Azure KI-Suche

Hinweis

strictPostFilter befindet sich derzeit in der Vorschau. Diese Vorschau wird ohne Vereinbarung auf Serviceebene bereitgestellt und wird für Produktionsworkloads nicht empfohlen. Bestimmte Features werden möglicherweise nicht unterstützt oder weisen eingeschränkte Funktionen auf. Weitere Informationen finden Sie unter Supplementale Nutzungsbedingungen für Microsoft Azure Previews.

prefilter und postfilter sind in der Regel in der neuesten stabilen REST-API-Version verfügbar.

In Azure KI-Suche können Sie einen Filterausdruck verwenden, um einer vectorabfrage Einschluss- oder Ausschlusskriterien hinzuzufügen. Sie können auch einen Filtermodus angeben, der den Filter anwendet:

  • Vor der Abfrageausführung, die als Vorabfilterung bezeichnet wird.
  • Nach der Abfrageausführung, die als Postfilterung bezeichnet wird.
  • Nachdem die globalen Top-k-Ergebnisse identifiziert wurden: Dies wird auch als strikte Nachfilterung (Vorschau) bezeichnet.

In diesem Artikel wird REST zur Veranschaulichung verwendet. Codebeispiele in anderen Sprachen und End-to-End-Lösungen, die Vektorabfragen enthalten, finden Sie im Repository azure-search-vector-samples GitHub.

Sie können auch Search Explorer im Azure-Portal verwenden, um Vektorinhalte abzufragen. In der JSON-Ansicht können Sie Filter hinzufügen und den Filtermodus angeben.

Funktionsweise des Filterns in Vektorabfragen

Azure KI-Suche verwendet den hierarchischen Navigable Small World (HNSW)-Algorithmus für die Suche nach nähersten Nachbarn (ANN), wobei HNSW-Diagramme über mehrere Shards hinweg gespeichert werden. Jeder Shard enthält einen Teil des gesamten Indexes.

Filter gelten für filterableNichtvektorfelder , entweder Zeichenfolgen oder numerische Felder, um Suchdokumente basierend auf Filterkriterien einzuschließen oder auszuschließen. Vektorfelder selbst sind nicht filterbar, Sie können jedoch Filter für andere Felder im selben Index verwenden, um die Dokumente einzuschränken, die für die Vektorsuche berücksichtigt werden. Wenn Ihr Index keine geeigneten Text- oder numerischen Felder aufweist, suchen Sie nach Dokumentmetadaten, die bei der Filterung hilfreich sein können, z. B. LastModified- oder CreatedBy-Eigenschaften.

Der vectorFilterMode Parameter steuert, wo Filtervorgänge während der Suchphasen angewendet werden, was sich darauf auswirkt, wie die Ergebnisse auf eine Teilmenge von Elementen gefiltert werden (z. B. nach Kategorie, Tag oder anderen Attributen), und wirkt sich auf Latenz, Rückruf und Durchsatz aus. Es gibt drei Modi:

  • preFilter wendet den Filter während der HNSW-Traversierung auf jeden Shard an. Dieser Modus maximiert den Rückruf, kann aber mehr des Diagramms durchlaufen und die CPU- und Latenzzeit für hoch selektive Filter erhöhen.

  • postFilter führt HNSW-Durchlauf und -Filterung für jeden Shard unabhängig voneinander durch, schneidet die Ergebnisse auf Shard-Ebene und aggregiert dann die Top-k aus jedem Shard zu einem globalen Top-k. Dieser Modus kann falsche Negative für hoch selektive Filter oder kleine k Werte erstellen.

  • strictPostFilter (Vorschau) findet die ungefilterten globalen Top-k, bevor der Filter angewendet wird. Dieser Modus hat das höchste Risiko, falsch negative Ergebnisse für hoch selektive Filter und kleine k Werte zurückzugeben.

Weitere Informationen zu diesen Modi finden Sie unter Festlegen des Filtermodus.

Definieren eines Filters

Filter bestimmen den Umfang von Vektorabfragen und werden mithilfe der Documents – Search Post (REST-API) definiert. Wenn Sie kein Vorschaufeature verwenden möchten, verwenden Sie die neueste stabile Version der REST-APIs des Suchdiensts , um die Anforderung zu formulieren.

Diese REST-API bietet Folgendes:

  • filter für die Kriterien.
  • vectorFilterMode um anzugeben, wann der Filter während der Vektorabfrage angewendet wird. Unterstützte Modi finden Sie unter "Festlegen des Filtermodus".
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
        }
    ]
}

In diesem Beispiel zielt die Einbettung des Vektors auf das contentVector Feld ab, und die Filterkriterien gelten für categoryein filterbares Textfeld. Da der preFilter Modus verwendet wird, wird der Filter angewendet, bevor die Suchmaschine die Abfrage ausführt, sodass nur Dokumente in der Kategorie während der Databases Vektorsuche berücksichtigt werden.

Festlegen des Filtermodus

Der vectorFilterMode Parameter bestimmt, wann und wie der Filter relativ zur Vektorabfrageausführung angewendet wird. Sie können die folgenden Modi verwenden:

  • preFilter (empfohlen)
  • postFilter
  • strictPostFilter (Vorschau)

Hinweis

preFilter ist die Standardeinstellung für Indizes, die nach ungefähr dem 15. Oktober 2023 erstellt wurden. Für Indizes, die vor diesem Datum erstellt wurden, postFilter ist die Standardeinstellung. Um erweiterte Vektorfeatures wie z. B. die Vektorkomprimierung zu verwenden preFilter , müssen Sie den Index neu erstellen.

Sie können die Kompatibilität testen, indem Sie eine Vektorabfrage mit "vectorFilterMode": "preFilter" der 2023-10-01-preview REST-API-Version oder höher senden. Wenn die Abfrage fehlschlägt, unterstützt Ihr Index preFilter nicht.

Vorfilterung wendet Filter vor der Abfrageausführung an, wodurch der Kandidatensatz für den Vektorsuchalgorithmus reduziert wird. Die oberstenk Ergebnisse werden dann aus diesem gefilterten Satz ausgewählt.

Bei einer Vektorabfrage ist preFilter der Standardmodus, da die Trefferquote und Qualität der Latenz vorgezogen werden.

Funktionsweise dieses Modus

  1. Auf jedem Shard erfolgt die Anwendung des Filterprädikats während des HNSW-Durchlaufs, wobei das Diagramm erweitert wird, bis k Kandidaten gefunden werden.

  2. Erzeugen Sie die vorfiltrierten lokalen Top-Ergebnissek pro Shard.

  3. Aggregieren Sie die gefilterten Ergebnisse in eine globale Top-Ergebnismengek.

Auswirkung dieses Modus

Traversal erweitert die Suchoberfläche, um mehr gefilterte Kandidaten zu finden, insbesondere, wenn der Filter selektiv ist. Dies erzeugt die ähnlichsten Top-k-Ergebnisse über alle Shards hinweg. Jeder Shard identifiziert die k Ergebnisse, die das Filter-Prädikat erfüllen.

Vorfilterung garantiert, dass k Ergebnisse zurückgegeben werden, wenn sie im Index vorhanden sind. Bei hoch selektiven Filtern kann dies dazu führen, dass ein erheblicher Teil des Diagramms durchlaufen wird, wodurch die Berechnungskosten und Latenz erhöht werden, während der Durchsatz reduziert wird. Wenn Ihr Filter sehr selektiv ist (hat nur wenige Übereinstimmungen), sollten Sie erwägen, eine erschöpfende Suche mit exhaustive: true durchzuführen.

Diagramm der Vorfilter.

Vergleichstabelle

Modus Rückruf (gefilterte Ergebnisse) Rechenkosten Risiko falsch negativer Ergebnisse Wann verwendet werden soll
preFilter Sehr hoch Höher (erhöht sich mit Filterauswahl und Komplexität) Kein Risiko Empfohlene Standardeinstellung für alle Szenarien, insbesondere wenn der Rückruf kritisch ist (vertrauliche Suchdomänen), bei verwendung selektiver Filter oder bei Verwendung kleiner k.
postFilter Mittel bis hoch (nimmt mit der Filterselektivität ab) Ähnlich wie ungefiltert, erhöht sich aber mit der Filterkomplexität Moderat (kann Übereinstimmungen pro Shard übersehen) Eine Option für Filter, die nicht zu selektiv sind und für höherek Abfragen.
strictPostFilter Niedrigste (verringert sich am schnellsten mit Filterselektivität) Ähnlich wie ungefiltert Am höchsten (kann null bis keine Ergebnisse für ausgewählte Filter oder kleine k zurückgeben) Eine Option für Faceted Search-Anwendungen, bei denen das Anzeigen zusätzlicher Ergebnisse nach der Filteranwendung die Benutzererfahrung stärker beeinflusst als das Risiko falscher negativer Ergebnisse. Nicht zusammen mit kleinem k verwenden.

Benchmarktests für Vorfilterung und Nachfilterung

Wichtig

Dieser Abschnitt bezieht sich auf Vorfilterung und Nachfilterung, nicht auf strikte Nachfilterung.

Um die Bedingungen zu verstehen, unter denen ein Filtermodus besser als der andere funktioniert, haben wir eine Reihe von Tests ausgeführt, um Abfrageergebnisse über kleine, mittlere und große Indizes auszuwerten.

  • Klein (100.000 Dokumente, 2,5 GB Index, 1.536 Dimensionen)
  • Mittel (1 Millionen Dokumente, 25 GB Index, 1.536 Dimensionen)
  • Groß (1 Milliarden Dokumente, 1,9 TB Index, 96 Dimensionen)

Für die kleinen und mittleren Workloads haben wir einen Standard 2 (S2)-Dienst mit einer Partition und einem Replikat verwendet. Für die große Workload haben wir einen Standard 3 (S3)-Dienst mit 12 Partitionen und einem Replikat verwendet.

Indizes hatten eine identische Konstruktion: ein Schlüsselfeld, ein Vektorfeld, ein Textfeld und ein numerisches filterbares Feld. Der folgende Index wird mithilfe der 2023-11-01 Syntax definiert.

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"
            }
        ]
      }
    }

In Abfragen haben wir einen identischen Filter sowohl für Prefilter- als auch für Postfiltervorgänge verwendet. Wir haben einen einfachen Filter verwendet, um sicherzustellen, dass Abweichungen in der Leistung aufgrund des Filtermodus und nicht der Filterkomplexität zurückzuführen waren.

Die Ergebnisse wurden in Abfragen pro Sekunde (QPS) gemessen.

Erkenntnisse

  • Die Vorfilterung ist fast immer langsamer als die Nachfilterung, außer bei kleinen Indizes, bei denen die Leistung ungefähr gleich ist.

  • Bei größeren Datensätzen ist die Vorfilterung um Größenordnungen langsamer.

  • Warum ist 'prefilter' der Standardwert, obwohl es fast immer langsamer ist? Durch die Vorfilterung wird sichergestellt, dass k Ergebnisse zurückgegeben werden, wenn sie im Index vorhanden sind, wobei die Priorität auf Rückruf und Genauigkeit vor Geschwindigkeit liegt.

  • Verwenden Sie die Nachfilterung, wenn Sie:

    • Ihnen Geschwindigkeit wichtiger als Auswahl ist (Postfilterung kann weniger als k Ergebnisse zurückgeben).

    • Verwenden Sie Filter, die nicht übermäßig selektiv sind.

    • Verfügen Sie über Indizes ausreichender Größe, sodass die Vorfilterleistung nicht akzeptabel ist.

Einzelheiten

  • Angesichts eines Datasets mit 100.000 Vektoren bei 1.536 Dimensionen:

    • Bei der Filterung von mehr als 30% des Datasets waren Vorfilterung und Nachfilterung vergleichbar.

    • Beim Filtern von weniger als 0,1% des Datasets betrug die Vorfilterung etwa 50% langsamer als die Nachfilterung.

  • Angesichts eines Datasets mit 1 Millionen Vektoren bei 1.536 Dimensionen:

    • Bei der Filterung von mehr als 30% des Datasets war die Vorfilterung etwa 30% langsamer.

    • Bei der Filterung von weniger als 2% des Datasets war die Vorfilterung etwa siebenmal langsamer.

  • Angesichts eines Datasets mit 1 Milliarden Vektoren mit 96 Dimensionen:

    • Beim Filtern von mehr als 5% des Datasets war die Vorfilterung etwa 50% langsamer.

    • Bei der Filterung von weniger als 10% des Datasets war die Vorfilterung etwa siebenmal langsamer.

Die folgende Grafik zeigt die relative Präfilter-QPS, die als Präfilter-QPS dividiert durch Postfilter-QPS berechnet wird.

Diagramm mit QPS-Leistung für kleine, mittlere und große Indizes für relative QPS.

Die vertikale Achse stellt die relative Leistung der Vorfilterung im Vergleich zur Postfilterung dar, ausgedrückt als Verhältnis von QPS (Abfragen pro Sekunde). Zum Beispiel:

  • Ein Wert von 0.0 bedeutet, dass das Prefiltering 100% langsamer als bei der Nachfilterung ist.
  • Ein Wert von 0.5 bedeutet, dass das Vorfiltern um 50 % langsamer ist.
  • Ein Wert von 1.0 bedeutet, dass Vor- und Nachfiltern gleichwertig sind.

Die horizontale Achse stellt die Filterrate oder den Prozentsatz der Kandidatendokumente nach dem Anwenden des Filters dar. Eine Rate von 1.00% bedeutet beispielsweise, dass die ausgewählten Filterkriterien ein Prozentwert des Suchkorpus betragen.