Con Python elegido (07-01) y NumPy, pandas y Matplotlib en la mano (07-02), el taller de Marta necesita el resto de la estantería: las librerías con las que se hace cada tarea de las que hemos recorrido en el curso. Algunas ya las hemos usado a fondo (scikit-learn en el módulo 4, PyTorch en el 5), otras las hemos citado sin instalar (Hugging Face, experta, pgmpy, OR-Tools) y otras aparecen por primera vez porque pertenecen a lo que viene después de entrenar: guardar el modelo, servirlo, seguir sus versiones. Esta lección dibuja el mapa del ecosistema por tarea (ML clásico, deep learning, PLN, visión, IA generativa, lógica y probabilidad, optimización, experimentación, despliegue, datos a escala), muestra el mismo MLP 21→16→8→1 de 05-02 escrito en PyTorch y en Keras lado a lado para que veas que las ideas son las mismas con distinta sintaxis, da criterios para elegir librería, y ejecuta lo que enlaza el entrenamiento con el despliegue: guardar y recargar el pipeline de scikit-learn con joblib y el MLP con state_dict, comprobando que predicen exactamente lo mismo. Cierra con la caja de herramientas de NovaMarket: qué librería para cada uno de los nueve casos de uso. Es importante porque el ecosistema es enorme y cambia deprisa; lo que no cambia es el mapa de tareas y los criterios. Cómo se instalan estas librerías, en qué entorno y con qué versiones es asunto de 07-04.

Contenido

  1. Mapa del ecosistema por tarea
  2. ML clásico: scikit-learn y las librerías de boosting
  3. Deep learning: PyTorch, TensorFlow/Keras y JAX
  4. El mismo MLP en PyTorch y en Keras, lado a lado
  5. PLN y modelos preentrenados: Hugging Face, spaCy, NLTK
  6. Visión: OpenCV, torchvision, Pillow
  7. IA generativa y LLM: SDK, Ollama, LangChain y LlamaIndex
  8. Lógica, probabilidad y optimización
  9. Experimentación, MLOps y datos a escala
  10. Despliegue: FastAPI y ONNX Runtime
  11. Criterios para elegir una librería
  12. Código: guardar y recargar el pipeline y el MLP
  13. La caja de herramientas de NovaMarket
  14. Errores Comunes y Consejos
  15. Ejercicios
  16. Conclusión

  1. Mapa del ecosistema por tarea

Todo se apoya en la capa de 07-02 y se organiza por lo que quieres hacer, no por moda:

flowchart TB
    subgraph BASE["Base: 07-02"]
        NP[NumPy] --- PD[pandas] --- MPL[Matplotlib / seaborn]
    end
    subgraph APRENDER["Aprender de datos"]
        SK["scikit-learn<br/>ML clásico"]
        XGB[XGBoost / LightGBM / CatBoost<br/>boosting]
        PT[PyTorch]
        TF[TensorFlow / Keras]
        JAX[JAX]
    end
    subgraph DOMINIO["Por dominio"]
        HF["Hugging Face transformers / datasets<br/>spaCy, NLTK: texto"]
        CV["OpenCV, torchvision, Pillow: imagen"]
        LLM[SDK de LLM, Ollama<br/>LangChain, LlamaIndex]
        LOG["experta, pgmpy<br/>Drools, CLIPS: reglas y probabilidad"]
        OPT["OR-Tools, PuLP: optimización"]
    end
    subgraph OPERAR["Operar"]
        MLF["MLflow, W&B, DVC: experimentos"]
        API["FastAPI, ONNX Runtime: servir"]
        BIG["Spark, Dask, Polars: escala"]
    end
    BASE --> APRENDER --> DOMINIO --> OPERAR
    PT --> HF
    PT --> CV
Tarea Librerías principales Dónde la vimos
ML clásico (tablas) scikit-learn; XGBoost, LightGBM, CatBoost Módulo 4
Deep learning PyTorch; TensorFlow/Keras; JAX Módulo 5
Texto y modelos preentrenados Hugging Face transformers/datasets, spaCy, NLTK 05-05 (reseñas, caso 4)
Imagen OpenCV, torchvision, Pillow 05-04 (fotos de incidencias)
IA generativa y LLM SDK de proveedores, Ollama, LangChain, LlamaIndex 05-05 (asistente, caso 7)
Reglas y probabilidad experta, pgmpy; Drools, CLIPS Módulo 6 (casos 8 y 9)
Optimización OR-Tools, PuLP, SciPy 03-04 (rutas y asignación, casos 5 y 6)
Experimentos y MLOps MLflow, Weights & Biases, DVC Nuevo (mención)
Despliegue FastAPI, ONNX Runtime 07-01 (entrenar en Python, servir en X)
Datos a escala Spark, Dask, Polars Nuevo (mención)

  1. ML clásico: scikit-learn y las librerías de boosting

