В предыдущей части нашей серии Facebook Prophet мы рассмотрели, как моделировать компонент тренда и настраивать точки изменения и регуляризацию для повышения точности прогнозирования. В этой статье мы сосредоточимся на сезонном компоненте и рассмотрим, как его эффективно моделировать с помощью Facebook Prophet.

Сезонная составляющая

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

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

По умолчанию Prophet соответствует годовой и еженедельной сезонности, если частота ваших данных ежедневная или ниже, и если она длится более двух циклов. Для годового компонента порядок Фурье по умолчанию равен 10, а для недельного — 3. Кроме того, если ваши данные содержат компонент времени, они также будут соответствовать ежедневной сезонности с порядком Фурье, установленным на 4.

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

# Import libraries
from prophet import Prophet

# Instantiate model and fit data
model = Prophet()
model.fit(df)

# Define forecasting period (daily by default)
future = model.make_future_dataframe(periods=72,
                                     freq='H')
# Predict future values
forecast = model.predict(future)

# Plot components
fig = model.plot_components(forecast)

Мы получим следующее разложение:

Чтобы подтвердить значения по умолчанию, мы можем проверить атрибут «сезонность» модели.

print(model.seasonalities)
OrderedDict([('weekly',
              {'period': 7,
               'fourier_order': 3,
               'prior_scale': 10.0,
               'mode': 'additive',
               'condition_name': None}),
             ('daily',
              {'period': 1,
               'fourier_order': 4,
               'prior_scale': 10.0,
               'mode': 'additive',
               'condition_name': None})])

Можно проверить, как порядок Фурье влияет на компонентное моделирование, например, недельного:

from prophet.plot import plot_seasonality

# Plot default weekly seasonality order (3)
plot_seasonality(model, name='weekly')

# Plot increased weekly seasonality order
model = Prophet(weekly_seasonality=5).fit(df)
plot_seasonality(model, name='weekly')

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

Мы также могли бы отключить любую нежелательную сезонность, например, еженедельную:

model = Prophet(weekly_seasonality=False)

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

# Add seasonality to instantiated model
model.add_seasonality(name='monthly',
                      period=30.5,
                      fourier_order=5)

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

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

model = Prophet(seasonality_mode='multiplicative')

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

# Add seasonality to instantiated model
model.add_seasonality(name='monthly',
                      period=30.5,
                      fourier_order=5,
                      mode='additive')

Условная сезонность

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

Для начала мы можем создать функцию, которая определяет, приходится ли данная дата на выходные (дни 5 или 6) или на будний день (дни с 0 по 4). Затем мы можем использовать эту функцию для создания двух различных функций в нашем наборе данных: одна указывает, является ли дата выходным, а другая указывает, является ли это будним днем.

# Create a function that determines if it is weekend or not
def is_weekend(ds):
    date = pd.to_datetime(ds)
    return date.weekday() >= 5

# Create two features that indicate if it is weekend or not
df['weekend'] = df['ds'].apply(is_weekend)
df['weekday'] = ~df['ds'].apply(is_weekend)

Мы получим что-то вроде этого:

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

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

# Instantiate the model without the daily seasonality
model = Prophet(daily_seasonality=False)

# Add the seasonality based on each condition
model.add_seasonality(name='daily_weekend',
                      period=1,
                      fourier_order=4,
                      condition_name='weekend')
                        
model.add_seasonality(name='daily_weekday',
                      period=1,
                      fourier_order=4,
                      condition_name='weekday')

# Add the condition to the future dataset too
future['weekend'] = future['ds'].apply(is_weekend)
future['weekday'] = ~future['ds'].apply(is_weekend)

# Fit the data and predict
forecast = model.fit(df).predict(future)

# Display the components
fig = model.plot_components(forecast)

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

Добавить регуляризацию

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

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

Мы можем повлиять на все сезонные компоненты, используя аргумент seasonality_prior_scale при создании экземпляра модели Prophet.

model = Prophet(seasonality_prior_scale=0.01).fit(df)

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

model.add_seasonality(name='daily',
                      period=1,
                      fourier_order=4,
                      prior_scale=0.01)

Мы можем сравнить ежедневный эффект сезонности с априорной шкалой по умолчанию (10) и с уменьшенной шкалой (0,01).

Мы можем заметить, что значения для второго сравнительно ниже.

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

Прогнозирование временных рядов с помощью Facebook Prophet: