Modèles de recherche hybride en texte intégral et vectorielle

Cette page explique comment effectuer des recherches hybrides en texte intégral et vectorielles dans Spanner. La recherche hybride combine la précision de la correspondance de mots clés (recherche en texte intégral, FTS) avec le rappel de la correspondance sémantique (recherche vectorielle) pour produire des résultats de recherche très pertinents.

Spanner est compatible avec les modèles de recherche hybride suivants, qui sont divisés en trois catégories principales :

Catégorie Description Objectif principal
Fusion La fusion récupère et classe indépendamment les documents à l'aide de la recherche par mots clés et vectorielle, puis combine (fusionne) les résultats. Obtenir un rappel et une pertinence maximaux en combinant plusieurs signaux de scoring.
Filtrée Les mots clés filtrent ou affinent l'espace de recherche. S'assurer que la correspondance de mots clés est une exigence tout en tirant parti de la pertinence sémantique.
Reclassement par ML Un modèle de machine learning affine un ensemble initial de candidats pour un classement final plus précis. Obtenir la plus grande précision possible pour un petit ensemble final de résultats.

La recherche par fusion consiste à exécuter indépendamment la recherche en texte intégral et la recherche vectorielle sur le même corpus de données. Elle fusionne ensuite les résultats pour créer une liste classée unique, unifiée et très pertinente.

Bien que vous puissiez exécuter des requêtes indépendantes côté client, la recherche hybride dans Spanner offre les avantages suivants :

  • Simplifie la logique d'application en gérant les requêtes parallèles et la fusion des résultats sur le serveur.
  • Évite de transférer des ensembles de résultats intermédiaires potentiellement volumineux au client.

Vous pouvez utiliser SQL pour créer des méthodes de fusion dans Spanner. Cette section fournit des exemples de fusion de classement réciproque et de fusion de scores relatifs. Toutefois, nous vous recommandons d'évaluer différentes stratégies de fusion pour déterminer celle qui répond le mieux aux exigences de votre application.

Fusion basée sur le classement

Utilisez la fusion basée sur le classement lorsque les scores de pertinence de différentes méthodes de récupération (tels que les scores de recherche en texte intégral et les distances vectorielles) sont difficiles à comparer ou à normaliser, car ils sont mesurés dans des espaces différents. Cette méthode utilise la position de classement de chaque document de chaque récupérateur pour générer un score et un classement finaux.

La fusion de classement réciproque (RRF) est une fonction de fusion basée sur le classement. Le score RRF d'un document est la somme des inverses de ses classements provenant de plusieurs récupérateurs. Il est calculé comme suit :

\[ score\ (d\epsilon D)\ =\ \sum\limits_{r\epsilon R}^{}(1\ /\ (\ 𝑘\ +\ ran{k}_{r}\ (𝑑)\ )\ )\ \]

Où :

  • d correspond au document.
  • R correspond à l'ensemble des récupérateurs (recherche en texte intégral et recherche vectorielle).
  • k est une constante (souvent définie sur 60) utilisée pour modérer l'influence des documents très bien classés.
  • w correspond au classement du document provenant du récupérateur r.

Implémenter la RRF dans une requête Spanner

Les métriques vectorielles (telles que APPROX_DOT_PRODUCT) et les scores de recherche textuelle (tels que SCORE_NGRAMS) fonctionnent sur des échelles incompatibles. Pour résoudre ce problème, l'exemple suivant implémente la RRF afin de normaliser les données en fonction de la position de classement plutôt que des scores bruts.

La requête utilise UNNEST(ARRAY(...) WITH OFFSET) pour attribuer un classement aux 100 meilleurs candidats de chaque méthode. Elle calcule ensuite un score standardisé à l'aide de l'inverse de ces classements et agrège les résultats pour renvoyer les cinq meilleures correspondances.

SELECT SUM(1 / (60 + rank)) AS rrf_score, key
FROM (
  (
    SELECT rank, x AS key
    FROM UNNEST(ARRAY(
      SELECT key
      FROM hybrid_search
      WHERE embedding IS NOT NULL
      ORDER BY APPROX_DOT_PRODUCT(@vector, embedding,
        OPTIONS => JSON '{"num_leaves_to_search": 50}') DESC
      LIMIT 100)) AS x WITH OFFSET AS rank
  )
  UNION ALL
  (
    SELECT rank, x AS key
    FROM UNNEST(ARRAY(
      SELECT key
      FROM hybrid_search
      WHERE SEARCH_NGRAMS(text_tokens_ngrams, 'foo')
      ORDER BY SCORE_NGRAMS(text_tokens_ngrams, 'foo') DESC
      LIMIT 100)) AS x WITH OFFSET AS rank
  )
)
GROUP BY key
ORDER BY rrf_score DESC
LIMIT 5;

Fusion basée sur le score

La fusion basée sur le score est efficace lorsque les scores de pertinence de différentes méthodes sont comparables ou que vous pouvez les normaliser, ce qui peut permettre un classement plus précis qui intègre la pondération de pertinence réelle de chaque méthode.

La fusion de scores relatifs (RSF) est une méthode basée sur le score qui normalise les scores de différentes méthodes par rapport aux scores les plus élevés et les plus bas de chaque méthode, généralement à l'aide des fonctions MIN() et MAX(). Le score RSF d'un document récupéré par un ensemble de récupérateurs est calculé comme suit :

\[ score(d\epsilon D)\ =\ \sum\limits_{r\epsilon R}^{}({w}_{r}*(scor{e}_{r}(d)\ -\ mi{n}_{r})\ /\ (ma{x}_{r\ }-mi{n}_{r})\ ) \]