scikit-learn es la librería del módulo 4 y la más importante que aprender bien, porque su API uniforme la han copiado casi todas las demás:

  • Todo objeto es un estimador con fit(X, y); los predictores añaden predict y predict_proba; los transformadores añaden transform (y fit_transform). Da igual que sea LogisticRegression, RandomForestClassifier, StandardScaler o OneHotEncoder: misma forma.
  • El estado aprendido se guarda en atributos con guion bajo final: esc = StandardScaler().fit([[1.0], [2.0], [3.0]]) deja esc.mean_ = [2.] y esc.scale_ = [0.816], y esc.transform([[4.0]]) devuelve [[2.449]]. Por eso un transformador ajustado con entrenamiento aplica las mismas medias en producción (04-03).
  • Pipeline encadena transformadores y un predictor final; ColumnTransformer aplica distintos transformadores a distintas columnas (nuestras 21 columnas de 04-03); GridSearchCV/RandomizedSearchCV y cross_val_score seleccionan modelos (04-06); sklearn.metrics mide (04-05). El pipeline entero se guarda y se sirve como una pieza (sección 12).

Lo que scikit-learn no cubre bien es el gradient boosting de máximo rendimiento sobre tablas grandes, y ahí están tres librerías con API compatible (fit/predict, y se pueden meter en un Pipeline): XGBoost (la clásica de las competiciones), LightGBM (más rápida y ligera en memoria, de Microsoft) y CatBoost (maneja categóricas sin one-hot, de Yandex). Para el predictor de devoluciones de NovaMarket, tras la regresión logística (AUC 0,844) y el bosque de 04-04, un boosting sería el candidato natural para arañar unas centésimas; scikit-learn incluye HistGradientBoostingClassifier, una implementación propia del mismo tipo que basta para empezar sin instalar nada. Ninguna de las tres está en el entorno del curso; su código es idéntico en forma:

# Ilustrativo (XGBoost no está instalado en el entorno del curso): la misma API que scikit-learn
from xgboost import XGBClassifier
modelo = XGBClassifier(n_estimators=300, max_depth=4, learning_rate=0.05)
modelo.fit(X_train, y_train)
prob = modelo.predict_proba(X_test)[:, 1]

  1. Deep learning: PyTorch, TensorFlow/Keras y JAX

PyTorch es la librería del módulo 5 y hoy la dominante en investigación y en la mayoría de proyectos nuevos. Sus cuatro piezas, que ya has manejado:

  • Tensores: arrays con dtype, forma y broadcasting como NumPy (07-02), que pueden vivir en GPU (.to("cuda")).
  • Autograd: si un tensor tiene requires_grad=True, PyTorch anota cada operación y backward() calcula los gradientes (05-03). Ejemplo mínimo ejecutable: x = torch.tensor(2.0, requires_grad=True); y = x**2 + 3*x; y.backward(); x.grad da tensor(7.), la derivada 2x + 3 en x = 2.
  • nn.Module: la clase base de capas y redes. nn.Sequential (05-02) es la forma rápida; para arquitecturas con ramas se hereda de nn.Module y se define forward.
  • Dataset y DataLoader: sirven los datos en lotes barajados (DataLoader(TensorDataset(X, y), batch_size=32, shuffle=True) produce lotes de forma (32, 21); con 100 filas salen 4 lotes).

TensorFlow (Google) fue el estándar de 2016-2019 y sigue muy presente en producción y en móviles (TensorFlow Lite); su API de alto nivel es Keras, que desde su versión 3 puede ejecutar sobre TensorFlow, PyTorch o JAX. Keras es la más amable para empezar (Sequential, compile, fit) y esconde el bucle de entrenamiento que en PyTorch escribes a mano. JAX (Google) es NumPy con diferenciación automática y compilación XLA, favorito en investigación de gran escala; sobre él viven Flax y Optax. La elección práctica hoy: PyTorch por comunidad, ejemplos y modelos preentrenados; Keras si valoras la concisión o vienes de TensorFlow; JAX para investigación numérica avanzada.

  1. El mismo MLP en PyTorch y en Keras, lado a lado

La red 21→16→8→1 de 05-02 (497 parámetros) escrita en las dos librerías. La versión PyTorch se ejecuta en el entorno del curso; la de Keras es ilustrativa (misma arquitectura, sin salidas inventadas):

# PyTorch (ejecutable): la arquitectura, y el bucle de entrenamiento explícito
import torch, torch.nn as nn

class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.capas = nn.Sequential(nn.Linear(21, 16), nn.ReLU(),
                                   nn.Linear(16, 8), nn.ReLU(),
                                   nn.Linear(8, 1))          # logit; la sigmoide va en la pérdida
    def forward(self, x):
        return self.capas(x)

