Módulo 3 · Aprendizaje supervisado

Lección 5 de 9

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 polinomioRMSE de entrenamiento (°C)
10.662
40.244
150.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ódigoPython
Python 3 · ejemplo reproducible
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.

Python 3 · ejemplo reproducible
print(X_ent.shape, X_val.shape)
print(X_ent[:3].round(3))
print(y_ent[:3].round(3))
Salida esperada

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.

Python 3 · ejemplo reproducible
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.

Python 3 · ejemplo reproducible
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.

gradoRMSE de entrenamiento (°C)RMSE de validación (°C)
10.6620.538
40.2440.371
150.1611.479
Lectura. El grado 4 tiene el menor error de validación. El grado 15 tiene el menor error de entrenamiento y el peor de validación: 1.479 °C, casi cinco veces el ruido. Ese modelo sobreajusta: aprendió el ruido de las 25 mediciones. El grado 1 subajusta: una recta no sigue la onda, y sus dos errores quedan lejos de 0.3. Su error de entrenamiento supera al de validación porque 25 mediciones son pocas y el azar pesa. El diagnóstico sale de comparar los dos errores con el nivel de ruido, no de un solo número.

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?

Python 3 · ejemplo reproducible
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)
Salida esperada

La salida esperada está oculta.

Explorador

Ajuste polinomial por grado

Mueve el grado y compara los dos errores en la curva de complejidad.

Parámetros
Puntos de entrenamiento

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.

Grado 4: RMSE de entrenamiento 0.29 y de validación 0.34. Zona del mínimo de validación: grado 4, RMSE 0.34.

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.

Python 3 · ejemplo reproducible
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?

Python 3 · ejemplo reproducible
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}")
Salida esperada

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?

Python 3 · ejemplo reproducible
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}")
Salida esperada

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.

Python 3 · ejemplo reproducible
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}")
Salida esperada

La salida esperada está oculta.

Los tres diagnósticos de la lección se resumen en esta tabla:

síntomadiagnósticocambios que ayudancambios que no ayudan
entrenamiento y validación altos y parecidossubajustemás grado o profundidad, mejores features, menos alphamás datos del mismo tipo
entrenamiento bajo, validación mucho más alta (brecha)sobreajustemenos grado o profundidad, más alpha, min_samples_leaf mayor, más datosmás capacidad
entrenamiento y validación bajos, cerca del nivel de ruidoajuste adecuadoconfirmar en holdoutseguir 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.

Python 3 · ejemplo reproducible
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:

  1. la profundidad con mejor exactitud media de validación
  2. la exactitud de entrenamiento del árbol más profundo y lo que indica
  3. el diagnóstico para max_depth=1 y para max_depth=10
  4. 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_curve o validation_curve devuelven números negativos con neg_root_mean_squared_error, cambia el signo antes de comparar.
  • Si el efecto de alpha parece arbitrario, revisa que StandardScaler esté antes de Ridge. 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 alpha sube el error de entrenamiento?

Documentación oficial

Cargando progreso…