Метод k-ближайших соседей: классификация по сходству

Метод k-ближайших соседей, или k-NN, откладывает основную работу до момента прогноза. Для нового объекта он находит похожие обучающие примеры и голосует по их классам либо…

Метод k-ближайших соседей, или k-NN, откладывает основную работу до момента прогноза. Для нового объекта он находит похожие обучающие примеры и голосует по их классам либо усредняет числовые ответы. Простота делает алгоритм полезным baseline и наглядным уроком о расстояниях.

Что значит «ближайший»

Для числовых признаков часто используют евклидово расстояние. Если один столбец измеряется тысячами, а другой единицами, первый почти полностью определит соседство. Поэтому стандартизация является частью постановки, а не косметикой.

Категории нельзя бездумно кодировать номерами и включать в евклидову формулу. Расстояние между городами 1 и 2 не становится вдвое меньше расстояния между 1 и 3 только из-за кодов.

Роль числа k

При k=1 модель повторяет класс самого близкого примера и чувствительна к шуму. Большое k сглаживает границу, но может игнорировать локальную структуру и предпочитать самый частый класс. Значение выбирают по кросс-валидации.

При чётном k в бинарной классификации чаще возникают ничьи. Нечётное число уменьшает их вероятность, но не является гарантированно оптимальным.

Pipeline с масштабированием

from sklearn.datasets import load_wine
from sklearn.metrics import f1_score
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

X, y = load_wine(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42, stratify=y
)

knn = make_pipeline(
    StandardScaler(),
    KNeighborsClassifier(n_neighbors=7, weights='distance'),
)

scores = cross_val_score(knn, X_train, y_train, cv=5, scoring='f1_macro')
print('F1 macro:', scores.mean().round(3), '+/-', scores.std().round(3))

knn.fit(X_train, y_train)
predictions = knn.predict(X_test)
print('Test F1 macro:', round(f1_score(y_test, predictions, average='macro'), 3))

weights='distance' даёт близким соседям больший вклад. Это может помочь, но не исправляет неподходящую метрику расстояния или плохие признаки.

Почему прогноз может быть дорогим

У классического k-NN почти нет компактной обученной формулы: он хранит примеры и ищет соседей при запросе. На большом наборе вычисление расстояний и память становятся проблемой. Структуры ускоренного поиска помогают в низкой размерности, но теряют эффективность, когда признаков много.

Это проявление curse of dimensionality: в высоких размерностях расстояния между объектами становятся менее различимыми, а локальное соседство требует всё больше данных.

Что посмотреть у спорного объекта

Метод позволяет вывести индексы и расстояния ближайших примеров через kneighbors. Это полезно для отладки: можно увидеть неверный масштаб, дубликат или область, где классы перемешаны. Однако показ похожих строк может раскрыть персональные данные, если обучающий набор чувствителен.

Практика: почувствуйте масштаб

Создайте точки с возрастом от 18 до 70 и доходом от 20 000 до 500 000. Найдите соседей для одного объекта до и после StandardScaler. Сравните список и объясните, почему доход доминировал. Затем переберите k от 1 до 25 по кросс-валидации.

Что важно запомнить

Источники