red = MLP()
print(sum(p.numel() for p in red.parameters()))            # 497
optimizador = torch.optim.Adam(red.parameters(), lr=0.001)
perdida = nn.BCEWithLogitsLoss()
# for xb, yb in cargador:                                   # el bucle de 05-03
#     optimizador.zero_grad(); L = perdida(red(xb), yb); L.backward(); optimizador.step()
# Keras (ilustrativo, no instalado en el entorno del curso): misma arquitectura, bucle escondido en fit
from tensorflow import keras
from keras import layers

red = keras.Sequential([
    layers.Input(shape=(21,)),
    layers.Dense(16, activation="relu"),
    layers.Dense(8, activation="relu"),
    layers.Dense(1, activation="sigmoid"),                   # aquí sí: la sigmoide en la capa
])
red.summary()                                                # tabla de capas y parámetros (497)
red.compile(optimizer=keras.optimizers.Adam(learning_rate=0.001),
            loss="binary_crossentropy", metrics=["AUC"])
# red.fit(X_train, y_train, epochs=100, batch_size=64, validation_split=0.2)
# red.predict(X_test)

Correspondencias: nn.Linearlayers.Dense; la activación en PyTorch es un módulo aparte y en Keras un argumento; en PyTorch la sigmoide la aplica BCEWithLogitsLoss por estabilidad numérica y en Keras suele ir en la capa con binary_crossentropy; el bucle zero_grad / backward / step de 05-03 es lo que compile + fit hacen por ti. Si entiendes una, lees la otra en diez minutos: la dificultad del deep learning está en 05-03, no en la sintaxis.

  1. PLN y modelos preentrenados: Hugging Face, spaCy, NLTK

  • Hugging Face transformers es la puerta a los modelos preentrenados de 05-05: miles de modelos (BERT, RoBERTa, T5, Llama, Whisper...) que se descargan por nombre y se usan con pipeline en tres líneas, o se afinan (fine-tuning) con Trainer. datasets carga conjuntos de datos públicos con el mismo estilo; tokenizers hace la tokenización rápida (escrita en Rust, 07-01). El Hub de Hugging Face es el repositorio donde se comparten modelos y datos, con licencias y fichas de modelo (model cards, recuerda 02-04).
  • spaCy: PLN "industrial": tokenización, lematización, entidades nombradas, análisis sintáctico, con modelos por idioma (incluido español) y velocidad de producción. Ideal para extraer de las reseñas de NovaMarket qué producto y qué componente se menciona.
  • NLTK: la librería académica clásica (corpus, stemming, n-gramas); útil para aprender, menos para producir.
# Ilustrativo (transformers no está instalado en el entorno del curso): clasificar reseñas (caso 4)
from transformers import pipeline
clasificador = pipeline("sentiment-analysis",
                        model="nlptown/bert-base-multilingual-uncased-sentiment")   # modelo multilingüe de 1-5 estrellas
clasificador(["Los auriculares NovaSound suenan de maravilla, muy cómodos.",
              "La cafetera NovaBrew llegó con la jarra rota y nadie responde."])
# devuelve una lista de diccionarios con 'label' (estrellas) y 'score' (confianza); no reproducimos cifras aquí

Detrás de pipeline está la misma cadena de 05-05: tokenizador, modelo transformer en PyTorch, capa de clasificación. Y detrás de todo, tensores.

  1. Visión: OpenCV, torchvision, Pillow

  • Pillow (PIL): abrir, redimensionar, recortar, convertir imágenes. Lo básico.
  • OpenCV (cv2): la librería de visión por computador clásica, escrita en C++: filtros, detección de bordes, contornos, calibración de cámaras, vídeo en tiempo real. Preprocesa las fotos de incidencias antes de que las vea una CNN, o resuelve por sí sola tareas simples (leer un código de barras, medir una caja).
  • torchvision: modelos preentrenados de visión (ResNet, EfficientNet, ViT...), transformaciones de aumento de datos y conjuntos de datos estándar, integrados con PyTorch. La CNN de 05-04 sobre fotos sintéticas 16×16 (98,7 %) se convertiría en producción en un modelo de torchvision afinado sobre fotos reales de los almacenes de Zaragoza y Getafe.
  • Encima: Ultralytics (YOLO) para detección de objetos, timm para más arquitecturas, albumentations para aumentos.

  1. IA generativa y LLM: SDK, Ollama, LangChain y LlamaIndex

