Scikit-Learn: тонкие вопросы о реализации методов машинного обучения

Рассмотрим несколько с виду простых вопросов об алгоритмах машинного обучения и их реализации, на которые, однако, немногие смогут верно ответить (можете попробовать сами – не читая объяснений, также в посте приведены дополнительные вопросы специально оставленные без ответа). Материал для среднего уровня (тех, кто уже знает ML и библиотеку sklearn).

Почему SVM в sklearn выдаёт неверные вероятности? Например, объект может классифицироваться к классу 1, а вероятность принадлежности к этому классу может не быть максимальной.

Можно провести такой эксперимент: взять обучающую выборку из двух объектов, принадлежащих разным классам (0 и 1). Эту же выборку используем в качестве тестовой (см. рис). Классифицируются объекты верно, но вот вероятности принадлежности к первому классу у них – 0.65 и 0.35. Во-первых, это очень странные значения, а во-вторых, у объекта из класса 0 большая вероятность принадлежности к классу 1 и наоборот. Неужели, в sklearn (библиотеке, которой активно пользуются столько лет) ошибка?

Этот вопрос мне задал один из читателей блога в комментариях. Строго говоря да, это баг, который до сих пор не исправлен. Связан он с тем, как в принципе в SVM вычисляются вероятности принадлежности к классам… обратите внимание, на функцию sklearn.svm.SVC:

sklearn.svm.SVC(C=1.0, kernel='rbf', degree=3, gamma='scale', coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=- 1, decision_function_shape='ovr', break_ties=False, random_state=None)

здесь есть специальный параметр «probability», который нужно сделать True, чтобы вычислялись вероятности. С чем связано его наличие, ведь его нет в других методах (случайные леса, логистическая регрессия, бустинг)? Связано с тем, что сам метод SVM просто разделяет точки гиперплоскостью в некотором пространстве. Никаких вероятностей он не получает, для этого используется дополнительная процедура – калибровка Платта (по сути, это логистическая регрессия на одном признаке – нормали к построенной гиперплоскости), для её активации и нужно указать «probability=True» (калибровка требует времени и по умолчанию отключена). Дальше самое интересное – как именно она выполняется?! Опуская подробности реализации, заметим, что для выполнения калибровки необходимо разбиение выборки на подвыборки (на одной строится алгоритм, на второй производится калибровка). Принцип такой же, как и в функции CalibratedClassifierCV, если мы попробуем откалибровать SVM с помощью неё в данной задаче, то получим ошибку:

Причина ошибки понятна, по умолчанию CalibratedClassifierCV делает разбивку на 5 фолдов (кстати, в прежних версиях библиотеки калибровка проводилась по 3 фолдам), в данном случае объектов банально не хватает. Если в задаче тупо продублировать объекты выборки, то SVM вдруг начнёт корректно определять вероятности (ниже иллюстрация – мы просто увеличили выборку в 10 раз).

Дополнительные вопросы: а как всё-таки делается калибровка в SVM? И где именно она реализована, ведь sklearn здесь выступает обёрткой над libsvm и liblinear? Кстати, чем эти методы отличаются?

Почему в разных методах sklearn регуляризация контролируется разными по смыслу параметрами, например в методе Ridge – коэффициентом при регуляризационном слагаемом, а в логистической регрессии – обратным к этому коэффициентом?

Это, конечно, может сбивать с толку, поскольку увеличивая/уменьшая контролирующий параметр мы приходим к противоположным эффектам для разных методов. Но, на самом деле, можно сразу сказать, где какой контроль реализован, даже не заглядывая в код, поскольку параметры приходят из теоретических описаний методов. Рассмотрим, например, Ridge-регрессию, в ней коэффициенты определяются по формуле

коэффициентом альфа регулируется добавления «гребня» к матрице XTX, что позволяет бороться с её плохой обусловленностью, ну или (что эквивалентно) добавление регуляризационного слагаемого к эмпирическому риску. Поэтому этот коэффициент проник и в параметры метода, реализованного в sklearn:

Если же вспомнить, как ставится решаемая задача при применении метода SVM, то мы увидим что оптимизируемая функция состоит из двух слагаемых:

Первое исторически появилось раньше и соответствует максимизации ширины разделяющей классы полосы, а второе появилось в т.н. «Soft-Margin SVM» и контролирует «залезание объектов чужих классов в полосу». Эту задачу можно переписать в таком виде

