Используйте хорошо распределенный столбец, чтобы разделить данные на обучающие / действительные / тестовые.

Периодическая серия шаблонов проектирования для инженеров машинного обучения. Полный список здесь.

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

df = pd.DataFrame(...)
rnd = np.random.rand(len(df))
train = df[ rnd < 0.8  ]
valid = df[ rnd >= 0.8 & rnd < 0.9 ]
test  = df[ rnd >= 0.9 ]

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

Решение состоит в том, чтобы разделить набор данных на основе столбца даты:

SELECT
  airline,
  departure_airport,
  departure_schedule,
  arrival_airport,
  arrival_delay
FROM
  `bigquery-samples`.airline_ontime_data.flights
WHERE
  ABS(MOD(FARM_FINGERPRINT(date), 10)) < 8 -- 80% for TRAIN

Помимо решения исходной проблемы (утечки данных), это также дает вам повторяемость:

  1. FARM_FINGERPRINT - это алгоритм хеширования с открытым исходным кодом, который последовательно реализован в C ++ (и, следовательно, в Java или Python) и в BigQuery SQL.
  2. Все рейсы в любую дату будут принадлежать к одному и тому же сплит-поезду, действительному или испытательному. Это повторяется независимо от таких вещей, как случайное семя.

Выбор разделенного столбца

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

  1. Строки на одну и ту же дату имеют тенденцию быть коррелированными - опять же, это ключевая причина, по которой мы хотим обеспечить, чтобы все строки на одну и ту же дату находились в одном и том же разбиении.
  2. Дата не является входными данными для вашей модели (функции, извлеченные из даты, такие как dayofweek или hourofday, могут быть входными данными, но вы не можете использовать фактические входные данные для разделения, потому что обученная модель тогда не увидит 20% возможных входных значений) .
  3. Значений даты должно быть достаточно. Поскольку вы вычисляете хэш и находите по модулю 10, вам нужно как минимум 10 уникальных значений хеш-функции. Чем больше у вас уникальных ценностей, тем, конечно, лучше. На всякий случай увеличьте знаменатель по модулю в 3–5 раз, поэтому в данном случае вам нужно 50 или более уникальных дат.
  4. Этикетка должна быть хорошо распределена между финиками. Если окажется, что все задержки произошли 1 января и в остальную часть года, задержек не было, это не сработает, поскольку разбитые наборы данных будут искажены. На всякий случай посмотрите на график и убедитесь, что все три разделения имеют одинаковое распределение меток по задержке отправления или какому-либо другому входному значению. Вы можете автоматизировать это с помощью теста Коломогорова-Смирнова.

Вариант 1: одиночный запрос

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

CREATE OR REPLACE TABLE mydataset.mytable AS
SELECT
  airline,
  departure_airport,
  departure_schedule,
  arrival_airport,
  arrival_delay,
  CASE(ABS(MOD(FARM_FINGERPRINT(date), 10)))
      WHEN 9 THEN 'test'
      WHEN 8 THEN 'validation'
      ELSE 'training' END AS split_col
FROM
  `bigquery-samples`.airline_ontime_data.flights

Вариант 2: случайное разделение

Что, если вы хотите случайное разделение, но вам нужна только повторяемость? В этом случае вы можете просто хешировать сами данные строки. Вот простой способ сделать это:

SELECT
  airline,
  departure_airport,
  departure_schedule,
  arrival_airport,
  arrival_delay
FROM
  `bigquery-samples`.airline_ontime_data.flights f
WHERE
  ABS(MOD(FARM_FINGERPRINT(TO_JSON_STRING(f)), 10)) < 8

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