Para el asistente de NovaMarket (caso 7) y las llamadas a LLM con RAG que esbozamos en 05-05, la caja tiene tres estantes, y conviene mirarlos con tono neutral porque es el rincón que más rápido cambia:

  • SDK de proveedores: cada proveedor de LLM ofrece una librería Python que envía mensajes a su API y devuelve texto (o llamadas a herramientas, o embeddings). El patrón es siempre el mismo: cliente, lista de mensajes con roles, respuesta. Su coste es por token y sus datos salen de la empresa: revisa 02-04 y el RGPD antes de enviar reseñas o datos de clientes.
  • Modelos locales: Ollama (y llama.cpp, vLLM para servir a escala) ejecutan modelos abiertos en tu máquina o servidor, con una API compatible con la de los proveedores. Menos capacidad, más privacidad, coste fijo: el argumento que Diego querrá oír para el asistente interno.
  • Orquestación: LangChain y LlamaIndex encadenan pasos (recuperar documentos, construir el prompt, llamar al modelo, analizar la respuesta, llamar a herramientas): son la manera estándar de montar el RAG de 05-05 sobre la base de conocimiento de NovaMarket (políticas de devolución, fichas de producto, incidencias resueltas) y agentes que ejecutan acciones. Se apoyan en bases de datos vectoriales (FAISS, Chroma, pgvector...) para buscar por similitud de embeddings. Son útiles, cambian de API con frecuencia y añaden una capa que a veces conviene sustituir por cien líneas propias cuando el flujo es simple.

  1. Lógica, probabilidad y optimización

  • Reglas: en 06-02 escribimos el motor Regla/MotorInferencia en Python puro. Sus equivalentes con más músculo: experta (motor de reglas en Python heredero de CLIPS, con hechos y reglas como clases decoradas), CLIPS (el motor clásico en C, con enlaces a Python) y Drools (Java, el estándar empresarial, con tablas de decisión DMN como las de 06-04). Para las devoluciones (caso 8) el motor propio basta mientras las reglas sean decenas; a partir de cientos, y con usuarios de negocio que quieran editarlas, un BRMS.
  • Probabilidad: pgmpy implementa las redes bayesianas de 06-03 con inferencia eficiente (eliminación de variables, no enumeración) y aprendizaje de parámetros desde datos; pomegranate y PyMC (inferencia bayesiana general) son alternativas. Para el diagnóstico de incidencias (caso 9), pgmpy es el sustituto natural de nuestra red por enumeración.
  • Optimización: OR-Tools (Google) resuelve rutas de vehículos, asignación y planificación con restricciones (los casos 5 y 6 de 03-04, donde usamos búsqueda local propia); PuLP y SciPy optimize cubren programación lineal y optimización numérica. Para la furgoneta de NovaMarket, OR-Tools daría en segundos rutas que nuestra búsqueda local de 03-04 solo aproximaba.

  1. Experimentación, MLOps y datos a escala

Cuando pasas de un notebook a un proyecto (07-04 y módulo 8), aparecen preguntas nuevas: ¿qué hiperparámetros dieron AUC 0,844? ¿Con qué datos? ¿Qué modelo está en producción? Herramientas, solo para situarlas:

  • MLflow: registra parámetros, métricas y artefactos de cada ejecución (mlflow.log_param, log_metric, log_model) y tiene un registro de modelos con versiones y etapas; se instala en local. Weights & Biases (W&B) hace lo mismo como servicio con paneles ricos. DVC versiona datos y modelos grandes junto a Git (07-04).
  • Datos a escala: cuando pedidos.csv son 3.000 pedidos/día × años y no cabe en memoria, Polars (DataFrame en Rust, API parecida a pandas, mucho más rápido en una máquina), Dask (pandas distribuido) y Spark/PySpark (el estándar en clusters, 07-01). La regla: pandas hasta unos pocos gigabytes; después, Polars en una máquina; Spark cuando hay cluster y equipo de datos.

  1. Despliegue: FastAPI y ONNX Runtime

Servir el predictor de devoluciones significa que la web de NovaMarket envíe los datos de un pedido y reciba una probabilidad. FastAPI es la librería Python más usada para ello: defines la forma de la petición con Pydantic, escribes una función, y obtienes una API HTTP con documentación automática. Ejemplo ilustrativo (FastAPI no está instalado en el entorno del curso; el modelo lo cargamos con joblib como en la sección 12):

# Ilustrativo: servidor de predicción de devoluciones (fichero servir.py; se lanza con `uvicorn servir:app`)
from fastapi import FastAPI
from pydantic import BaseModel
import joblib, pandas as pd

app = FastAPI(title="NovaMarket - riesgo de devolución")
modelo = joblib.load("models/modelo_devoluciones.joblib")   # se carga UNA vez al arrancar

class Pedido(BaseModel):                                     # esquema de entrada, validado automáticamente
    importe: float
    num_articulos: int
    dias_entrega: float
    cliente_nuevo: int
    categoria: str
    codigo_postal_zona: str
    metodo_pago: str
    tipo_envio: str
    dia_semana: int
    mes: int
    fin_de_semana: int
    dias_desde_inicio: int
    importe_por_articulo: float
    pedidos_previos: int
    tasa_devolucion_previa: float

