Рассмотрим несколько, казалось бы, простых вопросов об алгоритмах машинного обучения и их реализации, на которые, однако, правильно ответить смогут лишь единицы (вы можете попробовать сами — не читая пояснений. Обратите внимание, что дополнительные вопросы в этом посте намеренно оставлены без ответа). Материал в этом посте для среднего уровня (тех, кто уже знаком с машинным обучением (ML) и библиотекой scikit-learn)

Первоначально опубликовано на dasha.ai/en-us/blog/scikit-learn

Этот пост был первоначально написан Алексом Дьяконовым, главным научным сотрудником Dasha AI.

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

Можно провести такой эксперимент: взять обучающую выборку из двух объектов, принадлежащих к разным классам (0 и 1). Мы используем тот же образец в качестве тестового образца (см. рис. 1). Объекты классифицируются правильно, но вероятность их принадлежности к первому классу составляет 0,65 и 0,35. Во-первых, это очень странные значения, а во-вторых, объект из класса 0 имеет высокую вероятность принадлежности к классу 1 и наоборот. Действительно ли есть ошибка в sklearn (библиотеке, которой столько лет активно пользуются)?

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

класс sklearn.svm.SVC(*, C=1.0, ядро='rbf', степень=3, гамма='масштаб', coef0=0.0, сжатие=Истина, вероятность=Ложь, tol=0.001, cache_size=200, class_weight = Нет, подробно = Ложь, max_iter = - 1, solution_function_shape = 'ovr', break_ties = Ложь, random_state = Нет) [источник]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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