и здесь мы узнаём регуляризационное слагаемое и функцию, которая по смыслу является функцией ошибки и называется Hinge Loss. Теперь понятно откуда в реализации метода взялся параметр C и как он соотносится с упомянутым выше альфа:

Попробуйте теперь вспомнить, как контролируется регуляризация, например, у sklearn.linear_model.SGDClassifier (подсказка: этот классификатор изучают в разделе линейных алгоритмов с суррогатными функциями ошибок).

Дополнительные вопросы: почему в Ridge-регрессии есть параметр нормализация признаков (normalize), а в логистической регрессии его нет? Почему нормализация признаков отключается в регрессии без свободного члена (когда fit_intercept=False)?

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

Нет, никакой гарантии нет. Пример, когда это нарушается в стратифицированном контроле по фолдам приведён ниже (в последнем фолде нет представителей класса 1):

Подобное может привести к ошибке при использовании AUC ROC для оценки качества, поскольку этот функционал не определён для случая, когда в тестовой выборке содержатся только представители одного класса. Для обработки подобных случаев в функции cross_val_score есть параметр error_score (кстати, раньше по умолчанию при некорректном вычислении score-функции всё падало с ошибкой, а в последних версиях sklearn — оценка качества полагается равным np.nan).

Почему различаются результаты перекрёстной проверки и время её выполнения при использовании lgb.cv и при использовании стандартного средства cross_val_score?

Действительно, модель из библиотеки LightGBM, например, на 10-folds-cv-контроле можно проверить по-разному, см. код.

Второй предпочтительнее, поскольку в cv_results лежит ошибка при разном числе деревьев в ансамбле, это очень удобно для визуализации. Однако почти всегда результаты двух проверок отличаются. Различие в результатах может быть обосновано немного разным делением на фолды (тут мы добились идентичного разделения указанием folds=cv в lgb.cv). Параметры тестируемых алгоритмов совпадают – тут всё честно. Одна из  главных причин различия результатов – организация бининга (это процесс определения потенциальных порогов для разбиения признаков при построении деревьев). При использовании lgb.cv биннинг делается один раз на всём датасете, потом происходит разбиение по фолдам (заметьте, что это нечестно!). При использовании cross_val_score сначала идёт разбиение на фолды, а потом на каждом фолде делается биннинг. Кстати, в lgb.Datasets есть параметр «reference», который определяет где делать биннинг.

Дополнительные вопросы: есть ли такие сюрпризы в xgb.cv? Будет ли cross_val_score выдавать разные результаты при разных значениях n_jobs? Почему приведённый код вылетит с ошибкой, если folds=cv заменить на nfold=10?

Послесловие

Примеры кода можно найти здесь. Пишите в комментарии свои наблюдения или вопросы, которые бы Вы хотели разобрать в последующих постах. Также есть идея сделать похожую подборку про теоретические задачи машинного обучения (если это вызовет интерес). На всякий случай отмечу, что упомянутые вопросы лучше не задавать на собеседованиях;)

Другие заметки в блоге из этой же серии:Питон (Python), Python: категориальные признаки, Знакомство с Pandas, Знакомство с scikit-learn (слайды), Считаем категории, NumPy — делаем быстрее, Matlab.

Scikit-Learn: тонкие вопросы о реализации методов машинного обучения: 7 комментариев

  1. «Также есть идея сделать похожую подборку про теоретические задачи машинного обучения» — да, да, да!

  2. Ещё хотелось бы видеть обзор на набирающие популярность графовые сетки

  3. Вообще было бы интересно сравнить кросс-валидацию/grid search из sklearn и из lgbm/xgboost/catboost. Насколько я помню, лучше пользоваться как раз решением из sklearn, но тогда как там количество итераций настраивать?

    • На самом деле, для подбора параметров можно встроенные средства попробовать. Потом просто перепроверить с помощью sklearn.

      • Встроенные средства подбирают ещё и количество итераций, а sklearn — нет (там будет только заданное вручную/по-умолчанию)

      • Так поэтому я Вам и написал — используйте встроенные, а потом проверьте на sklearn (согласуется ли полученное качество с ним). Из-за чего может не согласоваться, я как раз в посте написал.

  4. «Также есть идея сделать похожую подборку про теоретические задачи машинного обучения» — было бы здорово

    Еще бы больше разборов вопросов на собеседовании

    Спасибо

Добавить комментарий для joitandr Отменить ответ

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google photo

Для комментария используется ваша учётная запись Google. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s