Todo este módulo — y buena parte del curso — ha ido dejando una cuenta pendiente: alpha en la regularización, C en la logística y las SVM, K en K-NN, max_depth en los árboles, learning rate y n_estimators en el boosting, capas y dropout en las redes… Hasta ahora los hemos elegido a ojo o con bucles manuales como los de 06-05. Esta lección cierra el módulo automatizando esa búsqueda: aprenderás a distinguir parámetros de hiperparámetros, a usar GridSearchCV y RandomizedSearchCV sobre el pipeline completo del churn de MercaFresh, a entender la búsqueda bayesiana, a evitar la trampa de evaluar con la misma validación que eligió al ganador, y a gestionar tu presupuesto de cómputo con cabeza.
Contenido
- Parámetros vs. hiperparámetros
- La búsqueda manual y sus límites
- GridSearchCV en detalle
- Grid search sobre el pipeline completo del churn
- RandomizedSearchCV: cuándo es mejor muestrear
- Búsqueda bayesiana: buscar con memoria
- Validación anidada: no evaluar con el juez que eligió
- Buenas prácticas de búsqueda
- Cierre del módulo: el arsenal completo
Parámetros vs. hiperparámetros
La distinción es la base de todo:
- Parámetros: los aprende el algoritmo de los datos durante
fit. No los eliges tú. - Hiperparámetros: los fijas tú antes de entrenar; configuran cómo aprende el algoritmo.
| Modelo (lección) | Parámetros (los aprende fit) |
Hiperparámetros (los eliges tú) |
|---|---|---|
| Regresión lineal (04-01) | Coeficientes e intercepto | — (por eso empezamos por ella) |
| Ridge/Lasso (07-01) | Coeficientes | alpha, l1_ratio |
| Regresión logística (04-02) | Coeficientes | C, penalty |
| SVM (04-04) | Vectores de soporte y sus pesos | C, kernel, gamma |
| K-NN (04-05) | — (memoriza el train) | n_neighbors (la K), weights |
| Árbol (04-03) | Las divisiones del árbol | max_depth, min_samples_leaf |
| Random Forest (07-02) | Todos los árboles | n_estimators, max_features |
| Gradient boosting (07-03) | La secuencia de árboles | learning_rate, n_estimators, max_depth |
| Red neuronal (04-07, 07-04) | Pesos y sesgos | Nº de capas y neuronas, dropout, learning rate, batch size |
La regla mnemotécnica: si aparece en el constructor (Ridge(alpha=...)), es hiperparámetro; si aparece como atributo con guion bajo final tras entrenar (ridge.coef_), es parámetro aprendido. Y el principio metodológico que gobierna la lección: los parámetros se ajustan con el train; los hiperparámetros se eligen con validación — nunca con el test, que sigue guardado bajo llave para el veredicto final (06-01).
La búsqueda manual y sus límites
Ya hemos hecho búsqueda de hiperparámetros tres veces sin ponerle nombre: las curvas de validación sobre max_depth en 06-05, el bucle de alphas en 07-01 y las combinaciones de η/n_estimators en 07-03. El método manual — bucle, CV, apuntar resultados — funciona, pero escala fatal:
- Explosión combinatoria: con 4 hiperparámetros y 5 valores cada uno son 625 combinaciones; a mano, imposible.
- Interacciones: el mejor
learning_ratedepende den_estimators(lo vimos en 07-03); explorar cada mando por separado se pierde las combinaciones cruzadas. - Errores de disciplina: en bucles artesanales es fácil ajustar sin querer con datos de test, o escalar fuera de la CV (la fuga de 06-01).
- Irreproducibilidad: "probé cosas y me quedé con esta" no es un procedimiento auditable.
sklearn empaqueta la solución en dos herramientas que hacen exactamente lo que hacíamos a mano, pero de forma exhaustiva, paralela y sin fugas.
GridSearchCV en detalle
GridSearchCV recibe un estimador, una rejilla (grid) de valores por hiperparámetro, y una estrategia de CV; entrena y evalúa todas las combinaciones con validación cruzada y se queda con la mejor.
Sus piezas:
param_grid: diccionario{hiperparámetro: lista de valores}. El producto cartesiano define las combinaciones.cv: la estrategia de validación cruzada — para el churn, laStratifiedKFoldde 06-03.scoring: la métrica que decide (06-02):"f1","roc_auc","neg_root_mean_squared_error"… Elegir bien esta métrica es elegir qué significa "mejor"; para el churn desbalanceado de MercaFresh, F1 o AUC, no accuracy.refit=True(por defecto): tras la búsqueda, reentrena automáticamente la mejor combinación sobre todo el train — el objeto resultante ya es un modelo listo para predecir.n_jobs=-1: paraleliza en todos los núcleos; las combinaciones son independientes entre sí.
Y sus resultados:
best_params_: la combinación ganadora.best_score_: su puntuación media de CV.cv_results_: la tabla completa de la búsqueda (todas las combinaciones, medias, desviaciones, tiempos) — conviértela en DataFrame y estúdiala: vale más que el ganador solo.best_estimator_: el modelo reentrenado (sirefit=True).
Grid search sobre el pipeline completo del churn
La regla de oro de 06-03 sigue vigente: lo que se busca es el pipeline completo, no el modelo suelto, para que el preprocesamiento se reajuste dentro de cada pliegue sin fugas. Los hiperparámetros de un paso del pipeline se nombran con la notación paso__parametro (doble guion bajo) que ya conocimos entonces — y que permite incluso tratar decisiones de preprocesamiento como hiperparámetros más.
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold, GridSearchCV, train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
# Dataset de churn de MercaFresh (features RFM, como en 07-02/07-03)
rng = np.random.default_rng(42)
n = 1000
recencia = rng.gamma(2, 15, n)
frecuencia = rng.poisson(5, n) + 1
monetario = rng.gamma(3, 40, n)
antiguedad = rng.uniform(1, 60, n)
incidencias = rng.poisson(0.5, n)
logits = 0.05 * recencia - 0.4 * frecuencia + 0.6 * incidencias - 0.01 * antiguedad - 0.5
y = (rng.random(n) < 1 / (1 + np.exp(-logits))).astype(int)
X = pd.DataFrame({"recencia": recencia, "frecuencia": frecuencia,
"monetario": monetario, "antiguedad": antiguedad,
"incidencias": incidencias})
# Test apartado ANTES de cualquier búsqueda (06-01)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42)
pipe = Pipeline([
("scaler", StandardScaler()),
("clf", LogisticRegression(max_iter=5000)),
])
param_grid = {
"clf__C": np.logspace(-3, 2, 6), # 0.001 ... 100, escala log (07-01)
"clf__penalty": ["l1", "l2"], # Lasso o Ridge sobre la logística
"clf__solver": ["liblinear"], # solver compatible con ambas
}
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
busqueda = GridSearchCV(pipe, param_grid, cv=cv, scoring="f1",
n_jobs=-1, refit=True)
busqueda.fit(X_train, y_train) # 6 x 2 = 12 combinaciones x 5 pliegues = 60 fits
print("Mejor combinación:", busqueda.best_params_)
print(f"Mejor F1 (CV): {busqueda.best_score_:.3f}")
# La tabla completa, para entender el paisaje y no solo el pico
resultados = pd.DataFrame(busqueda.cv_results_)
print(resultados[["param_clf__C", "param_clf__penalty",
"mean_test_score", "std_test_score"]]
.sort_values("mean_test_score", ascending=False).head())
# Veredicto final: el test intacto, una sola vez
from sklearn.metrics import f1_score
print(f"F1 en test: {f1_score(y_test, busqueda.predict(X_test)):.3f}")Puntos que merecen relectura:
- El coste real es
combinaciones × plieguesentrenamientos (aquí 60): el grid crece multiplicativamente y la CV lo multiplica otra vez. Presupuesta antes de lanzar. busquedase usa después como un modelo normal (predict,predict_proba): gracias arefit, dentro lleva el mejor pipeline reentrenado con todo el train.- Mira siempre
std_test_score: un "ganador" por 0.002 de media con desviaciones de 0.03 es un empate técnico; en ese caso elige la combinación más simple/regularizada, no la primera de la tabla. - El F1 de test suele quedar algo por debajo de
best_score_— y esa diferencia tiene nombre y sección propia más abajo.
RandomizedSearchCV: cuándo es mejor muestrear
El grid exhaustivo muere por combinatoria: con el gradient boosting de 07-03 querríamos explorar learning_rate, max_iter, max_depth, min_samples_leaf, subsample… 5 valores de cada = 3.125 combinaciones × 5 pliegues = 15.625 entrenamientos.
RandomizedSearchCV cambia la estrategia: en lugar de probarlo todo, muestrea n_iter combinaciones al azar de distribuciones que tú defines. Ventajas decisivas:
- Presupuesto fijo: tú decides cuántos entrenamientos pagas (
n_iter=50), sea cual sea el tamaño del espacio. - Distribuciones continuas en lugar de listas:
loguniform(0.001, 0.3)para el learning rate explora todo el rango, no 5 puntos sueltos. - Mejor cobertura de lo que importa: en la práctica solo unos pocos hiperparámetros son influyentes; el muestreo aleatorio prueba muchos valores distintos de cada hiperparámetro, mientras el grid desperdicia presupuesto repitiendo los mismos valores del hiperparámetro influyente para cada valor del irrelevante.
from sklearn.model_selection import RandomizedSearchCV
from sklearn.ensemble import HistGradientBoostingClassifier
from scipy.stats import loguniform, randint
pipe_gb = Pipeline([("clf", HistGradientBoostingClassifier(random_state=42))])
param_dist = {
"clf__learning_rate": loguniform(0.005, 0.3), # continuo, escala log
"clf__max_iter": randint(100, 1000),
"clf__max_depth": randint(2, 8),
"clf__min_samples_leaf": randint(10, 60),
}
azar = RandomizedSearchCV(pipe_gb, param_dist, n_iter=50, cv=cv,
scoring="f1", n_jobs=-1, random_state=42)
azar.fit(X_train, y_train) # 50 x 5 = 250 fits, decidido por ti
print(azar.best_params_, f"F1-CV: {azar.best_score_:.3f}")Regla práctica: grid para espacios pequeños y discretos (2-3 hiperparámetros con pocos valores razonables, como el ejemplo de la logística); randomized para todo lo demás, y especialmente para boosting y redes.
Búsqueda bayesiana: buscar con memoria
Grid y randomized comparten una ingenuidad: cada combinación se elige sin mirar los resultados anteriores. La búsqueda bayesiana (u optimización bayesiana) es la evolución natural: construye un modelo probabilístico de la función "hiperparámetros → puntuación" — sí, un modelo de ML para optimizar modelos de ML — y lo usa para decidir qué combinación probar a continuación, equilibrando explotar las zonas que pintan bien y explorar las zonas inciertas. Es la misma lógica de actualización de creencias con evidencia del teorema de Bayes (02-05), aplicada a la búsqueda.
Con presupuestos ajustados (decenas de evaluaciones de un modelo caro), suele encontrar mejores combinaciones que el azar puro. La biblioteca de referencia es Optuna (también scikit-optimize); su API se integra bien con sklearn y añade extras como descartar a mitad de entrenamiento las combinaciones que van mal (pruning). No la desarrollamos aquí: conceptualmente ya tienes lo esencial, y su sintaxis se aprende en una tarde cuando la necesites.
| Estrategia | Cómo elige | Cuándo usarla |
|---|---|---|
| Manual (06-05) | Tu intuición | Exploración inicial, aprendizaje |
| GridSearchCV | Todas las combinaciones | Espacios pequeños y discretos |
| RandomizedSearchCV | Muestreo aleatorio con presupuesto | Espacios grandes; opción por defecto |
| Bayesiana (Optuna) | Modelo que aprende de los intentos previos | Modelos caros de entrenar, presupuesto escaso |
Validación anidada: no evaluar con el juez que eligió
Una sutileza importante que ya asomó en el ejemplo del grid: best_score_ es una estimación optimista del rendimiento real. ¿Por qué? Porque probaste muchas combinaciones sobre la misma CV y te quedaste con la que mejor puntuó en esa CV concreta: parte de su ventaja es mérito y parte es suerte con esos pliegues. Es el mismo sesgo de selección que en 06-01 nos llevó a separar validación y test — reapareciendo un nivel más arriba.
La solución limpia es la validación anidada (nested CV): un bucle externo de CV para estimar rendimiento, y dentro de cada pliegue externo, un bucle interno completo de búsqueda que elige los hiperparámetros. Así, los datos que puntúan nunca participaron en la elección:
from sklearn.model_selection import cross_val_score
# La búsqueda entera (bucle interno) se trata como un estimador más
# y se evalúa con una CV externa que ella nunca ve
f1_anidado = cross_val_score(busqueda, X_train, y_train, cv=5, scoring="f1")
print(f"F1 honesto (anidado): {f1_anidado.mean():.3f} ± {f1_anidado.std():.3f}")El coste se multiplica (5 × 60 = 300 fits en nuestro ejemplo), así que en la práctica se reserva para cuando la cifra de rendimiento debe ser rigurosa — comparar algoritmos para un informe, o estimar qué rendirá el modelo antes de comprometerse con negocio. Para el día a día basta el esquema de esta lección: buscar con CV sobre el train y dar el veredicto final una única vez sobre el test intacto.
Buenas prácticas de búsqueda
- De grueso a fino. Primera pasada con rangos amplios y pocos puntos (o
n_itermoderado); miracv_results_, localiza la zona buena y lanza una segunda pasada refinada alrededor. Dos búsquedas baratas rinden más que una carísima. - Escala logarítmica para los multiplicativos.
alpha,C,learning_rate,gammaactúan por órdenes de magnitud: usanp.logspace/loguniform. Para los estructurales (max_depth,n_neighbors), escala lineal. - Presupuesta antes de lanzar. Calcula
combinaciones × pliegues × tiempo_por_fity decide si te lo puedes permitir; si no, reduce el grid, baja pliegues (5 → 3) o pasa a randomized. Mide primero un fit suelto con%timeitotime.perf_counter. - Busca menos donde importa menos. No todos los hiperparámetros merecen rejilla: en Random Forest,
n_estimatorsse fija alto y ya (07-02); en boosting, el early stopping eligen_estimatorspor ti (07-03) — quítalos del espacio de búsqueda. - Fija
random_stateen todo (CV, modelos, búsqueda) para que la búsqueda sea reproducible y las comparaciones justas. - Guarda
cv_results_. El paisaje completo dice cosas que el ganador calla: qué hiperparámetros influyen de verdad, dónde el modelo es robusto y dónde frágil.
Cierre del módulo: el arsenal completo
Este módulo respondió a la pregunta con la que lo abrimos — "¿puedo hacerlo aún mejor?" — con cinco técnicas complementarias:
| Técnica (lección) | Qué mejora | Cuándo aplicarla |
|---|---|---|
| Regularización L1/L2/EN (07-01) | Doma la varianza de los modelos lineales; L1 además selecciona features | Modelos lineales con muchas features o correlacionadas; siempre con escalado |
| Ensembles: bagging/RF, voting, stacking (07-02) | Cancela errores descorrelacionados; RF como baseline fuerte universal | Casi siempre en tabular: empieza aquí |
| Gradient boosting (07-03) | Techo de rendimiento en datos tabulares | Cuando cada punto de métrica vale dinero y puedes pagar el ajuste |
| Deep learning (07-04) | Aprende representaciones jerárquicas | Imágenes, audio, texto; tabular solo con datos masivos |
| Optimización de hiperparámetros (07-05) | Exprime cualquiera de las anteriores de forma sistemática y sin fugas | Siempre, con presupuesto proporcional a lo que hay en juego |
Y el flujo que las une para un problema tabular como el churn de MercaFresh: baseline Dummy (06-02) → modelo simple interpretable → Random Forest → gradient boosting con early stopping → búsqueda de hiperparámetros del finalista → veredicto único sobre el test.
Errores Comunes y Consejos
- Buscar hiperparámetros usando el test. El error capital. Si el test influye en cualquier decisión — hiperparámetros, features, umbral — deja de medir generalización. Test intacto hasta el final, una sola evaluación.
- Pasar el modelo suelto en vez del pipeline. Si escalas antes y por fuera de la búsqueda, cada pliegue de la CV ve estadísticas del resto: la fuga de 06-01 en versión sutil. Busca siempre sobre el
Pipelinecon la notaciónpaso__parametro. - Rejillas lineales para hiperparámetros multiplicativos.
Cen[1, 2, 3, 4, 5]explora una esquina minúscula del rango útil;np.logspace(-3, 2, 6)explora cinco órdenes de magnitud con el mismo coste. - Tomar
best_score_como el rendimiento esperado. Es optimista por sesgo de selección; el número honesto sale del test final o de la validación anidada. - Coronar ganadores por diferencias dentro del ruido. Compara
mean_test_scorejunto astd_test_score; en empate técnico, gana el modelo más simple. - Consejo: trata la búsqueda como un experimento científico: hipótesis (rangos razonados, no arbitrarios), procedimiento reproducible (semillas fijadas), resultados archivados (
cv_results_a CSV). Tu yo de dentro de tres meses — o el auditor del modelo — te lo agradecerá.
Ejercicios
- Grid sobre el K-NN del churn. Construye un pipeline
StandardScaler+KNeighborsClassifiery busca conGridSearchCV(scoring="f1", la CV estratificada de la lección) sobreclf__n_neighborsen[3, 5, 9, 15, 25, 41]yclf__weightsen["uniform", "distance"]. Reportabest_params_,best_score_y el F1 en test. ¿Cuántos entrenamientos ha ejecutado la búsqueda? - Grid vs. randomized con el mismo presupuesto. Sobre el
HistGradientBoostingClassifierdel ejemplo, compara: (a) unGridSearchCVcon 3 valores delearning_rate× 3 demax_depth(9 combinaciones) y (b) unRandomizedSearchCVconn_iter=9sobre las distribuciones continuas del ejemplo. Mismocv, mismoscoring. Repite (b) con tresrandom_statedistintos. ¿Quién gana y qué estabilidad observas? - El tamaño del optimismo. Calcula para la búsqueda de la logística del ejemplo: (a)
best_score_, (b) el F1 de validación anidada concross_val_score(busqueda, ...), y (c) el F1 en test. Ordena las tres cifras y explica cada diferencia con los conceptos de la lección.
Soluciones
- Estructura:
Pipeline([("scaler", StandardScaler()), ("clf", KNeighborsClassifier())])y el grid con la notaciónclf__. Con estos datos, el ganador suele rondarn_neighborsentre 15 y 41 conweights="distance"(el churn tiene ruido y las vecindades amplias promedian mejor), con F1-CV cercano al de la logística. Entrenamientos: 6 × 2 = 12 combinaciones × 5 pliegues = 60 fits (más 1 final delrefit). El F1 de test debería quedar en el entorno delbest_score_, ligeramente por debajo. - Con solo 2 hiperparámetros bien acotados, el grid es competitivo y a veces gana; el randomized queda muy cerca y, según la semilla, lo supera — sus 9 puntos cubren valores de learning rate que el grid ni prueba. Entre semillas, el
best_score_del randomized oscila (típicamente en la 2ª-3ª décima decimal): conn_itertan bajo, el azar de qué combinaciones salen pesa. Moraleja doble: en espacios pequeños el grid es defendible; en cuanto el espacio crece, la cobertura continua del randomized gana — y con másn_iter, su varianza entre semillas se encoge. - El orden esperado es
best_score_≥ F1 anidado ≈ F1 test. (a) > (b):best_score_lleva el sesgo de selección — la combinación ganadora lo es en parte por suerte con esos pliegues concretos, y la CV externa del anidado, que no participó en la elección, lo descuenta. (b) ≈ (c): ambos son estimaciones honestas sobre datos no usados en ninguna decisión; difieren solo por ruido de muestreo (el test es una única partición; el anidado promedia cinco). Si en tu ejecución (c) sale por encima de (a), también es lección: con datasets de este tamaño, el ruido entre particiones puede superar a los sesgos que medimos.
Conclusión
Has cerrado el círculo que abrió el módulo 6: los parámetros los aprende fit, los hiperparámetros los elige una búsqueda disciplinada — GridSearchCV para espacios pequeños, RandomizedSearchCV con distribuciones y presupuesto para los grandes, búsqueda bayesiana cuando cada entrenamiento cuesta caro —, siempre sobre el pipeline completo, siempre con CV sobre el train, con la validación anidada como árbitro cuando la cifra debe ser irreprochable y el test intacto para el veredicto final. Con esto, el módulo 7 queda completo: regularización para contener, ensembles y boosting para combinar, redes profundas para los datos no estructurados y optimización sistemática para exprimirlo todo. El equipo de MercaFresh tiene por fin su mejor modelo de churn, afinado y evaluado con honestidad; pero un modelo que vive en un notebook no retiene a ningún cliente. El mejor modelo del mundo no vale nada si no llega a producción, y a eso dedicamos el módulo 8: frameworks, despliegue, monitoreo y las consideraciones éticas de poner machine learning delante de personas reales.
Curso de Machine Learning
Módulo 1: Introducción al Machine Learning
- ¿Qué es el Machine Learning?
- Historia y evolución del Machine Learning
- Tipos de Machine Learning
- Aplicaciones del Machine Learning
- El flujo de trabajo de un proyecto de Machine Learning
Módulo 2: Fundamentos de Estadística y Probabilidad
- Conceptos básicos de estadística
- Distribuciones de probabilidad
- Correlación y covarianza
- Inferencia estadística
- Teorema de Bayes
Módulo 3: Preprocesamiento de Datos
- Limpieza de datos
- Manejo de datos faltantes
- Transformación de datos
- Codificación de variables categóricas
- Normalización y estandarización
- Ingeniería de características
Módulo 4: Algoritmos de Machine Learning Supervisado
- Regresión lineal
- Regresión logística
- Árboles de decisión
- Máquinas de soporte vectorial (SVM)
- K-Vecinos más cercanos (K-NN)
- Naive Bayes
- Redes neuronales
Módulo 5: Algoritmos de Machine Learning No Supervisado
- Clustering: K-means
- Clustering jerárquico
- Análisis de componentes principales (PCA)
- Análisis de agrupamiento DBSCAN
- Visualización de datos con t-SNE y UMAP
Módulo 6: Evaluación y Validación de Modelos
- División de datos: entrenamiento, validación y prueba
- Métricas de evaluación
- Validación cruzada
- Curva ROC y AUC
- Overfitting y underfitting
Módulo 7: Técnicas Avanzadas y Optimización
- Regularización: Ridge, Lasso y Elastic Net
- Ensemble Learning
- Gradient Boosting
- Redes neuronales profundas (Deep Learning)
- Optimización de hiperparámetros
Módulo 8: Implementación y Despliegue de Modelos
- Frameworks y bibliotecas populares
- Implementación de modelos en producción
- Mantenimiento y monitoreo de modelos
- Consideraciones éticas y de privacidad
Módulo 9: Proyectos Prácticos
- Proyecto 1: Predicción de precios de viviendas
- Proyecto 2: Clasificación de imágenes
- Proyecto 3: Análisis de sentimientos en redes sociales
- Proyecto 4: Detección de fraudes
- Proyecto 5: Segmentación de clientes
