Lo que hay debajo de la cabeza: cuatro mandos de entrenamiento, tres semillas que pasan y la seq_len que hizo todo el trabajo

zn13 min de lectura

Hace dos artículos reconstruimos un corpus que nos mentía. Hace un artículo te contamos las diecisiete ejecuciones de entrenamiento fallidas antes de que algo funcionara. Este es el capítulo tres de cómo nos equivocamos en público, con cifras adjuntas.

La versión corta: nuestra puerta de producción, supA-v3, superó cinco de las seis puertas de publicación preregistradas a la primera. La sexta —falsos negativos por idioma— fallaba exclusivamente porque los ataques en ruso se colaban. Podríamos haberla distribuido igualmente; nada en el contrato de publicación exige una división perfecta entre idiomas. Pero una puerta que prefiere en silencio el inglés es un riesgo para cualquiera que despliegue agentes fuera de los foros anglófonos, así que ejecutamos una campaña de entrenamiento centrada para averiguar por qué ocurrió.

La respuesta resultó ser embarazosamente mecánica. Lo que sigue es todo lo que aprendimos, cada cifra que vimos y una observación algo incómoda sobre lo fácil que habría sido pasar por alto esta corrección concreta.

Resumen

  • v3 superó la FPR (0,64 % frente a un techo de ≤1 %), pero dejó pasar el 9,11 % de los ataques en ruso, superando en 1,1 puntos porcentuales nuestro presupuesto de FNR por idioma.
  • Nuestro primer impulso —aumentar el peso de las muestras de ataque en ruso en la pérdida hasta un peso efectivo x5— empeoró las cosas. El FNR en RU saltó de 9,11 % a 31,72 %. Más peso movió las puntuaciones sin mejorar el ranking.
  • El AUROC se mantuvo entre 0,99957 y 0,99989 en las diez ejecuciones, lo que sugiere que no estábamos corrigiendo el ranking. Estábamos redistribuyendo los puntos de operación.
  • El verdadero ganador fue seq_len=96, no ningún mando de ponderación ni la tasa de aprendizaje. Fue el único cambio que amplió lo que el codificador podía ver lo suficiente como para notar que los patrones de inyección multilingües eran más largos que la ventana de codificación.
  • Tres reejecuciones independientes con semillas distintas confirmaron la estabilidad: los umbrales variaron (como exige la calibración), pero los resultados de la puerta se reprodujeron de forma idéntica.
  • Diez ejecuciones de entrenamiento en total en la campaña. El experimento más barato nos enseñó más que cualquier otra cosa que hicimos en todo el trimestre.
  • En producción en POST /v30/analyze; consulta la documentación para desarrolladores para la autenticación, el esquema de peticiones y los ejemplos de llamadas.

Dónde nos quedamos

El corpus está público en Hugging Face, CC-BY-4.0, con la procedencia registrada en cada fila. El pipeline de entrenamiento y las puertas de publicación están preregistrados como JSON legible por máquina antes de que ningún modelo toque los datos. Cada ejecución de entrenamiento se registra automáticamente con su configuración, el hash del artefacto y el resultado de la evaluación, de modo que la campaña se lee como una serie auditable y no como un resumen de los mejores momentos.

Para la campaña v3 entrenamos con 10.554 filas: 9.754 heredadas de v3 más 800 preguntas benignas nuevas en ruso que abarcan temas de afición que ningún elemento de la partición de validación toca. Las comprobaciones de contaminación salieron limpias. El umbral calibrado dio un FP cercano a cero. Un problema apareció de inmediato: las tasas de falsos negativos por idioma contaban una historia fea.

supA-v3 · FNR por idioma frente a la puerta preregistrada IDIOMA FNR FRENTE A PUERTA 8 % EN0,00 % APRUEBA ES0,12 % APRUEBA DE0,12 % APRUEBA PT0,00 % APRUEBA IT0,46 % APRUEBA RU9,11 % FALLA La única medición por idioma que falla en supA-v3. Todo lo demás aprobó con un margen de un orden de magnitud.

Cinco idiomas salieron prácticamente gratis. El sexto no.

Primer intento: apretar más

