Módulo 3 · Aprendizaje supervisado
Underfitting y overfitting
En esta páginaEl problema
El problema
Un sensor registra la temperatura de un salón durante el día. La variable hora es la hora del día escalada a [0, 1] y desviacion es la diferencia en °C respecto al promedio diario. La señal real sigue una onda, y cada medición trae ruido con desviación estándar de 0.3 °C.
Tienes 25 mediciones para entrenar y 200 mediciones distintas para validar. Ajustaste tres regresiones polinomiales y obtuviste este error de entrenamiento:
| grado del polinomio | RMSE de entrenamiento (°C) |
|---|---|
| 1 | 0.662 |
| 4 | 0.244 |
| 15 | 0.161 |
Alguien propone quedarse con el grado 15 porque tiene el error más bajo.
Entrenamiento y validación
La función generar(n, semilla) simula n mediciones: horas al azar en [0, 1], una onda seno de un periodo y ruido normal de 0.3 °C. La semilla 3 produce las 25 de entrenamiento y la semilla 103 las 200 de validación.
Mostrar códigoOcultar código
import numpy as np
def generar(n, semilla):
rng = np.random.default_rng(semilla)
hora = rng.uniform(0, 1, n)
desviacion = np.sin(2 * np.pi * hora) + rng.normal(0, 0.3, n)
# scikit-learn espera X como tabla (filas, columnas)
return hora.reshape(-1, 1), desviacion
X_ent, y_ent = generar(25, 3)
X_val, y_val = generar(200, 103)
X_ent es una tabla de una sola columna, hora, y y_ent guarda la desviación de cada medición.
print(X_ent.shape, X_val.shape)
print(X_ent[:3].round(3))
print(y_ent[:3].round(3))
La salida esperada está oculta.
PolynomialFeatures(grado) convierte la columna hora en hora, hora², …, hora^grado. Una regresión lineal sobre esas columnas dibuja un polinomio. Con más grado, la curva puede doblarse más veces. Esa flexibilidad es la complejidad del modelo.
make_pipeline encadena los pasos, de modo que fit y predict pasan los datos por cada uno en orden. StandardScaler lleva cada potencia a media 0 y desviación 1.
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
def polinomio(grado):
return make_pipeline(
# sin columna de unos: LinearRegression ya ajusta el intercepto
PolynomialFeatures(grado, include_bias=False),
StandardScaler(),
LinearRegression(),
)
La función rmse_ent_val entrena un modelo con las 25 mediciones y mide el RMSE en las mismas 25 y en las 200 de validación. root_mean_squared_error recibe los valores reales y los predichos.
from sklearn.metrics import root_mean_squared_error
def rmse_ent_val(modelo):
modelo.fit(X_ent, y_ent)
ent = root_mean_squared_error(y_ent, modelo.predict(X_ent))
val = root_mean_squared_error(y_val, modelo.predict(X_val))
return ent, val
Pregunta. Ordena los tres grados por error de validación, del menor al mayor. Decide también si algún grado tendrá error de validación mayor que 1 °C.
Ver resultado
Cada fila es un grado entrenado con rmse_ent_val(polinomio(grado)). La segunda columna es el RMSE en las 25 mediciones de entrenamiento y la tercera en las 200 de validación. Por ejemplo, la fila con 15, 0.161 y 1.479 dice que el error típico (RMSE) del grado 15 es 0.161 °C en los puntos que vio y 1.479 °C en los nuevos.
| grado | RMSE de entrenamiento (°C) | RMSE de validación (°C) |
|---|---|---|
| 1 | 0.662 | 0.538 |
| 4 | 0.244 | 0.371 |
| 15 | 0.161 | 1.479 |
Curva de complejidad
Tres grados son pocos puntos para ver una tendencia. Una curva de complejidad repite la misma medición para cada valor del hiperparámetro que controla la flexibilidad. Al final, np.argmin devuelve la posición del menor error de validación, y esa posición indexa la lista grados.
Pregunta. Al pasar del grado 1 al 15, ¿el error de entrenamiento puede subir en algún paso? ¿En qué zona de grados estará el mínimo de validación?
grados = list(range(1, 16))
curva = [rmse_ent_val(polinomio(grado)) for grado in grados]
print("grado entrenamiento validación")
for grado, (ent, val) in zip(grados, curva):
print(f"{grado:>5} {ent:>13.3f} {val:>10.3f}")
errores_val = [val for _, val in curva]
mejor_grado = grados[int(np.argmin(errores_val))]
print("mejor grado en validación:", mejor_grado)
La salida esperada está oculta.
Ajuste polinomial por grado
Mueve el grado y compara los dos errores en la curva de complejidad.
Los 10 puntos están dentro de los 20, y los 20 dentro de los 40. El ruido escala la distancia de cada punto a la señal real, sin cambiar su posición en x.
Curva de aprendizaje
Una curva de aprendizaje fija la complejidad y aumenta la cantidad de datos de entrenamiento. Aquí se usa un conjunto nuevo de 400 mediciones con validación cruzada de 5 folds, así que el máximo de entrenamiento es 320. KFold(n_splits=5, shuffle=True) mezcla las filas antes de repartirlas en 5 folds, y random_state fija esa mezcla.
from sklearn.model_selection import KFold, learning_curve
X_pool, y_pool = generar(400, 7)
folds = KFold(n_splits=5, shuffle=True, random_state=0)
learning_curve entrena con subconjuntos de tamaño creciente dentro de cada fold y devuelve un puntaje de entrenamiento y uno de validación por tamaño y por fold. train_sizes fija esos tamaños. El código promedia sobre los folds con mean(axis=1).
scoring="neg_root_mean_squared_error" devuelve el RMSE con signo negativo, porque scikit-learn trata cualquier puntaje como «más alto es mejor». El código cambia el signo antes de imprimir.
Pregunta. Compara el grado 1 y el grado 9. ¿Cuál de los dos baja su error de validación cerca de 0.3 al llegar a 320 mediciones? ¿Qué le pasa a la brecha de cada uno?
for grado in [1, 9]:
tamanos, ent, val = learning_curve(
polinomio(grado),
X_pool,
y_pool,
train_sizes=[30, 60, 120, 240, 320],
cv=folds,
scoring="neg_root_mean_squared_error",
shuffle=True, # mezcla antes de tomar cada subconjunto
random_state=0,
)
print(f"grado {grado}")
for n, e, v in zip(tamanos, -ent.mean(axis=1), -val.mean(axis=1)):
print(f" n={n:>3} entrenamiento {e:.3f} validación {v:.3f}")
La salida esperada está oculta.
Regularización
Bajar el grado no es la única forma de reducir complejidad. Ridge minimiza el error cuadrático más alpha veces la suma de los coeficientes al cuadrado. Un alpha grande obliga a usar coeficientes pequeños, y la curva se dobla menos aunque tenga las 15 potencias. StandardScaler va antes de Ridge para que la penalización trate por igual a hora y a hora^15.
Pregunta. Con grado 15 fijo, ¿qué error cambia de forma monótona al aumentar alpha y en qué dirección? ¿El error de validación mejora sin límite?
from sklearn.linear_model import Ridge
def polinomio_ridge(grado, alpha):
return make_pipeline(
PolynomialFeatures(grado, include_bias=False),
StandardScaler(),
Ridge(alpha=alpha),
)
print(" alpha entrenamiento validación")
for alpha in [1e-4, 1e-2, 1e-1, 1, 10, 100]:
ent, val = rmse_ent_val(polinomio_ridge(15, alpha))
print(f"{alpha:>8g} {ent:>13.3f} {val:>10.3f}")
La salida esperada está oculta.
Diagnóstico y acciones
La misma lectura aplica a otras familias. En un árbol de regresión, max_depth controla cuántas divisiones seguidas puede hacer. max_depth=None no pone límite.
Pregunta. Para max_depth=1, max_depth=4 y sin límite, anota qué modelo subajusta, cuál sobreajusta y cuál tendrá RMSE de entrenamiento exactamente 0.
from sklearn.tree import DecisionTreeRegressor
for profundidad in [1, 4, None]:
arbol = DecisionTreeRegressor(max_depth=profundidad, random_state=0)
ent, val = rmse_ent_val(arbol)
print(f"max_depth={profundidad}: entrenamiento {ent:.3f} validación {val:.3f}")
La salida esperada está oculta.
Los tres diagnósticos de la lección se resumen en esta tabla:
| síntoma | diagnóstico | cambios que ayudan | cambios que no ayudan |
|---|---|---|---|
| entrenamiento y validación altos y parecidos | subajuste | más grado o profundidad, mejores features, menos alpha | más datos del mismo tipo |
| entrenamiento bajo, validación mucho más alta (brecha) | sobreajuste | menos grado o profundidad, más alpha, min_samples_leaf mayor, más datos | más capacidad |
| entrenamiento y validación bajos, cerca del nivel de ruido | ajuste adecuado | confirmar en holdout | seguir bajando el error de entrenamiento |
Práctica
Traza guiada
K vecinos promedia las k mediciones de entrenamiento más cercanas. Antes de ejecutar, predice para k=1 y k=25: cuál tiene RMSE de entrenamiento exactamente 0, cuál predice lo mismo para cualquier hora y qué diagnóstico corresponde a cada uno.
from sklearn.neighbors import KNeighborsRegressor
vecino_unico = KNeighborsRegressor(n_neighbors=1)
todos_los_vecinos = KNeighborsRegressor(n_neighbors=25)
Comprueba con rmse_ent_val y compara el error de entrenamiento de k=25 con y_ent.std().
Ruido mayor
Cambia la desviación estándar del ruido en generar de 0.3 a 0.6 y repite la curva de complejidad. Antes de procesar, predice si el mejor grado sube o baja y qué valor aproximado tendrá el mínimo de validación. Explica el resultado con el nivel de ruido.
Curva de profundidad
Con load_breast_cancer(return_X_y=True), construye la curva de complejidad de DecisionTreeClassifier(random_state=0) para max_depth de 1 a 10. Usa validation_curve con param_name="max_depth" y StratifiedKFold(n_splits=5, shuffle=True, random_state=0). El puntaje por defecto es exactitud, así que aquí más alto es mejor y la brecha aparece con entrenamiento por encima de validación. Escribe:
- la profundidad con mejor exactitud media de validación
- la exactitud de entrenamiento del árbol más profundo y lo que indica
- el diagnóstico para
max_depth=1y paramax_depth=10 - un cambio concreto para el árbol más profundo
Reto opcional
Con los 25 puntos de entrenamiento, usa GridSearchCV sobre polinomio_ridge para elegir a la vez polynomialfeatures__degree y ridge__alpha. Compara el error en las 200 mediciones de validación con el del grado 3 sin regularización.
Errores que revisar
- Si eliges el modelo con menor error de entrenamiento, siempre gana el más complejo. El grado 15 tiene el menor error de entrenamiento y el peor de validación.
- Si ambos errores son altos y parecidos, más datos no los bajan. El grado 1 se estanca en 0.518 con 320 mediciones. Aumenta la capacidad o reduce
alpha. - Si
learning_curveovalidation_curvedevuelven números negativos conneg_root_mean_squared_error, cambia el signo antes de comparar. - Si el efecto de
alphaparece arbitrario, revisa queStandardScaleresté antes deRidge. Sin escalar, la penalización castiga distinto a columnas con escalas distintas. - Si un punto aislado de la curva salta, como el grado 12 frente al 13, no lo leas como tendencia. Con pocos datos, mira la forma general o usa validación cruzada.
Cierre
Con ruido de 0.3 °C, el modelo A tiene RMSE de 0.05 en entrenamiento y 0.60 en validación, y el modelo B tiene 0.55 y 0.57. Para cada uno, da el diagnóstico, un cambio que ayudaría y un cambio que no ayudaría.
Lista de comprobación
- ¿Puedes explicar por qué el error de entrenamiento no basta para elegir el grado?
- ¿Qué forma toma el error de validación al aumentar la complejidad y qué hay a cada lado del mínimo?
- ¿Qué curva de aprendizaje indica que conseguir más datos es útil?
- ¿Por qué aumentar
alphasube el error de entrenamiento?
Documentación oficial
- Curvas de validación y de aprendizaje:
validation_curve,learning_curvey su lectura para detectar subajuste y sobreajuste. - Underfitting vs. Overfitting: ejemplo con regresión polinomial de distintos grados.
- Regresión Ridge: penalización L2 y papel de
alpha. - Features polinomiales:
PolynomialFeaturesy las columnas que genera.