понедельник, 6 июня 2022 г.

Гибридная модель прогнозирования временных рядов : курсы Kaggle

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

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

 аддитивная модель Yt = Tt + St +et. 

 мультипликативная Yt = Tt*St*et.

 Случайную составляющую опустим и займемся трендом и сезонностью. Алгоритм у нас будет такой :

 1. Первой моделью сделаем подгонку тренда, для этого используем линейную регрессию

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

 3. На третьем шаги совместим два прогноза, для аддитивной модели как сумму, для мультипликативной - как произведение.

Загружаем необходимые библиотеки и делаем необходимые настройки :

from warnings import simplefilter
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from statsmodels.tsa.deterministic import CalendarFourier, DeterministicProcess
from xgboost import XGBRegressor

simplefilter("ignore")

plt.style.use("seaborn-whitegrid")
plt.rc(
    "figure",
    autolayout=True,
    figsize=(11, 4),
    titlesize=18,
    titleweight='bold',
)
plt.rc(
    "axes",
    labelweight="bold",
    labelsize="large",
    titleweight="bold",
    titlesize=16,
    titlepad=10,
)
plot_params = dict(
    color="0.75",
    style=".-",
    markeredgecolor="0.25",
    markerfacecolor="0.25",
)


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

df = pd.read_excel(
    "shops8_rev.xlsx",
    parse_dates=['month'])
df = df.set_index("month").to_period()
pd.concat([df.head(), df.tail()])









monthSh01Sh02Sh03Sh04Sh05Sh06Sh07Sh08
2015-01157408821901743007712087860518308179080500
2015-02127692830101245208333079210427006232076530
2015-0314014891590182450105470987805449075460110610
2015-0416792482680183340111520121480538508878096930
2015-05184196989302047701236001282006373092800116680
2021-08161308483201003808731073110835506250039640
2021-0916594861810136720105370915801094608365055120
2021-1021660466430181200105550923601338508585063220
2021-11276204701401977601194009850015498010153078540
2021-122366167848019144015036012604018361012460093280

Выделим для работы один магазин

df1=df[['Sh01']]
df1.head()


month Sh01
2015-01 157408
2015-02 127692
2015-03 140148
2015-04 167924
2015-05 184196


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

dp = DeterministicProcess(
    index=df1.index,  # даты из обучающих данных
    constant=True,       # фиктивная функция для смещения (y_intercept)
    order=2,        # квадратичный тренд
    drop=True,    # если необходимо, отбросить члены, чтобы избежать коллинеарности             
)