La intuición obvia cuando un segmento concreto falla en recall es presionar más sobre ese segmento durante el entrenamiento. Dar más peso en la pérdida a lo que quieras capturar con más frecuencia. Si el x3 en casos límite ya formaba parte de la receta base, quizá los ataques en ruso necesitaban un x15 total.

La ejecución funcionó mecánicamente: el entrenamiento terminó, el artefacto apareció, la contaminación salió limpia. Luego llegó la evaluación y nos hizo guardar silencio durante un segundo:

El FNR en RU pasó de 9,11 % a 31,72 %.

El AUROC apenas se movió (0,99957 → 0,99969). Esa discrepancia importa. Un modelo cuyo ranking no mejora no puede arreglarse cambiando los pesos de la pérdida: solo estás desplazando el punto de corte a lo largo de una curva fija. Y eso es exactamente lo que ocurrió: el umbral calibrado acabó empujado a 0,99, lo que significa que casi todas las decisiones se volvieron mucho más conservadoras, y el colapso del recall vino detrás de forma mecánica.

El barrido de umbrales mostró la imagen real. En cada punto de la cuadrícula donde la FPR bajaba del límite del 1 %, el FNR en RU quedaba por encima de la puerta del 8 %, y viceversa. Las dos restricciones no se cruzan para esa configuración. Ningún valor de umbral encuentra algo que no existe.

Segundo intento: dejar de lado los pesos

Si empujar el peso de la pérdida no funciona, la siguiente hipótesis razonable es algo arquitectónico. En concreto, el ruso se tokenizaba en más tokens que los otros cinco idiomas con el tokenizador de MiniLM: aproximadamente entre un 30 y un 40 % más de tokens en promedio para el mismo contenido semántico. Si las secuencias se recortaban a 64 tokens, la cola de un ataque podría ser invisible.

Probamos dos variantes:

  • seq_len = 96, todo lo demás por defecto
  • seq_len = 128 + lr_head subido a 2e-3

Ambas pasaron. Limpiamente. Todas las puertas. En secuencia.

Ese patrón —los dos experimentos de longitud de secuencia pasando sin importar los ajustes de la tasa de aprendizaje, mientras todas las variantes que no cambiaban la longitud de secuencia seguían fallando— es exactamente lo que parece una historia de ventana de tokenización cuando aparece en tus datos.

Cuatro configuraciones independientes alcanzaron TODAS LAS PUERTAS APRUEBAN:

Cuatro configuraciones que superan TODAS las puertas preregistradas EJECUCIÓN SEQ_LEN LR_HEAD UMBRAL FP /156 PEOR FNR v4cc4 961e-3 0,500 0,56 % v4c6 1282e-3 0,500 0,45 % v4cc3 642e-3 0,571 1,24 % v4c5 965e-4 0,681 1,35 % Las filas en azul marcan las configuraciones con 0 FP en este conjunto (los candidatos a despliegue).

Otros tres candidatos fallaron una o más puertas, sobre todo variantes de secuencia corta en las que una tasa de aprendizaje más alta seguía siendo insuficiente.

FNR del peor idioma según la longitud de secuencia (partición de retención congelada) 32 % 16 % 0 % puerta 8 % 31,72 17,55 18,56 16,09 seq_len 64 - todas FALLAN 0,56 1,35 seq_len 96 - aprueba todas las puertas 0,45 seq128 Cuanto más bajo, mejor. Las barras rojas fallan la puerta preregistrada por idioma.

La parte derecha de ese gráfico es aburrida a propósito. Las dos configuraciones que pasan están en el mismo entorno, tanto si duplicamos la tasa de aprendizaje como si no. La longitud de secuencia es la que hace el trabajo pesado.

Confirmar que no fue suerte

Cuatro aprobados en un barrido no demuestran causalidad. Para comprobar que estábamos ante una receta robusta y no ante una combinación afortunada de hiperparámetros, reentrenamos exactamente la configuración ganadora dos veces más con semillas aleatorias distintas. Todo lo demás, idéntico.

COMPROBACIÓN DE ESTABILIDAD: reejecuciones con semilla independiente de la configuración ganadora Semilla 20260827 FNR del peor idioma 2,81 %· FPR 0,64 %Aprueba Semilla 20260828 FNR del peor idioma 2,14 %· FPR 0,64 %Aprueba