@app.post("/riesgo-devolucion")
def riesgo(pedido: Pedido):
    X = pd.DataFrame([pedido.model_dump()])                  # una fila con las mismas 15 columnas del entrenamiento
    prob = float(modelo.predict_proba(X)[0, 1])
    return {"probabilidad_devolucion": round(prob, 3),
            "revisar": prob > 0.5}                           # el umbral de 04-05, ajustable

Una petición POST /riesgo-devolucion con un JSON de pedido devuelve algo como {"probabilidad_devolucion": 0.048, "revisar": false}. ONNX Runtime es la alternativa cuando el consumidor no es Python (07-01): se exporta el modelo a .onnx (con torch.onnx.export o skl2onnx) y se ejecuta desde Java, C++ o el navegador. Otras piezas del despliegue, para el mapa: BentoML y TorchServe (servidores de modelos), Streamlit y Gradio (interfaces rápidas para demos), Docker (07-04).

  1. Criterios para elegir una librería

Criterio Pregunta que hacerse Ejemplo
Madurez y mantenimiento ¿Cuántos años lleva? ¿Sigue recibiendo versiones? ¿Hay un equipo o una fundación detrás? scikit-learn, NumPy y PyTorch son apuestas seguras; una librería de RAG con seis meses, no tanto
Comunidad y documentación ¿Encuentras respuestas a los errores? ¿Hay tutoriales, libros, cursos? La comunidad de PyTorch y Hugging Face es una razón para elegirlos
Licencia ¿Permite uso comercial? ¿Obliga a liberar tu código? (MIT, BSD, Apache: permisivas; GPL: copyleft; licencias de modelos: revisar) Casi todo el ecosistema Python es BSD/MIT/Apache; algunos modelos preentrenados restringen usos
Rendimiento ¿Aguanta tu volumen? ¿Usa GPU? ¿Está el núcleo en C/C++/Rust? LightGBM frente a un bosque de scikit-learn en 10 millones de filas; Polars frente a pandas
Integración ¿Encaja con lo que ya tienes (pandas, scikit-learn, tu servidor, tu formato de modelo)? XGBoost dentro de un Pipeline; ONNX para Java
Coste y dependencia ¿Es gratis? ¿Te ata a un proveedor (nube, API de LLM)? ¿Puedes salir? Modelo local con Ollama frente a API de pago
Adecuación al problema ¿Resuelve tu tarea o la fuerzas? Reglas para la política de devoluciones (06-04), no una red neuronal

Y un criterio de sentido común que Diego aplaudiría: la mejor librería es la más simple que resuelve el problema con la que el equipo ya sabe trabajar.

  1. Código: guardar y recargar el pipeline y el MLP

Lo que une entrenamiento y despliegue es un fichero. Entrenamos el predictor de devoluciones de 04-03/04-04 (regresión logística sobre el ColumnTransformer de 21 columnas) y el MLP de 05-02, guardamos ambos, los recargamos como haría el servidor y comprobamos que las predicciones son idénticas:

import numpy as np, joblib, torch, torch.nn as nn
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from novamarket_ml import generar_pedidos_ml, ensuciar_pedidos, preparar_pedidos, crear_preparacion

# Datos de 04-03: pedidos ensuciados y preparados (15 columnas -> 21 tras el ColumnTransformer)
X, y = preparar_pedidos(ensuciar_pedidos(generar_pedidos_ml(3000, 42), 42))
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)

# --- 1) scikit-learn: pipeline completo y joblib
pipe = Pipeline([("prep", crear_preparacion()),
                 ("modelo", LogisticRegression(max_iter=1000))]).fit(Xtr, ytr)
prob_antes = pipe.predict_proba(Xte)[:, 1]
print(f"AUC test regresión logística: {roc_auc_score(yte, prob_antes):.3f}")

joblib.dump(pipe, "modelo_devoluciones.joblib")           # guarda TODO el pipeline (preparación + modelo)
pipe_cargado = joblib.load("modelo_devoluciones.joblib")   # en otro proceso, otro día, el servidor
prob_despues = pipe_cargado.predict_proba(Xte)[:, 1]
print("iguales tras recargar (sklearn):", np.allclose(prob_antes, prob_despues))

# --- 2) PyTorch: MLP 21->16->8->1 y state_dict
prep = crear_preparacion().fit(Xtr)                        # el preprocesado se ajusta con train...
Xtr_t = torch.tensor(prep.transform(Xtr), dtype=torch.float32)
Xte_t = torch.tensor(prep.transform(Xte), dtype=torch.float32)
ytr_t = torch.tensor(ytr.values, dtype=torch.float32).unsqueeze(1)

def crear_mlp():
    return nn.Sequential(nn.Linear(21, 16), nn.ReLU(),
                         nn.Linear(16, 8), nn.ReLU(),
                         nn.Linear(8, 1))

