Dodawanie filtru do zapytania wektorowego w Wyszukiwanie AI platformy Azure

Uwaga

strictPostFilter jest obecnie w publicznej wersji zapoznawczej. Ta wersja zapoznawcza jest udostępniana bez umowy dotyczącej poziomu usług i nie jest zalecana w przypadku obciążeń produkcyjnych. Niektóre funkcje mogą nie być obsługiwane lub mogą mieć ograniczone możliwości. Aby uzyskać więcej informacji, zobacz Wygólne warunki użytkowania Microsoft Azure Previews.

prefilter i postfilter są ogólnie dostępne w najnowszej stabilnej wersji interfejsu API REST.

W Wyszukiwanie AI platformy Azure można użyć wyrażenia filtr aby dodać kryteria dołączania lub wykluczania do zapytania vector. Można również określić tryb filtrowania, który stosuje filtr:

  • Przed wykonaniem zapytania, znanym jako wstępne filtrowanie.
  • Po wykonaniu zapytania, proces znany jako postfiltrowanie.
  • Po zidentyfikowaniu globalnych najlepszychk wyników, nazywanych ścisłym filtrowaniem postfiltrowania (wersja zapoznawcza).

W tym artykule użyto architektury REST na potrzeby ilustracji. Przykłady kodu w innych językach i kompleksowe rozwiązania, które obejmują zapytania wektorowe, zobacz repozytorium azure-search-vector-samples GitHub.

Można również użyć narzędzia Search Explorer w portalu Azure, aby zapytać o zawartość wektorową. W widoku JSON można dodawać filtry i określać tryb filtrowania.

Jak działa filtrowanie w zapytaniach wektorowych

Wyszukiwanie AI platformy Azure używa algorytmu hierarchicznego nawigowalnego małego świata (HNSW) do wyszukiwania przybliżonego najbliższego sąsiada (ANN), przechowując wykresy HNSW na wielu fragmentach. Każdy fragment zawiera część całego indeksu.

Filtry mają zastosowanie do filterable pól niewektorowych, ciągów lub liczb do uwzględnienia lub wykluczenia dokumentów wyszukiwania na podstawie kryteriów filtru. Same pola wektorowe nie są filtrowalne, ale można użyć filtrów w innych polach w tym samym indeksie, aby zawęzić dokumenty rozważane do wyszukiwania wektorowego. Jeśli indeks nie ma odpowiednich pól tekstowych lub liczbowych, sprawdź metadane dokumentu, które mogą ułatwić filtrowanie, takie jak LastModified lub CreatedBy właściwości.

Parametr vectorFilterMode określa, gdzie operacje filtrowania są stosowane na etapach wyszukiwania, co wpływa na sposób filtrowania wyników do podzestawu elementów (takich jak kategoria, tag lub inne atrybuty) i ma wpływ na opóźnienie, kompletność i przepływność. Istnieją trzy tryby:

  • preFilter stosuje filtr podczas przechodzenia HNSW na każdej partycji. Ten tryb maksymalizuje przypominanie, ale może przemieszczać się przez więcej elementów grafu, co zwiększa zużycie procesora i opóźnienie przy użyciu wysoce selektywnych filtrów.

  • postFilter Uruchamia przechodzenie HNSW i filtrowanie na każdym fragmencie niezależnie, łączy wyniki na poziomie fragmentu, a następnie agreguje najlepsze k z każdego fragmentu do globalnego najlepszego k. Ten tryb może tworzyć wyniki fałszywie negatywne dla wysoce selektywnych filtrów lub wartości k małej.

  • strictPostFilter (wersja zapoznawcza) znajduje niefiltrowaną globalną górę kprzed zastosowaniem filtru. Ten tryb ma największe ryzyko zwracania wyników fałszywie ujemnych dla wysoce selektywnych filtrów i małych k wartości.

Aby uzyskać więcej informacji na temat tych trybów, zobacz Ustawianie trybu filtrowania.

Definiowanie filtru

Filtry determinują zakres zapytań wektorowych i są definiowane przy użyciu Documents — Search Post (REST API). Jeśli nie chcesz używać funkcji w wersji zapoznawczej, użyj najnowszej stabilnej wersji interfejsów API REST usługi wyszukiwania , aby sformułować żądanie.

Ten interfejs API REST zapewnia:

  • filter dla kryterium.
  • vectorFilterMode aby określić, kiedy filtr jest stosowany podczas zapytania wektorowego. Aby uzyskać informacje o obsługiwanych trybach, zobacz Ustawianie trybu filtrowania.
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
        }
    ]
}

