"Начиная"

Улучшение разделения поезд-тест с помощью функции хеширования

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

Недавно я читал Практическое машинное обучение с помощью Scikit-Learn, Keras и TensorFlow Орелиена Жерона (2-е издание), и я понял, что может быть проблема с тем, как мы подходим к поезду. тестовое разделение при подготовке данных для моделей машинного обучения. В этой статье я быстро продемонстрирую, в чем проблема, и покажу, как ее исправить.

Иллюстрируя проблему

Сразу хочу сказать, что упомянутая мною проблема не всегда является проблемой per se, и все зависит от варианта использования. При подготовке данных для обучения и оценки мы обычно разделяем данные с помощью такой функции, как train_test_split в Scikit-Learn. Чтобы убедиться, что результаты воспроизводимы, мы используем аргумент random_state, поэтому, сколько бы раз мы не разбивали один и тот же набор данных, мы всегда будем получать одно и то же разбиение поезд-тест. И в этом предложении кроется потенциальная проблема, о которой я упоминал ранее, особенно в части, касающейся того же набора данных.

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

И тут как раз и возникает проблема. Когда вы используете старый добрый train_test_split в новом наборе данных (все старые наблюдения + новые, собранные вами с момента обучения), нет гарантии, что наблюдения, на которых вы тренировались в прошлом, по-прежнему будут использоваться для обучения, а то же самое будет верно и для тестового набора. Я проиллюстрирую это примером на Python.

Сначала я создал DataFrame с 1000 случайными наблюдениями. Я применил разделение «поезд-тест 80–20», используя random_state, чтобы гарантировать воспроизводимость результатов. Затем я создал новый DataFrame, добавив 500 наблюдений в конец начального DataFrame (в этом случае для отслеживания наблюдений важен сброс индекса!). Я снова применил разделение на поезд-тест, а затем исследовал, сколько наблюдений из начальных наборов на самом деле появляется во вторых. Для этого я использовал удобный intersection метод Python set. Ответ: 669 из 800 и 59 из 200. Это ясно показывает, что данные были перетасованы.

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

Решение вопроса

Итак, как мы можем решить эту проблему? Одна из возможностей - распределить наблюдения по обучающей и тестовой выборкам на основе определенного уникального идентификатора. Мы можем вычислить хэш идентификатора наблюдений, используя какую-то хеш-функцию, и если значение меньше x% от максимального значения, мы помещаем это наблюдение в набор тестов. В противном случае он принадлежит обучающей выборке.

Вы можете увидеть пример решения (основанный на решении, представленном Орелиеном Жероном в его книге) в следующей функции, которая использует алгоритм CRC32. Я не буду вдаваться в подробности алгоритма, про CRC вы можете прочитать здесь. В качестве альтернативы, здесь вы можете найти хорошее объяснение того, почему CRC32 вполне может служить функцией хеширования, и какие у него недостатки - в основном с точки зрения безопасности, но для нас это не проблема. Функция следует логике, описанной в предыдущем параграфе, где 2³² - максимальное значение этой хеш-функции.

Примечание. Вышеупомянутая функция будет работать для Python 3. Чтобы настроить ее для Python 2, мы должны следовать документации crc32 и использовать ее следующим образом: crc32(data) & 0xffffffff. Вы также можете прочитать более подробное описание здесь.

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

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

Чтобы убедиться, что функция выполняет то, что мы хотим, мы еще раз запускаем тестовый сценарий, как показано выше. На этот раз для обоих DataFrames мы используем функцию hashed_train_test_split.

Используя хешированный уникальный идентификатор для распределения, мы достигли идеального перекрытия как для обучающих, так и для тестовых наборов.

Выводы

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

Вы можете найти код, использованный для этой статьи, на моем GitHub. Как всегда, приветствуются любые конструктивные отзывы. Вы можете связаться со мной в Твиттере или в комментариях.

Если вам понравилась эта статья, возможно, вас заинтересует одно из следующего:







использованная литература

  • Жерон, А. (2019). Практическое машинное обучение с помощью Scikit-Learn, Keras и TensorFlow: концепции, инструменты и методы для создания интеллектуальных систем. 2-е издание, O’Reilly Media.