Aprendizaje supervisado
"El aprendizaje automático es estadística con mejor marketing." — breve y brutalmente honesto.
Qué vas a aprender en este capítulo
El aprendizaje supervisado es el tipo de IA más usado en la industria: se le da al modelo datos con respuestas conocidas (ejemplos etiquetados), y aprende a predecir la respuesta para ejemplos nuevos. Desde filtros de spam hasta diagnósticos médicos.
El proyecto: predecir si un cliente va a pedir postre basándose en su historial — así La Esquina puede ofrecer la sugerencia en el momento correcto.
2.1 El paradigma supervisado
💡 Intuición
Un niño aprende qué es un "perro" porque los adultos le muestran fotos de perros y no-perros, y le dicen cuál es cuál. Después de ver suficientes ejemplos, el niño puede clasificar fotos nuevas.
El aprendizaje supervisado funciona igual: ejemplos con etiquetas → modelo aprende patrones → predice etiquetas para ejemplos nuevos.
📐 Fundamento
Definición formal:
Dado un conjunto de entrenamiento donde:
- es el vector de características (features)
- es la etiqueta (label)
El objetivo es aprender una función tal que para ejemplos nuevos.
Tipos de problemas:
| Tipo | Ejemplo | |
|---|---|---|
| Regresión | Número continuo | Precio de un pedido |
| Clasificación binaria | {0, 1} | ¿El cliente deja propina? |
| Clasificación multiclase | {clase_1, ..., clase_k} | Tipo de platillo más probable |
El pipeline estándar:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# 1. Cargar datos
df = pd.read_csv('historial_pedidos.csv')
X = df[['hora_dia', 'num_personas', 'gasto_previo', 'es_fin_semana']]
y = df['pidio_postre'] # 0 o 1
# 2. Dividir en entrenamiento y prueba
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 3. Escalar features (muchos modelos lo necesitan)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # aprende media/std del train
X_test_scaled = scaler.transform(X_test) # aplica la misma escala
# 4. Entrenar modelo
# 5. Evaluar
Por qué dividir train/test:
Si evaluamos el modelo en los mismos datos con que entrenó, no sabemos si generalizó o simplemente memorizó. El test set simula datos nunca vistos — cómo se comportará en producción.
2.2 K-Nearest Neighbors (KNN)
💡 Intuición
KNN es el algoritmo más intuitivo: para clasificar un nuevo ejemplo, buscás los K ejemplos más similares en los datos de entrenamiento y votás por la mayoría.
"Dime con quién andas y te diré quién eres." Si los 5 clientes más similares a Ana pidieron postre, probablemente Ana también lo pida.
📐 Fundamento
Algoritmo KNN:
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, classification_report
# Entrenar (KNN no "aprende" — simplemente guarda los datos)
knn = KNeighborsClassifier(n_neighbors=5, metric='euclidean')
knn.fit(X_train_scaled, y_train)
# Predecir
y_pred = knn.predict(X_test_scaled)
# Evaluar
print(f"Accuracy: {accuracy_score(y_test, y_pred):.2%}")
print(classification_report(y_test, y_pred, target_names=['No postre', 'Sí postre']))
Distancias:
Efecto del hiperparámetro K:
| K pequeño | K grande |
|---|---|
| Modelo más complejo | Modelo más simple |
| Alta varianza (overfitting) | Alto sesgo (underfitting) |
| Sensible al ruido | Ignora detalles locales |
Elegir K con validación cruzada:
from sklearn.model_selection import cross_val_score
for k in range(1, 21):
knn = KNeighborsClassifier(n_neighbors=k)
scores = cross_val_score(knn, X_train_scaled, y_train, cv=5, scoring='accuracy')
print(f"K={k:2d}: {scores.mean():.3f} ± {scores.std():.3f}")
# Elegir K con el mejor promedio
Problemas de KNN:
- Lento en predicción: Necesita calcular distancia a todos los puntos de entrenamiento → O(n·d).
- Maldición de la dimensionalidad: Con muchas features, todas las distancias se vuelven similares.
- No maneja bien features irrelevantes.
Solución: KD-Tree o Ball-Tree para acelerar la búsqueda de vecinos. Selección de features antes de KNN.
2.3 Árboles de decisión
💡 Intuición
Un árbol de decisión imita la forma en que razonamos: "¿Es fin de semana? Si sí, ¿el grupo tiene más de 3 personas? Si sí, → recomendar postre."
Ventaja sobre KNN: el árbol es interpretable — podés leer las reglas que aprendió y explicarlas a un cliente o un auditor.
📐 Fundamento
Construcción del árbol — ID3/CART:
El árbol se construye eligiendo en cada nodo el feature que mejor separa los datos según una métrica de impureza:
Entropía (ID3):
Entropía 0 = todos los ejemplos son de la misma clase (perfecto).
Entropía 1 = mitad de cada clase (impuro).
Ganancia de información:
Se elige el feature A con mayor ganancia de información.
Índice Gini (CART — scikit-learn por defecto):
from sklearn.tree import DecisionTreeClassifier, plot_tree
import matplotlib.pyplot as plt
# Entrenar
tree = DecisionTreeClassifier(
max_depth=4, # limitar profundidad para evitar overfitting
min_samples_leaf=10, # cada hoja tiene al menos 10 ejemplos
random_state=42
)
tree.fit(X_train, y_train)
# Visualizar
plt.figure(figsize=(15, 8))
plot_tree(tree, feature_names=X.columns, class_names=['No', 'Sí'], filled=True)
plt.savefig('arbol_decision.png')
# Ver las reglas aprendidas
from sklearn.tree import export_text
print(export_text(tree, feature_names=list(X.columns)))
Overfitting en árboles:
Un árbol sin restricciones crece hasta memorizar cada ejemplo de entrenamiento (accuracy = 100% en train, malo en test). Técnicas de regularización:
- Limitar
max_depth— profundidad máxima. - Aumentar
min_samples_split— mínimo de ejemplos para dividir un nodo. - Post-pruning — crecer el árbol completo y luego podar las ramas que no mejoran la validación.
2.4 El tradeoff sesgo-varianza
💡 Intuición
Underfitting (alto sesgo): El modelo es demasiado simple — no captura los patrones reales. Un árbol de profundidad 1 que predice siempre "no postre" tiene alto sesgo.
Overfitting (alta varianza): El modelo es demasiado complejo — memorizó el ruido de los datos de entrenamiento y no generaliza. Un árbol de profundidad 100 que memorizó cada fila.
El objetivo: el punto medio donde el error en datos nuevos es mínimo.
📐 Fundamento
Diagnóstico gráfico (curva de aprendizaje):
from sklearn.model_selection import learning_curve
import numpy as np
train_sizes, train_scores, val_scores = learning_curve(
DecisionTreeClassifier(max_depth=4),
X_train_scaled, y_train,
cv=5, train_sizes=np.linspace(0.1, 1.0, 10)
)
# Graficar
plt.plot(train_sizes, train_scores.mean(axis=1), label='Train')
plt.plot(train_sizes, val_scores.mean(axis=1), label='Validación')
plt.xlabel('Tamaño del training set')
plt.ylabel('Accuracy')
plt.legend()
| Síntoma | Diagnóstico | Solución |
|---|---|---|
| Train alto, Val bajo | Overfitting | Regularizar, más datos, reduce features |
| Train bajo, Val bajo | Underfitting | Modelo más complejo, más features |
| Train ≈ Val ≈ alto | Bien ajustado | ✓ |
Validación cruzada (k-fold cross validation):
from sklearn.model_selection import cross_validate
resultados = cross_validate(
DecisionTreeClassifier(max_depth=4),
X, y,
cv=5,
scoring=['accuracy', 'f1', 'roc_auc'],
return_train_score=True
)
print(f"Val accuracy: {resultados['test_accuracy'].mean():.3f}")
print(f"Train accuracy: {resultados['train_accuracy'].mean():.3f}")
Métricas de clasificación:
Predicho + Predicho -
Real + │ TP (True Pos) │ FN (False Neg) │
Real - │ FP (False Pos) │ TN (True Neg) │
Accuracy = (TP + TN) / Total
Precision = TP / (TP + FP) — de los que predije +, ¿cuántos eran +?
Recall = TP / (TP + FN) — de los que son +, ¿cuántos predije +?
F1 = 2 * (Precision * Recall) / (Precision + Recall)
Cuándo usar Precision vs Recall:
- Alta Precision: cuando un FP es muy costoso (spam filter — no queremos marcar email válido como spam).
- Alto Recall: cuando un FN es muy costoso (diagnóstico de cáncer — no queremos perder un caso real).
🛠️ En la práctica
La Esquina: predecir si el cliente pedirá postre
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import classification_report
# Dataset simulado de pedidos de La Esquina
datos = {
'hora_dia': [12, 13, 19, 20, 12, 13, 20, 21, 12, 19],
'num_personas': [2, 4, 2, 5, 1, 3, 6, 2, 2, 4],
'gasto_total': [8, 18, 6, 22, 4, 12, 30, 7, 5, 20],
'es_fin_semana': [0, 0, 1, 1, 0, 1, 1, 0, 0, 1],
'pidio_postre': [0, 1, 0, 1, 0, 1, 1, 0, 0, 1]
}
df = pd.DataFrame(datos)
X = df[['hora_dia', 'num_personas', 'gasto_total', 'es_fin_semana']]
y = df['pidio_postre']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
modelo = DecisionTreeClassifier(max_depth=3, min_samples_leaf=2)
modelo.fit(X_train, y_train)
y_pred = modelo.predict(X_test)
print(classification_report(y_test, y_pred))
# La regla más importante que aprendió el árbol:
# Si gasto_total > 15 Y num_personas > 2 → recomendar postre (Precision 0.87)
2.5 Ejercicios
✏️ Ejercicio 2.1 — Elegir el algoritmo
Para cada problema, elegí el tipo de aprendizaje supervisado (regresión / clasificación binaria / clasificación multiclase) y el algoritmo más apropiado (KNN / árbol de decisión / regresión lineal):
a. Predecir el tiempo de preparación de un pedido (en minutos) basándose en el número de ítems y el horario.
b. Clasificar si un mensaje de cliente en la app es una queja, una consulta, o un elogio.
c. Predecir si un nuevo empleado pasará la prueba de atención al cliente (sí/no) basándose en su experiencia previa y puntaje de entrevista.
d. Predecir el costo total de delivery basándose en la distancia.
Solución
a. Regresión — el output es un número continuo (minutos). Algoritmo: Regresión lineal (si la relación es lineal) o árbol de decisión de regresión.
b. Clasificación multiclase — tres clases: queja, consulta, elogio. Algoritmo: Árbol de decisión (interpretable, útil para auditar qué palabras clasifica como queja).
c. Clasificación binaria — sí/no. Algoritmo: KNN (basado en empleados similares anteriores) o árbol de decisión.
d. Regresión — output continuo (costo en $). Algoritmo: Regresión lineal — si el costo es proporcional a la distancia, la relación es lineal.
✏️ Ejercicio 2.2 — Interpretar resultados
Un modelo de árbol de decisión para predecir si un cliente pedirá postre muestra:
Train accuracy: 98%
Test accuracy: 61%
a. ¿Cuál es el diagnóstico?
b. Propone tres cambios en los hiperparámetros del DecisionTreeClassifier para corregirlo.
c. Si después de los cambios el modelo queda: Train: 72%, Test: 70% — ¿es mejor o peor? ¿Por qué?
Solución
a. Overfitting — el modelo memorizó el training set (98%) pero no generaliza a datos nuevos (61%). La diferencia de 37 puntos es muy grande.
b. Cambios para regularizar:
max_depth=4(limitar profundidad, estaba sin límite).min_samples_leaf=10(obligar hojas con al menos 10 ejemplos).min_samples_split=20(solo dividir nodos con al menos 20 ejemplos).
c. Es mejor. 72% vs 70% es una diferencia pequeña (gap de 2%), indicando que el modelo generaliza bien. El accuracy absoluto pasó de 61% a 70% en test — mejora real. Un buen modelo tiene train ≈ test (pequeño gap).
2.6 Para profundizar
- Géron, Hands-On Machine Learning with Scikit-Learn, Keras & TensorFlow — el mejor libro práctico de ML.
- scikit-learn docs: User Guide — referencia completa con ejemplos.
- Siguiente: Redes neuronales.
2.7 Errores comunes
⚠️ Trampa común
Olvidar stratify=y con clases desbalanceadas. Si tu dataset tiene 95 % "no-fraude" y 5 % "fraude", train_test_split sin stratify puede dejar 0 % de fraude en el set de prueba. El modelo "predice todo no-fraude" y obtiene 95 % de accuracy. Falsa victoria.
Tip: siempre train_test_split(X, y, stratify=y, random_state=42) para clasificación. Y usar F1, AUC-ROC o recall, no accuracy, cuando las clases están desbalanceadas.
⚠️ Trampa común
Data leakage al normalizar antes del split. Si hacés StandardScaler().fit_transform(X) y después train_test_split, el mean y std del scaler ya vieron los datos de prueba. El modelo recibe información del futuro. La accuracy en test es optimista; en producción, decae.
Tip: usá Pipeline de scikit-learn: Pipeline([('scaler', StandardScaler()), ('model', ...)]). El pipeline ajusta el scaler solo con train dentro de cada fold de cross-validation.
Definiciones nuevas: aprendizaje supervisado, features, labels, train/test split, KNN, distancia euclidiana, árbol de decisión, entropía, ganancia de información, índice Gini, overfitting, underfitting, sesgo-varianza, validación cruzada, accuracy, precision, recall, F1.