K-vecinos más cercanos
Aprende cómo funciona KNN, cómo elegir K, escalar características y crear modelos de clasificación y regresión en Python con scikit-learn.
K-Nearest Neighbors (KNN) es uno de los algoritmos de machine learning más simples e intuitivos. Para clasificar un nuevo punto de datos, examina los K ejemplos de entrenamiento más cercanos y realiza una votación — la clase mayoritaria gana. Para regresión, promedia los valores de los K vecinos en su lugar.
Este capítulo abarca:
- Cómo funciona el algoritmo KNN paso a paso
- Métricas de distancia: Euclidiana, Manhattan y Minkowski
- Por qué el escalado de características es fundamental para KNN
- Cómo elegir el valor correcto para
K - Clasificación y regresión con scikit-learn
- Evaluación de un clasificador KNN con una matriz de confusión
- Fortalezas, limitaciones y cuándo usar KNN
Cómo funciona KNN
KNN es un aprendiz perezoso — no realiza ningún "entrenamiento" real. En cambio, memoriza el conjunto de datos completo y aplaza todo el cómputo al momento de la predicción.
Dado un nuevo punto, el algoritmo:
- Calcula la distancia desde el nuevo punto hasta cada punto de entrenamiento.
- Selecciona los
Kpuntos de entrenamiento con las menores distancias (los "vecinos más cercanos"). - Agrega sus etiquetas:
- Clasificación — el nuevo punto recibe la etiqueta de clase más común.
- Regresión — el nuevo punto recibe el promedio (o promedio ponderado) de los valores objetivo de los vecinos.
Como no hay modelo que entrenar, agregar nuevos datos es trivial — simplemente se añaden al conjunto de datos. La contrapartida es que la predicción es lenta para conjuntos de datos grandes, ya que el cálculo completo de distancias se ejecuta cada vez.
Métricas de distancia
El término "más cercano" en KNN se define mediante una función de distancia. La distancia predeterminada en scikit-learn es la distancia Euclidiana, la distancia en línea recta en un espacio n-dimensional:
d(p, q) = sqrt( (p1-q1)² + (p2-q2)² + … + (pn-qn)² )Dos alternativas comunes:
| Métrica | Fórmula | Mejor para |
|---|---|---|
| Euclidiana | sqrt(Σ(pᵢ-qᵢ)²) | Características continuas, pocas dimensiones |
| Manhattan | `Σ | pᵢ-qᵢ |
| Minkowski | `(Σ | pᵢ-qᵢ |
Puedes cambiar la métrica en scikit-learn con el parámetro metric:
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=5, metric='manhattan')Por qué el escalado de características es fundamental
KNN calcula distancias sin procesar. Una característica medida en miles (como el salario) dominará completamente a una característica medida en dígitos simples (como los años de experiencia), incluso si la característica menor es más informativa.
Siempre escala las características antes de usar KNN. Consulta el capítulo de escalado de características para una explicación completa; la versión corta es:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # fit on training data only
X_test_scaled = scaler.transform(X_test) # apply same transform to test dataNunca llames a fit_transform en el conjunto de prueba — eso filtraría las estadísticas del conjunto de prueba al escalador.
Cómo elegir K
K controla el equilibrio entre sesgo y varianza:
- K pequeño (ej. K=1) — muy flexible, se ajusta estrechamente a los datos de entrenamiento, pero es ruidoso y propenso al sobreajuste. Un único vecino atípico puede cambiar la predicción.
- K grande — frontera de decisión más suave, menor varianza, pero puede subajustar y difuminar las fronteras reales entre clases.
El enfoque estándar es probar un rango de valores de K y elegir el que tenga la mejor exactitud con validación cruzada:
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
iris = load_iris()
X, y = iris.data, iris.target
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
k_range = range(1, 31)
cv_scores = []
for k in k_range:
knn = KNeighborsClassifier(n_neighbors=k)
scores = cross_val_score(knn, X_scaled, y, cv=5, scoring='accuracy')
cv_scores.append(scores.mean())
best_k = k_range[np.argmax(cv_scores)]
print(f"Best K: {best_k}, CV Accuracy: {max(cv_scores):.4f}")Salida esperada:
Best K: 6, CV Accuracy: 0.9667Para más detalles sobre validación cruzada, consulta el capítulo de validación cruzada.
Reglas prácticas de referencia:
- Prefiere
Kimpar para clasificación binaria para evitar empates. - Un punto de partida habitual es
K = sqrt(n)dondenes el número de muestras de entrenamiento. - Siempre valida con validación cruzada en lugar de adivinar.
Clasificación KNN con scikit-learn
El siguiente ejemplo utiliza el conjunto de datos Iris — un problema multiclase real — y recorre el flujo de trabajo completo: dividir, escalar, entrenar, predecir, evaluar.
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, classification_report
# 1. Load a real dataset
iris = load_iris()
X, y = iris.data, iris.target
# 2. Split into training and test sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 3. Scale features — critical for KNN
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 4. Train the classifier
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X_train_scaled, y_train)
# 5. Predict and evaluate
y_pred = knn.predict(X_test_scaled)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.2f}")
print()
print(classification_report(y_test, y_pred, target_names=iris.target_names))Salida esperada:
Accuracy: 0.93
precision recall f1-score support
setosa 1.00 1.00 1.00 10
versicolor 0.83 1.00 0.91 10
virginica 1.00 0.80 0.89 10
accuracy 0.93 30
macro avg 0.94 0.93 0.93 30
weighted avg 0.94 0.93 0.93 30KNN con K=5 logra un 93% de exactitud en este conjunto de prueba de 30 muestras. Setosa se clasifica perfectamente porque es linealmente separable de las otras dos; versicolor y virginica se superponen en cierta medida, causando algunas clasificaciones erróneas.
Observa el argumento stratify=y en train_test_split — esto preserva las proporciones de clase en cada partición, lo cual es especialmente importante para conjuntos de datos desbalanceados. Consulta división entrenamiento/prueba para más detalles.
Evaluación con una matriz de confusión
Una matriz de confusión muestra exactamente qué clases confunde el modelo entre sí:
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import confusion_matrix
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X_train_s, y_train)
y_pred = knn.predict(X_test_s)
cm = confusion_matrix(y_test, y_pred)
print(cm)Salida esperada:
[[10 0 0]
[ 0 10 0]
[ 0 2 8]]Cada fila es una clase real; cada columna es una clase predicha. Los valores en la diagonal son predicciones correctas; los valores fuera de la diagonal son clasificaciones erróneas. Aquí, 2 muestras de virginica fueron clasificadas erróneamente como versicolor. Consulta el capítulo de matriz de confusión para una explicación más profunda.
Regresión KNN con scikit-learn
Para regresión, KNN predice la media de los valores objetivo de los K vecinos más cercanos. Reemplaza KNeighborsClassifier por KNeighborsRegressor:
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np
# Load a real regression dataset (a subset for speed)
housing = fetch_california_housing()
X, y = housing.data[:2000], housing.target[:2000]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
knn_reg = KNeighborsRegressor(n_neighbors=5)
knn_reg.fit(X_train_s, y_train)
y_pred = knn_reg.predict(X_test_s)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
print(f"RMSE: {rmse:.4f}")
print(f"R²: {r2:.4f}")Salida esperada:
RMSE: 0.4217
R²: 0.8053El RMSE está en las mismas unidades que el objetivo (valor medio de la vivienda en $100k). Un R² de 0.81 significa que el modelo explica aproximadamente el 81% de la varianza en este subconjunto de 2.000 muestras — un resultado sólido para una línea base KNN sin ajuste.
KNN ponderado
Por defecto, todos los K vecinos tienen el mismo peso independientemente de su cercanía. Establecer weights='distance' hace que los vecinos más cercanos cuenten más:
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
knn_uniform = KNeighborsClassifier(n_neighbors=5, weights='uniform')
knn_distance = KNeighborsClassifier(n_neighbors=5, weights='distance')
knn_uniform.fit(X_train_s, y_train)
knn_distance.fit(X_train_s, y_train)
print(f"Uniform weights accuracy: {accuracy_score(y_test, knn_uniform.predict(X_test_s)):.2f}")
print(f"Distance weights accuracy: {accuracy_score(y_test, knn_distance.predict(X_test_s)):.2f}")Salida esperada:
Uniform weights accuracy: 0.93
Distance weights accuracy: 0.97La ponderación por distancia mejora la exactitud de 0.93 a 0.97 — los vecinos más cercanos tienen mayor influencia, lo que ayuda a resolver casos ambiguos en la frontera.
Fortalezas y limitaciones
Cuándo usar KNN
- El conjunto de datos es de tamaño pequeño a mediano (decenas de miles de muestras).
- Necesitas una línea base rápida e interpretable — KNN es fácil de explicar y depurar.
- Tienes características bien escaladas y de baja dimensionalidad.
- La frontera de decisión es compleja y no lineal.
Cuándo evitar KNN
- Conjuntos de datos grandes. El tiempo de predicción escala con el número de muestras de entrenamiento (
O(n·d)por consulta). Para millones de muestras, considera librerías de vecinos más cercanos aproximados (Faiss, Annoy) o cambia a un algoritmo más rápido. - Datos de alta dimensionalidad. En muchas dimensiones, todos los puntos se vuelven aproximadamente equidistantes — la "maldición de la dimensionalidad". KNN se degrada rápidamente más allá de ~20 características. Aplica reducción de dimensionalidad primero (PCA, selección de características).
- Características irrelevantes. Cada característica participa en el cálculo de la distancia. Las características ruidosas o irrelevantes diluyen la señal. Elimínalas o redúcelas antes del entrenamiento.
- Entornos con memoria limitada. KNN almacena todo el conjunto de entrenamiento; un conjunto de datos con millones de filas ocupa una RAM significativa.
Resumen
| Propiedad | Detalle |
|---|---|
| Tipo | Aprendiz basado en instancias (perezoso) |
| Tareas | Clasificación, Regresión |
| Hiperparámetro clave | K (número de vecinos) |
| Métrica predeterminada | Distancia Euclidiana |
| Preprocesamiento requerido | Escalado de características (siempre) |
| Fortalezas | Simple, sin fase de entrenamiento, no paramétrico |
| Debilidades | Predicción lenta, consume mucha memoria, sensible a características irrelevantes |
Capítulos relacionados:
- Escalado de características — por qué y cómo escalar antes de KNN
- División entrenamiento/prueba — cómo dividir los datos correctamente
- Validación cruzada — elegir K con validación cruzada k-fold
- Matriz de confusión — interpretación de resultados de clasificación
- Búsqueda en cuadrícula — ajuste sistemático de hiperparámetros