Метод 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 по кросс-валидации.
Что важно запомнить
- k-NN предсказывает по ответам похожих обучающих объектов.
- Масштаб и смысл признаков определяют найденных соседей.
- Малое k чувствительно к шуму, большое чрезмерно сглаживает.
- Высокая размерность и большой train делают поиск дорогим.