El escenario del material es reconocible: alguien entrena un modelo, se va de la empresa, y meses después nadie consigue reproducir su precisión. No porque el código se perdiera —está en el repositorio— sino porque el código no era suficiente.
| Lo que faltaba | Por qué no estaba en el repositorio |
|---|---|
| La versión exacta de los datos | Se cargaban de una ruta que hoy tiene otro contenido |
| Los hiperparámetros usados de verdad | Se ajustaron a mano en la última ejecución |
| Las semillas aleatorias | Nadie las fijó |
| Variables de entorno | Vivían en la máquina de esa persona |
Y hay un contexto que lo convierte en algo más que una molestia: en sectores regulados, poder recrear el modelo exacto es un requisito. Hay que ser capaz de explicar por qué el sistema tomó una decisión, y eso empieza por poder reconstruir el modelo que la tomó.
| Condición | Cómo se consigue | Qué la rompe |
|---|---|---|
| Mismos datos | Artifacts versionados: cada dataset es inmutable y tiene identidad | Leer de una ruta o una consulta que cambia |
| Mismo código | El pipeline registra qué versión del código produjo cada ejecución | Lógica fuera de los steps, en el cuaderno o en el shell |
| Mismos parámetros | Pasarlos como parámetros del step, no como constantes editadas a mano | Cambiar un número «solo para probar» |
| Misma aleatoriedad | Fijar la semilla en todos los pasos que la usen | Un solo componente aleatorio sin semilla |
La cuarta es la que más se subestima, y el matiz está en «todos». El reparto entre entrenamiento y prueba usa aleatoriedad; el modelo también; a veces la inicialización de una red y el barajado de un lote, además. Fijar la semilla en el entrenador y olvidarla en el split ya rompe la reproducibilidad, y el síntoma —resultados que varían un poco entre ejecuciones— es lo bastante pequeño como para atribuirlo al azar y seguir adelante.
// una sola fuente de azar sin fijar basta para que nada sea repetibleCuando un step de entrenamiento devuelve el modelo, el framework detecta de qué biblioteca es y lo serializa en el formato adecuado, junto a sus metadatos: cuándo se ejecutó, con qué código, con qué entradas y con qué métricas asociadas. Eso convierte el modelo en algo que se puede auditar y desplegar sin volver a entrenar.
De ahí dos consejos que parecen menores y no lo son. El primero: devuelve el objeto modelo completo, no solo los pesos — un objeto entrenado se puede cargar y usar; un array de pesos necesita que alguien recuerde la arquitectura. El segundo: si la salida no se puede serializar, el paso falla, y esa es una restricción sana. Obliga a que la frontera entre pasos sean datos y no objetos vivos.
dropna() y continúa — con un comentario que reconoce que en un caso real
convendría lanzar una excepción. Un print no es una señal: en un pipeline desatendido
no lo ve nadie. Peor: su propia tabla de troubleshooting dice que un AssertionError
significa «el validador falló, como se esperaba», y no hay ningún assert en ese
código. La pregunta que desmonta cualquier validador: si el dato es malo, ¿qué pasa
exactamente? Si la respuesta es «se imprime un mensaje», no hay puerta.
Ejecuta tu entrenamiento dos veces seguidas, sin cambiar nada, y compara la métrica con todos los decimales que tengas. Si no son idénticas, tienes una fuente de azar sin fijar — y encontrarla es el ejercicio: ve poniendo semillas de una en una hasta que las dos ejecuciones coincidan.
Cuando coincidan, apunta cuántas semillas hicieron falta. Ese número suele sorprender, y es la medida honesta de cuántos sitios de tu código eran no deterministas sin que lo supieras.