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

Почему тебе должно быть до этого дело?

Во-первых, как специалист по анализу данных, работающий с клиентами каждый день, подавляющее большинство проблем, которые я решаю, представляют собой проблемы несбалансированной двоичной классификации. Если подумать, то, если у вас есть вопрос о ваших клиентах, пациентах, оборудовании и т. Д., Скорее всего, это будет вопрос типа «да / нет». Кроме того, весьма вероятно, что этот вопрос вас интересует, потому что один из двух ответов менее распространен, но более важен для вас: "купит ли мой клиент что-нибудь?" или «будет ли моя машина нуждаться в обслуживании в ближайшие X часов?».

Существует множество причудливых техник для решения несбалансированных проблем: случайная недостаточная выборка класса большинства, случайная избыточная выборка класса меньшинства, SMOTE, ADASYN… их. Эта статья посвящена гораздо более простой, но, на мой взгляд, более распространенной проблеме в несбалансированных контекстах: настройка порога принятия решения и встраивание его в вашу модель.

В следующих нескольких разделах я буду использовать scikit-lego, отличный набор расширений для sklearn (в основном проверяйте этот пакет всякий раз, когда вы думаете: Я бы хотел, чтобы sklearn мог это сделать…) и yellowbrick , пакет для визуализации машинного обучения. Оба они отлично работают со sklearn, поэтому вы можете смешивать и сопоставлять функции и классы вместе.

Несколько заявлений об отказе от ответственности перед тем, как мы начнем: Во-первых, я предполагаю, что вы знакомы со sklearn, поэтому не будем вдаваться в подробности каждой строки кода. Кроме того, моя цель здесь - просто получить какой-то рабочий конвейер в качестве примера, поэтому я намеренно пропускаю важные этапы процесса Data Science, включая проверку коллинеарности, выбор функций и т. Д.

Создание начального конвейера sklearn

TL; DR: В этом разделе я напоминаю вам, почему конвейеры качаются, и создают его на основе образца данных. Если вы профессионал в области конвейеров, переходите к следующему разделу!

Когда вы создаете и развертываете модели машинного обучения, обычно рекомендуется стараться сделать их «как можно более сквозными». Другими словами, постарайтесь сгруппировать большинство преобразований данных, связанных с моделью, в один объект, который вы называете своей моделью. А в мире sklearn это делается с помощью конвейера.

Я начал использовать конвейеры sklearn как лучшую практику Data Science. Если вы их не используете, обязательно прочтите эту статью от Андреаса Мюллера: в одном из разделов объясняется, какие проблемы могут возникнуть, если вы их не используете, в частности, об утечке данных. По мере того, как я использовал их больше, я понял, что используя конвейеры, вы также получаете преимущество абстрагирования от большего количества вещей в то, что вы называете вашей моделью, что отлично подходит для развертывания.

используя конвейеры, вы также получаете возможность абстрагироваться от большего количества вещей в то, что вы называете «своей моделью»

Чтобы сделать это более конкретным, давайте построим конвейер на примере набора данных. Я буду использовать образец набора данных о сердечных заболеваниях, который поставляется с sklego.

Этот набор данных очень прост: 9 числовых (для простоты я сохраняю упорядоченные категории, такие как cp как числовые значения), 3 двоичных (sex, fbs, exang), 1 категоричный (thal). Мишень бинарная и несбалансированная: только 27% образцов имеют положительную метку.

Я создаю быстрый конвейер, состоящий из двух конвейеров: один для числовых значений (для простоты я рассматриваю двоичные и упорядоченные категориальные признаки как числовые), а другой - для категориальных значений. В обучающих данных нет пропущенных значений, но на тот случай, если они появились в производстве, я включаю несколько простых вменений. Я заметил, что некоторые категории функции thal кажутся неправильными, поэтому я вручную указываю категории, которые хочу однократно кодировать, чтобы sklearn не кодировал их.

В приведенном ниже коде я уже использую sklego. Обычно я писал разные конвейеры для числовых и категориальных функций, затем определял списки функций для их применения и собирал все вместе с помощью ColumnTransformer. Со sklego это немного проще: существует класс PandasTypeSelector, который может выбирать столбцы в зависимости от их типа панд. Я просто помещаю один в начало двух моих конвейеров, а затем объединяю два с помощью FeatureUnion.

Теперь, когда мы закончили предварительную обработку, все, что нам нужно, это добавить модель. Это небольшой набор данных и простая проблема, поэтому я буду строить простую логистическую регрессию. Я настрою параметр регуляризации C, используя поиск по сетке, и оставлю другие параметры по умолчанию (опять же, пропуская важные части построения модели, не воспроизводите дома!).

Хорошо, теперь у нас есть обученный конвейер с парой шагов, включая простую модель. Обратите внимание, что пока мы ничего не сделали с дисбалансом, но мы готовы к интересной части!

Настройка порога принятия решения

Давайте посмотрим на прогнозы модели через призму матрицы путаницы. Обратите внимание, что, поскольку мы собираемся выполнить некоторую дополнительную настройку, мы делаем это на обучающем наборе (в реальном проекте я, вероятно, разделил бы на обучение, тестирование и проверку, чтобы все было правильно). Здесь мы начинаем использовать желтый кирпич, потому что, хотя создать такую ​​диаграмму с помощью matplotlib легко, как вы можете видеть, код намного проще с использованием класса ConfusionMatrix.