torch.manual_seed(42)
red = crear_mlp()
opt = torch.optim.Adam(red.parameters(), lr=0.003)
perdida = nn.BCEWithLogitsLoss()
for epoca in range(150):                                   # entrenamiento breve, lote completo
    opt.zero_grad(); L = perdida(red(Xtr_t), ytr_t); L.backward(); opt.step()
red.eval()
with torch.no_grad():
    prob_red = torch.sigmoid(red(Xte_t)).numpy().ravel()
print(f"AUC test MLP: {roc_auc_score(yte, prob_red):.3f}   parámetros: {sum(p.numel() for p in red.parameters())}")

torch.save(red.state_dict(), "mlp_devoluciones.pt")        # solo los pesos
joblib.dump(prep, "preparacion.joblib")                    # ...y se guarda aparte: el MLP no lo incluye
print(list(red.state_dict().keys()))

red2 = crear_mlp()                                         # misma arquitectura, pesos aleatorios nuevos
red2.load_state_dict(torch.load("mlp_devoluciones.pt"))    # se sobreescriben con los guardados
red2.eval()
prep2 = joblib.load("preparacion.joblib")
with torch.no_grad():
    prob_red2 = torch.sigmoid(red2(torch.tensor(prep2.transform(Xte), dtype=torch.float32))).numpy().ravel()
print("iguales tras recargar (PyTorch):", np.allclose(prob_red, prob_red2))

# --- 3) un pedido nuevo, como llegaría a la API de la sección 10
nuevo = Xte.iloc[[0]]                                      # DataFrame de una fila (doble corchete)
print(pipe_cargado.predict_proba(nuevo)[0, 1].round(3),
      torch.sigmoid(red2(torch.tensor(prep2.transform(nuevo), dtype=torch.float32))).item())

Salida:

AUC test regresión logística: 0.844
iguales tras recargar (sklearn): True
AUC test MLP: 0.841   parámetros: 497
['0.weight', '0.bias', '2.weight', '2.bias', '4.weight', '4.bias']
iguales tras recargar (PyTorch): True
0.048 0.0756823718547821

Explicación:

  • El AUC 0,844 es exactamente el de 04-04: mismos datos, misma semilla, mismo pipeline. joblib.dump serializa el objeto entero (imputadores con sus medianas, escalador con sus medias, one-hot con sus categorías, regresión con sus coeficientes) en un fichero de unos 7 KB, y np.allclose confirma que las 750 probabilidades coinciden tras recargar. Eso es lo que carga el servidor FastAPI de la sección 10.
  • En PyTorch guardamos solo el state_dict: un diccionario ordenado de tensores cuyas claves (0.weight, 0.bias...) son las posiciones de las capas en el Sequential. Para cargarlo hay que reconstruir la misma arquitectura con el mismo código y después load_state_dict; por eso crear_mlp va en un módulo compartido (07-04). El fichero pesa unos 5 KB (497 floats más metadatos).
  • El detalle que más despliegues rompe: la red no incluye el preprocesado. Guardamos prep aparte con joblib y lo aplicamos antes de la red; si en producción se escalara con otras medias, las predicciones serían distintas sin que nada diera error. El pipeline de scikit-learn evita ese riesgo llevando todo dentro; con PyTorch hay que disciplinarse.
  • Para el mismo pedido nuevo (portátil de 73 € de un cliente recurrente con tres pedidos previos sin devoluciones), la regresión da 4,8 % y el MLP 7,6 %: modelos distintos, misma conclusión, bajo riesgo. torch.load de ficheros ajenos tiene el riesgo de pickle de 07-01; para compartir pesos con terceros existe el formato safetensors.

  1. La caja de herramientas de NovaMarket

Caso de uso Tarea Librería elegida Alternativas / evolución
1 Recomendación Filtrado colaborativo, similitud scikit-learn (vecinos, factorización) + pandas implicit, modelos de dos torres en PyTorch
2 Previsión de demanda Series temporales pandas (resample, rolling) + scikit-learn (regresión con retardos) statsmodels, Prophet, sktime, redes recurrentes
3 Fraude / devoluciones Clasificación tabular scikit-learn Pipeline + regresión logística / bosque; HistGradientBoosting LightGBM/XGBoost, MLP en PyTorch
4 Reseñas Clasificación de texto, extracción Hugging Face transformers (pipeline, afinado), spaCy scikit-learn con TF-IDF como base rápida
5 Rutas Optimización combinatoria OR-Tools Búsqueda local propia (03-04), PuLP
6 Asignación Programación con restricciones OR-Tools (CP-SAT) PuLP
7 Asistente LLM + RAG SDK de proveedor u Ollama; LangChain o LlamaIndex; base vectorial Código propio si el flujo es simple
8 Reglas de devoluciones Motor de reglas Motor propio de 06-02 experta, Drools con DMN
9 Diagnóstico de incidencias Red bayesiana pgmpy Motor de reglas + probabilidades propias (06-03)
Transversal Guardar y servir joblib, state_dict, FastAPI ONNX Runtime; MLflow para el registro
Transversal Datos pandas + SQL Polars si crece; Spark si hay cluster

