Cuando entrenamos un modelo, no alcanza con que funcione bien sobre los datos vistos. Lo importante es que también funcione bien sobre datos nuevos.
En ese camino aparecen dos problemas clásicos:
Entenderlos es fundamental, porque gran parte del trabajo práctico en Machine Learning consiste en encontrar un equilibrio entre ambos.
Hay subajuste cuando el modelo es demasiado simple para capturar el patrón real de los datos.
En ese caso:
Es como intentar explicar una situación compleja con una regla demasiado pobre.
Hay sobreajuste cuando el modelo aprende demasiado los detalles del conjunto de entrenamiento, incluso el ruido o particularidades que no se repiten fuera de esa muestra.
En ese caso:
Una forma simple de detectar estos problemas es comparar el rendimiento en entrenamiento y en prueba.
Imaginemos que un estudiante prepara un examen:
El objetivo real no es memorizar ni quedarse corto, sino comprender lo suficiente para responder bien en casos nuevos.
Vamos a usar un problema de aprobación de estudiantes. Compararemos dos árboles:
Luego observaremos la exactitud en entrenamiento y en prueba para cada caso.
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
datos = pd.DataFrame({
"horas_estudio": [1, 2, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10, 3, 5, 7, 9],
"practicas": [0, 1, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 2, 3, 4, 6],
"aprobo": [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1]
})
X = datos[["horas_estudio", "practicas"]]
y = datos["aprobo"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)
modelo_simple = DecisionTreeClassifier(max_depth=1, random_state=42)
modelo_complejo = DecisionTreeClassifier(random_state=42)
modelo_simple.fit(X_train, y_train)
modelo_complejo.fit(X_train, y_train)
pred_train_simple = modelo_simple.predict(X_train)
pred_test_simple = modelo_simple.predict(X_test)
pred_train_complejo = modelo_complejo.predict(X_train)
pred_test_complejo = modelo_complejo.predict(X_test)
print("Modelo simple")
print("Exactitud en entrenamiento:", accuracy_score(y_train, pred_train_simple))
print("Exactitud en prueba:", accuracy_score(y_test, pred_test_simple))
print("Modelo complejo")
print("Exactitud en entrenamiento:", accuracy_score(y_train, pred_train_complejo))
print("Exactitud en prueba:", accuracy_score(y_test, pred_test_complejo))
nuevo_estudiante = pd.DataFrame({
"horas_estudio": [7],
"practicas": [4]
})
prediccion = modelo_complejo.predict(nuevo_estudiante)[0]
print("Predicción para el nuevo estudiante:", prediccion)
Salida resumida esperada:
Modelo simple
Exactitud en entrenamiento: ...
Exactitud en prueba: ...
Modelo complejo
Exactitud en entrenamiento: ...
Exactitud en prueba: ...
Predicción para el nuevo estudiante: ...
El modelo simple, con profundidad 1, tiene muy poca capacidad. Si los datos requieren una lógica más rica, tenderá a subajustar.
El modelo complejo, en cambio, puede adaptarse mucho mejor al entrenamiento. Pero si se vuelve demasiado específico, puede sobreajustar y perder rendimiento en prueba.
La diferencia entre entrenamiento y prueba es, muchas veces, más informativa que un único número aislado.
Si hay subajuste, suele ayudar:
Si hay sobreajuste, suele ayudar:
DecisionTreeClassifier(max_depth=1): crea un árbol muy simple, útil para mostrar subajuste.DecisionTreeClassifier(): crea un árbol sin límite explícito de profundidad, más propenso a sobreajustar.accuracy_score(y_train, ...): mide qué tan bien rinde en entrenamiento.accuracy_score(y_test, ...): mide qué tan bien generaliza a datos nuevos.