Dos búsquedas, dos puntos ciegos
La búsqueda híbrida lanza una búsqueda por palabras y otra por significado, y fusiona las dos listas ordenadas. Importa porque cada una pierde lo que la otra encuentra.
Alguien escribe «olvidé mi usuario». Ninguna página de ayuda contiene «usuario», así que las palabras no encuentran nada. Otra persona escribe «pedido 4417». La búsqueda por significado no sabe qué es 4417.
Al final de esta lección
Nota
Puedo combinar la búsqueda por palabras y la búsqueda por significado en una sola lista ordenada y decir cuál gana en cada caso.
Palabras clave
| Palabra | Qué significa |
|---|---|
| BM25 | una puntuación de palabras que premia las raras y las páginas cortas |
| Frecuencia del término | cuántas veces aparece una palabra en un documento |
| Frecuencia inversa | un peso alto para las palabras que pocos documentos contienen |
| Rango | el puesto de un resultado en una lista, contado desde 1 |
| Fusión por rango recíproco | unir listas sumando 1 entre k más el rango |
| Búsqueda léxica | una búsqueda que compara las palabras mismas |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
BM25 puntúa las palabras mismas
Una búsqueda por palabras cuenta las de la consulta en cada documento. BM25 mejora ese simple recuento de tres maneras. Una palabra pesa más cuando pocos documentos la tienen. Repetirla ayuda cada vez menos. Un documento corto gana a uno largo con el mismo recuento.
El resultado es exacto: una palabra ausente no suma nada. Es su fuerza y su punto ciego.
La búsqueda por significado compara vectores
En el módulo de Generación aumentada con recuperación convertiste el texto en vectores y comparaste su dirección. Aquí cada página de ayuda tiene tres números inventados: cuánto trata de cuenta, de envío y de dinero.
La consulta recibe números propios, y ganan las páginas más cercanas. «Olvidé mi usuario» trata sobre todo de cuenta, así que las páginas de cuenta salen primero, sin ninguna palabra en común. Un sistema real saca sus vectores de un modelo de embeddings, con cientos de números.
Fusiona los rangos, nunca las puntuaciones
Las puntuaciones BM25 van de 0 a diez o más. Las del coseno se quedan entre 0 y 1. Sumarlas dejaría que una lista ahogara a la otra.
La fusión por rango recíproco ignora las puntuaciones y usa los rangos. Un elemento recibe 1 / (60 + rango) de cada lista que lo contiene, y los puntos se suman. Un elemento que gusta a las dos listas gana a uno que gusta a una sola.
Ejemplo resuelto: pedido 4417
| Página | Rango por palabras | Rango por significado | Puntos fusionados |
|---|---|---|---|
| d4 seguir un paquete | 1 | 3 | 0,0323 |
| d3 reembolso | ninguno | 1 | 0,0164 |
| d5 pagar con tarjeta | ninguno | 2 | 0,0161 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente cree que la búsqueda híbrida consiste en sumar las dos puntuaciones. Las escalas difieren, así que una lista gana siempre. Suma mejor los rangos, con 1 / (k + rango), y las dos listas pesan lo mismo.
Escribe la búsqueda híbrida, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const docs = [' ' { id: \"d1\", text: \"restablecer tu contraseña desde la página de acceso\", v: [9, 0, 1] },' ' { id: \"d2\", text: \"cambiar el correo de tu cuenta\", v: [8, 1, 0] },' ' { id: \"d3\", text: \"política de reembolso de paquetes devueltos\", v: [1, 5, 8] },' ' { id: \"d4\", text: \"seguir un paquete con el número de pedido\", v: [0, 9, 1] },' ' { id: \"d5\", text: \"pagar con tarjeta o transferencia\", v: [1, 1, 9] },' ' { id: \"d6\", text: \"borrar tu cuenta y tus datos\", v: [7, 0, 2] },' '];' 'const K1 = 1.5, B = 0.75;' 'function words(t) { return t.toLowerCase().split(\" \"); }' 'const avg = docs.reduce(function (s, d) { return s + words(d.text).length; }, 0) / docs.length;' > hybrid1.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la búsqueda híbrida, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function bm25(query, doc) {' ' let score = 0;' ' words(query).forEach(function (w) {' ' const n = docs.filter(function (d) { return words(d.text).includes(w); }).length;' ' if (n === 0) return;' ' const idf = Math.log(1 + (docs.length - n + 0.5) / (n + 0.5));' ' const f = words(doc.text).filter(function (x) { return x === w; }).length;' ' const len = words(doc.text).length;' ' score += idf * f * (K1 + 1) / (f + K1 * (1 - B + B * len / avg));' ' });' ' return score;' '}' >> hybrid1.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la búsqueda híbrida, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function cosine(a, b) {' ' let dot = 0, sa = 0, sb = 0;' ' for (let i = 0; i < a.length; i++) { dot += a[i] * b[i]; sa += a[i] * a[i]; sb += b[i] * b[i]; }' ' return dot / Math.sqrt(sa * sb);' '}' 'function ranked(scores) {' ' return docs.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 fuse(lists) {' ' const total = {};' ' lists.forEach(function (list) {' ' list.forEach(function (id, at) { total[id] = (total[id] || 0) + 1 / (60 + at + 1); });' ' });' ' return Object.keys(total).sort(function (x, y) { return total[y] - total[x]; });' '}' >> hybrid1.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe la búsqueda híbrida, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const queries = [' ' { text: \"olvidé mi usuario\", v: [9, 0, 0] },' ' { text: \"pedido 4417\", v: [2, 3, 3] },' '];' 'queries.forEach(function (q) {' ' const a = ranked(docs.map(function (d) { return bm25(q.text, d); }));' ' const b = ranked(docs.map(function (d) { return cosine(q.v, d.v); })).slice(0, 3);' ' console.log(q.text);' ' console.log(\" palabras:\", a.join(\" \") || \"nada\");' ' console.log(\" significado:\", b.join(\" \"));' ' console.log(\" fusión:\", fuse([a, b]).join(\" \"));' '});' >> hybrid1.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta y compara las listas
docker exec -w /home/node/project agent node hybrid1.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Esto es un sustituto
El Run no llama a ningún modelo. Los vectores son tres números inventados y seis páginas hacen de índice. Un sistema real guarda millones de páginas y saca sus vectores de un modelo de embeddings.
La forma es la misma: dos listas ordenadas y luego una lista fusionada.
Lo que imprime el Run
| Consulta | Palabras | Significado | Fusión |
|---|---|---|---|
| olvidé mi usuario | nada | d1 d2 d6 | d1 d2 d6 |
| pedido 4417 | d4 | d3 d5 d4 | d4 d3 d5 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.