MLOps: mantener viva una IA
Qué es MLOps y por qué los modelos de IA necesitan mantenimiento.
Definición
MLOps (machine learning operations, operaciones de aprendizaje automático) es el conjunto de prácticas para llevar un modelo de aprendizaje automático a uso real y mantenerlo funcionando bien con el tiempo: datos y entrenamiento reproducibles, versiones registradas, despliegue seguro, monitoreo y reentrenamiento. Aplica a los modelos las ideas de DevOps y CI/CD (día 7), con una diferencia clave: un modelo puede degradarse sin que nadie cambie el código, porque el mundo del que aprendió cambia.
Google Cloud lo resume en su guía de MLOps: en un sistema de ML real, solo una pequeña fracción es código de ML; el resto es recolección y validación de datos, infraestructura de servicio, monitoreo y automatización. Ese «resto» es lo que MLOps organiza.
El ciclo de vida de un modelo
| Etapa | Qué ocurre | Artefacto que queda |
|---|---|---|
| Datos | Recolectar, limpiar, validar el esquema y versionar el conjunto de datos | Conjunto de datos con versión |
| Entrenamiento | Ajustar el modelo con código y parámetros reproducibles | Modelo entrenado + parámetros + versión del código |
| Evaluación | Medir con datos que el modelo no vio, contra el modelo actual | Métricas y reporte de comparación |
| Registro | Guardar la versión con sus métricas, datos de origen y estado | Entrada en el registro de modelos |
| Despliegue | Ponerlo a responder peticiones reales, de forma gradual | Versión en producción |
| Monitoreo | Vigilar datos de entrada, predicciones, métricas de negocio y latencia | Alertas y tableros |
| Reentrenamiento | Volver a entrenar con datos recientes, por calendario o por alerta | Nueva versión candidata |
Deriva: por qué un modelo se desfasa
La deriva (drift) es el cambio, con el tiempo, entre el mundo con el que se entrenó el modelo y el mundo en el que trabaja.
| Tipo | Qué cambia | Ejemplo |
|---|---|---|
| Deriva de datos (data drift) | La distribución de las entradas | Una app de entregas empieza a recibir más viajes largos |
| Deriva de concepto (concept drift) | La relación entre entradas y resultado | Con más tráfico, cada kilómetro ahora tarda más minutos |
La deriva de datos se puede ver sin esperar resultados reales; la de concepto solo aparece cuando llegan las etiquetas (el resultado verdadero), que muchas veces se conocen días o semanas después (etiquetas tardías).
Cómo se detecta
| Método | Qué compara | Lectura habitual |
|---|---|---|
| PSI (Population Stability Index) | Distribución de una variable en entrenamiento contra la actual, por intervalos | Regla práctica común: < 0.10 estable, 0.10–0.25 vigilar, > 0.25 cambio fuerte |
| Prueba de Kolmogorov-Smirnov (KS) | Distancia máxima entre dos distribuciones acumuladas | Si el estadístico D supera el valor crítico, las distribuciones difieren |
| Caída de métricas | Error o acierto cuando llegan las etiquetas; mientras tanto, métricas de negocio (quejas, devoluciones) | Alerta si empeora más de un margen fijado (por ejemplo 20 %) |
Una variable con deriva no siempre daña al modelo, y un modelo puede fallar sin deriva visible en las entradas. Por eso se vigilan las dos cosas: entradas y resultados.
Registro de modelos y versiones
Un registro de modelos (model registry) es el catálogo de todas las versiones de un modelo: con qué datos y código se entrenó, sus métricas, quién lo aprobó y en qué estado está (candidato, producción, archivado). Permite reproducir cualquier versión y volver a la anterior en minutos.
MLflow es una plataforma de código abierto (licencia Apache 2.0) que incluye seguimiento de experimentos y un registro de modelos con versiones, etiquetas y alias (por ejemplo @champion para la versión en producción). Las nubes ofrecen registros equivalentes en sus plataformas de ML.
Despliegue seguro
| Estrategia | Cómo funciona | Para qué |
|---|---|---|
| Sombra (shadow deployment) | El modelo nuevo recibe el tráfico real y predice, pero sus respuestas no se usan | Comparar con el actual sin riesgo |
| Canario (canary) | Un porcentaje pequeño del tráfico va al modelo nuevo; si sus métricas son buenas, se amplía | Limitar el daño si falla |
| Vuelta atrás (rollback) | Regresar a la versión anterior del registro, de forma manual o automática | Recuperarse rápido de una mala versión |
Regla de promoción: el candidato sustituye al actual solo si mejora en datos recientes que ninguno de los dos vio, sin empeorar en los segmentos importantes.
Niveles de madurez según Google Cloud
| Nivel | Nombre | Características |
|---|---|---|
| 0 | Proceso manual | Cada paso se hace a mano; el modelo se entrega a ingeniería para desplegarlo; reentrenar es raro. El salto más valioso suele ser del 0 al 1 |
| 1 | Automatización del pipeline de ML | Un pipeline reentrena con datos nuevos y valida datos y modelos automáticamente (entrenamiento continuo) |
| 2 | Automatización de CI/CD del pipeline | Los cambios al propio pipeline se construyen, prueban y despliegan de forma automática |
MLOps y LLMOps
LLMOps aplica estas ideas a apps con modelos de lenguaje. Cambia lo que se versiona y se vigila:
| MLOps (modelo propio) | LLMOps (app con LLM) | |
|---|---|---|
| Lo que se versiona | Datos, código de entrenamiento y pesos | Prompts, modelo del proveedor, índice de RAG, herramientas |
| Cómo se evalúa | Métricas numéricas con etiquetas | Evals con reglas, LLM como juez y revisión humana (día 14) |
| Por qué se degrada | Deriva de datos y de concepto | Cambio de versión del modelo, documentos desactualizados, nuevos tipos de preguntas |
| Costo principal | Entrenamiento e infraestructura | Tokens por llamada (día 15) |
Caso real: Uber Michelangelo. Uber presentó en 2017 Michelangelo, su plataforma interna de ML, que cubre el flujo completo: gestionar datos, entrenar, evaluar, desplegar, predecir y monitorear. En una publicación del 30 de octubre de 2025, Uber explica que Michelangelo soporta todo el ciclo de vida de ML desde 2016, ejecuta más de 400 casos de uso activos y más de 20,000 entrenamientos al mes, y advierte que un modelo puede ir bien fuera de línea y fallar en condiciones reales por deriva de datos. Por eso usa pruebas en sombra, despliegues que empiezan con una porción pequeña del tráfico y vuelta atrás automática a la última versión buena.
Práctica: el ciclo completo en un script
El script recorre el ciclo con un modelo trivial: una regresión lineal hecha a mano que predice minutos de entrega según distancia y pedidos en cola. Entrena la v1, la registra en un archivo JSON, simula datos recientes con deriva (más viajes largos y más minutos por kilómetro), calcula PSI y KS por variable, detecta la caída del error cuando llegan las etiquetas, reentrena una v2, la compara con datos no vistos y decide si promoverla. Al final prueba como canario una v3 entrenada con un error en el pipeline y vuelve atrás. Todos los datos son ficticios.
1. Guarda el script como mini_mlops.py:
"""MLOps mínimo: entrenar, registrar versiones, detectar deriva (PSI y KS), reentrenar y decidir promover o volver atrás.
Solo biblioteca estándar. Datos FICTICIOS: minutos de entrega según distancia (km) y pedidos en cola.
"""
import json
import math
import random
REGISTRO = "registro_modelos.json"
def datos(n, semilla, km_media, min_por_km):
rnd = random.Random(semilla)
filas = []
for _ in range(n):
km, cola = max(0.5, rnd.gauss(km_media, 2.0)), rnd.randint(0, 8)
filas.append((km, cola, 8 + min_por_km * km + 2.0 * cola + rnd.gauss(0, 3)))
return filas
def entrenar(filas):
"""Regresión lineal y = b0 + b1·km + b2·cola por mínimos cuadrados (ecuaciones normales)."""
X = [(1.0, km, c) for km, c, _ in filas]
A = [[sum(x[i] * x[j] for x in X) for j in range(3)] + [sum(x[i] * f[2] for x, f in zip(X, filas))] for i in range(3)]
for i in range(3): # eliminación de Gauss-Jordan
A[i] = [v / A[i][i] for v in A[i]]
for k in range(3):
if k != i:
A[k] = [a - A[k][i] * b for a, b in zip(A[k], A[i])]
return [round(A[i][3], 3) for i in range(3)]
def mae(coef, filas):
return sum(abs(coef[0] + coef[1] * km + coef[2] * c - y) for km, c, y in filas) / len(filas)
def psi(ref, nuevo, cortes=10):
"""Population Stability Index: compara la distribución de una variable contra la de entrenamiento."""
orden = sorted(ref)
limites = [orden[int(len(orden) * i / cortes)] for i in range(1, cortes)]
def proporciones(v):
cuenta = [0] * cortes
for x in v:
cuenta[sum(x > lim for lim in limites)] += 1
return [max(c / len(v), 1e-4) for c in cuenta]
return sum((n - r) * math.log(n / r) for r, n in zip(proporciones(ref), proporciones(nuevo)))
def ks(a, b):
"""Estadístico D de Kolmogorov-Smirnov (evaluado en 1 de cada 10 puntos) y su valor crítico aproximado al 5 %."""
puntos = sorted(set(a) | set(b))
a, b = sorted(a), sorted(b)
d = max(abs(sum(x <= p for x in a) / len(a) - sum(x <= p for x in b) / len(b)) for p in puntos[::10])
return d, 1.36 * math.sqrt((len(a) + len(b)) / (len(a) * len(b)))
def registrar(reg, version, coef, metrica, origen, estado):
for m in reg["modelos"]:
if estado == "produccion" and m["estado"] == "produccion":
m["estado"] = "archivado"
reg["modelos"].append({"version": version, "coef": coef, "mae_validacion": round(metrica, 2), "datos": origen, "estado": estado})
with open(REGISTRO, "w", encoding="utf-8") as f:
json.dump(reg, f, ensure_ascii=False, indent=1)
if __name__ == "__main__":
reg = {"modelos": []}
historico = datos(2000, 1, km_media=5, min_por_km=3.0) # el mundo de hace un año
v1 = entrenar(historico[:1500])
registrar(reg, 1, v1, mae(v1, historico[1500:]), "histórico", "produccion")
print(f"v1 entrenado: coeficientes {v1} · MAE de validación {mae(v1, historico[1500:]):.2f} min")
recientes = datos(1000, 2, km_media=8, min_por_km=4.2) # más viajes largos y más tráfico
print("\nDeriva de datos (PSI: <0.10 estable · 0.10-0.25 vigilar · >0.25 cambio fuerte):")
for nombre, i in (("km", 0), ("cola", 1)):
ref, nuevo = [f[i] for f in historico], [f[i] for f in recientes]
d, critico = ks(ref, nuevo)
print(f" {nombre:<5} PSI {psi(ref, nuevo):5.2f} · KS D={d:.2f} (crítico {critico:.2f})")
mae_v1 = mae(v1, recientes[:500]) # etiquetas reales que llegan después
alerta = mae_v1 > 1.2 * reg["modelos"][0]["mae_validacion"]
print(f"\nv1 con datos recientes: MAE {mae_v1:.2f} min -> {'ALERTA: el modelo se degradó' if alerta else 'sin cambio'}")
v2 = entrenar(recientes[:500]) # reentrenar con el mundo actual
prueba = recientes[500:] # datos recientes que ningún modelo vio
m1, m2 = mae(v1, prueba), mae(v2, prueba)
print(f"v2 reentrenado: coeficientes {v2}")
print(f"Comparación en datos recientes no vistos: v1 MAE {m1:.2f} · v2 MAE {m2:.2f}")
if m2 < 0.9 * m1:
registrar(reg, 2, v2, m2, "recientes", "produccion")
print("Decisión: PROMOVER v2 (mejora de más de 10 %)")
else:
registrar(reg, 2, v2, m2, "recientes", "rechazado")
print("Decisión: MANTENER v1")
actual = next(m for m in reg["modelos"] if m["estado"] == "produccion")
v3 = entrenar([(km, c, y / 60) for km, c, y in recientes[:500]]) # error en el pipeline: etiquetas en horas
trafico = datos(100, 3, km_media=8, min_por_km=4.2) # 5 % del tráfico va al canario
m_actual, m3 = mae(actual["coef"], trafico), mae(v3, trafico)
print(f"\nCanario v3: MAE {m3:.2f} contra v{actual['version']} MAE {m_actual:.2f}")
if m3 > 1.1 * m_actual:
registrar(reg, 3, v3, m3, "recientes (pipeline con error)", "revertido")
print(f"Decisión: VOLVER ATRÁS; todo el tráfico regresa a v{actual['version']}")
print("\nRegistro:", json.dumps([(m["version"], m["estado"], m["mae_validacion"]) for m in reg["modelos"]], ensure_ascii=False))
2. Ejecútalo: python mini_mlops.py. Salida real (Python 3.12, Windows):
v1 entrenado: coeficientes [8.358, 2.971, 1.981] · MAE de validación 2.25 min
Deriva de datos (PSI: <0.10 estable · 0.10-0.25 vigilar · >0.25 cambio fuerte):
km PSI 1.91 · KS D=0.55 (crítico 0.05)
cola PSI 0.01 · KS D=0.02 (crítico 0.05)
v1 con datos recientes: MAE 9.32 min -> ALERTA: el modelo se degradó
v2 reentrenado: coeficientes [7.614, 4.221, 2.033]
Comparación en datos recientes no vistos: v1 MAE 9.69 · v2 MAE 2.47
Decisión: PROMOVER v2 (mejora de más de 10 %)
Canario v3: MAE 48.13 contra v2 MAE 2.10
Decisión: VOLVER ATRÁS; todo el tráfico regresa a v2
Registro: [[1, "archivado", 2.25], [2, "produccion", 2.47], [3, "revertido", 48.13]]
3. Interpreta el resultado. La v1 aprendió bien su mundo (error medio de 2.25 minutos). Con los datos recientes, el PSI de km (1.91) y su KS muy por encima del valor crítico delatan la deriva de datos; cola sigue estable. Cuando llegan las etiquetas, el error de la v1 sube a 9.32 minutos: hay también deriva de concepto (cada kilómetro tarda más). La v2, entrenada con datos recientes, baja el error a 2.47 en datos que ningún modelo vio y se promueve; la v1 queda archivada en el registro (ábrelo: registro_modelos.json). La v3, con etiquetas en horas por un error del pipeline, falla en el canario y el tráfico regresa a la v2 sin que la mayoría de los usuarios lo note.
Preguntas frecuentes
¿Qué es MLOps en palabras simples?
Es el mantenimiento de un modelo de IA: vigilar que siga acertando, reentrenarlo cuando el mundo cambia y guardar cada versión para poder volver a la anterior.
¿Qué es la deriva de un modelo?
Es cuando los datos o la realidad cambian respecto a lo que el modelo aprendió y sus predicciones empeoran, aunque nadie haya tocado el código.
¿Necesito MLOps si uso ChatGPT o Claude por API?
No entrenas el modelo, pero sí necesitas su equivalente, LLMOps: versionar prompts, correr evaluaciones antes de cambiar de modelo y vigilar costo y calidad.
En el reel lo explicamos así
Una IA que hoy funciona puede fallar en seis meses sin que nadie la toque, porque el mundo cambia: eso es la deriva (drift). Es la diferencia entre una receta que sale bien en tu cocina y producirla en una fábrica con control de calidad. Se vigilan los aciertos y se reentrena con datos nuevos; cada versión se guarda en un registro (model registry) para volver a la anterior si la nueva sale peor. Uber tiene Michelangelo: más de 400 usos activos y más de 20 mil entrenamientos al mes.
Referencias
- Uber, «Raising the Bar on ML Model Deployment Safety» (30 de octubre de 2025): cifras de Michelangelo, deriva, sombra y vuelta atrás.
- Del Balso y Hermann, «Meet Michelangelo: Uber's Machine Learning Platform» (5 de septiembre de 2017).
- Google Cloud, «MLOps: Continuous delivery and automation pipelines in machine learning»: niveles de madurez 0, 1 y 2.
- MLflow, Model Registry: versiones, alias y etiquetas.
- SciPy,
ks_2samp: la prueba de Kolmogorov-Smirnov para dos muestras.
La diferencia entre una receta que sale bien en tu cocina y producirla en una fábrica con control de calidad.
Glosario: del inglés al español
| MLOps | operaciones de aprendizaje automático |
|---|---|
| Machine learning | aprendizaje automático |
| Drift | deriva (el modelo se desfasa) |
| Model registry | registro de versiones del modelo |