Plus facile à lire avec un fond crème, une teinte, plus de contraste ou un texte plus grand ? Choisissez vos couleurs et votre texte, une fois pour toutes les pages.

Leçon gratuite

Recherche hybride : BM25 et vecteurs

Après cette leçon: Je peux combiner la recherche par mots et la recherche par sens en une seule liste classée et dire laquelle gagne selon le cas.

Leçon de 65 min5 commandesLab agent de code IA : utiliser opencode pour expliquer et corriger du code, et vérifier son travail

Deux recherches, deux angles morts

La recherche hybride lance une recherche par mots et une recherche par sens, puis fusionne les deux listes classées. Elle sert parce que chacune rate ce que l’autre trouve.

Une personne tape « perdu mon identifiant ». Aucune page d’aide ne contient « identifiant », donc les mots ne trouvent rien. Une autre tape « commande numéro 4417 ». La recherche par sens ne sait pas ce qu’est 4417.

À la fin de cette leçon

Remarque

Je peux combiner la recherche par mots et la recherche par sens en une seule liste classée et dire laquelle gagne selon le cas.

Mots clés

MotCe que ça veut dire
BM25un score de mots qui récompense les mots rares et les pages courtes
Fréquence du termele nombre de fois où un mot apparaît dans un document
Fréquence inverseun poids fort pour les mots que peu de documents contiennent
Rangla place d’un résultat dans une liste, à partir de 1
Fusion par rang réciproquefusionner des listes en ajoutant 1 sur k plus le rang
Recherche lexicaleune recherche qui compare les mots eux-mêmes

Les cellules que les étudiants remplissent restent vides dans cet aperçu.

BM25 note les mots eux-mêmes

Une recherche par mots compte les mots de la requête dans chaque document. BM25 améliore ce simple compte de trois façons. Un mot pèse plus quand peu de documents le contiennent. Le répéter aide de moins en moins. Un document court l’emporte sur un long qui a le même compte.

Le résultat est exact : un mot absent ne rapporte rien. C’est sa force et son angle mort.

La recherche par sens compare des vecteurs

La recherche par sens transforme chaque texte en vecteur, une liste de nombres, et compare leur direction. Ici chaque page d’aide a trois nombres inventés : sa part de compte, de livraison et d’argent.

La requête reçoit ses propres nombres, et les pages les plus proches gagnent. « Perdu mon identifiant » parle surtout de compte, donc les pages de compte arrivent en tête, sans aucun mot commun. Un vrai système obtient ses vecteurs d’un modèle d’embeddings, avec des centaines de nombres.

Fusionnez les rangs, jamais les scores

Les scores BM25 vont de 0 à dix ou plus. Les scores cosinus restent entre 0 et 1. Les additionner laisserait une liste écraser l’autre.

La fusion par rang réciproque ignore les scores et utilise les rangs. Un élément reçoit 1 / (60 + rang) de chaque liste qui le contient, et les points s’additionnent. Un élément que les deux listes aiment bat un élément qu’une seule aime.

Exemple détaillé : commande numéro 4417

PageRang par motsRang par sensPoints fusionnés
d4 suivre un colis130,0323
d3 remboursementaucun10,0164
d5 payer par carteaucun20,0161

Les cellules que les étudiants remplissent restent vides dans cet aperçu.

Une erreur courante

Attention

Beaucoup pensent que la recherche hybride consiste à additionner les deux scores. Les échelles diffèrent, donc une liste gagne à chaque fois. Additionnez plutôt les rangs, avec 1 / (k + rang), et les deux listes ont le même poids.

