Métricas de búsqueda: recuerdo y rango recíproco
Muchas funciones de IA buscan en un conjunto de documentos y responden con lo que encuentran. Cuando la respuesta es mala, ¿qué parte falló: la búsqueda o la respuesta?
Puedes puntuar la búsqueda por separado. Aquí la búsqueda es un contador de palabras clave escrito en código, así que no hay embeddings.
Al final de esta lección
Nota
Puedo puntuar la búsqueda aparte de la respuesta.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Buscador | la parte que encuentra documentos para una pregunta | |
| Lista ordenada | documentos en orden, el mejor primero | |
| Relevante | un documento que de verdad tiene la respuesta | |
| Recuerdo a k | la parte de los documentos relevantes que está entre los k primeros | 0,5 |
| Rango recíproco | uno dividido entre el puesto del primer documento relevante | 0,5 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un buscador de palabras clave cuenta palabras compartidas
Toma la pregunta y quita las palabras pequeñas, como el, la y de. Para cada documento, cuenta cuántas palabras de la pregunta contiene. Ordena por ese número, el mejor primero.
También pliega las tildes, para que pagina y página sean una sola palabra. Es sencillo y tiene límites. No sabe que abre y abren son la misma palabra, ni que prolongar significa renovar.
Ejemplo resuelto: una pregunta, tres documentos
| Palabras de la pregunta | Documento | Palabras compartidas | Puesto |
|---|---|---|---|
| cuánto, tiempo, puedo, guardar, libros, antes, retraso | Los libros con retraso cuestan veinte céntimos al día | 2 | 1 |
| cuánto, tiempo, puedo, guardar, libros, antes, retraso | Los socios pueden llevarse seis libros | 1 | 2 |
| cuánto, tiempo, puedo, guardar, libros, antes, retraso | Pide en el mostrador renovar los libros | 1 | 3 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
El recuerdo y el rango puntúan la lista
El recuerdo a k pregunta cuántos documentos correctos están entre los k primeros. Si dos son correctos y uno está entre los 2 primeros, el recuerdo a 2 es 0,5.
El rango recíproco mira solo el primer documento correcto. En el puesto 1 vale 1. En el puesto 2 vale 0,5. En el 4 vale 0,25. La media sobre muchas preguntas es el rango recíproco medio.
Ejemplo resuelto: cinco preguntas
| Pregunta | Documento correcto en el puesto | Rango recíproco | Recuerdo a 1 |
|---|---|---|---|
| biblioteca abre | 1 | 1 | 1 |
| biblioteca cerrada (dos correctos) | 1 y 2 | 1 | 0,5 |
| cuánto tiempo puedo guardar los libros antes del retraso | 2 | 0,5 | 0 |
| prolongar mi préstamo | no se halla | 0 | 0 |
| imprimir pagina | 1 | 1 | 1 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente cree que una respuesta mala significa que el modelo es malo. Si el documento correcto nunca se halló, ni un modelo perfecto puede responder. Puntúa primero la búsqueda y después la respuesta.
Escribe el ayudante del modelo, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Los tokens de una respuesta, o null si no trae ninguno.' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.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 el ayudante del modelo, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"estado \" + res.status);' ' const d = await res.json();' ' return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.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 por palabras clave, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const docs = {' ' d1: \"La biblioteca abre a las nueve y cierra a las cinco\",' ' d2: \"Los socios pueden llevarse seis libros durante tres semanas\",' ' d3: \"Los libros con retraso cuestan veinte céntimos al día\",' ' d4: \"La cafetería vende sopa y té\",' ' d5: \"Los ordenadores son gratis durante una hora\",' ' d6: \"Pide en el mostrador renovar los libros por internet\",' ' d7: \"La biblioteca está cerrada los domingos\",' ' d8: \"Imprimir una página cuesta diez céntimos\",' '};' > search.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 por palabras clave, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const stop = [\"el\", \"la\", \"los\", \"las\", \"un\", \"una\", \"y\", \"a\", \"de\", \"del\", \"en\", \"al\", \"con\", \"por\", \"para\", \"es\", \"son\", \"mi\", \"me\", \"que\"];' 'const con = \"áéíóúüñ\";' 'const sin = \"aeiouun\";' 'function plain(text) {' ' let out = \"\";' ' for (const c of text) { const i = con.indexOf(c); out += i >= 0 ? sin[i] : c; }' ' return out;' '}' >> search.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 por palabras clave, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function tokens(text) {' ' const w = plain(text.toLowerCase()).split(\" \");' ' return w.filter(function (x) { return x.length > 0 && stop.indexOf(x) < 0; });' '}' 'function search(query) {' ' const q = tokens(query);' ' const scored = Object.keys(docs).map(function (id) {' ' const have = tokens(docs[id]);' ' return { id: id, score: q.filter(function (w) { return have.indexOf(w) >= 0; }).length };' ' }).filter(function (s) { return s.score > 0; });' ' scored.sort(function (a, b) { return b.score - a.score || (a.id < b.id ? -1 : 1); });' ' return scored.map(function (s) { return s.id; });' '}' >> search.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 por palabras clave, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const queries = [' ' [\"biblioteca abre\", [\"d1\"]], [\"biblioteca cerrada\", [\"d1\", \"d7\"]],' ' [\"cuánto tiempo puedo guardar los libros antes del retraso\", [\"d2\"]],' ' [\"prolongar mi préstamo\", [\"d6\"]], [\"imprimir pagina\", [\"d8\"]],' '];' 'let sumRr = 0;' 'let sumRecall = 0;' 'queries.forEach(function (item) {' ' const ranked = search(item[0]);' ' const at = ranked.findIndex(function (id) { return item[1].indexOf(id) >= 0; });' ' const rr = at < 0 ? 0 : 1 / (at + 1);' ' const hit = ranked.slice(0, 1).filter(function (id) { return item[1].indexOf(id) >= 0; }).length;' ' sumRr += rr;' ' sumRecall += hit / item[1].length;' ' console.log(item[0] + \" -> \" + (ranked.join(\",\") || \"nada\") + \" rr \" + rr.toFixed(2));' '});' >> search.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 por palabras clave, parte 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' console.log(\"rango recíproco medio: \" + (sumRr / queries.length).toFixed(2));' ' console.log(\"recuerdo medio a 1: \" + (sumRecall / queries.length).toFixed(2));' ' const q = \"Usando solo este texto, responde en una frase: \" + docs.d3 + \". Pregunta: ¿cuánto tiempo puedo guardar los libros antes del retraso?\";' ' const sample = \"El texto no lo dice. Solo dice que los libros con retraso cuestan veinte céntimos al día.\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 60);' ' console.log(\"respuesta desde el primer documento: \" + reply.text);' '}' 'main();' >> search.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 la búsqueda y puntúala
docker exec -w /home/node/project agent node search.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Lo que imprime el Run
| Línea | Qué muestra |
|---|---|
| biblioteca abre -> d1,d7 rr 1.00 | La lista ordenada y su nota de rango |
| cuánto tiempo puedo guardar los libros antes del retraso -> d3,d2,d6 rr 0.50 | El documento correcto es el segundo |
| prolongar mi préstamo -> nada rr 0.00 | Un sinónimo que el contador no ve |
| rango recíproco medio: 0.70 | La media de las cinco preguntas |
| respuesta desde el primer documento: ... | Una frase del laboratorio. Una muestra grabada si ningún modelo responde |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.