Три подхода к разработке признаков для временных рядов

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

Представьте, что вы только что начали новый проект по науке о данных. Цель состоит в том, чтобы построить модель, предсказывающую Y, целевую переменную. Вы уже получили некоторые данные от заинтересованных сторон/инженеров данных, провели тщательную EDA и выбрали некоторые переменные, которые, по вашему мнению, имеют отношение к рассматриваемой проблеме. Затем вы, наконец, построили свою первую модель. Оценка приемлема, но вы верите, что можете добиться большего. Что вы делаете?

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

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

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

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

Настройка и данные

В этой статье мы в основном используем очень известные пакеты Python, а также полагаемся на относительно неизвестный пакет, scikit-lego, который представляет собой библиотеку, содержащую множество полезных функций, расширяющих возможности scikit-learn’s. Мы импортируем необходимые библиотеки следующим образом:

Для простоты мы сами генерируем данные. В этом примере мы работаем с искусственным временным рядом. Мы начинаем с создания пустого DataFrame с индексом, охватывающим четыре календарных года (мы используем pd.date_range). Затем мы создаем два столбца:

  • day_nr — числовой индекс, представляющий течение времени.
  • day_of_year – порядковый день в году

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

Затем мы создаем новый DataFrame, в котором мы храним сгенерированные временные ряды. Этот DataFrame будет использоваться для сравнения производительности моделей с использованием различных подходов к разработке функций.

results_df = y.to_frame()
results_df.columns = ["actuals"]

Создание объектов, связанных со временем

В этом разделе мы опишем три рассмотренных подхода к генерации признаков, связанных со временем.

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

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

TRAIN_END = 3 * 365

Подход №1: фиктивные переменные

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

Ниже вы можете увидеть результат нашей операции.

Сначала мы извлекли информацию о месяце (закодированную как целое число в диапазоне от 1 до 12) из ​​файла DatetimeIndex. Затем мы использовали функцию pd.get_dummies для создания фиктивных переменных. Каждый столбец содержит информацию о том, относится ли наблюдение (строка) к данному месяцу или нет.

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

В нашем примере мы использовали метод фиктивной переменной, чтобы зафиксировать месяц, в котором было зарегистрировано наблюдение. Однако этот же подход можно использовать для указания диапазона другой информации из DatetimeIndex. Например, день/неделя/квартал года, отметка о том, является ли данный день выходным, первый/последний день периода и многое, многое другое. Вы можете найти список, содержащий все возможные функции, которые мы можем извлечь из указателя документации pandas, доступного на pandas.pydata.org.

Дополнительный совет. Это выходит за рамки этого простого упражнения, но в реальных сценариях мы также можем использовать информацию об особых днях (например, о национальных праздниках, Рождестве, Черной пятнице и т. д.). для создания функций. holidays — это хорошая библиотека Python, содержащая прошлую и будущую информацию об особых днях в каждой стране.

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

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

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

Подход №2: циклическое кодирование с преобразованием синус/косинус

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

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

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

В приведенном ниже фрагменте мы копируем исходный DataFrame, добавляем столбец с номерами месяцев, а затем кодируем столбцы month и day_of_year с помощью синусоидальных/косинусных преобразований. Затем мы строим обе пары кривых.

Есть два вывода, которые мы можем сделать из преобразованных данных, которые представлены на рис. 3. Во-первых, мы можем легко увидеть, что кривые являются ступенчатыми при использовании месяцев для кодирования, но при использовании дневной частоты кривые намного более гладкий; Во-вторых, мы также можем понять, почему мы должны использовать две кривые вместо одной. Из-за повторяющегося характера кривых, если вы проведете прямую горизонтальную линию через график для одного года, вы пересечете кривую в двух местах. Этого будет недостаточно, чтобы модель поняла момент времени наблюдения. Но с двумя кривыми такой проблемы нет, и пользователь может идентифицировать каждый момент времени. Это хорошо видно, когда мы наносим значения функций синуса/косинуса на точечную диаграмму. На рисунке 4 мы видим круговой шаблон без перекрывающихся значений.

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

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

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

Подход №3: радиальные базисные функции

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

Мы используем удобную библиотеку scikit-lego, которая предлагает класс RepeatingBasisFunction, и указываем следующие параметры:

  • Количество базовых функций, которые мы хотим создать (мы выбрали 12).
  • Какой столбец использовать для индексации RBF. В нашем случае это столбец, содержащий информацию о том, за какой день года произошло данное наблюдение.
  • Диапазон ввода — в нашем случае диапазон от 1 до 365.
  • Что делать с оставшимися столбцами DataFrame, которые мы будем использовать для подбора оценки. ”drop” сохранит только созданные функции RBF, ”passthrough” сохранит как старые, так и новые функции.

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

По замыслу базисные функции равномерно распределены по входному диапазону. Мы выбрали 12, так как хотели, чтобы RBF напоминали месяцы. Таким образом, каждая функция показывает примерно (из-за неравной длины месяцев) расстояние до первого дня месяца.

Как и в предыдущих подходах, давайте подгоним модель линейной регрессии, используя 12 функций RBF.

На рисунке 7 показано, что модель способна точно фиксировать реальные данные при использовании функций RBF.

Есть два ключевых параметра, которые мы можем настроить при использовании радиальных базисных функций:

  • количество радиальных базисных функций,
  • форма кривых колокола — ее можно изменить с помощью аргумента width для RepeatingBasisFunction.

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

Окончательное сравнение

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

На рис. 8 показано, что радиальные базисные функции получили наиболее близкое соответствие из рассмотренных подходов. Функции синуса/косинуса позволили модели уловить основные закономерности, но этого было недостаточно, чтобы полностью уловить динамику ряда.

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

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

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

Выводы

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

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

Понравилась статья? Станьте участником Medium, чтобы продолжить обучение, читая без ограничений. Если вы воспользуетесь этой ссылкой, чтобы стать участником, вы поддержите меня без каких-либо дополнительных затрат с вашей стороны. Заранее спасибо и до встречи!

Вас также может заинтересовать один из следующих материалов:







Рекомендации

Все изображения, если не указано иное, принадлежат автору.

Первоначально опубликовано в Блоге разработчиков NVIDIA 17 февраля 2022 г.