Écrire la recherche hybride, partie 1

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const pages = [' '  { id: \"d1\", texte: \"réinitialiser votre mot de passe depuis la page de connexion\", v: [9, 0, 1] },' '  { id: \"d2\", texte: \"changer l’adresse courriel de votre compte\", v: [8, 1, 0] },' '  { id: \"d3\", texte: \"politique de remboursement des colis retournés\", v: [1, 5, 8] },' '  { id: \"d4\", texte: \"suivre un colis avec le numéro de commande\", v: [0, 9, 1] },' '  { id: \"d5\", texte: \"payer par carte ou par virement\", v: [1, 1, 9] },' '  { id: \"d6\", texte: \"supprimer votre compte et vos données\", v: [7, 0, 2] },' '];' 'const K1 = 1.5, B = 0.75;' 'function mots(t) { return t.toLowerCase().split(\" \"); }' 'const moyenne = pages.reduce(function (s, d) { return s + mots(d.texte).length; }, 0) / pages.length;' > hybride1.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire la recherche hybride, partie 2

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function bm25(requete, page) {' '  let score = 0;' '  mots(requete).forEach(function (m) {' '    const n = pages.filter(function (d) { return mots(d.texte).includes(m); }).length;' '    if (n === 0) return;' '    const idf = Math.log(1 + (pages.length - n + 0.5) / (n + 0.5));' '    const f = mots(page.texte).filter(function (x) { return x === m; }).length;' '    const long = mots(page.texte).length;' '    score += idf * f * (K1 + 1) / (f + K1 * (1 - B + B * long / moyenne));' '  });' '  return score;' '}' >> hybride1.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire la recherche hybride, partie 3

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function cosinus(a, b) {' '  let prod = 0, sa = 0, sb = 0;' '  for (let i = 0; i < a.length; i++) { prod += a[i] * b[i]; sa += a[i] * a[i]; sb += b[i] * b[i]; }' '  return prod / Math.sqrt(sa * sb);' '}' 'function classer(scores) {' '  return pages.map(function (d, i) { return { id: d.id, s: scores[i] }; })' '    .filter(function (x) { return x.s > 0.01; })' '    .sort(function (x, y) { return y.s - x.s; })' '    .map(function (x) { return x.id; });' '}' 'function fusionner(listes) {' '  const total = {};' '  listes.forEach(function (liste) {' '    liste.forEach(function (id, rang) { total[id] = (total[id] || 0) + 1 / (60 + rang + 1); });' '  });' '  return Object.keys(total).sort(function (x, y) { return total[y] - total[x]; });' '}' >> hybride1.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Écrire la recherche hybride, partie 4

Commande
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const requetes = [' '  { texte: \"perdu mon identifiant\", v: [9, 0, 0] },' '  { texte: \"commande numéro 4417\", v: [2, 3, 3] },' '];' 'requetes.forEach(function (q) {' '  const a = classer(pages.map(function (d) { return bm25(q.texte, d); }));' '  const b = classer(pages.map(function (d) { return cosinus(q.v, d.v); })).slice(0, 3);' '  console.log(q.texte);' '  console.log(\"  mots :\", a.join(\" \") || \"rien\");' '  console.log(\"  sens :\", b.join(\" \"));' '  console.log(\"  fusion :\", fusionner([a, b]).join(\" \"));' '});' >> hybride1.js"

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Lancer et comparer les listes

Commande
docker exec -w /home/node/project agent node hybride1.js

Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.

Ceci est une maquette

Le Run n’appelle aucun modèle. Les vecteurs sont trois nombres inventés et six pages tiennent lieu d’index. Un vrai système contient des millions de pages et obtient ses vecteurs d’un modèle d’embeddings.

La forme reste la même : deux listes classées, puis une liste fusionnée.

Ce que le Run affiche

RequêteMotsSensFusion
perdu mon identifiantriend1 d2 d6d1 d2 d6
commande numéro 4417d4d3 d5 d4d4 d3 d5

Les cellules que les étudiants remplissent restent vides dans cet aperçu.

À propos de ce labo et de sa source: Lab agent de code IA : utiliser opencode pour expliquer et corriger du code, et vérifier son travail

Vous enseignez à une classe ? Proposez ce module à vos étudiants avec un guide de l'enseignant, des devoirs corrigés et un laboratoire pour chaque étudiant.