W tym przykładzie wektor osadzania jest przeznaczony dla contentVector pola, a kryteria filtrowania mają zastosowanie do categorypola tekstowego z możliwością filtrowania. Ponieważ używany jest preFilter tryb, filtr jest stosowany przed uruchomieniem zapytania przez silnik wyszukiwania, dlatego podczas wyszukiwania wektorowego uwzględniane są tylko dokumenty w tej Databases kategorii.

Ustawianie trybu filtrowania

Parametr vectorFilterMode określa, kiedy i jak filtr jest stosowany względem wykonywania zapytania wektorowego. Możesz użyć następujących trybów:

  • preFilter (zalecane)
  • postFilter
  • strictPostFilter (wersja zapoznawcza)

Uwaga

preFilter to wartość domyślna dla indeksów utworzonych po około 15 października 2023 r. W przypadku indeksów utworzonych przed tą datą postFilter jest wartością domyślną. Aby użyć preFilter i innych zaawansowanych funkcji wektorów, takich jak kompresja wektorów, należy ponownie utworzyć indeks.

Zgodność można przetestować, wysyłając zapytanie wektorowe "vectorFilterMode": "preFilter" w wersji 2023-10-01-preview interfejsu API REST lub nowszej. Jeśli zapytanie zakończy się niepowodzeniem, indeks nie obsługuje preFilterelementu .

Filtrowanie wstępne stosuje filtry przed wykonaniem zapytania, co zmniejsza zestaw kandydatów dla algorytmu wyszukiwania wektorowego. Wyniki top-k są następnie wybierane z tego filtrowanego zestawu.

W zapytaniu wektorowym preFilter jest trybem domyślnym, ponieważ faworyzuje kompletność i jakość ponad opóźnienia.

Jak działa ten tryb

  1. Dla każdego fragmentu zastosuj predykat filtru podczas przechodzenia HNSW, rozwijając graf do momentu k znalezienia kandydatów.

  2. Wstępnie przefiltrowane lokalne najlepsze wynikik na każdy fragment.

  3. Zagreguj przefiltrowane wyniki do globalnego zestawu wyników najwyższegok poziomu.

Efekt tego trybu

Przeszukiwanie rozszerza obszar wyszukiwania, aby znaleźć więcej filtrowanych kandydatów, zwłaszcza jeśli filtr jest precyzyjny. Daje to najbardziej podobnek wyniki we wszystkich fragmentach. Każdy fragment identyfikuje k wyniki spełniające predykat filtru.

Prefiltrowanie gwarantuje zwrot wyników k jeśli istnieją w indeksie. W przypadku wysoce selektywnych filtrów może to spowodować przejście znacznej części grafu, zwiększenie kosztów obliczeń i opóźnień przy jednoczesnym zmniejszeniu przepływności. Jeśli filtr jest wysoce selektywny (ma bardzo mało dopasowań), rozważ użycie exhaustive: true do przeprowadzenia dokładnego przeszukiwania.

Diagram prefiltrów.

Tabela porównania

Tryb Wyszukiwanie (przefiltrowane wyniki) Koszt obliczeniowy Ryzyko fałszywie ujemnych wyników Kiedy należy używać
preFilter Bardzo wysoki Wyższa (zwiększa się wraz z selektywnością filtra i złożonością) Brak ryzyka Zalecana wartość domyślna dla wszystkich scenariuszy, szczególnie gdy kompletność ma krytyczne znaczenie (wrażliwe domeny wyszukiwania), w przypadku korzystania z filtrów selektywnych lub używania małych k.
postFilter Średni do wysoki (zmniejsza się wraz z selektywnością filtru) Podobnie jak w przypadku niefiltrowanego, ale zwiększa się wraz z złożonością filtru. Umiarkowane (może przegapić dopasowania na fragment) Opcja dla filtrów, które nie są zbyt wybiórcze, oraz dla bardziej zaawansowanych zapytań.
strictPostFilter Najniższy (szybciej zmniejsza się przy użyciu selektora filtru) Podobne do niefiltrowanych Najwyższy (może zwracać zero wyników dla filtrów selektywnych lub małych k) Opcja dla aplikacji wyszukiwania fasetowego, w których wyświetlenie większej liczby wyników po zastosowaniu filtru wpływa na doświadczenie użytkownika bardziej niż ryzyko fałszywych negatywów. Nie używaj z małymi k.

Testowanie porównawcze wstępnego filtrowania i postfiltrowania

Ważne

Ta sekcja dotyczy filtrowania wstępnego i filtrowania końcowego, a nie ścisłego filtrowania końcowego.

