Search
Write a publication
Pull to refresh

Простой-сложный kNN

Введение


Большинство исследователей данных знакомо с алгоритмом k ближайших соседей и легко могут применить его, импортировав соответствующий класс из scikit-learn:

from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor

Читавшие документацию/курсы/книги немного дальше примера использования знают, что ещё хорошо бы свести признаки к одному масштабу:

from sklearn.preprocessing import StandardScaler, MinMaxScaler

ss = StandardScaler()
X_scale = ss.fit_transform(X)

Понявшие смысл этого действия могут даже попытаться понастраивать веса признаков, опираясь на собственное представление об их важности. Вроде всё строго, обыденно и даже слегка скучно. Может модель вытащит на малом числе фичей, а может и нет…

Капитан Очевидность – персонаж, олицетворяющий человека, который говорит банальные вещи, которые все кроме новичков давно знают и без него


Категориальные признаки


Что там было из простого? А, масштабирование признаков! Оно предполагает сведение векторов к «схожим» экстремальным показателям. Минимум в нуле, максимум в единице, что-то там про математическое ожидание, дисперсию и т.д. Работает на целых и не целых числах, но вот с категориальными не прокатит, даже если очень хочется. Исключение, когда они имеют упорядоченную структуру, но и то с опаской и долгими предварительными размышлениями.

Что же тогда делать с категориальными признаками?

1) Перевод в дамми переменные


Из длинного формата их представление переходит в широкий и появляется столько столбцов, сколько было категорий в признаке (или на один меньше, если в выборке предполагается наличие всех возможных в тесте/проде категорий). На выходе получаем много ноликов и мало единиц.

from sklearn.preprocessing import OneHotEncoder

one_hot = OneHotEncoder()
X_dummy = one_hot.fit_transform(X_cat)

И вроде вот, всё решено, можно прыгать использовать. Но есть одна маленькая проблемка: размерность, а с ней и затраты памяти, скачут дай боже (а мы сидим в гараже со стареньким ноутом). Частичным решением является использование разреженных матриц scipy:

OneHotEncoder(sparse=True)

Такой вариант не всегда спасёт и поможет, но обычно выбора и нет.

2) На первый взгляд глупо, на второй тоже, на третий score взлетает


Специфичная ситуация может возникнуть когда вы предполагаете (основываясь хотя бы на статистических тестах или бизнес-логике), что категориальные признаки играют первичную роль в формировании целевой переменной. Они явно выделяют кластеры (которые по размеру больше, чем ваше любимое число соседей) и уже внутри них по остальным переменным уточняют прогноз. Да, такое бывает. В финансах точно бывает.

Соглашение пользователя условия прочитали, поговорим об алгоритме:
а) Переводим категории в натуральные числа (реализация scikit-learn предполагает сделать это по отдельности для всех признаков);

from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
X_cat_int = le.fit_transform(X_cat)

б) Масштабируем остальные признаки ($inline$min=0, max=\frac{1}{n_{all} — n_{cat}}$inline$); (1)
с) Обучаем модель.

Почему может сработать? Как вы помните категориальные фичи играют у нас первую скрипку и когда мы выдаём им дельты между категориями начиная от 1, в то время как сумма дельт по всем признакам не может превышать единицы (1), то для модели kNN мы делаем их основой для вычисления ближайших соседей. В первую очередь, они ищутся среди друзей по кластеру, а уже потом из них выделяются самые близкие по остальным переменным.

В scikit-learn также реализована возможность кастомизации метрики расстояния, как на уровне степени для метрики Минковского, так и на уровне полностью своей функции. Можно поэкспериментировать со специальным классом, а потом вставить в модель:

from sklearn.neighbors import DistanceMetric

Звучит хорошо, выглядит гладко, но без подводных камней не обошлось. Сам scikit-learn под капотом имеет низкоуровневый C, а вот кастомная функция подвержена всем недостаткам Python, о чём честно сообщают разработчики. Сначала можно подумать, что это не критично и, поставив на ночь, вы утром получите готовый прогноз. Но все надежды ломаются при хоть сколько-нибудь большом числе соседей и размерах обучающей выборки.

Отношения с соседями


Что ещё было из простого? А, точно, веса признаков! Но сейчас не о них. Благодаря авторам библиотеки scikit-learn позволяет настроить веса соседей, которые изначально имеют равный вклад в прогноз. За это отвечает парметр weights. По дефолту имеет значение 'uniform', который прямо как унисекс подходит всем. Но если есть желание выработать собственный стиль, то стоит обратить внимание на 'distance' и callable. Первое задаёт вклад соседей пропорционально расстоянию до них, а второе открывает все возможности для проявления больной фантазии тайных знаний о признаках

Гиперпараметры


Для хорошего качества модели нужно не только обучить её на хороших данных, но и настроить её параметры, которые не будут меняться во время самого обучения, т.е. гиперпараметры.

В модели kNN гиперпараметрами обычно выступают:

а) Число соседей (n_neighbors) — с параметром стоит экспериментировать и не бояться ставить большим;

б) Степень для метрики Минковского (p) — 1 или 2 крайне часто будут лучшими.

Дополнительно можно настроить алгоритм под капотом модели (algorithm), рекомендуется делать при понимании его матчасти.

Заключение


Теперь вы знаете об алгоритме kNN чуть больше и можете дать свой фидбэк публикации.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.