Saltar al contenido

# machine-learning-en-python

Machine Learning en Python

Volver a la portada del curso
Lectura (Markdown)

Categoría de Clases con KNN

Sección 3 · Modelos Supervisados Básicos

Categoría de clases con KNN

PyShop quiere saber qué clientes contratarían el envío premium para ofrecérselo a ellos y no a todos. Ya no se predice un número, como el gasto anual, sino una categoría: 0 o 1. Eso es clasificación, y K vecinos más cercanos (KNN) es el clasificador más fácil de razonar: para decidir sobre un cliente nuevo, busca a los clientes más parecidos que ya conoce y copia lo que hicieron ellos.

La idea: votan los vecinos

KNN no aprende coeficientes. Al llamar a fit solo guarda los datos de entrenamiento. Al predecir, calcula la distancia del cliente nuevo a cada cliente guardado, toma los k más cercanos y devuelve la clase mayoritaria entre ellos. Con k=5, si tres de los cinco vecinos son premium, la predicción es premium y predict_proba da 0.6.

La distancia por defecto en scikit-learn es la euclidiana, sobre todas las variables a la vez:

$$d(a, b) = \sqrt{\sum_{j=1}^{p} (a_j - b_j)^2}$$

En el curso se usan 7 variables por cliente: edad, antigüedad, visitas al mes, minutos por sesión, compras en 12 meses, ticket promedio y gasto anual. Los 1000 clientes limpios se parten 80/20 con stratify=y, para que entrenamiento y prueba conserven la misma proporción de premium (0.362).

Primero, la línea base

Antes de entrenar nada, pregúntate cuánto acierta un modelo tonto. En prueba hay 128 clientes no premium y 72 premium. Predecir «no premium» para todos acierta 128 de 200: 0.64 de exactitud. Cualquier modelo que no supere ese número no aporta nada, aunque 0.64 suene a algo.

Escalar no es opcional

El gasto anual va de 0 a unos 20 000 pesos; las visitas al mes, de 0.5 a 12.7. En la fórmula de la distancia, una diferencia de 1000 pesos pesa lo mismo que mil visitas. Sin escalar, el vecino «más cercano» es simplemente el que gasta parecido, y las otras seis variables casi no cuentan.

StandardScaler resta la media y divide entre la desviación estándar de cada columna, así que todas quedan en la misma escala. Con k=5, KNN sin escalar da 0.81 en prueba y con escalado sube a 0.83. El escalador va dentro de un pipeline para que la media y la desviación se calculen solo con el entrenamiento:

from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

VARS = ["edad", "antiguedad_meses", "visitas_mes", "minutos_sesion",
        "compras_12m", "ticket_promedio", "gasto_anual"]
X, y = df[VARS], df["premium"]
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

knn = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=5))
knn.fit(Xtr, ytr)
print(round(knn.score(Xte, yte), 3))   # 0.83

Elegir k

k controla qué tan local es la decisión. Esta es la exactitud con escalado para varios valores:

kEntrenamientoPrueba
11.0000.795
30.8770.790
50.8630.830
110.8540.840
210.8300.855
510.8150.845

Con k=1, cada cliente de entrenamiento es su propio vecino y el acierto es perfecto: el modelo memoriza, incluido el ruido. Al subir k, la frontera entre clases se suaviza, el entrenamiento baja y la prueba sube hasta 0.855 con k=21. Con k=51 empieza a promediar clientes que ya no se parecen y la prueba cae a 0.845.

Un aviso: esta tabla usa el conjunto de prueba para comparar. Si eliges k así, el 0.855 deja de ser una estimación honesta. La forma correcta, con validación cruzada y GridSearchCV, se ve en la sección de optimización; ahí la búsqueda también elige k=21.

La matriz de confusión

La exactitud resume todo en un número y oculta qué tipo de error se comete. La matriz de confusión del modelo con k=5 lo muestra (filas: clase real; columnas: predicha):

from sklearn.metrics import confusion_matrix

print(confusion_matrix(yte, knn.predict(Xte)))
# [[115  13]
#  [ 21  51]]

Acierta 115 no premium y 51 premium: 166 de 200, el 0.83. Pero de los 72 premium reales detecta 51 y se le escapan 21. Dos métricas lo cuentan mejor:

  • Precisión = 51 / (51 + 13) = 0.797: de los que marca como premium, ocho de cada diez lo son.
  • Recall = 51 / (51 + 21) = 0.708: de los premium reales, encuentra siete de cada diez.

Cuál importa más depende del negocio. Si ofrecer el envío a quien no lo quiere cuesta poco, el error caro son los 21 premium no detectados, y conviene mirar el recall.

Exactitud con clases desbalanceadas

Aquí las clases están 64/36 y la exactitud todavía dice algo. Con 98/2, un modelo que nunca predice la clase minoritaria tendría 0.98 de exactitud y no serviría para nada. Por eso, en clasificación, compara siempre contra la línea base y revisa la matriz de confusión antes de celebrar un número.

Trampas comunes

  • No escalar. La variable con números más grandes decide sola la distancia.
  • Escalar antes de partir. Ajustar el StandardScaler con todos los datos filtra información del test; usa un pipeline.
  • Elegir k mirando el test. El resultado queda inflado; usa validación cruzada.
  • Reportar solo la exactitud. Sin línea base ni matriz de confusión, 0.83 no dice si el modelo sirve.

Cierre

Cuando entrenes un clasificador, escribe tres números antes de mirar la exactitud: la línea base, cuántos casos positivos hay en prueba y cuántos detecta el modelo. Si el modelo no mejora claramente la línea base en la clase que te importa, todavía no está listo.