Por qué su modelo de aprendizaje automático funciona en la capacitación pero falla en la producción

Trabajé en sistemas de detección de fraude en tiempo real y modelos de recomendación para empresas de productos que lucían excelentes durante el desarrollo. Las métricas fuera de línea fueron sólidas. Las curvas AUC se mantuvieron estables en todas las ventanas de validación. Las tramas de importancia de las características cuentan una historia clara e intuitiva. Enviamos con confianza.

Unas semanas más tarde, nuestras métricas empezaron a desviarse.

Las tasas de clics en las recomendaciones comenzaron a disminuir. Los modelos de fraude se comportaron de manera inconsistente durante las horas pico. Algunas decisiones parecían demasiado confiadas, otras extrañamente ciegas. Los modelos en sí no se habían degradado. No hubo cortes repentinos de datos ni tuberías rotas. Lo que fracasó fue nuestra comprensión de cómo se comportaba el sistema una vez que se topaba con el tiempo, la latencia y la verdad retrasada en el mundo real.

Este artículo trata sobre esos fracasos. Los problemas silenciosos y poco glamorosos que aparecen sólo cuando los sistemas de aprendizaje automático chocan con la realidad. Ni opciones de optimizador ni la última arquitectura. Los problemas que no aparecen en los cuadernos, sino que afloran en los paneles de las 3 de la madrugada.

Mi mensaje es simple: la mayoría de las fallas de ML en producción son problemas de datos y tiempo, no problemas de modelado. Si no diseña explícitamente cómo llega, madura y cambia la información, el sistema silenciosamente hará esas suposiciones por usted.

Viaje en el tiempo: una fuga de suposiciones

El viaje en el tiempo es el fallo de producción de ML más común que he visto, y también el menos discutido en términos concretos. Todos asienten cuando mencionas la fuga. Muy pocos equipos pueden señalar la fila exacta donde ocurrió.

Déjame hacerlo explícito.

Imagine un conjunto de datos sobre fraude con dos tablas:

transacciones: cuando ocurrió el pago

La tabla de transacciones muestra a un usuario realizando múltiples pagos el 24 de diciembre, todos antes de media tarde. (Imagen del autor, generada utilizando datos sintéticos para ilustración).

Devoluciones de cargo: cuándo se informó el resultado del fraude

La tabla de devolución de cargo muestra un informe de fraude que llegó a las 6:40 p. m. del mismo día (Imagen del autor, generada utilizando datos sintéticos para ilustración).

La característica que queremos es user_chargeback_count_last_30_days.

El trabajo por lotes se ejecuta al final del día, justo antes de la medianoche, y calcula el recuento de contracargos de los últimos 30 días. Para el usuario U123, el recuento es 1. A partir de la medianoche, eso es correcto.

Imagen del autor, generada a partir de datos sintéticos para ilustración.

Ahora mire el conjunto de datos de entrenamiento unido final.

Las transacciones matutinas a las 9:10 a. m. y a las 11:45 a. m. ya tienen un recuento de contracargo de 1. En el momento en que se realizaron esos pagos, el contracargo aún no se había informado. Pero los datos de entrenamiento no lo saben. El tiempo se ha aplanado.

Aquí es donde la modelo hace trampa.

Imagen del autor, generada a partir de datos sintéticos para ilustración.

Desde la perspectiva del modelo, las transacciones que parecen riesgosas ya vienen con señales de fraude confirmadas. La recuperación sin conexión mejora drásticamente. Nada parece estar mal en este momento.

Pero en producción, se supone que el modelo nunca ve el futuro.

Cuando se implementan, esas primeras transacciones aún no tienen un recuento de contracargos. La señal desaparece y el rendimiento colapsa.

Esto no es un error de modelado. Es una fuga de suposiciones.

La suposición oculta es que una función de lote diario es válida para todos los eventos de ese día. No lo es. Una característica sólo es válida si podría haber existido en el momento exacto en que se realizó la predicción.

Cada característica debe responder una pregunta:

“¿Podría haber existido este valor en el momento exacto en que se hizo la predicción?”

Si la respuesta no es un sí seguro, la función no es válida.

Funciones predeterminadas que se convierten en señales

Después de los viajes en el tiempo, este es un motivo de falla muy común que he visto en los sistemas de producción. A diferencia de las fugas, ésta no depende del futuro. Se basa en el silencio.

La mayoría de los ingenieros tratan los valores perdidos como un problema de higiene. Llénelos con promedio, mediana o alguna otra técnica de imputación y luego continúe.

Estos valores predeterminados parecen inofensivos. Algo lo suficientemente seguro para que el modelo pueda seguir funcionando.

Esa suposición resulta costosa.

En los sistemas reales, faltar rara vez significa azar. Desaparecido a menudo significa algo nuevo, desconocido, que aún no se ha observado o que aún no se confía en él. Cuando colapsamos todo eso en un único valor predeterminado, el modelo no ve ninguna brecha. Ve un patrón.

Déjame concretar esto.

La primera vez que me encontré con esto fue en un sistema de fraude en tiempo real donde usábamos una función llamada avg_transaction_amount_last_7_days. Para los usuarios activos, este valor se comportó bien. Para usuarios nuevos o inactivos, la canalización de funciones devolvió un valor predeterminado de cero.

Imagen del autor, generada a partir de datos sintéticos para ilustración.

Para ilustrar cómo el valor predeterminado se convirtió en un fuerte indicador del estado del usuario, calculé la tasa de fraude observada agrupada por el valor de la característica:

datos.groupby(“avg_txn_amount_last_7_days”)[“is_fraud”].significar()

Como se muestra, los usuarios con un valor de cero exhiben una tasa de fraude notablemente más baja, no porque el gasto cero sea inherentemente seguro, sino porque codifica implícitamente “usuario nuevo o inactivo”.

