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
- Mapa del ecosistema por tarea
- ML clásico: scikit-learn y las librerías de boosting
- Deep learning: PyTorch, TensorFlow/Keras y JAX
- El mismo MLP en PyTorch y en Keras, lado a lado
- PLN y modelos preentrenados: Hugging Face, spaCy, NLTK
- Visión: OpenCV, torchvision, Pillow
- IA generativa y LLM: SDK, Ollama, LangChain y LlamaIndex
- Lógica, probabilidad y optimización
- Experimentación, MLOps y datos a escala
- Despliegue: FastAPI y ONNX Runtime
- Criterios para elegir una librería
- Código: guardar y recargar el pipeline y el MLP
- La caja de herramientas de NovaMarket
- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- 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) |
- 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ñadenpredictypredict_proba; los transformadores añadentransform(yfit_transform). Da igual que seaLogisticRegression,RandomForestClassifier,StandardScaleroOneHotEncoder: misma forma. - El estado aprendido se guarda en atributos con guion bajo final:
esc = StandardScaler().fit([[1.0], [2.0], [3.0]])dejaesc.mean_ = [2.]yesc.scale_ = [0.816], yesc.transform([[4.0]])devuelve[[2.449]]. Por eso un transformador ajustado con entrenamiento aplica las mismas medias en producción (04-03). Pipelineencadena transformadores y un predictor final;ColumnTransformeraplica distintos transformadores a distintas columnas (nuestras 21 columnas de 04-03);GridSearchCV/RandomizedSearchCVycross_val_scoreseleccionan modelos (04-06);sklearn.metricsmide (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]
- 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 ybackward()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.graddatensor(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 denn.Moduley se defineforward.DatasetyDataLoader: 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.
- 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.Linear ↔ layers.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.
- PLN y modelos preentrenados: Hugging Face, spaCy, NLTK
- Hugging Face
transformerses 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 conpipelineen tres líneas, o se afinan (fine-tuning) conTrainer.datasetscarga conjuntos de datos públicos con el mismo estilo;tokenizershace 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.
- 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,
timmpara más arquitecturas,albumentationspara aumentos.
- 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.
- Lógica, probabilidad y optimización
- Reglas: en 06-02 escribimos el motor
Regla/MotorInferenciaen 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:
pgmpyimplementa las redes bayesianas de 06-03 con inferencia eficiente (eliminación de variables, no enumeración) y aprendizaje de parámetros desde datos;pomegranatey PyMC (inferencia bayesiana general) son alternativas. Para el diagnóstico de incidencias (caso 9),pgmpyes 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
optimizecubren 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.
- 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.csvson 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.
- 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, ajustableUna 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).
- 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.
- 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.dumpserializa 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, ynp.allcloseconfirma 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 elSequential. Para cargarlo hay que reconstruir la misma arquitectura con el mismo código y despuésload_state_dict; por esocrear_mlpva 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
prepaparte conjobliby 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.loadde ficheros ajenos tiene el riesgo de pickle de 07-01; para compartir pesos con terceros existe el formato safetensors.
- 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
Pipelinecompleto; con PyTorch, guarda elstate_dicty el transformador, y el código de la arquitectura en un módulo versionado. - Cargar
state_dicten 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__ytorch.__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
- Conceptos Fundamentales: Agentes, Entornos y Racionalidad
- Tipos de Inteligencia Artificial
- Los Datos como Materia Prima de la IA
- Ética y Consideraciones en IA
Módulo 3: Algoritmos en IA
- Introducción a los Algoritmos
- Algoritmos de Búsqueda
- Búsqueda con Adversario: Juegos y Minimax
- Algoritmos de Optimización
Módulo 4: Aprendizaje Automático (Machine Learning)
- Conceptos Básicos de Machine Learning
- Tipos de Aprendizaje Automático
- Preparación de Datos y Características
- Algoritmos de Machine Learning
- Evaluación y Validación de Modelos
- Sobreajuste, Regularización y Ajuste de Hiperparámetros
Módulo 5: Redes Neuronales y Deep Learning
- Introducción a las Redes Neuronales
- Arquitectura de Redes Neuronales
- Cómo Aprende una Red: Descenso del Gradiente y Retropropagación
- Deep Learning y sus Aplicaciones
- Transformers, Grandes Modelos de Lenguaje e IA Generativa
Módulo 6: Lógica y Sistemas Expertos
- Lógica en IA
- Sistemas Expertos
- Razonamiento con Incertidumbre: Probabilidad y Redes Bayesianas
- Aplicaciones de Sistemas Expertos
Módulo 7: Herramientas y Lenguajes de Programación en IA
- Lenguajes de Programación para IA
- Python Científico: NumPy, pandas y Matplotlib
- Herramientas y Librerías Populares
- Entornos de Desarrollo
Módulo 8: Proyectos y Casos de Estudio
Módulo 9: Ejercicios y Prácticas
- Ejercicios de Algoritmos
- Prácticas de Machine Learning
- Proyectos de Redes Neuronales
- Proyecto Integrador: de la Idea al Prototipo
