Machine learning desde cero · Lección 3.5 · 18 de 33 lecciones listas · 14 min
Ajustar hiperparámetros sin hacer trampa
En pocas palabras
El mejor de 9 ajustes del árbol saca un 93,7 % en validación cruzada y un 88,3 % con biopsias nuevas. Elegir al ganador infla su resultado.
Calentamiento Dos preguntas de lecciones anteriores. Un minuto, y no bloquea la lección.
En la lección 3.2 la validación cruzada evaluaba un solo ajuste del modelo. En la práctica pruebas muchos y te quedas con el mejor. Eso se llama ajuste de hiperparámetros. Los ajustes que eliges antes de entrenar (la profundidad de un árbol, la fuerza de la penalización α de la lección 3.4) son hiperparámetros: el modelo no los aprende de los datos, los eliges tú. Hoy verás que el resultado del ganador del ajuste es demasiado optimista, y por qué.
Elige una respuesta y tu grado de seguridad.
Responde para desbloquear el experimento.
Experimento interactivo
Este experimento interactivo funciona en un navegador con JavaScript activado. El texto que lo rodea describe lo que muestra.
Ver la respuesta
Respuesta correcta: Menos, por ejemplo un 88,3 %, porque también elegimos ruido favorable
Con las 171 biopsias de prueba el ganador saca un 88,3 %, 5,4 puntos menos que en validación cruzada. Una parte es selección, otra es lo pequeña que es la prueba: una biopsia vale 0,6 puntos. Elegir la mejor de 20 semillas de partición infla el resultado en 2,7 puntos. Esta partición está del lado pesimista: con otras 50 particiones (código de abajo) el ganador pierde de media 1,4 puntos.
Prueba
- Con la rejilla, sube el presupuesto de 4 a 9. ¿Cuánto saca el ganador en validación cruzada y cuánto en la prueba?
- Cambia a búsqueda aleatoria y pulsa «nuevo sorteo» varias veces. ¿Es el ganador alguna vez mejor en la prueba que en validación?
- En la segunda parte mueve el control de semillas de 1 a 20. ¿Cómo crece el mejor resultado y cuánto se aleja de la media de 200 particiones?
Cuando lo hayas comprobado en el lab, pongamos nombre a lo que ves.
La idea en palabras sencillas
Ajustar es comparar muchos ajustes con datos de validación y elegir el mejor. La búsqueda en rejilla prueba todas las combinaciones de unos pocos valores. La búsqueda aleatoria sortea combinaciones de todo el rango. Con el mismo presupuesto, una rejilla 3×3 prueba solo 3 profundidades: 1, 6 y 12. Nueve intentos al azar suelen ver más valores distintos de cada hiperparámetro.
Cada resultado de validación es la calidad real del ajuste más un poco de suerte: qué biopsias cayeron en qué parte. Cuando eliges el mejor resultado, eliges también el ajuste al que más favoreció la suerte. Con datos nuevos esa suerte ya no está. Es la maldición del ganador. Crece con el número de ajustes que comparas y se reduce con el tamaño de los datos.
Lo mismo pasa cuando buscas la mejor partición de los datos. La misma regresión logística en 200 particiones aleatorias saca de media un 93,8 % en la prueba. Si pruebas las semillas 1–20 y publicas la mejor, sale un 96,5 %, 2,7 puntos más. El código es el mismo; solo elegiste la suerte.
Jak to policzono
- Biopsias: Breast Cancer Wisconsin (Diagnostic), UCI, licencia CC BY 4.0. 569 biopsias, 10 medidas «mean» de cada una.
- Ajuste: partición estratificada, 398 biopsias para ajustar y 171 de prueba. Un árbol CART (Gini), 10 profundidades por 11 números mínimos de biopsias por hoja, es decir, 110 ajustes, cada uno en validación cruzada de 5 particiones con las 398 biopsias.
- Ganador: la mejor media de las 5 partes; en caso de empate, el árbol más simple. En la prueba se evalúa el árbol entrenado con las 398 biopsias.
- Semillas: regresión logística (C = 1) con medidas estandarizadas, 200 particiones aleatorias 70/30, con 171 biopsias de prueba en cada una.
Por qué funciona
Toma 9 ajustes de calidad real casi igual. Sus resultados de validación se diferencian sobre todo por ruido. El mayor de 9 resultados es casi siempre uno en el que el ruido fue positivo. En el lab la mejor CV acierta 373 de 398 biopsias, y varios ajustes están solo una o dos biopsias por detrás. Diferencias tan pequeñas son en buena parte azar.
La selección se ve en el modo aleatorio: en 200 sorteos de 9 ajustes, el ganador saca más en validación que en la prueba en todos y cada uno, 3,6 puntos de media. La prueba no participó en la elección, así que no lleva selección, solo el ruido normal de un conjunto finito. Pero los 200 sorteos son ajustes distintos sobre una misma partición de los datos, y esa partición cayó del lado pesimista: incluso sin elegir nada, los 110 ajustes sacan de media 0,6 puntos más en validación que en la prueba. En el código de abajo, con 50 particiones distintas, el ganador pierde de media 1,4 puntos, y en 17 de las 50 particiones la validación no queda por encima de la prueba.
Por eso la prueba no puede usarse para elegir. Si eligieras un ajuste por su resultado de prueba, la mejor celda sacaría un 92,4 % en la prueba. Ese número también estaría inflado, solo que costaría más notarlo, porque no quedarían datos para comprobarlo.
Error frecuente
Verdadero o falso: «el resultado del ganador del ajuste es una previsión para datos nuevos». Falso. El resultado de validación del ganador está inflado, porque es el máximo de muchos números con ruido. En el lab el ganador de 9 ajustes tiene un 93,7 % en validación y un 88,3 % en la prueba. En el código de abajo, con 50 particiones y 110 ajustes, el ganador saca de media un 93,3 % en validación y un 91,9 % en la prueba reservada.
Una previsión justa sale por uno de dos caminos. Uno: una prueba reservada que no usas para ninguna elección y miras una vez, al final. Dos: la validación cruzada anidada, es decir, todo el ajuste repetido dentro de cada parte de una validación cruzada exterior. Con las 569 biopsias el ganador saca un 94,0 % en la validación cruzada normal, mientras que la anidada da un 91,9 %. El segundo número está más cerca de lo que hará el modelo mañana.
Test
Tres preguntas para comprobar lo aprendido. Elige una respuesta o despliega la solución bajo cada pregunta. Con el teclado: las flechas eligen, Enter o Espacio confirma.
Ideas clave
- Los hiperparámetros los eliges tú, antes de entrenar; se ajustan en validación, nunca en la prueba.
- El resultado del ganador del ajuste está inflado: al elegir el mejor resultado también eliges la suerte (la maldición del ganador).
- Elegir la mejor semilla de partición también es ajustar con el conjunto de prueba.
- Una previsión justa sale de una prueba reservada que se mira una vez o de la validación cruzada anidada.
Más a fondo: fórmula, código, práctica
Las matemáticas
Sea μₖ la calidad real del ajuste k y Ŝₖ = μₖ + εₖ su resultado de validación, donde el ruido εₖ tiene media 0. Para un ajuste E[Ŝₖ] = μₖ. Para el ganador k = argmaxₖ Ŝₖ, en promedio E[Ŝ_k] ≥ μ_k*: el máximo de varios números con ruido es en promedio mayor que la calidad real del elegido. Con m ajustes de igual calidad, E[max εₖ] crece con m más o menos como σ · √(2 · ln m).
Una biopsia de prueba con n = 171 es 1/171, es decir, 0,6 puntos. El error típico de una exactitud p con n ejemplos: √(p(1 − p)/n).
Validación cruzada anidada: un bucle exterior divide los datos en K partes; para cada parte, todo el ajuste (una validación cruzada interior) se hace solo con las demás, y el modelo elegido se evalúa con la parte reservada.
En código (Python)
El mismo experimento en scikit-learn: la maldición del ganador en 50 particiones con los 110 ajustes del árbol, la validación cruzada normal y la anidada con todas las biopsias, y la mejor de 20 semillas de partición.
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold, cross_val_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
X, y = load_breast_cancer(return_X_y=True)
X = X[:, :10] # las 10 medidas «mean», como en el lab
grid = {"max_depth": [1, 2, 3, 4, 5, 6, 7, 8, 10, 12], "min_samples_leaf": [1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48]} # los 110 ajustes del lab
tree = DecisionTreeClassifier(random_state=0)
r = []
for s in range(50):
tr, te = train_test_split(np.arange(len(y)), test_size=0.3, stratify=y, random_state=s)
search = GridSearchCV(tree, grid, cv=StratifiedKFold(5, shuffle=True, random_state=s)).fit(X[tr], y[tr])
r.append((search.best_score_, search.score(X[te], y[te]))) # la CV del ganador y luego el ganador en la prueba reservada
r = np.array(r)
print(f"winner of 110 settings, 50 splits: CV {r[:, 0].mean():.1%}, held-out test {r[:, 1].mean():.1%}; CV higher in {(r[:, 0] > r[:, 1]).sum()} of 50")
outer = StratifiedKFold(5, shuffle=True, random_state=0)
flat = GridSearchCV(tree, grid, cv=outer).fit(X, y).best_score_ # el resultado del ganador en las mismas partes que lo eligieron
nested = cross_val_score(GridSearchCV(tree, grid, cv=StratifiedKFold(5, shuffle=True, random_state=1)), X, y, cv=outer).mean() # la búsqueda se hace dentro de cada parte exterior
print(f"all 569 biopsies: winner's CV {flat:.1%}, nested CV {nested:.1%}")
model = make_pipeline(StandardScaler(), LogisticRegression())
acc = []
for s in range(200): # 200 particiones aleatorias, 30 % de prueba, como en el lab
tr, te = train_test_split(np.arange(len(y)), test_size=0.3, random_state=s)
acc.append(model.fit(X[tr], y[tr]).score(X[te], y[te]))
acc = np.array(acc)
best = acc.reshape(10, 20).max(axis=1) # diez personas, cada una prueba 20 semillas y publica la mejor
print(f"logistic regression, 200 seeds: mean {acc.mean():.1%}; best of 20 seeds {best.mean():.1%} on average (+{(best.mean() - acc.mean()) * 100:.1f} points), higher in {(best > acc.mean()).sum()} of 10")
Resultado
winner of 110 settings, 50 splits: CV 93.3%, held-out test 91.9%; CV higher in 33 of 50
all 569 biopsies: winner's CV 94.0%, nested CV 91.9%
logistic regression, 200 seeds: mean 93.7%; best of 20 seeds 96.4% on average (+2.6 points), higher in 10 of 10
Las particiones son otras que en el lab, así que los números también. El cuadro es el mismo: el ganador del ajuste saca de media un 93,3 % en validación y un 91,9 % en la prueba reservada; la validación es mayor en 33 particiones de 50. La validación cruzada anidada da un 91,9 % en lugar de un 94,0 %, y la mejor de 20 semillas infla el resultado en 2,6 puntos, para cada una de las 10 personas.
Descargar el notebook (.ipynb). Ábrelo en Jupyter o súbelo a Google Colab; descarga por sí solo los datos que falten.
Para profesionales
- En scikit-learn:
GridSearchCVyRandomizedSearchCV. Elbest_score_está inflado por la misma razón que en el lab; evalúa al ganador conscoreen una prueba reservada. - Validación cruzada anidada:
cross_val_score(GridSearchCV(...), X, y, cv=...). Así el ajuste se hace dentro de cada parte exterior. - Con muchos hiperparámetros de los que solo importan unos pocos, la búsqueda aleatoria suele encontrar un buen ajuste antes que la rejilla (Bergstra y Bengio, 2012).
- Fija la semilla aleatoria una vez, antes del experimento, y anótala. Da la media y la dispersión de varias particiones o de la validación cruzada, no el resultado de la mejor semilla.
¿Te ha sido útil?
¡Gracias! Nos ayuda a mejorar.
Profundiza en el Atlas
Entradas de la enciclopedia sobre las ideas de esta lección: