Как разделять набор данных

Как оптимально разделить набор данных на обучающую, валидационную и тестовую выборки?

У каждого подмножества данных есть цель, от создания модели до обеспечения её производительности:

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

Размер и соотношение данных для обучения

Чтобы принять решение о размере каждого подмножества, я часто вижу стандартные правила и соотношения. Сталкиваюсь с разделением по правилу 80-20 (80 % для тренировочного сплита, 20% для тестового сплита) или по правилу 70-20-10 (70% для обучения, 20% для проверки, 10% для тестирования) и т.д.

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

В BigQuery, как правило, можно создать только подмножество для обучения и тестирования, потому что проверка может быть выполнена при создании модели с DATA_SPLIT опцией.

Прежде чем разделить набор данных, необходимо знать о некоторых распространённых ловушках:

  • Низкокачественные данные: если входные данные, использованные для создания вашей модели, имеют выбросы, это скорее всего негативно повлияет на качество модели.
  • Переобучение: когда входных данных недостаточно, но построенная модель хорошо объясняет параметры из обучающей выборки, то считается, что любой выброс или колебания приводят к недостоверным прогнозам.
  • Несбалансированный набор данных: представьте, что вы хотите обучить модель, которая предсказывает покупку клиента, но у вас есть 500 тысяч потенциальных покупателей и 300 покупателей. Для начала нужно создать более равный набор данных (используя технику повторной выборки). Исследовательский анализ данных (EDA) поможет выявить несбалансированные данные.
  • Утечка данных: означает, что входные данные были изменены (масштабированы, преобразованы) перед созданием обучающего набора данных или во время их разделения.

Разделение набора данных с помощью SQL

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

Как разделять набор данных

Разделю эту таблицу на обучающую и тестовую подгруппы.

Выбор правильного столбца для разделения

Чтобы сделать правильное разделение, используйте один столбец, который отличает каждую строку вашей базовой таблицы. В противном случае может быть трудно создать равномерно распределённое разделение.

Чтобы добиться уникальности, есть несколько вариантов в практических примерах:

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

Для второго варианта наиболее распространенной хеш-функцией, является FARM_FINGERPRINT().

Почему именно этот, а не MD5() или SHA()?

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

Создайте уникальный идентификатор строки

Чтобы создать уникальный идентификатор строки, использую FARM_FINGERPRINT() функцию:

WITH base_table AS ( SELECT * FROM `datastic.train_test_split.base_table`) -- Main Query SELECT *, FARM_FINGERPRINT(order_id) AS hash_value_order_id, TO_JSON_STRING(bt) AS full_json_row, FARM_FINGERPRINT(TO_JSON_STRING(bt)) AS hash_value_full_row, MOD(FARM_FINGERPRINT(order_id), 10) AS hash_mod, ABS(MOD(FARM_FINGERPRINT(order_id), 10)) AS hash_abs_mod FROM base_table bt

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

Как разделять набор данных

Влияние хеш-функции и функции по модулю на нашу базовую таблицу.

В базовой таблице уже есть уникальный идентификатор значения для каждой строки, это order_id поле. В этом случае мы можем просто хешировать его, а использовать ABS(MOD(x,10)) функции для классификации каждой строки в сегментах от 0 до 9.

Чтобы создать хеш всей строки, то есть всех столбцов, обозначим её bt, затем делаем JSON, используя TO_JSON_STRING (bt).

Затем хешировать? Цель этого метода избежать повторяющихся строк, которые всегда будут попадать в одно и то же разделение.

Создание неповторяемого разделения

Чтобы случайным образом разделить набор данных, можно использовать RAND () функцию. Это вернёт для каждой строки псевдослучайное десятичное число в интервале 0–1, включая 0 и исключая 1.

Эта функция обеспечивает равномерное распределение, что означает, что можно отфильтровать, например, все значения ниже 0,8-80% моих данных.

Но каждый раз, когда запускаем запрос, функция будет возвращать разные числа, поэтому наборы для обучения и тестирования будут разными.

Запущу случайное разделение для 80% обучения и 20% тестирования:

WITH base_table AS ( SELECT * FROM `datastic.train_test_split.base_table`) -- Main Query SELECT *, RAND() AS pseudo_random, IF (RAND() < 0.8, 'train', 'test') AS split_set, FROM base_table view raw

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

Но, не сможете воспроизвести точно такое же разделение, если не сохраните результаты как есть.

Как разделять набор данных

Разделить на обучение и тестирование с помощью RAND.

Создание повторяемого разделения

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

Использую уникальный ключ (order_id поле). Можно использовать оператор CASE WHEN со следующим диапазоном, чтобы сделать повторяемое разделение 80/10/10:

WITH base_table AS ( SELECT * FROM `datastic.train_test_split.base_table`) -- Main Query SELECT *, CASE WHEN ABS(MOD(FARM_FINGERPRINT(order_id), 10)) < 8 THEN 'train' WHEN ABS(MOD(FARM_FINGERPRINT(order_id), 10)) = 8 THEN 'validation' WHEN ABS(MOD(FARM_FINGERPRINT(order_id), 10)) = 9 THEN 'test' END AS split_set FROM base_table bt

Разделение на 3 подмножества (обучение, проверка и тестирование).Результат будет таким же, как и для предыдущего неповторяемого разделения, за исключением того, что этот запрос всегда будет производить одно и то же разделение.

Как разделять набор данных

Разделить на обучение, проверку и тестирование с помощью MOD() и FARM_FINGERPRINT().

Вывод

Теперь вы знаете, как лучше разделить набор данных на подмножества, с возможностью повторного использования. Этот метод дает 2 преимущества:

  1. Одни и те же результаты для входных данных.
  2. Возвращает значение в виде числа, а не комбинацию чисел и символов, которым можно легко управлять, что позволяет сократить время обработки. Это означает, что необходимо проверить, какой временной диапазон покрывает 80% (в случае разделения 80/20) наших базовых данных.

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

11
Начать дискуссию