Как закодировать периодические временные признаки

Кодировка периодических временных признаков

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

Иллюстрация от автора

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

В этом случае время в году является ценной сезонной информацией, которую мы должны включить в модель. Но как нам это сделать? Даты сложны, количество дней меняется в зависимости от месяца (и для февраля – даже в зависимости от года), и они существуют в различных форматах:

13-е Января 2023 года

13.01.2023

2023/03/13

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

Почему это плохая идея? Модели пришлось бы выучить, как работает христианский григорианский календарь (около 30 дней в месяце, 12 месяцев в году, високосные годы и т.д.). С достаточным объемом обучающих данных модель глубокого обучения, конечно, сможет “понять” наш календарь. В данном случае “понимание” означает: модель может выводить относительное положение времени в году из входных данных месяца и даты. Но мы должны сделать обучение как можно проще для нашей модели и взять на себя эту задачу (по крайней мере, мы уже знаем, как работает календарь). Мы используем библиотеку datetime в Python и вычисляем относительное положение времени в году с помощью довольно простой логики:import datetime as

from datetime import datetimeimport calendaryear = 2023month = 12day = 30passed_days = (datetime(year, month, day) - datetime(year, 1, 1)).days + 1nr_of_days_per_year= 366 if calendar.isleap(year) else 365position_within_year = passed_days / nr_of_days_per_year

Полученная функция position_within_year со значением от близкого к 0.0 (1 января) до 1.0 (31 декабря) гораздо легче интерпретируется моделью, чем (чертовски сложный) григорианский календарь.

Но это все еще не идеально. Функция position_within_year описывает “пилообразный” график с резким переходом от 1.0 до 0.0 при каждом обороте года. Это резкое изменение может быть проблемой для эффективного обучения. 31 декабря и 1 января – очень похожие даты: они являются соседями и имеют много общего (например, похожие погодные условия), и, вероятно, имеют сходный потенциал для продажи лимонада. Однако функция position_within_year не отражает эту схожесть для 31 декабря и 1 января; на самом деле, они такие разные, как только могут быть.

Идеально, чтобы точки времени, близкие друг к другу, имели похожие временные значения. Мы должны как-то создать признак, который отражает циклическую природу года. Другими словами, к 31 декабря мы должны вернуться в ту же позицию, в которой были 1 января. Поэтому, конечно же, имеет смысл моделировать положение в году как положение на окружности. Мы можем сделать это, преобразовав position_within_year в координаты x и y единичной окружности.

Для этого мы используем функции синуса и косинуса:

sin(α) = x

cos(α) = y

где α – это угол, применяемый к окружности. Если единичная окружность представляет год, α представляет собой время, прошедшее в течение года.

Таким образом, α эквивалентна свойству position_within_year, единственное отличие заключается в том, что α имеет другой масштаб (α: 0.0–2π¹, position_within_year: 0.0-1.0).

Просто масштабируя position_within_year до α и вычисляя синус и косинус, мы преобразуем “пиловидный” паттерн в круговое представление с плавными переходами.

import math# масштабирование до 2pi (360 градусов)alpha = position_within_year * math.pi * 2year_circle_x = math.sin(alpha)year_circle_y = math.cos(alpha)# масштабирование между 0 и 1 (оригинальные позиции окружности находятся между -1 и 1)year_circle_x = (year_circle_x + 1) / 2year_circle_y = (year_circle_y + 1) / 2time_feature = (year_circle_x, year_circle_y) # так красиво ;)

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

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

  • Логистика/Общественный транспорт: Относительное положение автобуса в его круговом маршруте по городу

  • Биология: Состояние клетки внутри клеточного цикла.
  • У вас есть другие варианты использования? Добро пожаловать, оставьте комментарий!

Дополнительная информация / Точки связи

  • Отличная практическая статья на ту же тему от Пьера-Луи Бескона.
  • Вы хотите узнать больше о создании признаков для моделей глубокого обучения? Ознакомьтесь с моей статьей о контекстно обогащенных данных.
  • У вас есть вопросы? Вам нужен фрилансер-эксперт по искусственному интеллекту, науке о данных, инженерии данных или разработке на Python? Посетите мой сайт и напишите мне сообщение.

[1] Угол здесь задан в радианах. 0 в радианах соответствует 0°, 2π в радианах соответствует 360°.

Все рисунки созданы автором.