in_sample` создает функции для дат, указанных в аргументе `index`

X = dp.in_sample()
X.head()



month const trendtrend_squared
2015-01 1.0 1.0 1.0
2015-02 1.0 2.0 4.0
2015-03 1.0 3.0 9.0
2015-04 1.0 4.0 16.0
2015-05 1.0 5.0 25.0

Разделим индексы дат на обучающие и тестовые




Для создания модели линейной регрессии используем класс LinearRegression из библиотеки Scikit-Learn. Так как раньше задали наличие константы в регрессионном уравнении, для исключения дублирования задаем fit_intercept=False

# Подгонка трендовой модели

model = LinearRegression(fit_intercept=False)
model.fit(X_train, y_train)

# Делаем прогноз

y_fit = pd.DataFrame(
    model.predict(X_train),
    index=y_train.index,
    columns=y_train.columns,
)
y_pred = pd.DataFrame(
    model.predict(X_test),
    index=y_test.index,
    columns=y_test.columns,
)

Выведем график

axs = y_train.plot(color='0.25', figsize=(11, 5))
axs = y_test.plot(color='0.25',ax=axs )
axs = y_fit.plot(color='C0',ax=axs)
axs = y_pred.plot(color='C3',ax=axs)
plt.legend(['train plot', 'test plot','fit plot', 'pred plot'])




Переходим к моделированию сезонности, как остатков модели тренда

Готовим независимые переменные, для этого добавляем номер месяца и убираем целевую переменную - выручку.

X = df
X["Month"] = X.index.month  
y = X.pop('Sh01')
X.head()


month Month
2015-01 1
2015-02 2
2015-03 3
2015-04 4
2015-05 5

Делим данные на обучение и тест

X_train, X_test = X.loc[idx_train, :], X.loc[idx_test, :]
y_train, y_test = y.loc[idx_train], y.loc[idx_test]

Готовим вектор остатков

y_fit = y_fit.stack().squeeze()
y_pred = y_pred.stack().squeeze()
y_resid = y_train - y_fit

Для подгонки остатков используем библиотеку XGBoost, основанную на алгоритме дерева решений и реализующая методы градиентного бустинга класс XGBRegressor

xgb = XGBRegressor()
xgb.fit(X_train, y_resid)


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

y_fit_boosted = xgb.predict(X_train) + y_fit
y_pred_boosted = xgb.predict(X_test) + y_pred

Визуализируем полученное

axs = y_train.plot(color='0.25', figsize=(11, 5))
axs = y_test.plot(color='0.25',ax=axs )
axs = y_fit_boosted.unstack().plot(color='C0',ax=axs)
axs = y_pred_boosted.unstack().plot(color='C3',ax=axs)
plt.legend(['train plot', 'test plot','fit plot', 'pred plot'])





Определим функцию для расчета MAPE и выведем метрики качества для обучающей тестовой частей

def MAPE(Y_actual,Y_Predicted):
    mape = np.mean(np.abs((Y_actual - Y_Predicted)/Y_actual))*100
    return mape


# Рассчитываем метрики качества для обучающей выборки

train_fcst=y_fit_boosted.unstack().loc[:,'Sh01']
mae = metrics.mean_absolute_error(y_train, train_fcst)
mse = metrics.mean_squared_error(y_train, train_fcst)
mape = MAPE(y_train, train_fcst)
rmse = math.sqrt(mse)
r2 = metrics.r2_score(y_train, train_fcst)
# Выводим метрики качества для обучающей выборки

print(f'R^2 Score                  = {r2:.3f}')
print(f'Mean Absolute Percentage Error    = {mape:.2f}')
print(f'Mean Absolute Error        = {mae:.2f}')
print(f'Mean Squared Error         = {mse:.2f}')
print(f'Root Mean Squared Error    = {rmse:.2f}')
R^2 Score                  = 0.977
Mean Absolute Percentage Error    = 3.68
Mean Absolute Error        = 7811.87
Mean Squared Error         = 91203651.63
Root Mean Squared Error    = 9550.06
# Рассчитываем метрики качества для тестовой выборки
test_fcst=y_pred_boosted.unstack().loc[:,'Sh01']
mae = metrics.mean_absolute_error(y_test, test_fcst)
mse = metrics.mean_squared_error(y_test, test_fcst)
mape = MAPE(y_test, test_fcst)
rmse = math.sqrt(mse)
r2 = metrics.r2_score(y_test, test_fcst)
# Выводим метрики для тестовой выборки
print(f'R^2 Score                  = {r2:.3f}')
print(f'Mean Absolute Percentage Error    = {mape:.2f}')
print(f'Mean Absolute Error        = {mae:.2f}')
print(f'Mean Squared Error         = {mse:.2f}')
print(f'Root Mean Squared Error    = {rmse:.2f}')
R^2 Score                  = 0.938
Mean Absolute Percentage Error    = 4.86
Mean Absolute Error        = 8448.78
Mean Squared Error         = 97119241.52
Root Mean Squared Error    = 9854.91

Переходим к мультипликативной модели сезонности и готовим вектор сезонных
коэффициентов

y_sс=y_train/y_fit
Для подгонки сезонных коэффициентов также используем библиотеку XGBoost, основанную на алгоритме дерева решений и реализующая методы градиентного бустинга класс XGBRegressor

xgb = XGBRegressor()
xgb.fit(X_train, y_sk)
y_sс_pred_train=xgb.predict(X_train)
y_sс_pred_test=xgb.predict(X_test)
Умножаем предсказания тренда и сезонных коэффициентов и получаем итоговый прогноз
y_fit_mult = y_sc_pred_train*y_fit
y_pred_mult = y_sc_pred_test*y_pred
Выводим графики
axs = y_train.plot(color='0.25', figsize=(11, 5))
axs = y_test.plot(color='0.25',ax=axs )
axs = pd.DataFrame(y_fit_mult).unstack().plot(color='C0',ax=axs)
axs = pd.DataFrame(y_pred_mult).unstack().plot(color='C3',ax=axs)
plt.title('multiplicative model') # 
plt.legend(['train plot', 'test plot','fit plot', 'pred plot'])


Выведем метрики качества для обучающей тестовой частей для этого варианта
# Рассчитываем метрики качества для обучающей выборки

train_fcst=y_fit_mult.unstack().loc[:,'Sh01']
mae = metrics.mean_absolute_error(y_train, train_fcst)
mse = metrics.mean_squared_error(y_train, train_fcst)
mape = MAPE(y_train, train_fcst)
rmse = math.sqrt(mse)
r2 = metrics.r2_score(y_train, train_fcst)

# Выводим метрики качества для обучающей выборки
print(f'R^2 Score                  = {r2:.3f}')
print(f'Mean Absolute Percentage Error    = {mape:.2f}')
print(f'Mean Absolute Error        = {mae:.2f}')
print(f'Mean Squared Error         = {mse:.2f}')
print(f'Root Mean Squared Error    = {rmse:.2f}')
R^2 Score                  = 0.987
Mean Absolute Percentage Error    = 2.79
Mean Absolute Error        = 6150.18
Mean Squared Error         = 49410004.75
Root Mean Squared Error    = 7029.23
# Рассчитываем метрики качества для тестовой выборки

test_fcst=y_pred_mult.unstack().loc[:,'Sh01']
mae = metrics.mean_absolute_error(y_test, test_fcst)
mse = metrics.mean_squared_error(y_test, test_fcst)
mape = MAPE(y_test, test_fcst)
rmse = math.sqrt(mse)
r2 = metrics.r2_score(y_test, test_fcst)

# Выводим метрики для тестовой выборки

print(f'R^2 Score                  = {r2:.3f}')
print(f'Mean Absolute Percentage Error    = {mape:.2f}')
print(f'Mean Absolute Error        = {mae:.2f}')
print(f'Mean Squared Error         = {mse:.2f}')
print(f'Root Mean Squared Error    = {rmse:.2f}')
R^2 Score                  = 0.909
Mean Absolute Percentage Error    = 4.32
Mean Absolute Error        = 8813.82
Mean Squared Error         = 142721216.77
Root Mean Squared Error    = 11946.60
Как видно, обе модели показали примерно равное качество.
И в заключении покажем, как можно сделать для первого варианта расчет сразу
для нескольких магазин
Загружаем данные 


df = pd.read_excel(
    "shops8_rev.xlsx",
    parse_dates=['month'],index_col='month',).to_period()
df = pd.concat({'Sales': df}, names=[None, 'Shops'], axis=1)
pd.concat([df.head(), df.tail()])


Sales
Shops Sh01 Sh02 Sh03 Sh04 Sh05 Sh06 Sh07 Sh08
month
2015-01 157408 82190 174300 77120 87860 51830 81790 80500
2015-02 127692 83010 124520 83330 79210 42700 62320 76530
2015-03 140148 91590 182450 105470 98780 54490 75460 110610
2015-04 167924 82680 183340 111520 121480 53850 88780 96930
2015-05 184196 98930 204770 123600 128200 63730 92800 116680

y = df.copy()

dp = DeterministicProcess(
    index=df.index,  # даты из обучающих данных
    constant=True,       # фиктивная функция для смещения (y_intercept)
    order=2,        # квадратичный тренд
    drop=True,    # если необходимо, отбросить члены, чтобы избежать коллинеарности             
)

in_sample` создает функции для дат, указанных в аргументе `index`

