Autor/a

Diego Villalba

En el ciclo CRISP-DM, la clasificación pertenece a la fase de modelado: ya entendimos el negocio, exploramos los datos y los preparamos; ahora queremos una regla que, a partir de lo que sabemos de un caso, anticipe a qué grupo pertenece. El capítulo anterior, sobre clasificación con reglas de asociación, mostró un primer ejemplo de esa idea. Esta parte del libro la desarrolla con las familias de clasificadores más usadas: vecinos cercanos, árboles y ensambles, modelos lineales, clasificadores probabilísticos y redes neuronales.

Antes de estudiar cualquiera de ellos conviene fijar el vocabulario y, sobre todo, la pregunta que los une: ¿cómo sabemos que un clasificador aprendió algo útil y no solo memorizó los datos? Este capítulo responde con un ejemplo de diez clientes que se puede calcular a mano y con un laboratorio en el que el lector controla la complejidad de un clasificador y observa cuándo deja de generalizar.

51.1 El problema de clasificación

Un problema de clasificación tiene dos ingredientes:

  • Variables predictoras (features) \(\mathbf{x} = (x_1, \ldots, x_p)\): lo que sabemos de cada caso antes de decidir.
  • Variable objetivo (target) \(y\), que toma valores en un conjunto finito de clases \(\mathcal{C} = \{c_1, \ldots, c_K\}\).

Un clasificador es una función \(f\) que asigna una clase a cada vector de predictoras, \(\hat{y} = f(\mathbf{x})\). Se construye con un conjunto de entrenamiento de ejemplos etiquetados \(\mathcal{D} = \{(\mathbf{x}_i, y_i)\}_{i=1}^{n}\) y se juzga por su desempeño en ejemplos nuevos.

Dominio Predictoras \(\mathbf{x}\) Objetivo \(y\)
Crédito ingreso, endeudamiento, historial paga / no paga
Salud edad, presión, glucosa diabetes sí / no
Correo frecuencia de palabras, remitente spam / no spam
Botánica largo y ancho de pétalos setosa, versicolor, virginica
Imágenes intensidad de cada píxel dígito 0, 1, …, 9
Tabla 51.1: Ejemplos de problemas de clasificación.

Tres distinciones aparecerán a lo largo de la parte:

  • Binaria o multiclase. Con \(K = 2\) clases el problema es binario y suele hablarse de clase positiva (la que interesa detectar: fraude, enfermedad, impago) y negativa. Con \(K > 2\) es multiclase. Cuando cada caso puede tener varias etiquetas a la vez (una película que es comedia y drama) el problema es multietiqueta, que no tratamos aquí.
  • Clasificación o regresión. Si \(y\) es numérica y continua (precio, temperatura) el problema es de regresión. Varios métodos de esta parte, como los árboles y los vecinos cercanos, sirven para ambas tareas.
  • Clase o puntaje. Muchos clasificadores no entregan solo una clase, sino un puntaje o una probabilidad estimada \(\hat{p}(y = c \mid \mathbf{x})\). La clase se obtiene después, comparando ese puntaje con un umbral. Separar ambas etapas será clave en el capítulo de métricas.

51.2 Intuición: ¿pagará el préstamo?

Una financiera registró a diez clientes a los que prestó dinero. De cada uno conoce su ingreso mensual (miles de pesos) y su endeudamiento (porcentaje del ingreso comprometido en deudas), y si pagó el préstamo.

Cliente Ingreso Endeudamiento (%) ¿Pagó?
1 20 20 sí
2 25 30 sí
3 32 25 sí
4 38 35 sí
5 15 40 sí
6 30 58 sí
7 12 55 no
8 22 60 no
9 35 62 no
10 28 48 no
Tabla 51.2: Conjunto de entrenamiento: diez clientes, seis pagaron y cuatro no.

Sin fórmulas ya se ve un patrón: quien compromete menos del 45 % de su ingreso tiende a pagar. El ingreso parece importar poco. Hay una excepción: el cliente 6 pagó con un endeudamiento del 58 %. ¿Es un caso que debe aprenderse o es ruido, un cliente que pagó por razones que los datos no registran (una herencia, un aval)? Esa pregunta, aprender la regla general sin aprender las excepciones, es el corazón de este capítulo.