El umbral calibrado se movió (0,50 → 0,91 → 0,80), como era de esperar: las distribuciones de probabilidad cambian ligeramente con cada semilla. Los resultados de la puerta no se movieron. Cuando distintas inicializaciones aleatorias producen ubicaciones de umbral independientes pero conclusiones idénticas en las puertas duras, tienes un hallazgo y no una anécdota.

Combinando esto con los barridos de estilo validación cruzada de arriba, ahora tenemos siete configuraciones distintas que superan el conjunto completo preregistrado, todas remontables a la misma elección estructural de fondo.

Cómo es realmente la ejecución ganadora

En la partición de retención de regresión congelada, con el umbral calibrado de la ejecución que desplegamos:

  • Tasa de falsos positivos: 0,64 % medido: 1 de cada 156 prompts negativos limpios se activó
  • Falsos negativos por idioma: RU 0,56 % · IT 0,00 % · PT 0,00 % · ES 0,00 % · DE 0,12 % · EN 0,00 %
  • AUROC sobre bench-core: 0,99986
  • Tasa de detección en los fallos de PromptGuard: 98,8 %
  • Latencia de inferencia en CPU (int8 ONNX, batch=1): p50 ≈ 27 ms, p95 ≈ 45 ms
supA-v4cc4 - retención de regresión congelada - cifras finales Tasa de falsos positivos 0,64 % FNR del peor idioma (ruso) 0,56 % FNR global 0,15 % Inferencia p50 en CPU (int8 ONNX) 27 ms

Lo que esto NO resuelve

Ser honesto con los límites es precisamente el sentido de publicar estas cifras en público.

Probamos la capacidad de cada modelo para detectar parafraseos de cosas que el motor de reglas ya conoce, además de ataques sintéticos genuinamente nuevos generados contra esta familia de corpus. El comportamiento real del adversario cambia más rápido que cualquier dataset offline. Algo acabará eludiendo tanto las expresiones regulares como el clasificador de maneras que nuestras particiones de retención nunca anticiparon. El objetivo de medir con rigor es saber con qué estamos trabajando, no fingir que hemos resuelto la detección para siempre.

Segundo: la configuración ganadora usa secuencias de 96 tokens en lugar de 64. El coste de inferencia en CPU sube moderadamente (unas 45 % más lento que seq64 en nuestro hardware). Tampoco hay motivo para que secuencias más largas sigan ayudando indefinidamente; el fallo que se aborda es específicamente el truncado de frases rusas más largas, y más allá de esa ventana pagas cómputo por un beneficio marginal.

Tercero: las exclusiones de las particiones canario y bench-private-holdout se mantienen mediante aserciones de ID. Esa disciplina de contaminación supera las auditorías, pero es frágil frente a fuentes de corpus nuevas que todavía no hemos ingerido.

Cuarto: nueve configuraciones de esta campaña fallaron una o más puertas antes de que alguna tuviera éxito. Publicar solo historias de aprobados no enseña nada sobre cómo construir el pipeline que los detecta. Cada fallo —incluidos los catastróficos, como el colapso del peso x5 hasta 31,72 %— se conserva en el registro de commits exactamente como se ejecutó.

Lo siguiente

Despliegue en sombra junto al motor de reglas deterministas en POST /v30/analyze en modo OR combinado: una coincidencia de regex o una puntuación del modelo por encima del umbral produce bloqueo; cada una por separado no necesariamente produce permiso. El motor de reglas se ocupa de lo que se le escape a la cabeza; la red neuronal capta todo lo demás que las regex no pueden ver. La acumulación de latencia es intencionada: la ruta de regex es barata y sirve de respaldo si el runtime de ONNX falla.

¿Quieres probarlo tú mismo? Ve a la documentación para desarrolladores: el contrato completo de /v30/analyze, la configuración de autenticación y el esquema de peticiones están documentados allí, junto con /openapi.json cuando las métricas en sombra se estabilicen. Las notas de integración de cliente llegarán al changelog tras el periodo de prueba en staging.

Actualizado el 14 de octubre de 2026: el texto se ha cambiado para indicar resultados medidos.

Share