6 досадных ошибок Sklearn, которые вы можете совершать, и как их избежать
Научитесь избегать шести самых серьёзных ошибок, связанных с машинным обучением, которые новички часто совершают через Sklearn.
Часто при работе Sklearn выдаёт большие красные сообщения об ошибках и предупреждения, когда вы делаете что-то не так. Эти сообщения предполагают, что в вашем коде содержатся ошибки, которые мешают магии Sklearn делать свою работу.
@machinelearning_ru – наш телеграм канал о машинном обучении
Но что произойдёт, если у вас нет никаких ошибок или предупреждений в коде? Означает ли это, что вы делаете всё правильно? Не обязательно.
Ошибки при написании кода Sklearn можно легко исправить. Что может остаться незамеченным, так это ошибки, связанные с внутренней логикой и теорией машинного обучения, которые лежат в основе алгоритмов и преобразователей Sklearn.
Эти ошибки особенно распространены и незаметны, когда вы новичок. Эта статья будет о шести таких ошибках, которые я совершил и научился избегать, когда сам был новичком.
1️⃣. Использование fit
или fit_transform
везде
Начнём с самой серьезной ошибки — ошибки, связанной с утечкой данных . Утечка данных малозаметна и может быть разрушительной для производительности модели.
Это происходит, когда во время обучения модели используется информация, которая не была бы доступна во время прогнозирования. Утечка данных приводит к тому, что модели дают очень оптимистичные результаты даже при перекрёстной проверке, но ужасно работают при тестировании на реальных новых данных.
Утечка данных является обычным явлением во время предварительной обработки данных, особенно если обучающая и тестовая выборки не разделены. Многие преобразователи предварительной обработки Sklearn, такие как импутеры, нормализаторы, функции стандартизации и логарифмические преобразователи, подключаются к основному распределению данных во время подбора.
Например, данные StandardScaler
нормализуются путём вычитания среднего значения из каждой выборки и деления его на стандартное отклонение. Вызов функции fit()
для полных данных (X) позволяет преобразователю узнать среднее значение и стандартное отклонение всего распределения каждой функции.
Если после преобразования эти данные будут разделены на тренировочные и тестовые наборы, тренировочный набор будет загрязнён, поскольку из фактического распределения StandardScaler
просочилась важная информация.
Хотя это может быть неочевидно для нас, алгоритмы Sklearn достаточно мощны, чтобы заметить это и воспользоваться преимуществами во время тестирования. Другими словами, данные наборы были бы слишком идеальными для модели, потому что они содержат полезную информацию о тестовом наборе, а тест не был бы достаточно новым, чтобы проверить производительность модели на реальных невидимых данных.
Самое простое решение — никогда не обращаться к полным данным fit
. Прежде чем выполнять какую-либо предварительную обработку, всегда разделяйте данные на обучающие и тестовые наборы. Даже после разделения вы никогда не должны использовать fit
или fit_transform
при обучении, потому что в конечном итоге вы столкнётесь с той же проблемой.
Поскольку и обучающие, и тестовые наборы должны проходить одни и те же этапы предварительной обработки, золотое правило заключается в использовании тренировочные данных fit_transform
— это гарантирует, что преобразователь учится только на обучающем наборе и одновременно преобразует его. Затем вызовите метод transform
в тестовом наборе, чтобы преобразовать его на основе информации, полученной из обучающих данных.
Более надёжным решением было бы использование встроенных конвейеров Sklearn. Классы конвейеров специально созданы для защиты алгоритмов от утечки данных. Использование конвейеров гарантирует, что обучающие данные используются во время, а тестовые данные fit
используются только для вычислений.
2️⃣. Оценка производительности модели только по результатам тестов
У вас результат выше 0,85 — стоит ли радоваться? НЕТ!
Несмотря на то, что высокие результаты тестов обычно означают надёжную работу, есть важные оговорки в отношении интерпретации результатов тестов. Во-первых, и самое главное, независимо от значения, результаты тестов должны оцениваться только на основе баллов, полученных в результате обучения.
Единственный раз, когда вы должны быть довольны своей моделью, это когда оценка за обучение выше, чем оценка за тест, и обе они достаточно высоки, чтобы удовлетворить ожидания вашего уникального случая. Однако это не означает, что чем выше разница между результатами обучения и теста, тем лучше.
Например, 0,85 балла за обучение и 0,8 балла за тест предполагают хорошую модель, которая не является ни переобученной, ни недостаточной. Но если оценка обучения превышает 0,9, а оценка теста равна 0,8, ваша модель переобучается. Вместо того, чтобы обобщать во время обучения, модель запоминала некоторые данные, что привело к гораздо более низкому результату теста.
Вы часто будете видеть такие случаи с древовидными и ансамблевыми моделями . Например, такие алгоритмы, как Random Forest, имеют тенденцию достигать очень высоких результатов обучения, если их глубина дерева не контролируется, что приводит к переоснащению. Вы можете прочитать это обсуждение на StackExchange, чтобы узнать больше о разнице между результатами обучения и тестов.
Бывает и так, что результат теста выше, чем у тренировки. Если результат теста хоть немного выше, чем на тренировке, встревожьтесь, потому что вы допустили ошибку! Основной причиной таких сценариев является утечка данных, и мы обсуждали пример этого в последнем разделе.
Иногда также можно получить хороший балл за обучение и очень низкий балл за тестирование. Когда разница между результатами обучения и теста огромна, проблема часто будет связана с набором тестов, а не с переоснащением. Это может произойти из-за использования разных шагов предварительной обработки для обучающих и тестовых наборов или просто из-за того, что вы забыли применить предварительную обработку к тестовому набору.
Таким образом, всегда внимательно проверяйте разрыв между результатами обучения и тестов. Это подскажет вам, следует ли вам применять регуляризацию для преодоления переобучения, искать возможные ошибки, допущенные вами во время предварительной обработки , или наилучший сценарий, готовить модель для окончательной оценки и развёртывания.
3️⃣. Генерация неверных наборов тренировок/тестов в классификации
Распространённой ошибкой среди новичков является то, что они забывают создать стратифицированные обучающие и тестовые наборы для классификации.
Модель с большей вероятностью будет генерировать правильные прогнозы, когда новое распределение данных максимально соответствует обучению. В классификации нас интересуют только веса или пропорции классов.
Например, в задаче классификации с тремя классами веса классов равны 0,4, 0,3, 0,3. Когда мы делим эти данные на обучающие и тестовые наборы, распределения обоих наборов должны отражать распределение полных данных.
Мы обычно используем функцию train_test_split
для разделения данных, а Sklearn предоставляет удобный аргумент — stratify
для создания стратифицированных разделений . Вот пример обучающих/тестовых наборов со стратифицированным разделением и без него:
# Look at the class weights before splitting
>>> pd.Series(y).value_counts(normalize=True)
1 0.4
0 0.4
2 0.2
dtype: float64
# Generate unstratified split
X_train, X_test, y_train, y_test = train_test_split(X, y)
# Look at the class weights of train set
>>> pd.Series(y_train).value_counts(normalize=True)
0 0.42
1 0.38
2 0.20
dtype: float64
# Look at the class weights of the test set
>>> pd.Series(y_test).value_counts(normalize=True)
1 0.46
0 0.34
2 0.20
dtype: float64
Как видите, и обучающие, и тестовые наборы имеют разные веса для первого и второго классов. Давайте исправим это:
# Generate stratified split
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y)
# Train set class weights
>>> pd.Series(y_train).value_counts(normalize=True)
1 0.4
0 0.4
2 0.2
dtype: float64
# Train set class weights
>>> pd.Series(y_test).value_counts(normalize=True)
1 0.4
0 0.4
2 0.2
dtype: float64
Установка цели stratify
( y
) привела к идентичным весам классов как в обучающем, так и в тестовом наборах.
Измененные веса классов — серьёзная проблема, которая может сделать модель более предвзятой по отношению к конкретному классу. Если вы забудете создать стратифицированные разбиения, это может привести к более благоприятному набору обучающих или тестовых наборов или вызвать следующие проблемы:
Выше приведена производительность классификатора KNN, который я построил, когда начал изучать Sklearn. Как видите, почти все результаты тестов выше, чем при обучении, потому что я забыл сгенерировать стратифицированные сплиты. В результате тестовый набор дал слишком благоприятное распределение для моей модели.
После устранения проблемы:
Всё пришло в норму.
При использовании перекрёстной проверки или конвейеров вам не нужно беспокоиться об этой проблеме, потому что сплиттеры CV выполняют скрытую стратификацию, используя StratifiedKFold
для задач классификации.
4️⃣. Использование LabelEcoder
для кодирования массива X
Вы когда-нибудь раздражались, узнав, что категориальные столбцы LabelEncoder
кодируются только по одному за раз? По сравнению с другими преобразователями текста, которые могут одновременно преобразовывать несколько функций, это кажется разочарованием от Sklearn.
Но я здесь, чтобы сказать вам, что это не так! Это просто результат нашего нежелания читать документацию. Вот выдержка из документации LabelEncoder
с двумя предложениями:
Этот преобразователь следует использовать для кодирования целевых значений, т. е.
y
, а не входныхX
.
Что же мы используем для кодирования порядковых текстовых признаков? Если мы любезно перейдём к руководству пользователя Sklearn по кодированию категориальных функций , мы увидим, что в нём четко указано:
Чтобы преобразовать категориальные признаки в целочисленные коды, мы можем использовать расширение
OrdinalEncoder
. Этот оценщик преобразует каждую категориальную характеристику в одну новую целочисленную характеристику (от 0 до n_categories – 1).
Использование OrdinalEncoder
позволяет нам преобразовывать сразу несколько столбцов, как и ожидалось, и даёт возможность интегрироваться в экземпляры Pipeline, что невозможно. Кодер следует знакомому API преобразования Sklearn:
from sklearn.preprocessing import OrdinalEncoder
oe = OrdinalEncoder(handle_unknown='ignore')
X_train = oe.fit_transform(X_train)
X_test = oe.transform(X_test)
Вы можете многое узнать о Sklearn, просто прочитав документацию и руководство пользователя!
5️⃣. Оценка производительности модели без перекрёстной проверки
Я думаю, вы уже знакомы с темой переобучения. Это настолько насущная проблема в машинном обучении, что для её преодоления было разработано бесчисленное множество методов.
Самый простой — это использование части данных в качестве тестового набора для имитации и измерения производительности модели на невидимых данных. Однако гиперпараметры моделей можно настраивать до тех пор, пока модель не достигнет максимальной оценки в этом конкретном наборе тестов, что опять-таки означает переоснащение.
Мы могли бы взять другую часть полных данных в качестве «проверочного набора», чтобы ещё раз обойти это. Модель будет обучаться на обучающих данных, точно настраивать её производительность на проверочном наборе и запускать её через тестовый набор для окончательной оценки.
Но разделение наших драгоценных данных на 3 набора означало бы меньший объём данных, из которых модель может извлечь уроки. Вся производительность модели будет зависеть от этой конкретной пары тренировочного и тестового наборов.
Итак, специалисты по машинному обучению обычно используют процедуру, называемую K-кратной перекрёстной проверкой (сокращенно CV). В зависимости от его значения полные данные делятся на наборы K , называемые сгибами, и для каждого сгиба модель будет использовать число сгибов K-1 в качестве обучающих данных, а остальные — в качестве тестового набора. После того, как резюме будет выполнено, модель будет обучена и протестирована на всех данных. Вот схема этого процесса Sklearn для 5-кратного CV:
Ещё одно преимущество перекрёстной проверки заключается в том, что она полностью исключает случайность. Другими словами, вам не придётся беспокоиться о случайном создании слишком благоприятных обучающих и тестовых наборов train_test_split , которые искажают целевую функцию вашей модели.
Подробнее о реализации CV в коде вы можете узнать из официального руководства пользователя .
6️⃣. Использование точности в качестве показателя для оценки эффективности классификаторов
По умолчанию все классификаторы Sklearn используют точность в качестве метода оценки, когда мы вызываем функцию .score
. Из-за лёгкого доступа к метрике новички часто используют её для оценки производительности своей модели.
К сожалению, стандартная оценка точности полезна только в одном сценарии — задаче бинарной классификации с равными сбалансированными весами классов.
В других случаях это настолько вводящая в заблуждение метрика, что даже самые неэффективные модели могут прятаться за высокими показателями точности. Например, если модель обнаруживает электронные письма со спамом, она может достичь точности более 90%, даже не найдя ни одного письма со спамом.
Почему? Поскольку спам-письма встречаются не так часто, классификатор может обнаруживать все письма, не являющиеся спамом, что может повысить его точность, даже если классификатор полностью не справляется со своей задачей — обнаружением спама!
Эта проблема ещё хуже для многоклассовой классификации . Если вы достигаете точности 80 %, означает ли это, что модель более точно определяет класс 1, класс 2, класс 3 или даже все классы?
Точность никогда не может ответить на такие вопросы, но, к счастью, множество других показателей классификации дают гораздо более информативную сводку производительности.
Заключение
Совершать неловкие ошибки, конечно, отстой, но всё это часть пути. Даже самые опытные люди признаются в совершении ошибок, из-за которых они в своё время очень пострадали.
Но у каждого из них был кто-то, кто, наконец, указал на их ошибки и показал, как правильно поступать. Если вы совершали какую-либо из вышеперечисленных ошибок, надеюсь, я стал для вас этим человеком.
Спасибо за чтение!