// M3 · lección 01
El encoder: la línea que lo cambia todo
El mismo bloque del decoder sin la máscara causal: qué se pierde y qué se gana al dejar de predecir el futuro, masked move modeling con la receta 80/10/10, pooling, tres cabezas sobre un tronco, el afinado por etapas, la curva por número de etiquetas y las dos representaciones de una posición.
// el recorrido · dónde estás
- M0 El tallerEntorno reproducible(hecho)
- M1 Datos y tokensDatos y tokenización(hecho)
- M2 El decoderPreentrenar un GPT(hecho)
- M3 El encoderEmbeddings y clasificar(estás aquí)
- M4 Fine-tuningSFT y LoRA(por delante)
- M5 AlineamientoPreferencias: DPO, GRPO(por delante)
- M6 Evaluar y publicarMedir y desplegar(por delante)
- FASE 2 El entrenadorAgentes, RAG, MCP
M3, lección a lección
- 01El encoder: la línea que lo cambia todo
- 02Las capas compartidas: un bloque, dos modelos
- 03El encoder a mano: el mismo bloque con `causal=False`
- 04Cabezas y casillas: tres preguntas y una segunda entrada
- 05Masked move modeling: el preentrenamiento y la receta compartida
- 06Entrenar las cabezas: etiquetas, congelación y la curva
- 07Evaluar el encoder: qué se mide y qué sale
- 08El encoder: medir sin engañarse
- 09Exportar el encoder: dos salidas, tres precisiones y una model card
- 10La barra de evaluación: el segundo worker y la alerta que nombra la jugada
- 11El encoder: los labs
entra
- los mismos tokens
- evaluaciones de Stockfish
se construye
- encoder bidireccional
- masked move modeling
- cabezas de valor y error
- métricas honestas
sale
- rukh-encoder
- barra de evaluación
el aprendiz, ahora
Aprende a mirar una posición entera y juzgarla: quién va mejor y si la última jugada fue un error.
por qué este paso · fuera del ajedrez
Comprender no es continuar. Los encoders están detrás de los embeddings, los clasificadores y la búsqueda semántica.
Al acabar M2, el aprendiz juega de oído: ha leído millones de partidas y sabe qué jugada suele venir después, pero no sabe decir por qué ni si la que acaba de ver era buena. En este módulo aprende a mirar una posición entera y juzgarla: quién va mejor, si la última jugada fue un error, cómo suele acabar esto. Es el mismo paso que da cualquier modelo de lenguaje cuando pasa de generar texto a clasificarlo o a convertirlo en un vector para buscar.
Esta primera lección es la conceptual. Las diez siguientes traen el código, fichero a fichero.
Qué vas a construir
Un encoderEncoderTransformer con atención bidireccional: cada posición ve toda la secuencia. No genera; representa. En Rukh el encoder lee una partida entera y produce un vector por posición del que salen el valor y la detección de errores. bidireccional de 15 052 800 parámetros —poco más de un tercio
del small de M2— que no juega. Mira una posición y dice cuánto vale, si la jugada que llevó hasta
ella fue un error y cómo acabó la partida de la que salió. Es el mismo bloque que escribiste en M2;
lo único que cambia es la máscara, y ese cambio de una línea convierte un generador en un lector.
Ocho capas, seis cabezas de atenciónAtenciónOperación que mezcla las posiciones de una secuencia: cada posición emite una consulta (Q), cada una ofrece una clave (K) y un valor (V); el producto escalar entre consulta y claves, escalado por 1/√d y pasado por softmax, da los pesos con los que se promedian los valores. Es la única parte del Transformer donde las posiciones se hablan entre sí., 384 dimensiones, y dos
representaciones de entrada que vas a comparar: la partida como secuencia de
jugadas UCIUCIDos cosas con el mismo nombre. La notación UCI escribe una jugada como casilla de origen, casilla de destino y promoción opcional (e7e8q): no depende del contexto, por eso es la tokenización por defecto. El protocolo UCI es la forma en que hablamos con Stockfish desde python-chess. —los mismos 2 030 tokensTokenUnidad mínima que el modelo lee y escribe. En Rukh, por defecto, un token es una jugada completa en notación UCI (e2e4); en otras tokenizaciones puede ser un carácter o un trozo de texto aprendido por BPE. El modelo nunca ve letras ni tableros: ve identificadores enteros de tokens. del decoder— o
la posición como 69 casillas leídas de un FENFENCadena de texto que describe una posición completa: piezas por fila, turno, derechos de enroque, casilla al paso y contadores de jugadas. Es la clave con la que se cruzan las posiciones de las partidas con las evaluaciones públicas de Stockfish.. El preentrenamiento
es masked move modelingMLM (masked language modeling)Objetivo de preentrenamiento de BERT: se esconde una parte de los tokens y el modelo, que ve la secuencia por los dos lados, tiene que reconstruirlos. En Rukh se llama masked move modeling porque el token es una jugada: se tapa el 15 % de las jugadas de la partida y de esas el 80 % se sustituye por <mask>, el 10 % por otra jugada al azar y el 10 % se deja tal cual. Los tokens de control (<bos>, Elo, resultado, <eos>) nunca se tapan: son la condición, no la señal.: se tapa el 15 % de las jugadas de una partida y el
modelo las reconstruye mirando a los dos lados del hueco. Encima montas tres cabezasCabeza (head)La capa final que traduce la representación en la salida de una tarea concreta; en inglés, head. El encoder de M3 lleva tres sobre el mismo tronco —valor, resultado y error garrafal— y cada una es una sola capa lineal, a propósito: si una lineal acierta, la información ya estaba en la representación. No confundir con la cabeza de atención, que es otra cosa.
lineales y las afinas en tres etapas.
Al terminar tendrás:
src/rukh/models/encoder.py, el modelo, ysrc/rukh/models/squares.py, el traductor de FEN a 69 tokens, con tests que comprueban que no hay causalidad (cambiar un token futuro sí mueve los estados pasados), que el relleno no influye y que la traducción de un FEN es reversible.- Un encoder preentrenado con masked move modeling sobre el mismo flujo de tokens del decoder.
- Tres cabezas afinadas en tres modos —probeProbe linealCongelar un modelo y entrenar encima solo una capa lineal para una tarea nueva. Si la capa lineal acierta, la información ya estaba en la representación y el preentrenamiento la puso ahí; si no acierta, no se puede concluir que no esté, solo que no está de forma linealmente legible. En Rukh es el modo
probederukh train heads, y un test comprueba que los pesos del encoder salen idénticos bit a bit. con el tronco congelado, últimas capas y completo— sobre 438 093 posiciones etiquetadas de entrenamiento (las 488 159 con evaluación de Stockfish, menos las partidas que se reservan para validar), y una curva por número de etiquetas al 10, 25, 50 y 100 %. - Una fila de la tabla única con precisiónPrecisiónDe todo lo que el detector marcó, qué fracción era de verdad:
VP/(VP+FP). Responde a «¿me puedo fiar cuando avisa?». Es la mitad de F1 que le importa a la demo: una barra que grita «error» en jugadas correctas se apaga a los cinco minutos. No confundir con exactitud, que cuenta también los aciertos sobre la clase mayoritaria., exhaustividadExhaustividadDe todo lo que había que marcar, qué fracción marcó el detector:VP/(VP+FN). Responde a «¿cuántos errores se me escapan?». Se mueve en contra de la precisión: bajar el umbral captura más errores reales y también más falsas alarmas, y elegir el punto de esa curva es una decisión de producto, no de entrenamiento. En Rukh el umbral por defecto es 0,5 y está enconfigs/eval/encoder.yaml. y F1F1Media armónica de precisión y exhaustividad:2·P·R/(P+R). Se usa cuando una clase es rara y la exactitud engaña: en M3 los errores son el 3,7 % de las filas, así que un detector que dijera siempre «no es error» acertaría el 96,3 % de las veces y tendría F1 cero. La armónica castiga el desequilibrio: la pata mala manda, y con precisión 1,0 y exhaustividad 0,01 da 0,02, no 0,5. Depende del punto de operación: la cabeza publicada da 0,0000 en el umbral de fábrica de 0,5 y 0,180 en el umbral elegido en la mitadtune(0,0663) y medido en la mitadscore. El listón deGOAL.mdpara M3 es superar en cinco puntos de F1 a la heurística de material; medido, +9,2. de detección de errores, del encoder y de un baselineBaselineEl rival tonto contra el que se mide un modelo, para saber si su número significa algo. En M3 es una heurística de material (peón 1, caballo y alfil 3, torre 5, dama 9) más movilidad, que llama error a toda jugada que pierda un punto neto. Se mide sobre las mismas filas y con la misma definición de acierto que el encoder: un margen medido sobre dos conjuntos distintos no es un margen. de material medido sobre las mismas filas, más la correlaciónCorrelación (Pearson y Spearman)Un número entre −1 y 1 que dice si dos cantidades suben juntas: 1, a la par; 0, sin relación; −1, una baja cuando la otra sube. Hay dos maneras de medir si un número predicho acompaña al real. Pearson mide si los valores se alinean en una recta y le importa la escala; Spearman es Pearson sobre los rangos, así que solo mide si el orden coincide: el podio frente a los cronos. Se publican las dos porque discrepan exactamente cuando el modelo tiene bien el orden y mal la escala, que es lo que le hace untanhacotado a una puntuación en centipeones. Las dos se calculan contratanh(cp/400), la escala con la que se entrena la cabeza, nunca contra elcpen bruto.GOAL.mdpide ≥ 0,80 leído sobre Spearman; el checkpoint publicado en M3 se queda en 0,520 (Pearson 0,648) y la pérdida con término de orden sube a 0,6665: criterio no cumplido. entre el valor predicho y los centipeonesCentipeón (cp)Unidad de la evaluación de un motor: 100 centipeones equivalen a un peón de ventaja. Es la etiqueta que Rukh cruzó en M1 desde las evaluaciones de Lichess y la que el encoder aprende a predecir, acotada contanh(cp/400)para que un mate valga ±1 en vez de diez mil y no domine la pérdida. Un error se define como perder ≥ 100 cp respecto a la mejor línea de la posición anterior. de Stockfish. - El modelo en ONNXONNXFormato abierto para describir el grafo de una red y sus pesos, independiente del framework que la entrenó. Es lo que permite entrenar en PyTorch y ejecutar en el navegador con
onnxruntime-web, sin Python ni backend. con dos salidas —valueyblunder— y la barra de evaluación de la demo, junto al tablero, con el fallo de entrada que la lección 10 destapa: la barra del commit que estudia tokeniza casillas y el encoder publicado espera jugadas (la demo publicada ya le da la partida). - En los labs, una partida entera dibujada con dos curvas de evaluación, la del encoder y la de Stockfish.
La tarjeta de abajo no cuadra con esas cifras: lo que vive en chorcat/rukh-encoder es el hermano
mayor, de 39 millones de parámetros (12 capas y 512 dimensiones), entrenado con la misma receta
después de cerrar el módulo; la lección 11 lo cuenta. Los labs se hacen con el de 15 millones, que
se entrena en un cuarto de hora.
Todas las salidas del módulo son de una tirada real en una RTX 5090. Los resultados, por adelantado: uno de los dos criterios del hito se cumple con holgura y el otro no; afinar solo los dos últimos bloques dio mejor error de valor que afinar el modelo entero; y la curva por etiquetas se aplana en el 25 %, así que tres cuartas partes de las evaluaciones de Stockfish no compraron nada medible.
De dónde vienes: M2 predice, M3 mira
M2 y M3 comparten el bloque Transformer, el tokenizador y casi toda la receta de entrenamiento; lo que cambia es qué se le tapa al modelo y qué se le pregunta. El decoder ve el pasado y predice la jugada siguiente. El encoder ve la secuencia entera y responde preguntas sobre ella:
| Pieza | Cómo era en M2 | Cómo es en M3 | Por qué cambia |
|---|---|---|---|
| Bloque Transformer | pre-norm, atención + MLP | el mismo, sin tocar | El bloque no depende de la dirección; lo hereda entero. |
| Máscara | causal: el token t solo ve ≤ t | ninguna: cada token ve la secuencia entera | La pregunta ya no es «qué viene después», así que el futuro no es trampa. |
| Objetivo | siguiente token | MLMMLM (masked language modeling)Objetivo de preentrenamiento de BERT: se esconde una parte de los tokens y el modelo, que ve la secuencia por los dos lados, tiene que reconstruirlos. En Rukh se llama masked move modeling porque el token es una jugada: se tapa el 15 % de las jugadas de la partida y de esas el 80 % se sustituye por <mask>, el 10 % por otra jugada al azar y el 10 % se deja tal cual. Los tokens de control (<bos>, Elo, resultado, <eos>) nunca se tapan: son la condición, no la señal.: reconstruir lo tapado |
Sin máscara causal, predecir el siguiente token es copiarlo. |
| Salida que se usa | logits sobre el vocabulario | el estado del tronco, que alimenta varias cabezas | Una posición admite muchas preguntas: valor, jugada, casillas. |
| Cómo se mide | legalidad, exactitud, puzles, Elo | F1 y correlación contra un baseline | Con clases raras, un acierto alto puede no significar nada. |
| Qué se congela | nada | el tronco, mientras se entrenan las cabezas | Para saber si la cabeza aprende o el tronco se está reescribiendo. |
Las dos filas de medición son la lección 8, «Medir sin engañarse», la parte del módulo que más vas a reutilizar fuera del ajedrez.
Teoría justa
Causal frente a bidireccional: la línea que lo cambia todo
Abre src/rukh/models/decoder.py y src/rukh/models/encoder.py uno al lado del otro. Los dos
suman un embeddingEmbeddingTabla que asigna un vector aprendido a cada id del vocabulario, y por extensión ese vector. En rukh-small la tabla es de 2 030 × 512: cada jugada UCI entra en el modelo como un punto en un espacio de 512 dimensiones, aprendido a la vez que el resto de la red. de token y uno de posición, pasan por una pila de
bloques pre-norm importados de rukh.models.layers y terminan en un LayerNorm. La diferencia cabe
en un argumento:
src/rukh/models/decoder.py:58 super().__init__(cfg.d_model, cfg.n_head, cfg.ff, cfg.dropout, causal=True)src/rukh/models/encoder.py:80 self.cfg.d_model, self.cfg.n_head, self.cfg.ff, self.cfg.dropout, causal=FalseEse booleano acaba en el is_causal de la llamada de atención, que decide si la matriz de pesos se
recorta a su triángulo inferior o se queda entera.
La mitad vacía de la izquierda es la razón de que un decoder no sirva para valorar: cuando juzga la jugada 20 no puede leer la 21, y a menudo la 21 lo explica todo (si el rival captura una dama, ya sabes bastante de la 20).
Lo que separa a BERT de GPT es la tarea, y la máscara es su consecuencia. Si el objetivo es «predice
el siguiente token», tienes que impedir que la posición t lea el token t+1, o la tarea se reduce
a copiar. Si el objetivo es «reconstruye lo que he tapado», tienes que dejarlo mirar hacia delante,
porque la mitad de la información sobre un hueco está a su derecha. Es la diferencia entre un
mecanógrafo al dictado y un corrector con la página delante: el primero escribe sabiendo solo lo que
ya se ha dicho; el segundo puede decir si una palabra está mal porque lee la frase que la sigue.
El resto de EncoderConfig es un presupuesto más pequeño que el del decoder: n_layer = 8,
n_head = 6, d_model = 384 (head_dim de 64, igual que en M2), d_ff de 4 × d_model = 1 536,
block = 200, dropout = 0.1 —el decoder entrena sin dropout y este no, porque va a ver muchas
menos etiquetas en el afinado— y posiciones aprendidas. La cuenta:
por bloque: 2·384 (ln1) + (384·1152 + 1152) (qkv) + (384·384 + 384) (proyección) + 2·384 (ln2) + (384·1536 + 1536) + (1536·384 + 384) = 1 774 464
8 bloques = 14 195 712embedding de tokens 2030 · 384 = 779 520posiciones 200 · 384 = 76 800LayerNorm final 2 · 384 = 768cabeza MMM atada al embedding = 0 ----------total = 15 052 800La cabeza que predice el token tapado es una matriz de 384 × 2030, la traspuesta del embedding, y
el código ata las dos (tie_embeddings): son los mismos 779 520 números contados una vez. Además de
ahorrar memoria, obliga a que el vector que representa e2e4 y el vector contra el que se puntúa
e2e4 sean el mismo. Solo se ata en el esquema de jugadas, donde entrada y salida comparten
vocabulario.
Qué se pierde y qué se gana al dejar de predecir el futuro
Se pierde la generación. Si intentaras muestrear de un modelo bidireccional token a token, cada paso le daría una secuencia cuya mitad derecha no existe, un régimen que jamás vio. El encoder no juega, y la demo sigue pidiéndole la jugada al decoder de M2.
Se ganan tres cosas:
- Contexto completo por token. En un decoder, el vector de la posición 5 resume las jugadas 1 a 5; en el encoder resume la partida entera desde el punto de vista de la jugada 5.
- Otra forma de gastar la señal. El MLM solo puntúa el 15 % de las posiciones por pasada —por eso BERT necesita más épocas que GPT para el mismo corpus—, pero cada predicción se hace con el doble de contexto. Es un intercambio, no una mejora gratis.
- Un vector de la posición. Con un poolingPoolingReducir los T vectores que devuelve un encoder a uno solo que represente la secuencia entera. Rukh implementa los dos clásicos:
clstoma el vector de la primera posición (<bos>en la entrada de jugadas,<cls>en la de casillas) ymeanpromedia solo los tokens reales, nunca el relleno. Ese único vector es lo que leen las tres cabezas de M3 y lo que se guarda como embedding de posición. sale un único vector sobre el que se montan cabezas baratas y que sirve para buscar posiciones parecidas. Es lo que justifica el módulo.
Masked move modeling: 15 %, 80/10/10 y un menos cien
El preentrenamiento es el de BERT con las palabras cambiadas por jugadas. Se elige al azar el 15 % de
las posiciones de una partida tokenizada, y de las elegidas: el 80 % se sustituye por <mask>, el
10 % por otra jugada cualquiera y el 10 % se deja como estaba. La pérdida es entropía cruzada solo
sobre esas posiciones elegidas.
| Rama | Qué se hace con la posición elegida | Qué evita |
|---|---|---|
80 % <mask> |
Se sustituye la jugada por el símbolo <mask>. |
Es la tarea en sí: reconstruir mirando a los dos lados del hueco. |
| 10 % aleatoria | Se sustituye por otra jugada cualquiera del vocabulario. | Que la política óptima sea «copia lo que ves salvo donde ponga <mask>». Con ruido, cualquier token puede ser mentira y cada posición necesita su propia opinión; en ajedrez, la jugada falsa suele ser ilegal. |
| 10 % intacta | Se deja tal cual, pero se puntúa igual. | Que un token que no es <mask> ni está corrupto no necesite representación propia. Es la rama que convierte «rellena huecos» en «representa la partida entera». |
Nunca: <bos>, Elo, resultado, <eos> |
No se eligen jamás, y el ruido se sortea por encima de sus 62 ids. | Regalarle una tarea trivial —predecir el resultado con el mate a la vista— que no enseña ajedrez y contamina la pérdida, o inventar un Elo falso. |
El 15 % es el valor de BERT, adoptado sin cambios. Si tapas poco, casi todo el cómputo se gasta en contexto que no puntúa nadie; si tapas mucho, lo que queda no basta para reconstruir y el modelo aprende a adivinar la jugada media.
Las dos ramas del 10 % existen porque el afinado ve secuencias completas, sin un solo <mask>. Si
todo lo elegido fuera <mask>, el modelo aprendería una representación que funciona cuando falta
algo y se degrada cuando no falta nada. La rama intacta es la que la gente olvida: sin ella, el
vector de una jugada normal no tendría por qué contener qué jugada es.
Por qué ignore_index es −100 y no 0. En M2 las posiciones que no había que puntuar eran el
relleno, el token 0, así que ignore_index=0 funcionaba. Aquí no: en el esquema de casillas un 0
puede aparecer como etiqueta legítima. Es la diferencia entre un 0 y un N/A en una hoja de
cálculo: el cero entra en la media, el N/A se salta. La solución es un valor que no es el id de
nada, -100, la convención de PyTorch y de Hugging Face. En el código se llama MMM_IGNORE_INDEX.
Pooling: una representación de la posición
El encoder devuelve (B, T, d_model), un vector por token. Las cabezas necesitan uno por secuencia,
y reducir lo uno a lo otro es el pooling. Hay dos formas clásicas:
- CLS. Se usa el vector de salida de la posición 0, un token sin contenido propio (
<bos>en jugadas,<cls>en casillas) que el entrenamiento empuja a acumular lo que la atención le trae del resto. En un modelo solo preentrenado con MLM puede ser poco informativo. - Media. Se promedian los vectores de todos los tokens. No depende de que una posición haya
aprendido a hacer de resumen, y es la que Rukh usa por defecto (
pooling: mean).
La media tiene que ignorar el relleno. Un lote completa las secuencias cortas con <pad>, y si
promedias los T vectores sin más, la misma partida da un vector distinto según con quién le haya
tocado viajar en el lote. Eso envenena el entrenamiento y la evaluación. En el código son cuatro
líneas de pool (lección 3): suma solo los tokens reales y divide por cuántos eran.
El resultado es un vector de 384 números del que se puede leer, con una transformación lineal, si las blancas están mejor, si la última jugada fue una barbaridad y quién va a ganar. Es lo mismo que hace un sistema de búsqueda semántica con un párrafo: comprimirlo en un vector que conserve lo que importa para compararlo con otros.
Tres cabezas sobre un mismo tronco
Las tres cabezas son una capa lineal cada una, y eso es el diseño del experimento. Una capa lineal no tiene capacidad para inventar información: si basta para decir cuánto vale una posición, esa información ya estaba en el vector. Con dos capas ocultas, la cabeza podría aprender la tarea por su cuenta y el resultado no diría nada del encoder. Vale en cualquier proyecto: para medir una representación, usa la sonda más tonta que funcione. Es como un mozo de almacén sin permiso para mover cajas: si encuentra lo que le piden solo leyendo las etiquetas, el almacén estaba bien ordenado.
La cabeza de valor es una regresión con tanh contra la etiqueta tanh(cp / 400). Se acota porque
un mate vale ±10 000 en la convención del repo, y un puñado de mates dominaría el error cuadrático.
Con el tanh, 400 centipeones son 0,76 y un mate es 1; el divisor deja la zona donde la partida
todavía se juega, de −300 a +300, en la parte de la curva que tiene pendiente.
La cabeza de error emite un logit, y muchas filas no tienen etiqueta: hace falta la posición anterior
de la misma partida para saber cuánto se perdió. Esas filas se saltan con una máscara y la ausencia
se guarda como null, nunca como un 0: etiquetar «no lo sé» como «no fue error» es de las maneras
más rápidas de arruinar un clasificador.
La cabeza de resultado son tres clases, y es la más ruidosa: la etiqueta es de la partida entera, y la jugada 3 de una partida que ganaron las blancas no es una posición ganadora. Por eso pesa la mitad en la pérdida conjunta. Las tres pérdidas se registran también por separado, porque con un tronco compartido una tarea puede mejorar a costa de otra sin que el total lo diga.
Afinado por etapas: probe, últimas capas, completo
Los tres modos de rukh train heads son tres preguntas distintas, y se ejecutan en este orden.
probe. El encoder se congela entero y solo se mueven las cabezas. Pregunta: ¿la información
ya está en la representación? Es el único modo que dice algo del preentrenamiento. Congelar incluye
poner el encoder en modo eval; en train, el dropout daría un vector distinto para la misma
posición en cada época.
last-n. Se descongelan las dos últimas capas y la normalización final. Pregunta: ¿cuánto se
gana dejando que las capas altas se especialicen? Es el punto intermedio habitual en producción, a
una fracción del coste del afinado completo.
full. Se mueve todo. Pregunta: ¿qué es lo mejor que esta arquitectura puede hacer con estas
etiquetas? Se suele dar por hecho que da los mejores números, pero es el que más etiquetas
necesita, el que antes se sobreajusta y el que menos dice del encoder. En este módulo, además, no
ganó.
Lo interesante es la comparación, más que el número del mejor. Si probe ya va bien, el
preentrenamiento funcionó y tienes un modelo barato de adaptar. Si probe va mal y full va bien,
el MLM aportó poco en este dominio y a esta escala. Es la misma escalera que recorrerás en M4 con un
LLM: congelar, adaptar un trozo (LoRA) o afinarlo todo.
La curva por número de etiquetas
Se entrena la misma receta cuatro veces, con el 10, 25, 50 y 100 % de las etiquetas, y se dibuja la métrica contra el número de filas. La curva responde a una pregunta de presupuesto de cualquier clasificador: ¿cuántas etiquetas hacen falta de verdad? Si se aplana pronto, etiquetar más es tirar dinero y el cuello de botella está en el modelo, en la tarea o en el ruido de las etiquetas. Si sigue subiendo en el 100 %, lo más rentable es etiquetar más. Aquí se aplana en el 25 %.
Los subconjuntos son anidados: el 10 % está dentro del 25 %, que está dentro del 50 %. Con cuatro muestras independientes, un bache en el 50 % podría ser un sorteo desafortunado; creciendo un único conjunto, cada punto es el anterior más filas nuevas, y una bajada significa algo (lección 6).
Cartilla de métricas: cómo se mide un detector
La cabeza de error es un detector: mira cada jugada y dice «error» o «no». Las lecciones 6 y 7 lo miden con seis números, y los cuatro primeros salen de una misma tabla. Un ejemplo de juguete: cien jugadas, diez son errores de verdad, y el detector marca ocho, de las que seis aciertan. Cada decisión cae en una de cuatro casillas, la matriz de confusión:
| Era error | No era error | |
|---|---|---|
| Marcada | 6 (verdaderos positivos) | 2 (falsos positivos) |
| No marcada | 4 (falsos negativos) | 88 (verdaderos negativos) |
- La tasa baseTasa baseFrecuencia de la clase positiva en los datos: en M3, los errores son el 3,7 % de las filas etiquetadas. Es el número que hay que preguntar antes de leer cualquier exactitud, porque un detector que siempre dice «no» acierta uno menos la tasa base (96,3 %) sin saber nada de ajedrez, y es la referencia de la precisión media: un orden aleatorio saca exactamente la tasa base (0,037), no 0,5. es la frecuencia de lo que se busca: 10 de 100, el 10 %.
Es lo primero que se pregunta, porque un detector que diga siempre «no» acierta el 90 % sin mirar
nada. La exactitud de este,
(6 + 88) / 100 = 94 %, apenas lo supera. - La precisiónPrecisiónDe todo lo que el detector marcó, qué fracción era de verdad:
VP/(VP+FP). Responde a «¿me puedo fiar cuando avisa?». Es la mitad de F1 que le importa a la demo: una barra que grita «error» en jugadas correctas se apaga a los cinco minutos. No confundir con exactitud, que cuenta también los aciertos sobre la clase mayoritaria. es, de lo marcado, cuánto era:6 / 8 = 0,75. - La exhaustividadExhaustividadDe todo lo que había que marcar, qué fracción marcó el detector:
VP/(VP+FN). Responde a «¿cuántos errores se me escapan?». Se mueve en contra de la precisión: bajar el umbral captura más errores reales y también más falsas alarmas, y elegir el punto de esa curva es una decisión de producto, no de entrenamiento. En Rukh el umbral por defecto es 0,5 y está enconfigs/eval/encoder.yaml. (recall) es, de lo que había, cuánto se marcó:6 / 10 = 0,60. - El F1F1Media armónica de precisión y exhaustividad:
2·P·R/(P+R). Se usa cuando una clase es rara y la exactitud engaña: en M3 los errores son el 3,7 % de las filas, así que un detector que dijera siempre «no es error» acertaría el 96,3 % de las veces y tendría F1 cero. La armónica castiga el desequilibrio: la pata mala manda, y con precisión 1,0 y exhaustividad 0,01 da 0,02, no 0,5. Depende del punto de operación: la cabeza publicada da 0,0000 en el umbral de fábrica de 0,5 y 0,180 en el umbral elegido en la mitadtune(0,0663) y medido en la mitadscore. El listón deGOAL.mdpara M3 es superar en cinco puntos de F1 a la heurística de material; medido, +9,2. es la media armónica de las dos,2 · 0,75 · 0,60 / 1,35 = 0,67: un solo número que cae en cuanto una de las dos patas cojea.
Esas cuatro dependen de un umbral. El detector no dice «sí» o «no», sino una puntuación, y marca lo que pasa del corte; moverlo cambia la tabla entera. Las otras dos miran solo el orden. Toma diez jugadas ordenadas por puntuación, de más sospechosa a menos, con los dos errores reales en las posiciones 1 y 4:
- El ROC AUCROC AUCProbabilidad de que, cogidos al azar un positivo y un negativo, el modelo puntúe más alto al positivo: 0,5 es una moneda y 1 es orden perfecto. No depende de ningún umbral, así que ningún punto de operación puede halagarla ni hundirla; en M3 el mismo modelo con F1 0,0000 en el umbral de 0,5 tiene 0,740 de ROC AUC. Con una clase muy rara se deja impresionar por lo bien que se ordenan los negativos entre sí, por lo que se publica junto a la precisión media, que solo mira si los positivos suben. En Rukh está escrita a mano sobre rangos en
src/rukh/eval/. es la probabilidad de que, cogidos al azar un error y una jugada buena, el error puntúe más alto. El de la posición 1 supera a las ocho buenas; el de la 4, a las seis que tiene detrás:(8 + 6) / 16 = 0,875. Una moneda saca 0,5. - La precisión mediaPrecisión mediaÁrea bajo la curva de precisión-exhaustividad: resume todos los puntos de operación posibles en un número que no depende de ninguno. Su referencia no es 0,5 sino la tasa base, que es lo que saca un orden aleatorio (0,037 en M3); el encoder publicado saca 0,112, el triple. Es más pesimista que el ROC AUC con una clase rara porque solo mira si los positivos están arriba —la primera página del buscador—, y cuando las dos discrepan es la que más se parece a lo que ve el usuario. (AP) recorre la lista y apunta la precisión
cada vez que aparece un error:
1/1en la posición 1 y2/4en la 4, que dan0,75de media. Su referencia no es 0,5 sino la tasa base, 0,2 aquí: es lo que saca un orden al azar.
En M3 la tasa base es del 3,7 %, así que esta cartilla se aplica con una lupa: la lección 8 cuenta qué engaña cuando la clase es tan rara y cómo se elige el umbral sin hacer trampa.
Dos representaciones de entrada: jugadas o 69 casillas
Rukh implementa las dos entradas del encoder y las compara, porque la respuesta a «qué es una buena representación» no es evidente.
Jugadas. La secuencia UCI de la partida hasta ahora, con el vocabulario del decoder. Contiene la posición, pero hay que reconstruirla. Y contiene la historia: cómo se llegó ahí y, sobre todo, cuál fue la última jugada, el dato central para detectar un error.
Casillas. El FEN convertido en 69 tokens fijos: un <cls>, las 64 casillas por columnas (a1,
a2, …, h8), el turno, los cuatro derechos de enroque en un solo token de 16 combinaciones, la casilla
de al paso y el reloj de 50 jugadas por tramos. La posición está explícita: la capa 1 ya sabe que hay
un caballo en f3. A cambio, la historia desaparece. (El token del reloj es constante en todos los
datos de este módulo; la lección 4 explica por qué se queda.)
Piensa en un accidente de tráfico. La foto del coche después del golpe dice cuánto daño hay: eso son las casillas. El vídeo del salpicadero dice de quién fue la culpa, porque la culpa está en lo que pasó justo antes: eso son las jugadas.
Las hipótesis antes de medir eran que las casillas ganaran en valor, donde importan el material y la estructura actual, y las jugadas en detección de errores, donde la entrada por casillas no tiene la posición anterior. Escribirlas antes de mirar es la diferencia entre un experimento y una justificación a posteriori.
El resultado medido
Las dos líneas se entrenaron con las mismas 438 093 posiciones etiquetadas, el mismo reparto por partida, los mismos 4 000 pasos de afinado y la misma evaluación sobre 10 000 posiciones held-out. La diferencia es la entrada y, con ella, que la línea de jugadas parte del preentrenamiento y la de casillas no.
| Esquema | F1 de error (umbral ajustado) | Margen sobre la heurística | ROC AUC | Spearman valor | Pearson valor |
|---|---|---|---|---|---|
moves · afinado full |
0,179 | +9,0 | 0,738 | 0,407 | 0,442 |
moves · afinado last-n |
0,180 | +9,2 | 0,740 | 0,520 | 0,648 |
squares (desde cero) |
0,146 | +5,7 | 0,696 | 0,422 | 0,688 |
Para leerla basta con la cartilla de arriba y con esto: Spearman mide si el orden de los valores
predichos coincide con el del motor, sin mirar la escala, y Pearson sí la mira (las dos, con un
ejemplo de cinco números, en la estadística justa
de M2). Las tres,
y el umbral «ajustado» del F1, tienen su sección en la lección 8. La fila last-n es el checkpoint
que se publicó y el que usan todas las cifras de aquí en adelante.
Las jugadas ganan en detectar errores: 0,180 de F1 contra 0,146, y 0,740 de ROC AUC contra 0,696. Un error es una propiedad de la transición, no de la posición, y la línea de jugadas lleva la partida dentro. Además, un modelo que ha pasado dieciséis minutos adivinando jugadas tapadas sabe qué jugadas son plausibles, y una jugada implausible es el primer indicio de un error.
Las casillas ganan en la escala del valor (Pearson 0,688 contra 0,648), porque el material está en la entrada sin reconstruirlo. Pero en Spearman el orden se invierte (0,422 frente a 0,520). Guarda ese detalle: en la lección 8 es la pista que lleva al problema del criterio de valor.
Dos matices. La línea de casillas arranca de pesos aleatorios —masked move modeling solo existe para jugadas—, así que es el baseline que el preentrenamiento tenía que batir: lo batió en errores y perdió en la escala del valor. Y la tabla supervisada está deduplicada por FEN, así que el prefijo de jugadas es una línea que llega a esa posición, no necesariamente la de la partida que puso la etiqueta; el margen se lee sabiéndolo.
Ver el tablero es mejor para «cuánto vale esto»; ver la línea es mejor para «qué se acaba de tirar». Que cada una gane en una cosa dice que llevan información distinta.
Lo siguiente es el código, y empieza por sacar de decoder.py los bloques que los dos modelos
comparten, para que el encoder sea el mismo bloque con otro booleano. Sigue en «Las capas
compartidas».