20. Ajuste de hiperparámetros con GridSearchCV

20.1 El problema de elegir parámetros a ojo

Muchos modelos de Machine Learning tienen parámetros que debemos definir antes del entrenamiento. Por ejemplo:

  • cuántos vecinos usar en KNN;
  • qué profundidad darle a un árbol;
  • qué valor usar para C en SVM.

Esos valores no se aprenden automáticamente a partir del dataset. Debemos elegirlos nosotros. A esos valores se los llama hiperparámetros.

20.2 Qué es un hiperparámetro

Conviene distinguir dos ideas:

  • Parámetros del modelo: se aprenden durante el entrenamiento.
  • Hiperparámetros: se fijan antes de entrenar y condicionan cómo aprende el modelo.

Por ejemplo, en KNN el valor de n_neighbors es un hiperparámetro. El algoritmo no lo descubre solo: nosotros debemos decidirlo.

20.3 Por qué no conviene probar a mano sin método

Podríamos entrenar un modelo con distintos valores y anotar resultados. Pero ese enfoque se vuelve desordenado rápidamente:

  • es fácil olvidar qué combinación se probó;
  • es fácil mezclar resultados de distintas corridas;
  • no escala cuando hay varios hiperparámetros al mismo tiempo.

Scikit-learn ofrece una herramienta para sistematizar este proceso: GridSearchCV.

20.4 Qué hace GridSearchCV

GridSearchCV prueba automáticamente varias combinaciones de hiperparámetros y evalúa cada una con validación cruzada.

La idea general es:

  • definir un modelo o un pipeline;
  • definir una grilla de valores posibles;
  • hacer que Scikit-learn pruebe todas las combinaciones;
  • quedarse con la mejor según una métrica elegida.

20.5 Por qué suele combinarse con Pipeline

Cuando el flujo incluye preprocesamiento y modelo, lo más seguro es envolver todo en un Pipeline y aplicar la búsqueda sobre ese flujo completo.

Así, cada combinación de parámetros se evalúa respetando correctamente el preprocesamiento dentro de cada partición de validación cruzada.

20.6 Ejemplo muy claro: buscar el mejor K en KNN

Vamos a usar un pipeline con escalado y KNN para predecir si un cliente compra. Probaremos varias combinaciones de:

  • n_neighbors;
  • weights.

Luego veremos cuál fue la mejor y usaremos ese modelo para predecir un nuevo caso.

import pandas as pd
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score

clientes = pd.DataFrame({
    "paginas_vistas": [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 5, 8],
    "minutos_sitio": [1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 3, 4],
    "compra": [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1]
})

X = clientes[["paginas_vistas", "minutos_sitio"]]
y = clientes["compra"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42, stratify=y
)

pipeline = Pipeline([
    ("escalador", StandardScaler()),
    ("knn", KNeighborsClassifier())
])

parametros = {
    "knn__n_neighbors": [1, 3, 5],
    "knn__weights": ["uniform", "distance"]
}

busqueda = GridSearchCV(
    estimator=pipeline,
    param_grid=parametros,
    cv=3,
    scoring="accuracy"
)

busqueda.fit(X_train, y_train)

mejor_modelo = busqueda.best_estimator_
y_pred = mejor_modelo.predict(X_test)

print("Mejores parámetros:", busqueda.best_params_)
print("Mejor score en validación:", busqueda.best_score_)
print("Exactitud en prueba:", accuracy_score(y_test, y_pred))

nuevo_cliente = pd.DataFrame({
    "paginas_vistas": [7],
    "minutos_sitio": [4]
})

prediccion = mejor_modelo.predict(nuevo_cliente)[0]
probabilidad = mejor_modelo.predict_proba(nuevo_cliente)[0, 1]

print("Predicción para el nuevo cliente:", prediccion)
print(f"Probabilidad de compra: {probabilidad:.3f}")

Salida resumida esperada:

Mejores parámetros: {...}
Mejor score en validación: ...
Exactitud en prueba: ...
Predicción para el nuevo cliente: ...
Probabilidad de compra: ...

20.7 Qué está pasando en el ejemplo

El programa no prueba un solo modelo, sino varios. Cada combinación de la grilla se evalúa con validación cruzada y recibe un puntaje medio.

Al final, GridSearchCV guarda la mejor combinación y la expone en best_params_ y best_estimator_.

20.8 Cómo leer los nombres de parámetros en un Pipeline

En la grilla aparece esta notación:

knn__n_neighbors
knn__weights

Eso significa:

  • knn: nombre del paso dentro del pipeline;
  • n_neighbors o weights: hiperparámetro interno de ese modelo.

El doble guion bajo __ es la forma estándar de Scikit-learn para referirse a parámetros dentro de pipelines.

20.9 Explicación detallada del código

  • GridSearchCV(...): organiza y ejecuta la búsqueda de combinaciones.
  • param_grid: define la grilla de valores a probar.
  • cv=3: indica cuántas particiones usar en validación cruzada.
  • best_params_: devuelve la mejor combinación encontrada.
  • best_estimator_: devuelve el flujo ya configurado con esos parámetros.

20.10 Ventajas y límites

Ventajas:

  • ordena la búsqueda de hiperparámetros;
  • evita pruebas manuales desprolijas;
  • se integra naturalmente con validación cruzada.

Límites:

  • si la grilla es muy grande, puede tardar bastante;
  • no garantiza encontrar la combinación perfecta universal, solo la mejor dentro de la grilla definida.

20.11 Errores frecuentes

  • Definir una grilla enorme sin criterio: puede volver la búsqueda muy lenta.
  • Buscar hiperparámetros sin pipeline cuando hay preprocesamiento: puede introducir errores metodológicos.
  • Mirar solo el mejor score de validación: conviene revisar también el rendimiento final en prueba.
  • Confundir hiperparámetros con parámetros aprendidos: no son lo mismo.

20.12 Qué deberías retener

  • Los hiperparámetros se definen antes de entrenar.
  • GridSearchCV prueba combinaciones de forma sistemática.
  • La validación cruzada ayuda a comparar esas combinaciones con más criterio.
  • Cuando hay preprocesamiento, conviene incluirlo dentro de un pipeline.
  • La mejor combinación es la mejor dentro de la grilla que decidimos explorar.