Saltar al contenido

# fundamentos-de-scikit-learn

Fundamentos de Scikit-learn

Volver a la portada del curso
Lectura (Markdown)

Grid Search para Optimización

Sección 5 · Tuning de Hiperparámetros

Grid Search para optimización

Un RandomForestClassifier trae decenas de perillas: cuántos árboles, qué tan profundos, cuántas entregas mínimas por hoja. Los valores por omisión son razonables, pero no están pensados para las entregas de PyEnvíos. Probar combinaciones a mano, mirando el test cada vez, es la forma más rápida de engañarse. GridSearchCV automatiza esa búsqueda con validación cruzada y deja el test intacto para el final.

Parámetros e hiperparámetros

Los parámetros los aprende el modelo con fit: los coeficientes de la regresión logística, los cortes de cada árbol. Terminan en guion bajo (coef_). Los hiperparámetros los eliges tú antes de entrenar y se pasan al constructor: n_estimators, max_depth, min_samples_leaf, el C de la logística. Buscar hiperparámetros es entrenar muchas veces con valores distintos y quedarse con los que mejor generalizan.

La rejilla y los nombres paso__parametro

El modelo vive dentro de un Pipeline con pre, así que para llegar a un hiperparámetro hay que decir en qué paso está: el nombre del paso, dos guiones bajos y el hiperparámetro. pipe.get_params().keys() lista todos los nombres válidos.

rf = Pipeline([("pre", pre),
               ("modelo", RandomForestClassifier(random_state=42))])
grid = {
    "modelo__n_estimators": [100, 300],
    "modelo__max_depth": [4, 8, None],
    "modelo__min_samples_leaf": [1, 5, 20],
}
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
gs = GridSearchCV(rf, grid, cv=skf, scoring="roc_auc", n_jobs=-1)
gs.fit(X_tr, y_tr)

La rejilla tiene $2 \times 3 \times 3 = 18$ combinaciones. Cada una se evalúa con 5 pliegues, así que se entrenan $18 \times 5 = 90$ modelos, más uno final. El costo es el producto de todas las listas: agregar un cuarto hiperparámetro con 4 valores lo multiplica por 4. n_jobs=-1 reparte los ajustes entre todos los núcleos. Se usa scoring="roc_auc" porque, como viste en métricas, la exactitud premia al modelo que nunca avisa.

Leer el resultado

print(gs.best_params_)
print(round(gs.best_score_, 4))     # 0.9354
res = pd.DataFrame(gs.cv_results_)
cols = ["params", "mean_test_score", "rank_test_score"]
print(res[cols].sort_values("rank_test_score").head())
  • best_params_: la mejor combinación, max_depth=None, min_samples_leaf=5, n_estimators=100.
  • best_score_: el AUC medio de validación cruzada de esa combinación, 0.9354. No es una cifra de test.
  • cv_results_: un diccionario con una fila por combinación (params, mean_test_score, std_test_score, rank_test_score, tiempos y la nota de cada pliegue). En DataFrame se ordena por rango. La segunda combinación (profundidad 8, hoja 1, 300 árboles) da 0.9336: la diferencia con la primera es menor que la variación entre pliegues.

refit y la evaluación final

Con refit=True (el valor por omisión), al terminar la búsqueda GridSearchCV vuelve a entrenar la mejor combinación con todo X_tr y la guarda en best_estimator_. Por eso el propio objeto gs sabe predecir. Ahora, y solo ahora, se mira el test, una vez:

proba = gs.predict_proba(X_te)[:, 1]
print(round(roc_auc_score(y_te, proba), 4))   # 0.9108
print(round(gs.score(X_te, y_te), 4))         # 0.9108 (AUC)

El AUC de test (0.9108) es menor que el de validación cruzada (0.9354). Es normal: al elegir la mejor de 18 combinaciones, el best_score_ queda un poco optimista. Fíjate en la segunda línea: gs.score usa el scoring de la búsqueda, así que devuelve AUC, no exactitud.

No siempre la búsqueda cambia mucho. Con la regresión logística y {"modelo__C": [0.01, 0.1, 1, 10, 100]}, el mejor es C=1 (el valor por omisión) con 0.9317, y desde C=0.1 la curva es casi plana (0.9313 a 0.9317). Una búsqueda que confirma el valor por omisión también es información.

Trampas comunes

  • Buscar con el test. Si eliges hiperparámetros mirando el test, su cifra deja de ser una estimación honesta.
  • Preprocesar fuera del pipeline. Escalar X_tr completo antes de la búsqueda filtra información de cada pliegue de validación al de entrenamiento.
  • Reportar best_score_ como resultado final. Es una cifra de selección; la del modelo es la del test.
  • Rejillas enormes. 4 valores en 4 hiperparámetros ya son 256 combinaciones y 1280 ajustes; para eso está la búsqueda aleatoria de la próxima lección.
  • Nombres mal escritos. max_depth sin el prefijo modelo__ lanza ValueError: Invalid parameter.

El notebook busqueda-de-hiperparametros repite esta búsqueda paso a paso con la salida a la vista. Empieza con rejillas pequeñas y valores espaciados en escala logarítmica; afina solo alrededor de lo que funcionó.

Recursos