Mostrar código
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

clientes = pd.DataFrame({
    "ingreso": [20, 25, 32, 38, 15, 30, 12, 22, 35, 28],
    "deuda":   [20, 30, 25, 35, 40, 58, 55, 60, 62, 48],
    "paga":    [1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
}, index=range(1, 11))

nuevos = pd.DataFrame({
    "ingreso": [18, 31, 26, 36, 40],
    "deuda":   [35, 60, 45, 30, 50],
    "paga":    [1, 0, 0, 1, 1],
}, index=list("ABCDE"))

fig, ax = plt.subplots(figsize=(6, 4.2))
for clase, color, marca, nombre in [(1, "#2563eb", "o", "pagó"), (0, "#ea580c", "s", "no pagó")]:
    d = clientes[clientes.paga == clase]
    ax.scatter(d.ingreso, d.deuda, c=color, marker=marca, s=70, label=nombre, edgecolor="white")
for i, fila in clientes.iterrows():
    ax.annotate(str(i), (fila.ingreso, fila.deuda), textcoords="offset points", xytext=(6, 4), fontsize=9)
ax.axhline(44, ls="--", c="gray")
ax.text(12, 45.5, "frontera: deuda = 44 %", color="gray", fontsize=9)
ax.set(xlabel="Ingreso (miles $/mes)", ylabel="Endeudamiento (%)", xlim=(8, 44), ylim=(15, 68))
ax.legend(loc="upper right")
plt.tight_layout()
plt.show()
Figura 51.1: Los diez clientes de entrenamiento. La línea discontinua es la frontera de la regla «endeudamiento ≤ 44 % ⇒ paga» que se construye en el Paso 2. El cliente 6 queda del lado equivocado.

51.3 Construcción paso a paso

51.3.1 Paso 1. La referencia mínima

El clasificador más simple ignora las predictoras y responde siempre la clase más frecuente. Aquí, «sí paga», con una exactitud de \(6/10 = 0.60\). A este clasificador se le llama referencia (baseline) y todo modelo debe superarlo; si no, no aprendió nada de las predictoras1.

1 Con clases muy desbalanceadas la referencia es engañosamente alta: si el 97 % de las transacciones son legítimas, «nunca hay fraude» acierta el 97 %. Por eso el capítulo de métricas introduce medidas que no se dejan engañar por el desbalance.

51.3.2 Paso 2. Una regla de un solo corte

Busquemos la mejor regla de la forma «si el endeudamiento es a lo más \(t\), entonces paga». Ordenamos los valores de endeudamiento y probamos un corte \(t\) en el punto medio entre cada par de valores consecutivos:

Corte \(t\) Clasificados «paga» Errores Quiénes fallan
32.5 1, 2, 3 3 4, 5, 6
37.5 1, 2, 3, 4 2 5, 6
44.0 1, 2, 3, 4, 5 1 6
51.5 1–5 y 10 2 6, 10
59.0 1–7 y 10 2 7, 10
Tabla 51.3: Errores de entrenamiento de la regla «deuda ≤ t ⇒ paga» para algunos cortes.

El mejor corte es \(t = 44\) (punto medio entre 40 y 48), con un solo error: el cliente 6. Haciendo lo mismo con el ingreso, el mejor corte comete 3 errores, así que el endeudamiento es la variable más informativa. Esta regla de una sola pregunta, llamada tocón de decisión (decision stump), acierta \(9/10 = 0.90\) en entrenamiento.

Geométricamente, la regla divide el plano en dos regiones de decisión separadas por la recta horizontal \(\text{deuda} = 44\): la frontera de decisión de la Figura 51.1.

51.3.3 Paso 3. Memorizar: el vecino más cercano

Otra estrategia es no resumir nada: guardar los diez clientes y, ante uno nuevo, responder lo mismo que el cliente más parecido, medido con la distancia euclidiana2. Este es el clasificador del vecino más cercano (1-NN).

2 La distancia euclidiana entre \(\mathbf{a}\) y \(\mathbf{b}\) es \(\sqrt{\sum_j (a_j - b_j)^2}\), la longitud del segmento que los une. Aquí las dos variables tienen rangos parecidos; cuando no es así, hay que escalarlas primero, como se explica en el capítulo de vecinos cercanos.

En entrenamiento acierta \(10/10\) por construcción: el vecino más cercano de cada cliente es él mismo. Pero ¿qué responde ante un cliente nuevo con ingreso 31 y endeudamiento 60? Calculamos su distancia a los tres clientes más próximos:

\[ \begin{aligned} d(\text{nuevo}, 6) &= \sqrt{(31-30)^2 + (60-58)^2} = \sqrt{5} \approx 2.24 \quad (\text{pagó}),\\ d(\text{nuevo}, 9) &= \sqrt{(31-35)^2 + (60-62)^2} = \sqrt{20} \approx 4.47 \quad (\text{no pagó}),\\ d(\text{nuevo}, 8) &= \sqrt{(31-22)^2 + (60-60)^2} = 9.00 \quad (\text{no pagó}). \end{aligned} \]

El más cercano es el cliente 6, la excepción, así que 1-NN predice «paga» para alguien con 60 % de endeudamiento. Si en lugar de un vecino consultamos a los tres más cercanos y votamos (3-NN), ganan dos «no» contra un «sí»: la votación diluye la excepción.

51.3.4 Paso 4. La prueba: cinco clientes nuevos

La financiera observa a cinco clientes que no se usaron para construir las reglas:

Cliente Ingreso Deuda (%) Real Referencia Corte 44 1-NN 3-NN
A 18 35 sí sí ✓ sí ✓ sí ✓ sí ✓
B 31 60 no sí ✗ no ✓ sí ✗ no ✓
C 26 45 no sí ✗ no ✓ no ✓ sí ✗
D 36 30 sí sí ✓ sí ✓ sí ✓ sí ✓
E 40 50 sí sí ✓ no ✗ no ✗ no ✗
Tabla 51.4: Predicciones sobre el conjunto de prueba.

Resumimos el desempeño de los cuatro clasificadores en ambos conjuntos:

Clasificador Exactitud en entrenamiento Exactitud en prueba
Referencia (siempre «sí») 6/10 = 0.60 3/5 = 0.60
Corte en deuda = 44 9/10 = 0.90 4/5 = 0.80
1-NN 10/10 = 1.00 3/5 = 0.60
3-NN 9/10 = 0.90 3/5 = 0.60
Tabla 51.5: Exactitud de entrenamiento frente a exactitud de prueba.

Tres lecciones que se repetirán en toda la parte:

  1. La exactitud de entrenamiento es optimista. El 1-NN es perfecto en los datos que memorizó y no mejor que la referencia en datos nuevos.
  2. Más flexible no es mejor. La regla de un solo corte es la menos flexible de las tres que usan predictoras y es la que mejor generaliza aquí, porque se niega a aprender la excepción.
  3. Con pocos datos, la evaluación es ruidosa. Cinco clientes de prueba son muy pocos: un solo caso, el E, cambia la exactitud en 0.20. Los capítulos de evaluación muestran cómo obtener estimaciones más estables.

51.4 Formalización

51.4.1 Pérdida, riesgo y error empírico

Para medir los errores usamos la pérdida 0-1, que vale 1 si la predicción falla y 0 si acierta: \(L(y, \hat{y}) = \mathbb{1}[y \neq \hat{y}]\). Lo que realmente queremos minimizar es el riesgo o error esperado sobre la población de la que vienen los datos3:

3 La esperanza \(\mathbb{E}[\cdot]\) es el promedio de una cantidad sobre todos los casos posibles, ponderado por su probabilidad. El riesgo es, entonces, la proporción de errores que cometería el clasificador sobre la población completa, de la cual nuestros datos son solo una muestra.

\[ R(f) = \mathbb{E}_{(\mathbf{x}, y)}\big[\mathbb{1}[y \neq f(\mathbf{x})]\big] = P\big(y \neq f(\mathbf{x})\big). \tag{51.1}\]

Como no conocemos la población, lo aproximamos con un promedio sobre una muestra, el error empírico:

\[ \hat{R}_{\mathcal{S}}(f) = \frac{1}{|\mathcal{S}|} \sum_{(\mathbf{x}_i, y_i) \in \mathcal{S}} \mathbb{1}[y_i \neq f(\mathbf{x}_i)]. \tag{51.2}\]

Si \(\mathcal{S}\) es el conjunto de entrenamiento, \(\hat{R}\) es el error de entrenamiento; si \(\mathcal{S}\) es un conjunto de prueba independiente, es el error de prueba. Solo el segundo es una estimación honesta de \(R(f)\), porque \(f\) se eligió precisamente para que el primero fuera pequeño.

51.4.2 Regiones y fronteras de decisión

Todo clasificador parte el espacio de predictoras en regiones de decisión \(\mathcal{R}_c = \{\mathbf{x} : f(\mathbf{x}) = c\}\). Las fronteras de decisión son los bordes entre regiones. La forma de esas fronteras distingue a las familias de clasificadores: rectas o hiperplanos en los modelos lineales, escalones paralelos a los ejes en los árboles, fronteras poligonales en el vecino más cercano y curvas suaves en las redes neuronales.

51.4.3 El mejor clasificador posible: el clasificador de Bayes

Supongamos que conociéramos la probabilidad de cada clase dado \(\mathbf{x}\), \(p(c \mid \mathbf{x}) = P(y = c \mid \mathbf{x})\)4. Entonces la regla que minimiza el riesgo de la Ecuación 51.1 es elegir la clase más probable:

4 \(P(y = c \mid \mathbf{x})\) es una probabilidad condicional: la proporción de casos de clase \(c\) entre todos los que tienen exactamente las predictoras \(\mathbf{x}\). Por ejemplo, entre todos los clientes con ingreso 30 y endeudamiento 58, la fracción que paga.

\[ f^{*}(\mathbf{x}) = \arg\max_{c \in \mathcal{C}} \, p(c \mid \mathbf{x}). \tag{51.3}\]

Este es el clasificador de Bayes. Su riesgo, el error de Bayes \(R^{*} = \mathbb{E}_{\mathbf{x}}\big[1 - \max_c p(c \mid \mathbf{x})\big]\), es el mínimo alcanzable por cualquier clasificador. No es cero cuando las clases se traslapan: dos clientes con el mismo ingreso y el mismo endeudamiento pueden comportarse distinto, y ninguna regla basada solo en esas dos variables puede acertar ambos. Ese error es irreducible con las predictoras disponibles; solo se reduce midiendo variables nuevas.

Todos los clasificadores de esta parte pueden leerse como intentos de aproximar \(f^{*}\) a partir de una muestra finita:

  • los vecinos cercanos estiman \(p(c \mid \mathbf{x})\) con la proporción de cada clase entre los vecinos;
  • los árboles la estiman con la proporción de cada clase en la hoja;
  • la regresión logística modela \(p(c \mid \mathbf{x})\) directamente con una función paramétrica;
  • los clasificadores bayesianos modelan \(p(\mathbf{x} \mid c)\) y \(P(c)\) y aplican el teorema de Bayes.

Los dos últimos ilustran una distinción importante: los modelos discriminativos aprenden la frontera o \(p(c \mid \mathbf{x})\); los generativos aprenden cómo se generan los datos de cada clase5.

5 Un modelo generativo aprende la distribución conjunta \(p(\mathbf{x}, y) = p(\mathbf{x} \mid y)\,P(y)\) y con ella podría simular datos nuevos de cada clase; luego clasifica con el teorema de Bayes. Un modelo discriminativo solo aprende lo necesario para separar las clases. Los primeros funcionan bien con pocos datos si sus supuestos son correctos; los segundos suelen ganar con muchos datos.

51.4.4 Generalización, sobreajuste y subajuste

Un clasificador generaliza cuando su error de prueba se parece a su error de entrenamiento y ambos son bajos. Hay dos maneras de fallar:

  • Subajuste (underfitting): el modelo es demasiado rígido para el patrón real; ambos errores son altos. La referencia del Paso 1 es el caso extremo.
  • Sobreajuste (overfitting): el modelo es tan flexible que aprende también el ruido de la muestra; el error de entrenamiento es bajo y el de prueba, alto. El 1-NN del Paso 3 es el caso extremo.

La complejidad (o flexibilidad) de un clasificador se controla con hiperparámetros6: el número de vecinos \(k\), la profundidad de un árbol, la regularización de un modelo lineal. Al aumentar la complejidad, el error de entrenamiento baja casi siempre, mientras que el de prueba suele dibujar una U: baja mientras el modelo aprende el patrón y sube cuando empieza a aprender el ruido.

6 Un hiperparámetro es una opción del algoritmo que se fija antes de entrenar (\(k\), profundidad máxima, tasa de aprendizaje). Los parámetros, en cambio, los aprende el algoritmo a partir de los datos (los coeficientes de un modelo lineal, los cortes de un árbol).

7 Formalmente, el sesgo mide cuánto se aleja la predicción promedio (sobre muchas muestras de entrenamiento posibles) de la predicción del clasificador de Bayes, y la varianza mide cuánto fluctúa la predicción de una muestra a otra. Para la pérdida cuadrática la descomposición es exacta; para la pérdida 0-1 es solo aproximada, pero la intuición es la misma (Hastie et al. 2009).

Esta U es la cara visible del compromiso sesgo–varianza (Geman et al. 1992). Un modelo rígido tiene sesgo: aunque lo entrenáramos con muchas muestras distintas, se equivocaría de forma sistemática. Un modelo flexible tiene varianza: con otra muestra de entrenamiento produciría una frontera muy distinta7. El error de prueba se descompone, de forma aproximada, en

\[ \text{error de prueba} \;\approx\; \underbrace{R^{*}}_{\text{irreducible}} \;+\; \text{sesgo} \;+\; \text{varianza}, \]

y elegir la complejidad es buscar el punto donde la suma es mínima. Con más datos, la varianza baja y el punto óptimo se desplaza hacia modelos más flexibles.

51.4.5 Paramétricos y no paramétricos

Los clasificadores paramétricos (regresión logística, discriminante lineal, Naive Bayes) resumen los datos en un número fijo de parámetros; después de entrenarlos se pueden desechar los datos. Los no paramétricos (vecinos cercanos, árboles sin límite de profundidad) crecen con los datos: el 1-NN es el conjunto de entrenamiento. Los primeros suelen tener más sesgo y menos varianza; los segundos, lo contrario.

51.5 Laboratorio: complejidad y generalización

El laboratorio usa el clasificador de \(k\) vecinos más cercanos (\(k\)-NN): para un punto nuevo busca los \(k\) puntos de entrenamiento más próximos y responde la clase mayoritaria entre ellos. El hiperparámetro \(k\) controla la complejidad: con \(k = 1\) la frontera rodea cada punto; con \(k\) grande se vuelve suave y, en el extremo, todo el plano recibe la clase mayoritaria.

Tiene dos conjuntos de datos. Diez clientes reproduce las Tabla 51.4 y Tabla 51.5. Muestra grande usa 120 puntos de entrenamiento y 400 de prueba simulados a partir de una probabilidad real conocida, de modo que se puede calcular el error de Bayes (alrededor de 0.09) y compararlo con lo que logra \(k\)-NN.

Laboratorio · Generalización

¿Cuántos vecinos hay que consultar?

Mueve k y observa la frontera de decisión y los dos errores. Las regiones sombreadas son la predicción del clasificador en cada punto del plano; los puntos con borde grueso son errores.

Datos
Mostrar
Error de entrenamiento
Error de prueba
Error de la referencia (mayoritaria)
Error de Bayes

Regiones de decisión

clase 1 clase 0 mal clasificado frontera de Bayes (muestra grande)

Error según k

entrenamiento prueba error de Bayes k actual

Predicciones para los clientes de prueba

Guía del laboratorio.

  1. Con Diez clientes y \(k = 1\), comprueba la Tabla 51.4: ¿qué vecino decide la predicción del cliente B? ¿A qué distancia está?
  2. Predice antes de mover: con \(k = 3\), ¿cambia la predicción de B? ¿Y la de C? Compruébalo en la tabla y explica por qué C empeora.
  3. Cambia a Muestra grande con \(k = 1\) y alterna entre Entrenamiento y Prueba. ¿Por qué no hay puntos con borde grueso en entrenamiento?
  4. Recorre \(k\) de 1 a 59. Localiza la U del error de prueba y el valor de \(k\) donde se alcanza el mínimo. ¿Qué tan lejos queda del error de Bayes?
  5. Con \(k = 5\), baja el tamaño de entrenamiento a 20. ¿Qué le pasa a la frontera y al error de prueba? ¿Dónde está ahora el mínimo de la curva?
  6. Explica con tus palabras por qué el error de entrenamiento casi siempre crece con \(k\), mientras que el de prueba no.

51.6 El flujo de trabajo de un clasificador

Las lecciones anteriores se convierten en un procedimiento que seguiremos en todos los capítulos:

  1. Definir la clase positiva, la métrica y la referencia. ¿Qué error es más costoso? ¿Cuánto acierta responder siempre la clase mayoritaria? Las métricas se estudian en el capítulo siguiente sobre evaluación.
  2. Separar datos de prueba antes de cualquier otra cosa. El conjunto de prueba se guarda y se usa una sola vez, al final.
  3. Preparar las predictoras dentro de un pipeline. Imputación, escalado y codificación se ajustan solo con el entrenamiento y se aplican después a la prueba, para evitar la fuga de datos8.
  4. Elegir hiperparámetros con validación, nunca con la prueba: se reserva una parte del entrenamiento (validación) o se usa validación cruzada.
  5. Comparar con la referencia y con un modelo simple. Un modelo complejo debe justificar su costo frente a uno interpretable.
  6. Evaluar una sola vez en prueba y reportar la métrica junto con su incertidumbre.

8 La fuga de datos (data leakage) ocurre cuando información del conjunto de prueba influye en el entrenamiento, por ejemplo al escalar con la media de todos los datos. El resultado es una evaluación optimista que no se sostiene en producción.

flowchart TD
    D[Datos etiquetados] --> E[Entrenamiento]
    D --> P[Prueba: se guarda]
    E --> V[Validación cruzada: elegir modelo e hiperparámetros]
    V --> F[Ajuste final con todo el entrenamiento]
    F --> R[Evaluación única en prueba]
    P --> R
Figura 51.2: Flujo de trabajo. El conjunto de prueba se separa al inicio y solo se usa en la evaluación final.

51.7 Un mapa de los clasificadores de esta parte

Familia Idea Frontera típica Interpretabilidad Capítulo
Vecinos cercanos Responder como los casos más parecidos Poligonal, muy flexible Media (por ejemplos) Vecinos más cercanos
Árboles de decisión Preguntas sucesivas que parten los datos Escalones paralelos a los ejes Alta (reglas) Árboles (ID3), CART
Ensambles Combinar muchos árboles Escalones finos, casi suave Baja (requiere SHAP) Ensambles, XGBoost
Modelos lineales Puntaje lineal \(\mathbf{w}^\top\mathbf{x} + b\) Recta o hiperplano Alta (coeficientes) Lineales, logística
Máquinas de soporte vectorial Margen máximo, núcleos Hiperplano o curva (con núcleo) Media SVM
Probabilísticos Modelar \(p(\mathbf{x} \mid c)\) y aplicar Bayes Lineal o cuadrática Alta Bayesianos, GMM
Redes neuronales Composición de transformaciones no lineales Curva arbitraria Baja Redes, PyTorch y JAX
Tabla 51.6: Familias de clasificadores estudiadas en esta parte.

No existe un clasificador que sea el mejor en todos los problemas9. Por eso el método importa más que el algoritmo: definir bien la métrica, validar sin fugas y comparar contra referencias.

9 Este resultado se conoce como el teorema de no hay almuerzo gratis (no free lunch) (Wolpert 1996): promediado sobre todos los problemas posibles, ningún algoritmo de aprendizaje supera a otro. Un algoritmo gana en un problema concreto porque sus supuestos encajan con la estructura de ese problema.

Más de dos clases. Algunos clasificadores (árboles, vecinos, Naive Bayes, regresión logística multinomial, redes) manejan \(K > 2\) clases de forma natural. Otros son binarios por diseño y se extienden con dos estrategias: uno contra el resto (OvR), que entrena \(K\) clasificadores «clase \(c\) contra todas las demás» y elige el de mayor puntaje, y uno contra uno (OvO), que entrena \(K(K-1)/2\) clasificadores por pares y decide por votación. scikit-learn aplica estas estrategias automáticamente cuando hace falta.

51.8 Implementación en Python

51.8.1 La interfaz de scikit-learn

Todos los clasificadores de scikit-learn comparten la misma interfaz (Pedregosa et al. 2011): se crean con sus hiperparámetros, se ajustan con fit(X, y) y predicen con predict(X). Muchos ofrecen además predict_proba(X), la probabilidad estimada de cada clase, y score(X, y), la exactitud. Reproducimos la Tabla 51.5 con cuatro clasificadores: DummyClassifier es la referencia, un árbol de profundidad 1 es el tocón y KNeighborsClassifier es \(k\)-NN.

Mostrar código
from sklearn.dummy import DummyClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier

X_tr, y_tr = clientes[["ingreso", "deuda"]], clientes["paga"]
X_te, y_te = nuevos[["ingreso", "deuda"]], nuevos["paga"]

modelos = {
    "Referencia (mayoritaria)": DummyClassifier(strategy="most_frequent"),
    "Tocón (árbol de profundidad 1)": DecisionTreeClassifier(max_depth=1, random_state=0),
    "1-NN": KNeighborsClassifier(n_neighbors=1),
    "3-NN": KNeighborsClassifier(n_neighbors=3),
}
filas = []
for nombre, modelo in modelos.items():
    modelo.fit(X_tr, y_tr)
    filas.append({"clasificador": nombre,
                  "exactitud entrenamiento": modelo.score(X_tr, y_tr),
                  "exactitud prueba": modelo.score(X_te, y_te),
                  "predicciones A–E": "".join("S" if p else "N" for p in modelo.predict(X_te))})
pd.DataFrame(filas)
clasificador exactitud entrenamiento exactitud prueba predicciones A–E
0 Referencia (mayoritaria) 0.6 0.6 SSSSS
1 Tocón (árbol de profundidad 1) 0.9 0.8 SNNSN
2 1-NN 1.0 0.6 SSNSN
3 3-NN 0.9 0.6 SNSSN

El árbol de profundidad 1 encontró el mismo corte que calculamos a mano. Lo podemos leer directamente:

Mostrar código
from sklearn.tree import export_text
print(export_text(modelos["Tocón (árbol de profundidad 1)"], feature_names=["ingreso", "deuda"]))
|--- deuda <= 44.00
|   |--- class: 1
|--- deuda >  44.00
|   |--- class: 0

El método predict_proba de 3-NN devuelve la proporción de vecinos de cada clase. Para el cliente B, dos de sus tres vecinos no pagaron:

Mostrar código
proba = modelos["3-NN"].predict_proba(X_te)
pd.DataFrame(proba, index=nuevos.index, columns=["P(no paga)", "P(paga)"]).round(3)
P(no paga) P(paga)
A 0.000 1.000
B 0.667 0.333
C 0.333 0.667
D 0.000 1.000
E 0.667 0.333

51.8.2 La curva de complejidad con la muestra grande

Generamos los mismos datos que usa el laboratorio. La probabilidad real de la clase 1 depende de una frontera curva, \(x_2 = 5 + 2\sin(0.8\,x_1)\), y se suaviza con una función logística; así las clases se traslapan cerca de la frontera y el error de Bayes no es cero.

Mostrar código
def prob_clase1(X):
    return 1 / (1 + np.exp(-1.6 * (X[:, 1] - 5 - 2 * np.sin(0.8 * X[:, 0]))))

def generar(n, semilla):
    rng = np.random.default_rng(semilla)
    X = rng.uniform(0, 10, size=(n, 2)).round(2)
    y = (rng.uniform(size=n) < prob_clase1(X)).astype(int)
    return X, y

X_big, y_big = generar(120, semilla=1)        # entrenamiento
X_test, y_test = generar(400, semilla=2)      # prueba

p_real = prob_clase1(X_test)
error_bayes = np.minimum(p_real, 1 - p_real).mean()
print(f"Error de Bayes estimado en la muestra de prueba: {error_bayes:.3f}")
Error de Bayes estimado en la muestra de prueba: 0.092
Mostrar código
import plotly.graph_objects as go

ks = list(range(1, 60, 2))
err_tr, err_te = [], []
for k in ks:
    knn = KNeighborsClassifier(n_neighbors=k, algorithm="brute").fit(X_big, y_big)
    err_tr.append(1 - knn.score(X_big, y_big))
    err_te.append(1 - knn.score(X_test, y_test))

fig = go.Figure()
fig.add_trace(go.Scatter(x=ks, y=err_tr, name="entrenamiento", mode="lines+markers", line=dict(color="#2563eb")))
fig.add_trace(go.Scatter(x=ks, y=err_te, name="prueba", mode="lines+markers", line=dict(color="#7c3aed")))
fig.add_hline(y=error_bayes, line=dict(color="#16a34a", dash="dash"), annotation_text="error de Bayes")
fig.update_layout(xaxis_title="k (número de vecinos)", yaxis_title="error de clasificación",
                  height=380, margin=dict(l=10, r=10, t=20, b=40), legend=dict(x=0.02, y=0.98))
fig.show()
Figura 51.3: Error de entrenamiento y de prueba de k-NN según el número de vecinos. El error de entrenamiento crece con k; el de prueba dibuja una U con mínimo cerca de k = 5. La línea verde es el error de Bayes, el mínimo alcanzable por cualquier clasificador.
Mostrar código
from matplotlib.colors import ListedColormap

g1, g2 = np.meshgrid(np.linspace(0, 10, 200), np.linspace(0, 10, 200))
malla = np.c_[g1.ravel(), g2.ravel()]
fig, ejes = plt.subplots(1, 3, figsize=(12, 4.2), sharey=True)
for ax, k in zip(ejes, [1, 5, 59]):
    knn = KNeighborsClassifier(n_neighbors=k, algorithm="brute").fit(X_big, y_big)
    zona = knn.predict(malla).reshape(g1.shape)
    ax.contourf(g1, g2, zona, levels=[-0.5, 0.5, 1.5], cmap=ListedColormap(["#fde2cf", "#dbe6fd"]))
    ax.scatter(X_big[:, 0], X_big[:, 1], c=np.where(y_big == 1, "#2563eb", "#ea580c"), s=16, edgecolor="white", lw=0.4)
    x1 = np.linspace(0, 10, 200)
    ax.plot(x1, 5 + 2 * np.sin(0.8 * x1), c="black", lw=1.4)
    ax.set(title=f"k = {k}   (prueba: {1 - knn.score(X_test, y_test):.3f})", xlabel="$x_1$", xlim=(0, 10), ylim=(0, 10))
ejes[0].set_ylabel("$x_2$")
plt.tight_layout()
plt.show()
Figura 51.4: Regiones de decisión de k-NN con k = 1, 5 y 59 sobre los 120 puntos de entrenamiento. La curva negra es la frontera del clasificador de Bayes. Con k = 1 la frontera rodea puntos aislados (sobreajuste); con k = 59 es demasiado suave (subajuste).

Los números coinciden con el laboratorio: el mínimo del error de prueba está en \(k = 5\) con 0.110, todavía unos dos puntos porcentuales por encima del error de Bayes. Esa diferencia se debe a que 120 puntos son una muestra finita; con más datos, \(k\)-NN se acerca al clasificador de Bayes10.

10 Se dice que \(k\)-NN es consistente: si \(n \to \infty\) y \(k \to \infty\) con \(k/n \to 0\), su error converge al error de Bayes (Stone 1977). En la práctica, la velocidad de esa convergencia empeora rápidamente con el número de variables.

51.9 Resumen

  • Un clasificador asigna una de \(K\) clases a cada vector de predictoras; se entrena con ejemplos etiquetados y se juzga por su error en ejemplos nuevos.
  • Toda evaluación debe compararse con la referencia de la clase mayoritaria.
  • El error de entrenamiento es optimista. El error de prueba, sobre datos que no participaron en ninguna decisión, es la estimación honesta del riesgo.
  • El clasificador de Bayes elige la clase más probable dada \(\mathbf{x}\); su error es el mínimo posible y no es cero cuando las clases se traslapan.
  • La complejidad se controla con hiperparámetros. Poca complejidad produce subajuste (sesgo); demasiada, sobreajuste (varianza). El error de prueba dibuja una U.

El siguiente capítulo estudia a fondo el primer clasificador que usamos aquí, los \(k\) vecinos más cercanos: cómo medir la distancia, por qué hay que escalar las variables y qué ocurre cuando hay muchas dimensiones.

Referencias