W3docs

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:

  1. Calcula la distancia desde el nuevo punto hasta cada punto de entrenamiento.
  2. Selecciona los K puntos de entrenamiento con las menores distancias (los "vecinos más cercanos").
  3. 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étricaFórmulaMejor para
Euclidianasqrt(Σ(pᵢ-qᵢ)²)Características continuas, pocas dimensiones
Manhattanpᵢ-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 data

Nunca 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.9667

Para más detalles sobre validación cruzada, consulta el capítulo de validación cruzada.

Reglas prácticas de referencia:

  • Prefiere K impar para clasificación binaria para evitar empates.
  • Un punto de partida habitual es K = sqrt(n) donde n es 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        30

KNN 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.8053

El 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.97

La 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

PropiedadDetalle
TipoAprendiz basado en instancias (perezoso)
TareasClasificación, Regresión
Hiperparámetro claveK (número de vecinos)
Métrica predeterminadaDistancia Euclidiana
Preprocesamiento requeridoEscalado de características (siempre)
FortalezasSimple, sin fase de entrenamiento, no paramétrico
DebilidadesPredicción lenta, consume mucha memoria, sensible a características irrelevantes

Capítulos relacionados:

Was this page helpful?