Errores Comunes y Consejos

  • Aprender cinco librerías a la vez. Domina scikit-learn (API, Pipeline, validación) y una de deep learning; el resto se aprende en días porque copian la misma API.
  • Guardar el modelo sin el preprocesado (o al revés): predicciones distintas en producción sin ningún error. Con scikit-learn, guarda el Pipeline completo; con PyTorch, guarda el state_dict y el transformador, y el código de la arquitectura en un módulo versionado.
  • Cargar state_dict en una arquitectura distinta: PyTorch falla con un error de claves o formas; si cambias la red, reentrena o convierte explícitamente.
  • Confiar en que el fichero cargará siempre: pickle/joblib dependen de las versiones (07-01, 07-04). Anota sklearn.__version__ y torch.__version__ junto al modelo y prueba la recarga en el entorno de destino.
  • Instalar de todo "por si acaso": cada librería trae dependencias y conflictos (07-04). Añade cuando la necesites.
  • Elegir por benchmark de Internet: los rankings cambian por meses; los criterios de la sección 11 y una prueba con tus datos, no.
  • Meter un LLM donde bastaba una regla o un groupby: coste, latencia y opacidad (06-04). Empieza por lo simple.

Ejercicios

Ejercicio 1. Sustituye en la sección 12 la regresión logística por HistGradientBoostingClassifier de scikit-learn (importa de sklearn.ensemble; deja el resto del Pipeline igual). Entrena, mide el AUC de test, guarda con joblib, recarga y comprueba con np.allclose que las predicciones coinciden. ¿Mejora la regresión logística? Compara también el tamaño en bytes de los dos ficheros .joblib (os.path.getsize) y explica la diferencia.

Ejercicio 2. Escribe (ilustrativo, sin ejecutar) el MLP de la sección 4 con una capa oculta más de 32 neuronas y dropout 0,2 tanto en PyTorch (nn.Module) como en Keras. Indica en cada versión dónde va el dropout y calcula el número de parámetros de la nueva red 21→32→16→8→1.

Ejercicio 3. Rellena la tabla de la sección 13 para un décimo caso de uso que Marta propone: detección de productos dañados en las fotos que suben los clientes al abrir una incidencia (caso 9 ampliado con imagen). Indica tarea, librería elegida, alternativas y qué formato usarías para servir el modelo si el desarrollador Java quiere integrarlo en la app de incidencias sin depender de un servicio Python. Justifica con los criterios de la sección 11.

Soluciones

Solución 1.

from sklearn.ensemble import HistGradientBoostingClassifier
import os
pipe_hgb = Pipeline([("prep", crear_preparacion()),
                     ("modelo", HistGradientBoostingClassifier(max_depth=4, learning_rate=0.05,
                                                              max_iter=200, random_state=42))]).fit(Xtr, ytr)
p1 = pipe_hgb.predict_proba(Xte)[:, 1]
print(f"AUC test HGB: {roc_auc_score(yte, p1):.3f}")
joblib.dump(pipe_hgb, "modelo_hgb.joblib")
p2 = joblib.load("modelo_hgb.joblib").predict_proba(Xte)[:, 1]
print(np.allclose(p1, p2), os.path.getsize("modelo_devoluciones.joblib"), os.path.getsize("modelo_hgb.joblib"))

Salida: AUC test HGB: 0.805, True 6650 337074. El boosting no mejora aquí a la regresión logística (0,805 frente a 0,844): con 2.249 pedidos y una "verdad oculta" casi lineal en el logit (04-01), el modelo lineal es el adecuado, y el boosting con 200 árboles de profundidad 4 sobreajusta (recuerda 04-06: la complejidad hay que ganársela con datos; con menos iteraciones o early_stopping=True se acercaría). Sí sirve para comprobar que la API es idéntica y que joblib guarda cualquier pipeline. El fichero pesa unas 50 veces más (unos 330 KB frente a 7 KB) porque guarda 200 árboles con sus umbrales, frente a 21 coeficientes y un sesgo: el tamaño del modelo es otro criterio de despliegue (memoria del servidor, tiempo de carga, dispositivos).

Solución 2.

# PyTorch (ilustrativo)
class MLP2(nn.Module):
    def __init__(self):
        super().__init__()
        self.capas = nn.Sequential(nn.Linear(21, 32), nn.ReLU(), nn.Dropout(0.2),
                                   nn.Linear(32, 16), nn.ReLU(), nn.Dropout(0.2),
                                   nn.Linear(16, 8), nn.ReLU(),
                                   nn.Linear(8, 1))
    def forward(self, x):
        return self.capas(x)
