Un modelo con un 97 % de precisión que solo existe en tu sesión de Python vale exactamente cero para el negocio. El equipo de TecnoMarket lo tiene claro: el clasificador de reseñas de 04-03 solo aporta valor cuando la web puede consultarlo en vivo, y el clasificador de fotos de 05-03 solo ahorra trabajo cuando el sistema del almacén lo invoca con cada producto nuevo. Esta lección cierra el ciclo que empezó con aquel tímido model.save() de 02-05: aprenderás los formatos de guardado de Keras y PyTorch y cuándo usar cada uno, por qué el preprocesado debe guardarse junto al modelo, las opciones para servir predicciones (de un script batch a una API REST con FastAPI, con ejemplo completo), ONNX como puente entre frameworks, y cómo validar y vigilar un modelo antes y después de desplegarlo. Es la última milla: donde el deep learning se convierte en producto.
Contenido
- Guardar y cargar en Keras: .keras, SavedModel y solo pesos
- Guardar y cargar en PyTorch: state_dict y checkpoints
- El modelo no viaja solo: guardar el preprocesado
- Servir un modelo: opciones en escala
- Una API REST con FastAPI para las reseñas de TecnoMarket
- ONNX: el formato puente
- Validar antes de desplegar y vigilar después
- Checklist de despliegue de TecnoMarket
Guardar y cargar en Keras: .keras, SavedModel y solo pesos
En 02-05 hiciste model.save("tecnomarket-dl/models/mi_red.keras") y seguimos adelante. Profesionalicémoslo: Keras tiene tres formas de guardar, cada una con su caso de uso.
from tensorflow import keras
# 1. Formato .keras (el recomendado): TODO en un fichero
model.save("models/resenas_v3.keras")
modelo_cargado = keras.models.load_model("models/resenas_v3.keras")
# Guarda: arquitectura + pesos + estado del optimizador + compile()
# -> puedes seguir entrenando exactamente donde lo dejaste
# 2. SavedModel (formato de TensorFlow, un directorio completo)
model.export("models/resenas_v3_savedmodel")
# Guarda el grafo de computación autocontenido, SIN necesitar tu código Python
# -> es lo que consumen TF Serving y los conversores (TFLite)
# 3. Solo pesos
model.save_weights("models/resenas_v3.weights.h5")
modelo_nuevo = crear_modelo() # necesitas RECONSTRUIR la arquitectura
modelo_nuevo.load_weights("models/resenas_v3.weights.h5")| Formato | Qué guarda | Cuándo usarlo |
|---|---|---|
.keras |
Arquitectura + pesos + optimizador | Uso general: pausar/reanudar, archivar, compartir con quien tenga Keras |
SavedModel (export) |
Grafo autocontenido para inferencia | Entregar a sistemas de producción TF (Serving, TFLite) |
| Solo pesos | Únicamente los números aprendidos | El código define la arquitectura (transfer learning como en 05-03, checkpoints ligeros) |
Nota práctica: el ModelCheckpoint de 06-01 escribe en formato .keras; ya llevabas media lección aprendida.
Guardar y cargar en PyTorch: state_dict y checkpoints
PyTorch gira en torno al state_dict: un diccionario {nombre_de_parametro: tensor} con todos los pesos del modelo. La forma canónica de guardar es ese diccionario, no el objeto modelo:
import torch
# GUARDAR: la forma recomendada (solo el state_dict)
torch.save(model.state_dict(), "models/resenas_v3.pt")
# CARGAR: reconstruir la arquitectura y volcarle los pesos
model = ClasificadorResenas() # la clase de 06-02
model.load_state_dict(torch.load("models/resenas_v3.pt"))
model.eval() # ¡a modo inferencia! (ver 06-02)¿Por qué no guardar el modelo entero con torch.save(model, ...)? Puede hacerse, pero serializa referencias a tu código (clase, módulo, rutas): al cargarlo meses después con el proyecto reorganizado, se rompe. El state_dict son solo tensores con nombre: robusto y portable. Es el análogo del "solo pesos" de Keras — en PyTorch, la opción por defecto.
Checkpoint completo: pausar y reanudar entrenamientos
Para reanudar un entrenamiento no basta con los pesos: Adam (02-04) mantiene estadísticas internas por parámetro que también hay que restaurar. El patrón profesional:
# Guardar un checkpoint completo al final de cada época
checkpoint = {
"epoch": epoch,
"model_state": model.state_dict(),
"optimizer_state": optimizer.state_dict(), # ¡el estado de Adam también!
"val_loss": val_loss,
}
torch.save(checkpoint, "models/checkpoint_ultimo.pt")
# Reanudar días después, exactamente donde se quedó
ckpt = torch.load("models/checkpoint_ultimo.pt")
model.load_state_dict(ckpt["model_state"])
optimizer.load_state_dict(ckpt["optimizer_state"])
epoca_inicial = ckpt["epoch"] + 1| Necesidad | Keras | PyTorch |
|---|---|---|
| Archivar/compartir modelo listo | .keras |
state_dict + código de la clase |
| Reanudar entrenamiento | .keras (lleva el optimizador) |
checkpoint dict (modelo + optimizador + época) |
| Entregar a producción | SavedModel | state_dict (o exportar a ONNX/TorchScript) |
El modelo no viaja solo: guardar el preprocesado
Aquí está el error que más despliegues rompe, y es conceptual, no técnico. Tu modelo aprendió sobre datos transformados: si en producción recibe datos crudos o transformados de otra manera, sus predicciones serán basura sin dar ningún error. Recuerda dos casos del curso:
- En 04-04, el predictor de demanda entrenó con ventas escaladas por un
scalerajustado sobre el train (con cuidado de no fugar información). Si en producción le pasas euros crudos donde esperaba valores en [0, 1], predirá disparates con total seguridad en sí mismo. - En 04-03, el clasificador de reseñas usa una capa
TextVectorizationcuyo vocabulario se aprendió del corpus de entrenamiento. Otro vocabulario = otros índices = otra "lengua": la red recibiría galimatías.
La regla de oro: el par (preprocesado, modelo) es LA unidad desplegable. Nunca viajan por separado.
# Opción A (Keras): meter el preprocesado DENTRO del modelo antes de guardar
# (TextVectorization es una capa: puede formar parte del grafo)
modelo_completo = keras.Sequential([
keras.Input(shape=(1,), dtype="string"), # ¡entra TEXTO crudo!
vectorizador, # la TextVectorization ya adaptada (04-03)
modelo_entrenado, # la red que espera índices
])
modelo_completo.save("models/resenas_completo.keras")
# En producción: modelo.predict(["El cargador llego roto"]) -> directo del texto
# Opción B (general, válida también para PyTorch): guardar el transformador aparte
import joblib
joblib.dump(scaler, "models/demanda_scaler.joblib") # el scaler de 04-04
# ...y en producción, SIEMPRE en pareja:
scaler = joblib.load("models/demanda_scaler.joblib")
x = scaler.transform(datos_crudos) # mismo scaler, mismos parámetros
prediccion = model.predict(x)La opción A es la más segura (imposible desincronizar); la B es la más flexible. Elijas cual elijas: mismos ficheros versionados juntos, con el mismo nombre de versión.
Servir un modelo: opciones en escala
"Desplegar" no significa siempre "montar un servidor". Hay una escala de opciones, de menor a mayor complejidad, y lo profesional es elegir la mínima que cubra la necesidad:
| Opción | Cómo funciona | Caso de TecnoMarket |
|---|---|---|
| Script batch | Un script se ejecuta periódicamente, predice sobre lo acumulado y guarda resultados | Predicción de demanda semanal (04-04): no hace falta tiempo real |
| API REST | Un servidor expone el modelo por HTTP; otras aplicaciones piden predicciones al momento | Clasificar cada reseña al publicarse (04-03) |
| Servidor de modelos (TF Serving / TorchServe) | Software especializado: versiones conviviendo, lotes automáticos, alto rendimiento | Cuando las peticiones/segundo desborden la API artesanal |
| Edge (TFLite) | El modelo, comprimido, corre EN el dispositivo, sin red | El clasificador de fotos en la app del almacén (plan de 06-03) |
Un script batch mínimo (a veces la mejor arquitectura es la más aburrida):
# predecir_demanda_semanal.py -- se programa para ejecutarse cada lunes
import joblib
from tensorflow import keras
model = keras.models.load_model("models/demanda_v2.keras")
scaler = joblib.load("models/demanda_scaler_v2.joblib") # SIEMPRE en pareja
ventas = cargar_ventas_ultimas_semanas() # de la base de datos
pred = model.predict(scaler.transform(ventas))
guardar_predicciones(scaler_inverso(pred)) # a la tabla que lee comprasTF Serving y TorchServe quedan mencionados en tu radar: son la evolución natural cuando la escala apriete, y consumen precisamente los formatos "de producción" de la primera parte (SavedModel y state_dict empaquetado).
Una API REST con FastAPI para las reseñas de TecnoMarket
Vamos a servir el clasificador de reseñas en vivo. FastAPI es un framework web de Python moderno y minimalista, ideal para envolver un modelo. Instalación: pip install fastapi uvicorn.
# api_resenas.py
from fastapi import FastAPI
from pydantic import BaseModel
from tensorflow import keras
app = FastAPI(title="TecnoMarket - Analisis de resenas")
# 1. Cargar el modelo UNA vez, al arrancar el servidor (no en cada peticion)
# Es el modelo "completo" con TextVectorization dentro (opcion A de antes)
model = keras.models.load_model("models/resenas_completo.keras")
UMBRAL_REVISION = 0.65 # el umbral de confianza del pipeline de 03-04
class Resena(BaseModel): # 2. contrato de entrada: JSON con un campo "texto"
texto: str
@app.post("/clasificar") # 3. endpoint: POST /clasificar
def clasificar(resena: Resena):
prob = float(model.predict([resena.texto], verbose=0)[0][0]) # 4. inferencia
sentimiento = "positiva" if prob >= 0.5 else "negativa"
confianza = max(prob, 1 - prob)
return { # 5. respuesta JSON para la web/CRM
"sentimiento": sentimiento,
"confianza": round(confianza, 3),
"revision_humana": confianza < UMBRAL_REVISION, # cola de revision (03-04)
"version_modelo": "resenas_v3",
}Explicación de las decisiones:
- Cargar al arrancar: cargar el modelo tarda segundos; una predicción, milisegundos. Cargarlo en cada petición mataría la API. La carga va a nivel de módulo.
- Pydantic (
BaseModel) define y valida la entrada: si llega un JSON sintexto, FastAPI devuelve un error claro automáticamente, sin que el modelo vea nada. - El endpoint es
POSTporque envía datos en el cuerpo de la petición. - Gracias a la opción A (vectorizador dentro), la API recibe texto crudo: cero riesgo de desincronizar preprocesados.
- La respuesta incluye la versión del modelo (trazabilidad) y el campo
revision_humana: el mismo patrón de umbral de confianza + cola de revisión que diseñamos en 03-04, ahora en producción.
Arrancar y probar:
uvicorn api_resenas:app --port 8000
# Documentacion interactiva automatica: http://localhost:8000/docs
curl -X POST http://localhost:8000/clasificar \
-H "Content-Type: application/json" \
-d '{"texto": "El cargador llego roto y nadie responde"}'
# {"sentimiento":"negativa","confianza":0.94,"revision_humana":false,"version_modelo":"resenas_v3"}Con esto, cualquier sistema de TecnoMarket (la web, el CRM de atención al cliente) consume el modelo con una simple petición HTTP, sin saber nada de deep learning.
ONNX: el formato puente
En 06-03 dejamos anotado ONNX (Open Neural Network Exchange): un formato estándar de intercambio de modelos. La idea: entrenas donde quieras, exportas a .onnx, y ejecutas con ONNX Runtime en casi cualquier sitio (Python, C#, Java, móvil...), sin instalar el framework de origen.
# Exportar el modelo PyTorch de resenas (06-02) a ONNX
import torch
model.eval()
ejemplo = torch.randn(1, 200) # entrada de ejemplo (define shapes)
torch.onnx.export(model, ejemplo, "models/resenas.onnx",
input_names=["resena"], output_names=["logit"])
# Ejecutarlo SIN PyTorch, solo con onnxruntime (pip install onnxruntime)
import onnxruntime as ort
import numpy as np
sesion = ort.InferenceSession("models/resenas.onnx")
salida = sesion.run(None, {"resena": np.random.rand(1, 200).astype("float32")})
print(salida[0]) # el logit, identico al que daria el modelo originalCuándo interesa: entregar un modelo a un equipo que trabaja en otro lenguaje o framework, unificar la inferencia de modelos de orígenes distintos (justo el caso de TecnoMarket tras la decisión de 06-03: Keras en producción, PyTorch en exploración), o aprovechar las optimizaciones de inferencia de ONNX Runtime. Buenas prácticas mínimas: tras exportar, comparar las salidas del modelo original y el ONNX sobre un lote de prueba (deben coincidir salvo decimales) — y recuerda que el preprocesado externo sigue viajando aparte, también aquí.
Validar antes de desplegar y vigilar después
Desplegar no es un acto de fe; es un proceso con dos guardias:
Antes: validación sobre un conjunto congelado. El equipo mantiene un conjunto de test fijo y versionado (reseñas reales etiquetadas a mano, nunca usadas para entrenar — la disciplina anti-fuga de 04-04 elevada a norma de equipo). Todo candidato a producción, sea Keras o PyTorch (la regla acordada en 06-03), debe:
- Igualar o superar en ese conjunto las métricas del modelo actual en producción.
- Comprobarse también en los segmentos críticos (reseñas cortas, productos nuevos): un modelo mejor "en media" puede ser peor justo donde más duele.
- Pasar una prueba de humo de la unidad desplegable completa: levantar la API con el artefacto final y verificar predicciones conocidas de extremo a extremo.
Después: monitorizar la deriva (drift). El mundo cambia y los datos de producción se alejan poco a poco de los de entrenamiento: TecnoMarket lanza categorías nuevas, el vocabulario de las reseñas evoluciona ("no me funciona el emparejamiento BLE" no existía en el corpus original). El rendimiento decae en silencio: no hay excepción ni log de error, solo predicciones cada vez peores. Vigilancia conceptual mínima:
- Registrar cada predicción con su confianza y la versión del modelo (por eso la API la devuelve).
- Vigilar señales indirectas: % de casos enviados a revisión humana subiendo, distribución de confianzas desplazándose, y las correcciones de los revisores humanos como muestreo continuo de la verdad.
- Definir el umbral de acción de antemano: "si X empeora más de Y durante Z semanas, se reentrena con datos recientes" — y el reentrenamiento vuelve a pasar por el conjunto congelado. El ciclo se cierra.
Checklist de despliegue de TecnoMarket
La lista que el equipo repasa antes de cada puesta en producción:
- Modelo guardado en formato correcto (
.keras/ state_dict) con nombre de versión (resenas_v3), nomodelo_final_BUENO. - Preprocesado guardado y versionado junto al modelo (dentro del grafo o como artefacto pareja).
- Métricas sobre el conjunto congelado ≥ modelo actual, incluidos los segmentos críticos.
- Semillas, config y versiones de librerías registradas (la reproducibilidad de 06-04): se puede reconstruir este modelo desde cero.
- Prueba de humo de la unidad desplegable: API levantada en local, predicciones conocidas verificadas de extremo a extremo.
- La respuesta de la API incluye versión del modelo y señal de confianza; los casos dudosos van a la cola de revisión humana (03-04).
- Monitorización activa: logging de predicciones y alertas de deriva definidas antes del lanzamiento, no después del primer susto.
- Plan de vuelta atrás: la versión anterior (
resenas_v2) queda archivada y lista para restaurar en minutos.
Errores Comunes y Consejos
- Desplegar el modelo sin su preprocesado (o con otra versión de este): el fallo silencioso número uno. No hay error, solo predicciones absurdas con confianza alta. La unidad desplegable es el par, siempre.
torch.save(model, ...)del objeto entero: funciona hoy, se rompe cuando refactorizas el proyecto. State_dict + código de la clase, como norma.- Olvidar
model.eval()al cargar en PyTorch para inferencia: con dropout activo, cada petición a tu API daría una predicción distinta para la misma reseña (lo viste en 06-02; en producción es un bug desconcertante). - Cargar el modelo dentro del endpoint: cada petición tardaría segundos y la API se arrastraría. Cargar una vez al arrancar; predecir muchas.
- Validar sobre un test que cambia: si cada candidato se mide contra datos distintos, las comparaciones no significan nada. Conjunto congelado y versionado.
- Confiar en que "si no hay errores, funciona": la degradación por deriva no lanza excepciones. Sin monitorización, tu modelo puede llevar meses equivocándose cuando alguien lo note.
- Consejo: tras cada
save, haz el ciclo completo en un intérprete limpio: cargar, predecir sobre 3 ejemplos conocidos, comparar con los valores esperados. Treinta segundos que detectan el 90 % de los problemas de serialización.
Ejercicios
Ejercicio 1: elegir formato
Para cada situación, indica el mecanismo de guardado adecuado y por qué: (a) el entrenamiento PyTorch del módulo 7 durará tres noches en Colab y debe poder reanudarse cada mañana; (b) hay que entregar el clasificador Keras de fotos al equipo de la plataforma para servirlo con TF Serving; (c) quieres archivar el clasificador de reseñas Keras para retomarlo (incluso reentrenarlo) dentro de seis meses; (d) el equipo backend, que programa en C# y no quiere instalar PyTorch, necesita ejecutar el modelo de fraude.
Ejercicio 2: el despliegue que predice disparates
El predictor de demanda de 04-04 se despliega como script batch. En pruebas daba error medio del 8 %; en producción predice cantidades absurdas (miles de unidades de un producto que vende decenas), sin ningún error en los logs. El script carga demanda_v2.keras correctamente y la base de datos entrega bien las ventas. ¿Cuál es la causa más probable, por qué no aparece ningún error, y qué dos cambios harías para arreglarlo y para que no vuelva a pasar?
Ejercicio 3: ampliar la API
Amplía api_resenas.py con un endpoint POST /clasificar_lote que reciba una lista de reseñas (hasta 100) y devuelva una lista de resultados, aprovechando que el modelo predice más eficientemente por lotes que de una en una. Añade también un endpoint GET /salud que devuelva {"estado": "ok", "version_modelo": "resenas_v3"} y explica para qué sirve en producción.
Soluciones
Solución 1:
- (a) Checkpoint completo PyTorch (dict con
model_state,optimizer_statey época) guardado en Drive cada época: reanudar exige el estado de Adam, no solo pesos. - (b) SavedModel (
model.export(...)): es el formato autocontenido que TF Serving consume directamente. - (c)
.keras: un solo fichero con arquitectura, pesos y optimizador; en seis meses,load_modely seguir (reentrenamiento incluido). - (d) ONNX: exportar con
torch.onnx.exporty entregar el.onnx; el backend lo ejecuta con ONNX Runtime para C#, sin PyTorch. (Verificando antes que las salidas coinciden con el original.)
Solución 2: Causa más probable: el script aplica mal (o no aplica) el scaler — usa uno reajustado sobre otros datos, una versión desincronizada, o pasa ventas crudas al modelo que esperaba valores escalados; también encaja olvidar la transformación inversa de la salida. No hay error porque las shapes y dtypes son válidos: el modelo opera con números "legales" pero en la escala equivocada — el fallo silencioso clásico. Arreglos: (1) desplegar el par como unidad — cargar demanda_scaler_v2.joblib versionado junto al modelo, mismo sufijo de versión, y aplicar transform a la entrada e inversa a la salida; (2) añadir una prueba de humo al checklist: tras cada despliegue, predecir sobre 3 casos históricos conocidos y comparar con los valores esperados antes de escribir nada en la tabla de compras (más una validación de rangos: si la predicción sale del rango histórico razonable, alerta y no publicar).
Solución 3:
from typing import List
from pydantic import BaseModel, Field
class LoteResenas(BaseModel):
textos: List[str] = Field(..., max_length=100) # valida el limite de 100
@app.post("/clasificar_lote")
def clasificar_lote(lote: LoteResenas):
probs = model.predict(lote.textos, verbose=0)[:, 0] # UNA pasada por lotes
resultados = []
for texto, prob in zip(lote.textos, probs):
prob = float(prob)
confianza = max(prob, 1 - prob)
resultados.append({
"sentimiento": "positiva" if prob >= 0.5 else "negativa",
"confianza": round(confianza, 3),
"revision_humana": confianza < UMBRAL_REVISION,
})
return {"version_modelo": "resenas_v3", "resultados": resultados}
@app.get("/salud")
def salud():
return {"estado": "ok", "version_modelo": "resenas_v3"}La clave de eficiencia: una sola llamada a model.predict con las 100 reseñas (el paralelismo por lotes de todo el curso), no 100 llamadas. El endpoint /salud (health check) permite a la infraestructura comprobar cada pocos segundos que la API vive y qué versión sirve: si deja de responder, se reinicia o se avisa automáticamente — y durante un despliegue confirma que la versión nueva está realmente en el aire.
Conclusión
Se cierra el círculo que abrió el model.save() de 02-05. Ahora sabes guardar con criterio (.keras/SavedModel/pesos en Keras; state_dict y checkpoints en PyTorch), sabes que la unidad desplegable es el par preprocesado + modelo (el scaler de 04-04 y el vocabulario de 04-03 viajan siempre con su red), sabes elegir la forma de servir según la necesidad (batch → FastAPI → servidores de modelos → edge), tienes a ONNX como puente entre mundos, y entiendes que desplegar exige validar contra un conjunto congelado antes y vigilar la deriva después — todo condensado en el checklist de TecnoMarket.
Y con esto, el módulo 6 queda completo: conoces TensorFlow por dentro, hablas PyTorch, sabes elegir entre ellos, trabajas con entorno y disciplina profesionales, y llevas modelos hasta producción. Técnicas (módulos 2-5) y herramientas (módulo 6): la caja está llena. En el módulo 7 la vaciaremos entera: construiremos de principio a fin los proyectos completos de TecnoMarket — clasificación de imágenes, generación de texto, detección de anomalías, una GAN y el fine-tuning de un modelo preentrenado. Deja de ser un curso; empieza a ser un portafolio.
Curso de Deep Learning
Módulo 1: Introducción a Deep Learning
- ¿Qué es Deep Learning?
- Historia y evolución del Deep Learning
- Aplicaciones de Deep Learning
- Conceptos básicos de redes neuronales
- Preparación del entorno de trabajo
Módulo 2: Fundamentos de Redes Neuronales
- Perceptrón y Perceptrón Multicapa
- Función de activación
- Propagación hacia adelante y hacia atrás
- Optimización y función de pérdida
- Tu primera red neuronal completa
Módulo 3: Redes Neuronales Convolucionales (CNN)
- Introducción a las CNN
- Capas convolucionales y de pooling
- Arquitecturas populares de CNN
- Aplicaciones de CNN en reconocimiento de imágenes
Módulo 4: Redes Neuronales Recurrentes (RNN)
- Introducción a las RNN
- LSTM y GRU
- Aplicaciones de RNN en procesamiento del lenguaje natural
- Secuencias y series temporales
Módulo 5: Técnicas Avanzadas en Deep Learning
- Redes Generativas Adversariales (GAN)
- Autoencoders
- Transfer Learning
- Regularización y técnicas de mejora
- Mecanismos de atención y Transformers
Módulo 6: Herramientas y Frameworks
- Introducción a TensorFlow
- Introducción a PyTorch
- Comparación de frameworks
- Entornos de desarrollo y recursos adicionales
- Guardar, cargar y desplegar modelos
Módulo 7: Proyectos Prácticos
- Clasificación de imágenes con CNN
- Generación de texto con RNN
- Detección de anomalías con Autoencoders
- Creación de una GAN para generación de imágenes
- Fine-tuning de un modelo preentrenado