Todos los usuarios con un monto promedio de transacción de cero no son fraudes. No porque cero sea inherentemente seguro, sino porque esos usuarios son nuevos/inactivos. El modelo no aprende que “el gasto bajo es seguro”. Aprende que “la historia perdida significa estar a salvo”.

El default se ha convertido en una señal.

Durante el entrenamiento, esto se ve bien ya que mejora la precisión. Entonces el tráfico de producción cambia.

Un servicio descendente comienza a agotarse durante las horas pico. De repente, los usuarios activos pierden temporalmente sus funciones de historial. Su avg_transaction_amount_last_7_days cambia a cero. El modelo los marca con confianza como de bajo riesgo.

Los equipos experimentados manejan esto de manera diferente. Separan la ausencia del valor y rastrean explícitamente la disponibilidad de las funciones. Lo más importante es que nunca permiten que el silencio se haga pasar por información.

Cambio de población sin cambio de distribución

Me tomó mucho más tiempo reconocer este modo de falla, principalmente porque todas las alarmas habituales permanecieron en silencio.

Cuando la gente habla de deriva de datos, normalmente se refiere a un cambio de distribución. Los histogramas de características se mueven. Los percentiles cambian. Las pruebas KS iluminan los tableros. Todos entienden qué hacer a continuación. Investigue datos ascendentes, vuelva a entrenar y recalibrar.

El cambio de población sin cambio de distribución es diferente. Aquí, las distribuciones de características se mantienen estables. Las estadísticas resumidas apenas se mueven. Los paneles de seguimiento parecen tranquilizadores. Y, sin embargo, el comportamiento del modelo se degrada constantemente.

La primera vez que encontré esto fue en un sistema de riesgo de pagos a gran escala que operaba en múltiples segmentos de usuarios. El modelo consumía características a nivel de transacción como cantidad, hora del día, señales del dispositivo, contadores de velocidad y códigos de categoría de comerciante. Todas estas características fueron fuertemente monitoreadas. Sus distribuciones apenas cambiaron mes tras mes.

Aún así, las tasas de fraude comenzaron a aumentar en una porción de tráfico muy específica. Lo que cambió no fueron los datos. Era a quién representaban los datos.

Con el tiempo, el producto se expandió a nuevas cohortes de usuarios. Nuevas geografías con diferentes hábitos de pago. Nuevas categorías de comerciantes con patrones de transacciones desconocidos. Campañas promocionales que atrajeron a usuarios que se comportaban de manera diferente pero que aún se encontraban dentro de los mismos rangos numéricos. Desde el punto de vista de la distribución, nada parecía inusual. Pero la población subyacente había cambiado.

El modelo había sido entrenado principalmente en usuarios maduros con largos historiales de comportamiento. A medida que la base de usuarios crecía, una fracción mayor del tráfico procedía de usuarios más nuevos cuyo comportamiento parecía estadísticamente similar pero semánticamente diferente. Un monto de transacción de 2000 significó algo muy diferente para un usuario veterano que para alguien en su primer día. El modelo no lo sabía porque no le habíamos enseñado a preocuparse.

Cambio de población sin cambio de distribución

Vea esta figura arriba. Muestra por qué este modo de fallo es difícil de detectar en la práctica. Los dos primeros gráficos muestran el monto de la transacción y las distribuciones de velocidad a corto plazo para usuarios nuevos y maduros. Desde una perspectiva de seguimiento, estas características parecen estables con la superposición. Si esta fuera la única señal disponible, la mayoría de los equipos concluirían que la canalización de datos y las entradas del modelo se mantienen saludables.

La tercera trama revela el verdadero problema. Aunque las distribuciones de características son casi idénticas, la tasa de fraude difiere sustancialmente entre las poblaciones. El modelo aplica los mismos límites de decisión a ambos grupos porque los datos parecen familiares, pero el riesgo subyacente no es el mismo. Lo que ha cambiado no son los datos en sí, sino a quién representan.

A medida que la composición del tráfico cambia a través del crecimiento o la expansión, esos supuestos dejan de ser válidos, aunque los datos siguen pareciendo estadísticamente normales. Sin modelar explícitamente el contexto de la población o evaluar el desempeño entre cohortes, estas fallas permanecen invisibles hasta que las métricas comerciales comienzan a degradarse.

Antes de ir

Ninguna de las fallas de este artículo fue causada por malos modelos.

Las arquitecturas eran razonables. Las funciones fueron cuidadosamente diseñadas. Lo que falló fue el sistema en torno al modelo, específicamente las suposiciones que hicimos sobre el tiempo, la ausencia y a quién representaban los datos.

El tiempo no es un índice estático. Las etiquetas llegan tarde. Los rasgos maduran de manera desigual. Los límites de los lotes rara vez se alinean con los momentos de decisión. Cuando ignoramos eso, los modelos aprenden de información que nunca volverán a ver.

Si hay una conclusión, es la siguiente: las métricas fuera de línea sólidas no son prueba de corrección. Son prueba de que el modelo se ajusta a los supuestos que usted le dio. El verdadero trabajo del aprendizaje automático comienza cuando esas suposiciones se hacen realidad.

Diseño para ese momento.

Referencias y lecturas adicionales

[1] Curvas ROC y AUC (Curso intensivo de aprendizaje automático de Google)
https://developers.google.com/machine-learning/crash-course/classification/roc-and-auc

[2] Prueba de Kolmogorov-Smirnov (Wikipedia)
https://en.wikipedia.org/wiki/Kolmogorov%E2%80%93Smirnov_test[3] Turnos de Distribución de Datos y Monitoreo (Chip Huyen)
https://huyenchip.com/2022/02/07/data-distribution-shifts-and-monitoring.html