# Keras (ilustrativo)
red = keras.Sequential([layers.Input(shape=(21,)),
                        layers.Dense(32, activation="relu"), layers.Dropout(0.2),
                        layers.Dense(16, activation="relu"), layers.Dropout(0.2),
                        layers.Dense(8, activation="relu"),
                        layers.Dense(1, activation="sigmoid")])

El dropout va después de la activación de la capa que se quiere regularizar, en ambas librerías como una capa/módulo sin parámetros; en PyTorch se activa solo en modo train() y se desactiva con eval(), en Keras fit y predict lo gestionan solos. Parámetros: (21 × 32 + 32) + (32 × 16 + 16) + (16 × 8 + 8) + (8 × 1 + 1) = 704 + 528 + 136 + 9 = 1.377, casi el triple que los 497 de la red original, con los mismos 2.249 pedidos: la razón de añadir dropout.

Solución 3. Caso 10, "productos dañados en foto": tarea de clasificación de imágenes (o detección, si se quiere localizar el daño). Librería elegida: PyTorch + torchvision con un modelo preentrenado (una ResNet pequeña) afinado sobre las fotos etiquetadas de incidencias, y Pillow/OpenCV para el preprocesado (redimensionar, normalizar); es lo maduro, con comunidad y modelos disponibles, licencia permisiva y rendimiento en CPU aceptable para unas decenas de fotos al día. Alternativas: Keras con la misma idea; Ultralytics/YOLO si hay que localizar el daño; un servicio de visión de un proveedor en la nube si no hay fotos etiquetadas suficientes (con la cautela de 02-04 sobre enviar datos fuera). Para integrarlo en la app Java sin servicio Python: exportar a ONNX con torch.onnx.export y ejecutarlo con ONNX Runtime para Java (07-01); el preprocesado de imagen se replica en Java (redimensionar y normalizar es sencillo, a diferencia de un ColumnTransformer). Criterios aplicados: madurez y comunidad (PyTorch/torchvision), integración (ONNX para Java), rendimiento (CPU suficiente al volumen de NovaMarket), coste (modelo local, sin API de pago), adecuación (imágenes reales piden red preentrenada, no la CNN sintética de 05-04).

Conclusión

Hemos dibujado el mapa de herramientas del taller de Marta por tareas: scikit-learn y su API fit/predict/transform con Pipeline para el ML clásico, y las librerías de boosting como refuerzo; PyTorch (tensores, autograd, nn.Module, DataLoader) frente a TensorFlow/Keras y JAX, con el MLP 21→16→8→1 escrito en las dos sintaxis para comprobar que la idea es la misma; Hugging Face, spaCy y NLTK para el texto; OpenCV, torchvision y Pillow para la imagen; los SDK de LLM, Ollama y los orquestadores LangChain/LlamaIndex para el asistente con RAG; experta, pgmpy y los motores de reglas para la mitad simbólica; OR-Tools y PuLP para la optimización; MLflow, W&B y DVC para no perder el rastro de los experimentos; FastAPI y ONNX Runtime para servir; Spark, Dask y Polars cuando los datos crecen. Hemos fijado criterios para elegir (madurez, comunidad, licencia, rendimiento, integración, coste, adecuación) y hemos ejecutado la pieza que une entrenamiento y despliegue: el pipeline de devoluciones guardado con joblib (AUC 0,844) y el MLP guardado como state_dict con su preprocesado aparte, ambos recargados con predicciones idénticas. La caja de herramientas de NovaMarket asigna una librería a cada uno de los nueve casos de uso.

Falta la última pieza del taller, y es la que evita que todo esto se venga abajo el día que otro compañero intente ejecutar el código: el entorno. Qué versión de Python y de cada librería, en qué entorno virtual, con qué fichero de dependencias; cómo usar Jupyter sin caer en sus trampas; qué IDE; qué versionar con Git y qué no; cómo pasar de los scripts sueltos de los módulos 3-6 a una estructura de proyecto con módulos, un entrenar.py y tests; y cuándo hace falta GPU, nube o Docker. Es 07-04, Entornos de Desarrollo, con la que cerramos el módulo.

Fundamentos de Inteligencia Artificial (IA)

Módulo 1: Introducción a la Inteligencia Artificial

Módulo 2: Principios Básicos de la IA

Módulo 3: Algoritmos en IA

Módulo 4: Aprendizaje Automático (Machine Learning)

Módulo 5: Redes Neuronales y Deep Learning

Módulo 6: Lógica y Sistemas Expertos

Módulo 7: Herramientas y Lenguajes de Programación en IA

Módulo 8: Proyectos y Casos de Estudio

Módulo 9: Ejercicios y Prácticas

Módulo 10: Recursos Adicionales

© Copyright 2026. Todos los derechos reservados