Во-первых, мы видим, что с этой моделью все в порядке (перекрестная проверка Средняя точность составляет 0,78): точность (когда мы прогнозируем 1, как часто мы получаем ее правильно?) Составляет 0,8, в то время как отзыв (сколько из истинных единиц, которые мы предсказали?) составляет 0,67. Теперь мы спрашиваем себя: правильный ли баланс между точностью и запоминанием? Мы предпочитаем занижать или переоценивать нашу цель? В реальной жизни это когда вы связываете проблему Data Science с реальной основной проблемой бизнеса.

Теперь мы спрашиваем себя: правильный ли баланс между точностью и запоминанием? Мы предпочитаем занижать или переоценивать нашу цель? В реальной жизни это когда вы связываете проблему Data Science с реальной основной проблемой бизнеса.

Чтобы принять это решение, мы смотрим на другую диаграмму, и именно здесь желтый кирпич действительно эффективен, предоставляя нам класс DiscriminationThreshold. Любая бинарная модель обычно выводит либо вероятность, либо оценку, которую можно использовать в качестве прокси для вероятности, а окончательный результат, который вы получаете (0 или 1), получается путем применения порога к этой оценке. Что мы можем сделать, так это определить различные количества, которые зависят от этого порога, а затем непрерывно перемещать порог, чтобы увидеть, как эти количества увеличиваются или уменьшаются. Как правило, точность будет увеличиваться, а отзыв уменьшаться по мере увеличения порога: например, если вы установите порог на 0,9, ваша модель будет предсказывать 1 только тогда, когда 1 имеет гораздо более высокую вероятность, чем 0, поэтому вы прогнозируете меньше единиц, но имеете больше шансов оказаться правым, когда вы это сделаете.

На этом графике много информации. Во-первых, синие, зеленые и красные линии соответствуют очень распространенным метрикам. Мы уже обсуждали точность и отзывчивость, а f1 - гармоническое среднее из двух. Мы видим, что первое увеличивается, когда второе уменьшается, и есть золотая середина, где f1 является максимальным, то есть где баланс между точностью и отзывом идеален. Yellowbrick помогает нам, нанося пунктирную линию на этом идеальном пороге.

Фиолетовое количество относится к другому количеству, о котором вы не обязательно узнаете при изучении Data Science, но очень важно в реальных случаях использования Data Science. Документация из желтого кирпича отлично описывает эту метрику:

Скорость очереди: «Очередь» - это папка для спама или почтовый ящик службы расследования мошенничества. Этот показатель описывает процент экземпляров, которые необходимо проверить. Если проверка связана с высокими затратами (например, предотвращение мошенничества), то ее необходимо минимизировать в соответствии с требованиями бизнеса; если нет (например, фильтр спама), это можно оптимизировать, чтобы почтовый ящик оставался чистым.

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

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

И последнее, что следует отметить: когда мы выполняли поиск по сетке, мы выбрали лучшую модель, используя average_precision, что важно. Если бы мы использовали оценку f1, то, например, мы бы полагались только на порог по умолчанию и, следовательно, могли бы пропустить комбинацию гиперпараметров, которые дают лучший результат f1 при другом пороге. Поэтому я убедился, что выбрал модель с использованием метрики, не зависящей от порога, и только затем настроил порог.

Изменение порога принятия решения для нашей модели

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

Но как именно преодолеть этот порог? Что ж, я довольно долго зацикливался на этой проблеме. В серии моделей, которые я запускаю в производство, мы сохраняем этот идеальный порог где-нибудь, например, в переменной среды в среде развертывания, затем вызываем predict_proba() метод модели и применяем порог. Поддерживать это намного труднее, чем кажется, потому что вам нужно беспокоиться о двух вещах: модели и ее пороговом значении. Каждый раз, когда вы повторно развертываете новую версию модели, вы должны убедиться, что нести порог с собой… Безумно, но уже более 5 лет в репозитории sklearn открыта проблема!

Как я уже упоминал в начале этой статьи, всякий раз, когда вы думаете: «Я бы хотел, чтобы sklearn сделал это…», проверьте sklego!

Приведенный выше фрагмент кода труднее читать, но он действительно полезен! Во-первых, мы извлекаем лучший порог из визуализатора желтого кирпича, обращаясь к базовому массиву оценок cv для нашей выбранной метрики (здесь visualizer.argmax равно f1) и получая его argmax(). Это дает нам позицию лучшего порога в массиве visualizer.thresholds_. Насколько мне известно (напишите, пожалуйста, комментарий, если есть способ лучше!), Это единственный способ получить лучший порог, напечатанный ранее желтым кирпичом (пунктирная линия).

Как только у нас есть это, мы определяем наш финальный конвейер, который состоит из всех шагов начального (настроенного) конвейера (*best_model[:-1]), за которым следует объект Thresholder, который является оболочкой вокруг нашей модели sklearn, которая применяет указанный порог. Когда мы вызываем .predict() в этом конвейере, Thresholder позаботится о применении правильного порога вместо порога 0,5 по умолчанию.

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