Aby zrozumieć warunki, w których jeden tryb filtrowania działa lepiej niż drugi, przeprowadziliśmy serię testów w celu oceny wyników zapytań w przypadku małych, średnich i dużych indeksów.

  • Mały (100 000 dokumentów, indeks 2,5 GB, 1536 wymiarów)
  • Średni (1 milion dokumentów, indeks 25 GB, 1536 wymiarów)
  • Duży (1 miliard dokumentów, indeks 1,9 TB, 96 wymiarów)

W przypadku małych i średnich obciążeń użyliśmy usługi standard 2 (S2) z jedną partycją i jedną repliką. W przypadku dużego obciążenia użyliśmy usługi Standard 3 (S3) z 12 partycjami i jedną repliką.

Indeksy miały identyczną konstrukcję: jedno pole klucza, jedno pole wektorowe, jedno pole tekstowe i jedno pole filtrowalne liczbowo. Poniższy indeks jest definiowany przy użyciu składni 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"
            }
        ]
      }
    }

W zapytaniach użyliśmy identycznego filtru dla operacji filtrowania wstępnego i postfiltru. Użyliśmy prostego filtru, aby upewnić się, że zmiany wydajności były spowodowane trybem filtrowania, a nie złożonością filtrowania.

Wyniki były mierzone w zapytaniach na sekundę (QPS).

Wnioski

  • Filtrowanie wstępne jest prawie zawsze wolniejsze niż filtrowaniu końcowym, z wyjątkiem małych indeksów, gdzie wydajność jest mniej więcej równa.

  • W przypadku większych zestawów danych filtrowanie wstępne jest o rzędy wielkości wolniejsze.

  • Dlaczego filtrowanie wstępne jest domyślne, jeśli jest prawie zawsze wolniejsze? Wstępne filtrowanie gwarantuje, że jeśli w indeksie istnieją wyniki k, to zostaną zwrócone, gdzie priorytetem jest kompletność i precyzja ponad szybkością.

  • Użyj filtrowania końcowego, jeśli:

    • Przywiązuj wagę do szybkości zamiast wyboru (post-filtrowanie może zwracać mniej niż k wyników).

    • Używaj filtrów, które nie są nadmiernie selektywne.

    • Posiadać indeksy o wystarczających rozmiarach, aby wydajność filtrowania wstępnego była nieakceptowalna.

Szczegóły

  • Biorąc pod uwagę zestaw danych z 100 000 wektorów o wymiarach 1536:

    • Podczas filtrowania ponad 30% zestawu danych porównywano filtrowanie wstępne i filtrowanie postfiltrujące.

    • Podczas filtrowania mniejszego niż 0,1% zestawu danych filtrowanie wstępne było o około 50% wolniejsze niż filtrowanie postfiltrujące.

  • Biorąc pod uwagę zestaw danych z 1 milionami wektorów o wymiarach 1536:

    • Podczas filtrowania ponad 30% zestawu danych wstępne filtrowanie było o około 30% wolniejsze.

    • Podczas filtrowania mniejszego niż 2% zestawu danych wstępne filtrowanie było o siedem razy wolniejsze.

  • Biorąc pod uwagę zestaw danych z 1 miliardami wektorów o wymiarach 96:

    • Podczas filtrowania ponad 5% zestawu danych filtrowanie wstępne było o około 50% wolniejsze.

    • Podczas filtrowania mniejszego niż 10% zestawu danych filtrowanie wstępne było około siedmiu razy wolniejsze.

Na poniższym wykresie przedstawiono względne QPS prefiltrowania, obliczone jako QPS prefiltrowania podzielone przez QPS postfiltrowania.

Wykres przedstawiający wydajność QPS dla małych, średnich i dużych indeksów dla względnych QPS.

Oś pionowa reprezentuje względną wydajność filtrowania wstępnego w porównaniu do filtrowania postfiltrującego, wyrażonego jako stosunek QPS (zapytania na sekundę). Na przykład:

  • Wartość 0.0 oznacza, że filtrowanie wstępne jest o 100% wolniejsze niż filtrowanie końcowe.
  • Wartość 0.5 oznacza, że filtrowanie wstępne wynosi 50% wolniej.
  • Wartość 1.0 oznacza, że filtrowanie wstępne i filtrowanie po są równoważne.

Oś pozioma reprezentuje współczynnik filtrowania lub procent dokumentów kandydatów po zastosowaniu filtru. Na przykład współczynnik 1.00% oznacza, że kryteria filtrowania wybrały jeden procent korpusu wyszukiwania.