Où :

  • d correspond au document.
  • R correspond à l'ensemble des récupérateurs (recherche en texte intégral et recherche vectorielle).
  • w correspond à la pondération attribuée à un récupérateur spécifique.

Implémenter la RSF dans une requête Spanner

Pour implémenter la RSF, vous devez normaliser les scores du vecteur et de la recherche en texte intégral sur une échelle commune. L'exemple suivant calcule les scores minimal et maximal dans des clauses WITH distinctes pour dériver les facteurs de normalisation. Il combine ensuite les résultats à l'aide d'une FULL OUTER JOIN, en additionnant les scores normalisés de la recherche du voisin le plus proche approximative (ANN) (convertie à partir de cosine_distance) et de la recherche en texte intégral.

WITH ann AS (
  SELECT key, APPROX_COSINE_DISTANCE(@vector, embedding,
    OPTIONS => JSON '{"num_leaves_to_search": 50}') AS cosine_distance,
  FROM hybrid_search
  WHERE embedding IS NOT NULL
  ORDER BY cosine_distance
  LIMIT 100
),
fts AS (
  SELECT key, SCORE_NGRAMS(text_tokens_ngrams, 'Green') AS score,
  FROM hybrid_search
    WHERE SEARCH_NGRAMS(text_tokens_ngrams, 'Green')
    ORDER BY score DESC
    LIMIT 100
),
ann_min AS (
  SELECT MIN(1 - cosine_distance) AS min
  FROM ann
),
ann_max AS (
  SELECT MAX(1 - cosine_distance) AS max
  FROM ann
),
fts_min AS (
  SELECT MIN(score) AS min
  FROM fts
),
fts_max AS (
  SELECT MAX(score) AS max
  FROM fts
)
SELECT IFNULL(ann.key, fts.key),
       IFNULL(((1 - ann.cosine_distance) - ann_min.min) /
               (ann_max.max - ann_min.min), 0) +
       IFNULL((fts.score - fts_min.min) /
               (fts_max.max - fts_min.min), 0) AS score
FROM ann
FULL OUTER JOIN fts
ON ann.key = fts.key
CROSS JOIN ann_min
CROSS JOIN ann_max
CROSS JOIN fts_min
CROSS JOIN fts_max
ORDER BY score DESC
LIMIT 5;

Les recherches filtrées utilisent la recherche en texte intégral pour créer un filtre qui réduit l'ensemble des documents pris en compte pour une recherche vectorielle des k plus proches voisins (KNN). Vous pouvez également utiliser un tri préalable pour limiter la taille de l'ensemble de résultats.

L'exemple de recherche de cette section suit les étapes suivantes pour réduire l'espace de recherche vectorielle au sous-ensemble de données qui correspondent aux mots clés :

  • Utilise SEARCH (text_tokens, 'Green') pour rechercher les lignes où la text_tokens colonne contient le texte Green. Les 1 000 premières lignes sont renvoyées par un ordre de tri défini par l'index de recherche.
  • Utilise une fonction vectorielle, DOT_PRODUCT(@vector, embedding), pour calculer la similarité entre le vecteur de requête (@vector) et le vecteur de document stocké (embedding). Il trie ensuite les résultats et renvoie les 10 meilleures correspondances finales.
SELECT key
FROM (
  SELECT key, embedding
  FROM hybrid_search
  WHERE SEARCH (text_tokens, 'Green')
  ORDER BY presort
  LIMIT 1000)
ORDER BY DOT_PRODUCT(@vector, embedding) DESC
LIMIT 10;

Reclassement par ML

Le reclassement basé sur le ML est une approche gourmande en calcul, mais très précise. Il applique un modèle de machine learning à un petit ensemble de candidats qui a été réduit par la recherche en texte intégral, la recherche vectorielle ou une combinaison des deux. Pour en savoir plus sur l'intégration de Spanner Agent Platform, consultez la présentation de l'intégration de Spanner Agent Platform.

Vous pouvez intégrer le reclassement par ML à l'aide de la fonction Spanner ML.PREDICT avec un modèle Gemini Enterprise Agent Platform déployé.

Implémenter le reclassement basé sur le ML

  1. Déployez un modèle de reclassement (par exemple, à partir de HuggingFace) sur un point de terminaison Agent Platform.
  2. Créez un objet MODEL Spanner qui pointe vers le point de terminaison Agent Platform. Par exemple, dans l'exemple de modèle Reranker suivant :

    • text ARRAY<string(max)> correspond aux documents.
    • text_pair ARRAY<string(max)> correspond au texte de la requête dans l'exemple.
    • score correspond aux scores de pertinence attribués par le modèle de ML pour les paires de textes d'entrée.
    CREATE MODEL Reranker
    INPUT (text ARRAY<string(max)>, text_pair ARRAY<string(max)>)
    OUTPUT (score FLOAT32)
    REMOTE
    OPTIONS (
    endpoint = '...'
    );
    
  3. Utilisez une sous-requête pour récupérer les candidats initiaux (par exemple, les 100 premiers résultats d'une recherche ANN) et transmettez-les à ML.PREDICT. La fonction renvoie une nouvelle colonne de score pour l'ordre final.

    SELECT key
    FROM ML.PREDICT(
        MODEL Reranker, (
          SELECT key, text, "gift for 8-year-old" AS text_pair
          FROM hybrid_search
          WHERE embedding IS NOT NULL
          ORDER BY APPROX_DOT_PRODUCT(@vector, embedding, options=>JSON '{"num_leaves_to_search": 50}') DESC
          LIMIT 100)
    )
    ORDER BY score DESC
    LIMIT 3;
    

Étape suivante