Generar casos de prueba sin hacer trampa
Los datos de evaluación sintéticos son casos de prueba hechos por un programa. Escribir mil casos a mano es lento, así que los equipos los generan a partir de plantillas.
Los casos inventados pueden repetirse o revelar su propia respuesta. Esta lección hace casos a partir de una semilla y quita los malos.
Al final de esta lección
Nota
Puedo crear casos nuevos a partir de una semilla y decir que no son copias.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Plantilla | una frase con huecos que rellenar | Cambiar la {item} |
| Semilla | un número de inicio que hace que un generador se repita | |
| Casi duplicado | un caso casi igual que otro | |
| Filtración | un caso que revela su respuesta o su texto de entrenamiento | |
| Similitud | cuántas palabras comparten dos textos, en porcentaje |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Una plantilla y una semilla hacen casos
Una plantilla es una frase con huecos, como Cambiar la {item}. Una lista de artículos rellena el hueco. Cada plantilla lleva una etiqueta, la respuesta que debería dar una buena función.
Una semilla es un número que arranca un contador. La misma semilla elige siempre las mismas plantillas y artículos, así que los casos se repiten.
Ejemplo resuelto: un generador con semilla
| Paso | Qué ocurre |
|---|---|
| Inicio | La semilla es el primer número |
| Sorteo | Multiplica por 16807 y quédate con el resto al dividir entre 2147483647 |
| Elección | Divide ese número entre el tamaño de la lista y toma el resto |
| Relleno | Pon el artículo elegido en el hueco |
| Repetición | La misma semilla da los mismos casos cada vez |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Los casi duplicados inflan un conjunto
Dos casos que difieren en una palabra no enseñan nada nuevo. Cuenta las palabras que comparten. Divide entre todas las palabras distintas de los dos textos.
Di que el 80 % o más es un casi duplicado. Conserva el primer caso y rechaza el posterior.
Un error frecuente
Advertencia
Mucha gente cree que más casos generados siempre es una prueba mejor. Si la mayoría son copias, el conjunto es menor de lo que parece. Cuenta solo los casos que no son casi duplicados.
Dos tipos de filtración estropean una prueba
Una filtración de la respuesta es un caso que lleva su propia etiqueta, como Quiero el reembolso de mi lámpara con la etiqueta reembolso. Una función puede aprobar con solo ver la palabra.
Una filtración del texto de entrenamiento es un caso igual a un texto que la función ya ha visto. Una buena nota en él prueba memoria, no habilidad.
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 el generador, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const templates = [' ' [\"Mi {item} llegó {fault}, quiero recuperar mi dinero\", \"reembolso\"],' ' [\"Quiero el reembolso de mi {item}, está {fault}\", \"reembolso\"],' ' [\"¿Dónde está mi {item}? Todavía no ha llegado\", \"seguimiento\"],' ' [\"¿Puedo cambiar mi {item} por otro color?\", \"cambio\"],' ' [\"La {item} está {fault} y quiero que me devuelvan el dinero\", \"reembolso\"],' ' [\"Estado del pedido de mi {item}, por favor\", \"seguimiento\"],' '];' 'const items = [\"tetera\", \"lámpara\", \"bufanda\", \"taza\"];' 'const faults = [\"rota\", \"dañada\", \"agrietada\"];' 'const train = [\"¿Dónde está mi tetera? Todavía no ha llegado\", \"¿Puedo cambiar mi lámpara por otro color?\"];' > synth.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 generador, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'let s = 23;' 'function next(size) { s = (s * 16807) % 2147483647; return s % size; }' 'function generate(n) {' ' const out = [];' ' for (let i = 1; i <= n; i++) {' ' const t = templates[next(templates.length)];' ' let text = t[0];' ' if (text.indexOf(\"{item}\") >= 0) text = text.replace(\"{item}\", items[next(items.length)]);' ' if (text.indexOf(\"{fault}\") >= 0) text = text.replace(\"{fault}\", faults[next(faults.length)]);' ' out.push({ id: i, text: text, label: t[1] });' ' }' ' return out;' '}' >> synth.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 generador, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function wordList(text) {' ' const w = text.toLowerCase().replace(/[,?.¿]/g, \" \").split(\" \").filter(function (x) { return x.length > 0; });' ' return w.filter(function (x, i) { return w.indexOf(x) === i; });' '}' 'function similarity(a, b) {' ' const wa = wordList(a);' ' const wb = wordList(b);' ' const shared = wa.filter(function (w) { return wb.indexOf(w) >= 0; }).length;' ' return Math.floor(shared * 100 / (wa.length + wb.length - shared));' '}' 'function near(text, list) {' ' return list.some(function (t) { return similarity(text, t) >= 80; });' '}' >> synth.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 generador, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const kept = [];' ' const reasons = {};' ' generate(12).forEach(function (c) {' ' let why = \"\";' ' if (wordList(c.text).indexOf(c.label) >= 0) why = \"revela la respuesta\";' ' else if (near(c.text, train)) why = \"revela el texto de entrenamiento\";' ' else if (near(c.text, kept.map(function (k) { return k.text; }))) why = \"duplicado\";' ' if (why === \"\") kept.push(c);' ' else reasons[why] = (reasons[why] || 0) + 1;' ' });' >> synth.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 generador, parte 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' console.log(\"se quedan \" + kept.length + \" de 12\");' ' console.log(\"descartados: \" + JSON.stringify(reasons));' ' console.log(\"primero que se queda: \" + kept[0].text + \" -> \" + kept[0].label);' ' const sample = \"Cambia la plantilla para que la palabra de la etiqueta nunca esté en el texto.\";' ' const q = \"En una frase corta, ¿cómo evitas que un caso inventado revele su respuesta?\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 60);' ' console.log(\"el modelo dijo: \" + reply.text);' '}' 'main();' >> synth.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 el generador
docker exec -w /home/node/project agent node synth.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 |
|---|---|
| se quedan 7 de 12 | Casos que quedan tras las comprobaciones |
| descartados: {...} | Cuántos se quitaron y por qué |
| primero que se queda: ... -> ... | Un caso limpio y su etiqueta |
| el modelo dijo: ... | 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.