August 14, 2022

Обработка цикличных признаков

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

Для примера сгенерируем датасет с примерными средними температурами по месяцам:

import pandas as pd
import numpy as np
np.random.seed(5)

temp_avg_d = {1:0, 2:-2, 3:15, 4:20, 5:25, 6:28, 7:30, 8:27, 9:20, 10:12, 11:7, 12: 2} 
mon_ar  = np.tile(np.arange(1, 13), 3)
ts = pd.DataFrame({'mon':mon_ar})
ts['t'] = ts['mon'].map(lambda x:temp_avg_d[x] + np.random.normal(loc=0, scale=2))
ts.head()

Разобьем датасет на тренировочную и валидационную части:

from sklearn.model_selection import train_test_split

X_tr, X_val, y_tr, y_val = train_test_split(ts.drop(columns='t'), ts['t'])

Теперь попытаемся предсказать температуру по месяцу:

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error

model = LinearRegression()
model.fit(X_tr, y_tr)

mean_absolute_error(y_val, model.predict(X_val))

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

pd.DataFrame({'mon':X_val['mon'], 'pred':model.predict(X_val), 'y':y_val})

Теперь отобразим номер месяц в синус и косинус аналогично правилу выше:

ts_ext = ts.copy()
ts_ext['mon_sin'] = np.sin(2*np.pi*ts['mon']/ts['mon'].max()).round(2)
ts_ext['mon_cos'] = np.cos(2*np.pi*ts['mon']/ts['mon'].max()).round(2)
ts_ext.head()

Посчитаем регрессию по двум новым признакам:

tr_index = X_tr.index
val_index = X_val.index

model = LinearRegression()
model.fit(ts_ext.drop(columns=['t', 'mon']).loc[tr_index], y_tr)

mean_absolute_error(y_val, model.predict(ts_ext.drop(columns=['t', 'mon']).loc[val_index]))

Как можно заметить, новая модель гораздо лучше справляется с предсказаниями:

pd.DataFrame({'mon':ts_ext['mon'].loc[val_index], 'y':y_val, 
              'pred':model.predict(ts_ext.drop(columns=['t', 'mon']).loc[val_index])})