X = dp.in_sample()
X.head()



monthconsttrendtrend_squared
2015-011.01.01.0
2015-021.02.04.0
2015-031.03.09.0
2015-041.04.016.0
2015-051.05.025.0


Делим данные на обучение и тест

X_train, X_test = X.loc[idx_train, :], X.loc[idx_test, :]
y_train, y_test = y.loc[idx_train], y.loc[idx_test]

# Подгонка трендовой модели

model = LinearRegression(fit_intercept=False)
model.fit(X_train, y_train)

# Делаем прогноз

y_fit = pd.DataFrame(
    model.predict(X_train),
    index=y_train.index,
    columns=y_train.columns,
)
y_pred = pd.DataFrame(
    model.predict(X_test),
    index=y_test.index,
    columns=y_test.columns,
)


Выводим график

axs = y_train.plot(color='0.25', figsize=(11, 15),subplots=True, sharex=True,
      title=['Sh01','Sh02','Sh03','Sh04','Sh05','Sh06','Sh07','Sh08',],            )
axs = y_test.plot(color='0.25', subplots=True, sharex=True, ax=axs)
axs = y_fit.plot(color='C0', subplots=True, sharex=True, ax=axs)
axs = y_pred.plot(color='C3', subplots=True, sharex=True, ax=axs)
for ax in axs: ax.legend([])
_ = plt.suptitle("Trends")




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

X = df.stack() 
display(X.head())

y = X.pop('Sales')  
X = X.reset_index('Shops')
X['Shops'] , _= X['Shops'].factorize()
X["Month"] = X.index.month  
X.head()


month
Shops

Month
2015-01 0 1
2015-01 1 1
2015-01 2 1
2015-01 3 1
2015-01 4 1


X_train, X_test = X.loc[idx_train, :], X.loc[idx_test, :]
y_train, y_test = y.loc[idx_train], y.loc[idx_test]

y_fit = y_fit.stack().squeeze()
y_pred = y_pred.stack().squeeze()

y_resid = y_train - y_fit

xgb = XGBRegressor()
xgb.fit(X_train, y_resid)

y_fit_boosted = xgb.predict(X_train) + y_fit
y_pred_boosted = xgb.predict(X_test) + y_pred

axs = y_train.unstack(['Shops']).plot(
    color='0.25', figsize=(11, 20), subplots=True, sharex=True,
    title=['Sh01','Sh02','Sh03','Sh04','Sh05','Sh06','Sh07','Sh08',],
)
axs = y_test.unstack(['Shops']).plot(
    color='0.25', subplots=True, sharex=True, ax=axs,
)
axs = y_fit_boosted.unstack(['Shops']).plot(
    color='C0', subplots=True, sharex=True, ax=axs,
)
axs = y_pred_boosted.unstack(['Shops']).plot(
    color='C3', subplots=True, sharex=True, ax=axs,
)
for ax in axs: ax.legend([])






Комментариев нет:

Отправить комментарий