Примечание
Перейти в конец, чтобы скачать полный код примера или запустить этот пример в браузере через JupyterLite или Binder.
Создание признаков, связанных со временем
В этой записной книжке представлены различные стратегии использования признаков, связанных со временем, для задачи регрессии спроса на прокат велосипедов, который в значительной степени зависит от деловых циклов (дней, недель, месяцев) и годовых сезонных циклов.
В ходе работы мы расскажем, как выполнять периодическое создание признаков с помощью класса sklearn.preprocessing.SplineTransformer и его параметра extrapolation="periodic".
# Authors: The scikit-learn developers # SPDX-License-Identifier: BSD-3-Clause
Исследование данных набора Bike Sharing Demand
Сначала загрузим данные из репозитория OpenML.
from sklearn.datasets import fetch_openml
bike_sharing = fetch_openml("Bike_Sharing_Demand", version=2, as_frame=True)
df = bike_sharing.frame
Чтобы получить общее представление о периодических закономерностях в данных, рассмотрим средний спрос по часам в течение недели.
Обратите внимание, что неделя начинается в воскресенье, в выходной день. Мы можем четко различить поездки на работу утром и вечером в рабочие дни и досуговое использование велосипедов в выходные дни: пиковый спрос в это время более равномерно распределен вокруг середины дня:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12, 4))
average_week_demand = df.groupby(["weekday", "hour"])["count"].mean()
average_week_demand.plot(ax=ax)
_ = ax.set(
title="Average hourly bike demand during the week",
xticks=[i * 24 for i in range(7)],
xticklabels=["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
xlabel="Time of the week",
ylabel="Number of bike rentals",
)

Целевая переменная задачи прогнозирования — абсолютное количество аренд велосипедов за каждый час:
df["count"].max()
np.int64(977)
Изменим масштаб целевой переменной (количества аренд велосипедов за час), чтобы прогнозировать относительный спрос. Так среднюю абсолютную ошибку будет проще интерпретировать как долю от максимального спроса.
Примечание
Метод fit используемых в этой записной книжке моделей минимизирует среднеквадратичную ошибку для оценки условного среднего. Однако абсолютная ошибка позволяет оценить условную медиану.
Тем не менее, при обсуждении результатов мы сосредоточимся на средней абсолютной ошибке, а не на среднеквадратичной ошибке (или ее корне) на тестовом наборе, поскольку ее проще интерпретировать. Однако обратите внимание, что в этом исследовании лучшие модели для одной метрики также оказываются лучшими и для другой метрики.
y = df["count"] / df["count"].max()
fig, ax = plt.subplots(figsize=(12, 4))
y.hist(bins=30, ax=ax)
_ = ax.set(
xlabel="Fraction of rented fleet demand",
ylabel="Number of hours",
)

Входной кадр данных признаков представляет собой почасовой журнал с отметками времени и переменными, описывающими погодные условия. Он включает как числовые, так и категориальные переменные. Обратите внимание, что информация о времени уже представлена в нескольких взаимодополняющих столбцах.
X = df.drop("count", axis="columns")
X
| season | year | month | hour | holiday | weekday | workingday | weather | temp | feel_temp | humidity | windspeed | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | spring | 0 | 1 | 0 | False | 6 | False | clear | 9.84 | 14.395 | 0.81 | 0.0000 |
| 1 | spring | 0 | 1 | 1 | False | 6 | False | clear | 9.02 | 13.635 | 0.80 | 0.0000 |
| 2 | spring | 0 | 1 | 2 | False | 6 | False | clear | 9.02 | 13.635 | 0.80 | 0.0000 |
| 3 | spring | 0 | 1 | 3 | False | 6 | False | clear | 9.84 | 14.395 | 0.75 | 0.0000 |
| 4 | spring | 0 | 1 | 4 | False | 6 | False | clear | 9.84 | 14.395 | 0.75 | 0.0000 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 17374 | spring | 1 | 12 | 19 | False | 1 | True | misty | 10.66 | 12.880 | 0.60 | 11.0014 |
| 17375 | spring | 1 | 12 | 20 | False | 1 | True | misty | 10.66 | 12.880 | 0.60 | 11.0014 |
| 17376 | spring | 1 | 12 | 21 | False | 1 | True | clear | 10.66 | 12.880 | 0.60 | 11.0014 |
| 17377 | spring | 1 | 12 | 22 | False | 1 | True | clear | 10.66 | 13.635 | 0.56 | 8.9981 |
| 17378 | spring | 1 | 12 | 23 | False | 1 | True | clear | 10.66 | 13.635 | 0.65 | 8.9981 |
17379 rows × 12 columns
Примечание
Если бы информация о времени содержалась только в столбце с датой или датой и временем, мы могли бы с помощью pandas разбить ее на час суток, день недели, день месяца и месяц года: https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#time-date-components
Теперь рассмотрим распределение категориальных переменных, начав с "weather":
X["weather"].value_counts()
weather clear 11413 misty 4544 rain 1419 heavy_rain 3 Name: count, dtype: int64
Поскольку событий "heavy_rain" всего 3, мы не можем использовать эту категорию для обучения моделей машинного обучения с перекрестной проверкой. Вместо этого мы упростим представление, объединив их с категорией "rain".
X["weather"] = (
X["weather"]
.astype(object)
.replace(to_replace="heavy_rain", value="rain")
.astype("category")
)
X["weather"].value_counts()
weather clear 11413 misty 4544 rain 1422 Name: count, dtype: int64
Как и ожидалось, переменная "season" имеет сбалансированное распределение:
X["season"].value_counts()
season fall 4496 summer 4409 spring 4242 winter 4232 Name: count, dtype: int64
Перекрестная проверка с учетом времени
Поскольку набор данных представляет собой упорядоченный по времени журнал событий (почасовой спрос), для максимально реалистичной оценки модели прогнозирования спроса мы будем использовать разделитель для перекрестной проверки с учетом времени. Между обучающей и тестовой частями разбиений мы оставим промежуток в 2 дня. Кроме того, мы ограничим размер обучающего набора, чтобы повысить стабильность результатов фолдов перекрестной проверки.
1000 тестовых точек данных должно быть достаточно для оценки эффективности модели. Это чуть меньше полутора месяцев непрерывных тестовых данных:
from sklearn.model_selection import TimeSeriesSplit
ts_cv = TimeSeriesSplit(
n_splits=5,
gap=48,
max_train_size=10000,
test_size=1000,
)
Давайте вручную проверим различные разбиения, чтобы убедиться, что TimeSeriesSplit работает так, как мы ожидаем. Начнем с первого разбиения:
all_splits = list(ts_cv.split(X, y)) train_0, test_0 = all_splits[0]
X.iloc[test_0]
| season | year | month | hour | holiday | weekday | workingday | weather | temp | feel_temp | humidity | windspeed | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12379 | summer | 1 | 6 | 0 | False | 2 | True | clear | 22.14 | 25.760 | 0.68 | 27.9993 |
| 12380 | summer | 1 | 6 | 1 | False | 2 | True | misty | 21.32 | 25.000 | 0.77 | 22.0028 |
| 12381 | summer | 1 | 6 | 2 | False | 2 | True | rain | 21.32 | 25.000 | 0.72 | 19.9995 |
| 12382 | summer | 1 | 6 | 3 | False | 2 | True | rain | 20.50 | 24.240 | 0.82 | 12.9980 |
| 12383 | summer | 1 | 6 | 4 | False | 2 | True | rain | 20.50 | 24.240 | 0.82 | 12.9980 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 13374 | fall | 1 | 7 | 11 | False | 1 | True | clear | 34.44 | 40.150 | 0.53 | 15.0013 |
| 13375 | fall | 1 | 7 | 12 | False | 1 | True | clear | 34.44 | 39.395 | 0.49 | 8.9981 |
| 13376 | fall | 1 | 7 | 13 | False | 1 | True | clear | 34.44 | 39.395 | 0.49 | 19.0012 |
| 13377 | fall | 1 | 7 | 14 | False | 1 | True | clear | 36.08 | 40.910 | 0.42 | 7.0015 |
| 13378 | fall | 1 | 7 | 15 | False | 1 | True | clear | 35.26 | 40.150 | 0.47 | 16.9979 |
1000 rows × 12 columns
X.iloc[train_0]
| season | year | month | hour | holiday | weekday | workingday | weather | temp | feel_temp | humidity | windspeed | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2331 | summer | 0 | 4 | 1 | False | 2 | True | misty | 25.42 | 31.060 | 0.50 | 6.0032 |
| 2332 | summer | 0 | 4 | 2 | False | 2 | True | misty | 24.60 | 31.060 | 0.53 | 8.9981 |
| 2333 | summer | 0 | 4 | 3 | False | 2 | True | misty | 23.78 | 27.275 | 0.56 | 8.9981 |
| 2334 | summer | 0 | 4 | 4 | False | 2 | True | misty | 22.96 | 26.515 | 0.64 | 8.9981 |
| 2335 | summer | 0 | 4 | 5 | False | 2 | True | misty | 22.14 | 25.760 | 0.68 | 8.9981 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 12326 | summer | 1 | 6 | 19 | False | 6 | False | clear | 26.24 | 31.060 | 0.36 | 11.0014 |
| 12327 | summer | 1 | 6 | 20 | False | 6 | False | clear | 25.42 | 31.060 | 0.35 | 19.0012 |
| 12328 | summer | 1 | 6 | 21 | False | 6 | False | clear | 24.60 | 31.060 | 0.40 | 7.0015 |
| 12329 | summer | 1 | 6 | 22 | False | 6 | False | clear | 23.78 | 27.275 | 0.46 | 8.9981 |
| 12330 | summer | 1 | 6 | 23 | False | 6 | False | clear | 22.96 | 26.515 | 0.52 | 7.0015 |
10000 rows × 12 columns
Теперь рассмотрим последнее разбиение:
train_4, test_4 = all_splits[4]
X.iloc[test_4]
| season | year | month | hour | holiday | weekday | workingday | weather | temp | feel_temp | humidity | windspeed | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 16379 | winter | 1 | 11 | 5 | False | 2 | True | misty | 13.94 | 16.665 | 0.66 | 8.9981 |
| 16380 | winter | 1 | 11 | 6 | False | 2 | True | misty | 13.94 | 16.665 | 0.71 | 11.0014 |
| 16381 | winter | 1 | 11 | 7 | False | 2 | True | clear | 13.12 | 16.665 | 0.76 | 6.0032 |
| 16382 | winter | 1 | 11 | 8 | False | 2 | True | clear | 13.94 | 16.665 | 0.71 | 8.9981 |
| 16383 | winter | 1 | 11 | 9 | False | 2 | True | misty | 14.76 | 18.940 | 0.71 | 0.0000 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 17374 | spring | 1 | 12 | 19 | False | 1 | True | misty | 10.66 | 12.880 | 0.60 | 11.0014 |
| 17375 | spring | 1 | 12 | 20 | False | 1 | True | misty | 10.66 | 12.880 | 0.60 | 11.0014 |
| 17376 | spring | 1 | 12 | 21 | False | 1 | True | clear | 10.66 | 12.880 | 0.60 | 11.0014 |
| 17377 | spring | 1 | 12 | 22 | False | 1 | True | clear | 10.66 | 13.635 | 0.56 | 8.9981 |
| 17378 | spring | 1 | 12 | 23 | False | 1 | True | clear | 10.66 | 13.635 | 0.65 | 8.9981 |
1000 rows × 12 columns
X.iloc[train_4]
| season | year | month | hour | holiday | weekday | workingday | weather | temp | feel_temp | humidity | windspeed | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 6331 | winter | 0 | 9 | 9 | False | 1 | True | misty | 26.24 | 28.790 | 0.89 | 12.9980 |
| 6332 | winter | 0 | 9 | 10 | False | 1 | True | misty | 26.24 | 28.790 | 0.89 | 12.9980 |
| 6333 | winter | 0 | 9 | 11 | False | 1 | True | clear | 27.88 | 31.820 | 0.79 | 15.0013 |
| 6334 | winter | 0 | 9 | 12 | False | 1 | True | misty | 27.88 | 31.820 | 0.79 | 11.0014 |
| 6335 | winter | 0 | 9 | 13 | False | 1 | True | misty | 28.70 | 33.335 | 0.74 | 11.0014 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 16326 | winter | 1 | 11 | 0 | False | 0 | False | misty | 12.30 | 15.150 | 0.70 | 11.0014 |
| 16327 | winter | 1 | 11 | 1 | False | 0 | False | clear | 12.30 | 14.395 | 0.70 | 12.9980 |
| 16328 | winter | 1 | 11 | 2 | False | 0 | False | clear | 11.48 | 14.395 | 0.81 | 7.0015 |
| 16329 | winter | 1 | 11 | 3 | False | 0 | False | misty | 12.30 | 15.150 | 0.81 | 11.0014 |
| 16330 | winter | 1 | 11 | 4 | False | 0 | False | misty | 12.30 | 14.395 | 0.81 | 12.9980 |
10000 rows × 12 columns
Все в порядке. Теперь можно приступать к построению прогностических моделей!
Градиентный бустинг
Регрессия с градиентным бустингом на основе деревьев решений часто достаточно гибка, чтобы эффективно работать с разнородными табличными данными, содержащими категориальные и числовые признаки, при условии, что число образцов достаточно велико.
Здесь мы используем современный HistGradientBoostingRegressor со встроенной поддержкой категориальных признаков. Поэтому нам нужно лишь установить categorical_features="from_dtype", чтобы признаки с категориальным dtype считались категориальными. Для справки мы извлекаем категориальные признаки из фрейма данных на основе dtype. Для этих признаков внутренние деревья используют специальное правило разбиения.
Числовые переменные не требуют предварительной обработки, и для простоты мы пробуем только гиперпараметры модели по умолчанию:
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.model_selection import cross_validate
from sklearn.pipeline import make_pipeline
gbrt = HistGradientBoostingRegressor(categorical_features="from_dtype", random_state=42)
categorical_columns = X.columns[X.dtypes == "category"]
print("Categorical features:", categorical_columns.tolist())
Categorical features: ['season', 'holiday', 'workingday', 'weather']
Оценим модель градиентного бустинга с помощью средней абсолютной ошибки относительного спроса, усреднённой по 5 разбиениям временной кросс-валидации:
import numpy as np
def evaluate(model, X, y, cv, model_prop=None, model_step=None):
cv_results = cross_validate(
model,
X,
y,
cv=cv,
scoring=["neg_mean_absolute_error", "neg_root_mean_squared_error"],
return_estimator=True,
)
if model_prop is not None:
if model_step is not None:
values = [
getattr(m[model_step], model_prop) for m in cv_results["estimator"]
]
else:
values = [getattr(m, model_prop) for m in cv_results["estimator"]]
print(f"Mean model.{model_prop} = {np.mean(values)}")
mae = -cv_results["test_neg_mean_absolute_error"]
rmse = -cv_results["test_neg_root_mean_squared_error"]
print(
f"Mean Absolute Error: {mae.mean():.3f} +/- {mae.std():.3f}\n"
f"Root Mean Squared Error: {rmse.mean():.3f} +/- {rmse.std():.3f}"
)
# To display the fitted estimator diagrams in the notebook.
return cv_results["estimator"][0]
evaluate(gbrt, X, y, cv=ts_cv, model_prop="n_iter_")
Mean model.n_iter_ = 100.0 Mean Absolute Error: 0.044 +/- 0.003 Root Mean Squared Error: 0.068 +/- 0.005
HistGradientBoostingRegressor(random_state=42)В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьте выполнение блокноту.
На GitHub HTML-представление отобразить невозможно; попробуйте открыть эту страницу с помощью nbviewer.org.
Параметры
Обученные атрибуты
Мы видим, что задали max_iter достаточно большим, чтобы сработала ранняя остановка.
Средняя ошибка этой модели составляет около 4–5% от максимального спроса. Это очень хороший результат для первой попытки без настройки гиперпараметров! Нам нужно было лишь явно указать категориальные переменные. Обратите внимание, что временные признаки передаются без изменений, то есть без предварительной обработки. Однако для моделей на основе деревьев это не представляет большой проблемы: они могут обучаться немонотонной зависимости между порядковыми входными признаками и целевой переменной.
Как мы увидим далее, для моделей линейной регрессии это не так.
Наивная линейная регрессия
Как обычно для линейных моделей, категориальные переменные необходимо преобразовать в one-hot-представление. Для единообразия масштабируем числовые признаки к одному диапазону 0–1 с помощью MinMaxScaler, хотя в данном случае это не сильно влияет на результаты, поскольку признаки уже имеют сопоставимые масштабы:
from sklearn.linear_model import RidgeCV
from sklearn.preprocessing import MinMaxScaler, OneHotEncoder
one_hot_encoder = OneHotEncoder(handle_unknown="ignore", sparse_output=False)
alphas = np.logspace(-6, 6, 25)
naive_linear_pipeline = make_pipeline(
ColumnTransformer(
transformers=[
("categorical", one_hot_encoder, categorical_columns),
],
remainder=MinMaxScaler(),
verbose_feature_names_out=False,
),
RidgeCV(alphas=alphas),
)
evaluate(
naive_linear_pipeline, X, y, cv=ts_cv, model_prop="alpha_", model_step="ridgecv"
)
Mean model.alpha_ = 2.7298221281347037 Mean Absolute Error: 0.142 +/- 0.014 Root Mean Squared Error: 0.184 +/- 0.020
Pipeline(steps=[('columntransformer',
ColumnTransformer(remainder=MinMaxScaler(),
transformers=[('categorical',
OneHotEncoder(handle_unknown='ignore',
sparse_output=False),
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str'))],
verbose_feature_names_out=False)),
('ridgecv',
RidgeCV(alphas=array([1.00000000e-06, 3.16227766e-06, 1.00000000e-05, 3.16227766e-05,
1.00000000e-04, 3.16227766e-04, 1.00000000e-03, 3.16227766e-03,
1.00000000e-02, 3.16227766e-02, 1.00000000e-01, 3.16227766e-01,
1.00000000e+00, 3.16227766e+00, 1.00000000e+01, 3.16227766e+01,
1.00000000e+02, 3.16227766e+02, 1.00000000e+03, 3.16227766e+03,
1.00000000e+04, 3.16227766e+04, 1.00000000e+05, 3.16227766e+05,
1.00000000e+06])))])
В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьте выполнение ноутбуку. На GitHub HTML-представление не отображается; попробуйте открыть эту страницу с помощью nbviewer.org.
Параметры
Обученные атрибуты
| Имя | Тип | Значение |
|---|---|---|
| feature_names_in_ feature_names_in_: ndarray формы (`n_features_in_`,) Имена признаков, полученные при :term:`обучении`. Определяется, только если базовый оцениватель предоставляет такой атрибут после обучения. .. versionadded:: 1.0 | ndarray[object](12,) | ['season','year','month',...,'feel_temp','humidity','windspeed'] |
| n_features_in_ n_features_in_: int Количество признаков, полученных при :term:`обучении`. Определяется, только если первый базовый оцениватель в `steps` предоставляет такой атрибут после обучения. .. versionadded:: 0.24 | int | 12 |
Параметры
Обученные атрибуты
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')
Параметры
Обученные атрибуты
11 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
['year', 'month', 'hour', 'weekday', 'temp', 'feel_temp', 'humidity', 'windspeed']
Параметры
Обученные атрибуты
8 признаков
| year |
| month |
| hour |
| weekday |
| temp |
| feel_temp |
| humidity |
| windspeed |
19 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
| year |
| month |
| hour |
| weekday |
| temp |
| feel_temp |
| humidity |
| windspeed |
Параметры
Обученные атрибуты
Радует, что выбранное alpha_ находится в указанном нами диапазоне.
Результаты неудовлетворительны: средняя ошибка составляет около 14% от максимального спроса. Это более чем втрое превышает среднюю ошибку модели градиентного бустинга. Можно предположить, что наивное исходное кодирование периодических временных признаков (простое масштабирование min-max) мешает модели линейной регрессии правильно использовать временную информацию: линейная регрессия не моделирует автоматически немонотонные зависимости между входными признаками и целевой переменной. Нелинейные члены необходимо создавать для входных данных.
Например, исходное числовое кодирование признака "hour" не позволяет линейной модели распознать, что увеличение часа утром с 6 до 8 должно сильно увеличить количество аренд велосипедов, тогда как увеличение на сопоставимую величину вечером с 18 до 20 должно сильно уменьшить прогнозируемое количество аренд велосипедов.
Временные шаги как категории
Поскольку временные признаки закодированы дискретным образом с использованием целых чисел (24 уникальных значения в признаке «часы»), мы можем рассматривать их как категориальные переменные, используя кодирование one-hot, и тем самым игнорировать любые предположения, связанные с упорядочиванием значений часа.
Использование кодирования one-hot для временных признаков предоставляет линейной модели гораздо больше возможностей, поскольку мы добавляем по одному дополнительному признаку для каждого дискретного временного уровня.
one_hot_linear_pipeline = make_pipeline(
ColumnTransformer(
transformers=[
("categorical", one_hot_encoder, categorical_columns),
("one_hot_time", one_hot_encoder, ["hour", "weekday", "month"]),
],
remainder=MinMaxScaler(),
verbose_feature_names_out=False,
),
RidgeCV(alphas=alphas),
)
evaluate(one_hot_linear_pipeline, X, y, cv=ts_cv)
Mean Absolute Error: 0.099 +/- 0.011 Root Mean Squared Error: 0.131 +/- 0.011
Pipeline(steps=[('columntransformer',
ColumnTransformer(remainder=MinMaxScaler(),
transformers=[('categorical',
OneHotEncoder(handle_unknown='ignore',
sparse_output=False),
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')),
('one_hot_time',
OneHotEncoder(handle_unknown='ignore',
sparse_output=False),
['hour', 'weekday',
'month'])],
verbose_featur...
RidgeCV(alphas=array([1.00000000e-06, 3.16227766e-06, 1.00000000e-05, 3.16227766e-05,
1.00000000e-04, 3.16227766e-04, 1.00000000e-03, 3.16227766e-03,
1.00000000e-02, 3.16227766e-02, 1.00000000e-01, 3.16227766e-01,
1.00000000e+00, 3.16227766e+00, 1.00000000e+01, 3.16227766e+01,
1.00000000e+02, 3.16227766e+02, 1.00000000e+03, 3.16227766e+03,
1.00000000e+04, 3.16227766e+04, 1.00000000e+05, 3.16227766e+05,
1.00000000e+06])))])
В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьте выполнение блокноту. На GitHub HTML-представление не отображается; попробуйте загрузить эту страницу с помощью nbviewer.org.
Параметры
Обученные атрибуты
Параметры
Обученные атрибуты
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')
Параметры
Обученные атрибуты
11 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
['hour', 'weekday', 'month']
Параметры
Обученные атрибуты
43 признака
| hour_0 |
| hour_1 |
| hour_2 |
| hour_3 |
| hour_4 |
| hour_5 |
| hour_6 |
| hour_7 |
| hour_8 |
| hour_9 |
| hour_10 |
| hour_11 |
| hour_12 |
| hour_13 |
| hour_14 |
| hour_15 |
| hour_16 |
| hour_17 |
| hour_18 |
| hour_19 |
| hour_20 |
| hour_21 |
| hour_22 |
| hour_23 |
| weekday_0 |
| weekday_1 |
| weekday_2 |
| weekday_3 |
| weekday_4 |
| weekday_5 |
| weekday_6 |
| month_1 |
| month_2 |
| month_3 |
| month_4 |
| month_5 |
| month_6 |
| month_7 |
| month_8 |
| month_9 |
| month_10 |
| month_11 |
| month_12 |
['year', 'temp', 'feel_temp', 'humidity', 'windspeed']
Параметры
Обученные атрибуты
5 признаков
| year |
| temp |
| feel_temp |
| humidity |
| windspeed |
59 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
| hour_0 |
| hour_1 |
| hour_2 |
| hour_3 |
| hour_4 |
| hour_5 |
| hour_6 |
| hour_7 |
| hour_8 |
| hour_9 |
| hour_10 |
| hour_11 |
| hour_12 |
| hour_13 |
| hour_14 |
| hour_15 |
| hour_16 |
| hour_17 |
| hour_18 |
| hour_19 |
| hour_20 |
| hour_21 |
| hour_22 |
| hour_23 |
| weekday_0 |
| weekday_1 |
| weekday_2 |
| weekday_3 |
| weekday_4 |
| weekday_5 |
| weekday_6 |
| month_1 |
| month_2 |
| month_3 |
| month_4 |
| month_5 |
| month_6 |
| month_7 |
| month_8 |
| month_9 |
| month_10 |
| month_11 |
| month_12 |
| year |
| temp |
| feel_temp |
| humidity |
| windspeed |
Параметры
Обученные атрибуты
Средняя частота ошибок этой модели составляет 10%, что значительно лучше результата при использовании исходного (порядкового) кодирования временного признака. Это подтверждает наше предположение о том, что линейная регрессионная модель выигрывает от дополнительной гибкости, позволяющей не считать изменение времени монотонным.
Однако при этом появляется очень большое количество новых признаков. Если бы время суток было представлено в минутах с начала дня, а не в часах, кодирование one-hot создало бы 1440 признаков вместо 24. Это могло бы привести к значительному переобучению. Чтобы избежать этого, можно использовать sklearn.preprocessing.KBinsDiscretizer, чтобы заново разбить на интервалы уровни детализированных порядковых или числовых переменных, сохранив при этом преимущества кодирования one-hot, обеспечивающего немонотонную выразительность.
Наконец, мы также замечаем, что кодирование one-hot полностью игнорирует порядок уровней часа, хотя этот порядок может представлять собой интересное индуктивное смещение, которое стоит в некоторой степени сохранить. Далее мы попробуем исследовать гладкое немонотонное кодирование, которое локально сохраняет относительный порядок временных признаков.
Тригонометрические признаки
В качестве первой попытки можно попробовать закодировать каждый из этих периодических признаков с помощью преобразования синуса и косинуса с соответствующим периодом.
Каждый порядковый временной признак преобразуется в 2 признака, которые вместе кодируют эквивалентную информацию немонотонным способом и, что более важно, без скачка между первым и последним значениями периодического диапазона.
from sklearn.preprocessing import FunctionTransformer
def sin_transformer(period):
return FunctionTransformer(
lambda x: np.sin(x / period * 2 * np.pi), feature_names_out="one-to-one"
)
def cos_transformer(period):
return FunctionTransformer(
lambda x: np.cos(x / period * 2 * np.pi), feature_names_out="one-to-one"
)
Визуализируем влияние такого расширения признаков на синтетических данных о часах, немного экстраполировав их за пределы hour=23:
import pandas as pd
hour_df = pd.DataFrame(
np.arange(26).reshape(-1, 1),
columns=["hour"],
)
hour_df["hour_sin"] = sin_transformer(24).fit_transform(hour_df)["hour"]
hour_df["hour_cos"] = cos_transformer(24).fit_transform(hour_df)["hour"]
hour_df.plot(x="hour")
_ = plt.title("Trigonometric encoding for the 'hour' feature")

Используем двумерную диаграмму рассеяния, закодировав часы цветом, чтобы нагляднее увидеть, как это представление отображает 24 часа суток в двумерное пространство, напоминающее своего рода аналоговые часы на 24 часа. Обратите внимание, что «25-й» час отображается как 1-й час из-за периодической природы представления с помощью синуса и косинуса.
fig, ax = plt.subplots(figsize=(7, 5))
sp = ax.scatter(hour_df["hour_sin"], hour_df["hour_cos"], c=hour_df["hour"])
ax.set(
xlabel="sin(hour)",
ylabel="cos(hour)",
)
_ = fig.colorbar(sp)

Теперь можно построить конвейер извлечения признаков, используя эту стратегию:
cyclic_cossin_transformer = ColumnTransformer(
transformers=[
("categorical", one_hot_encoder, categorical_columns),
("month_sin", sin_transformer(12), ["month"]),
("month_cos", cos_transformer(12), ["month"]),
("weekday_sin", sin_transformer(7), ["weekday"]),
("weekday_cos", cos_transformer(7), ["weekday"]),
("hour_sin", sin_transformer(24), ["hour"]),
("hour_cos", cos_transformer(24), ["hour"]),
],
remainder=MinMaxScaler(),
verbose_feature_names_out=True,
)
cyclic_cossin_linear_pipeline = make_pipeline(
cyclic_cossin_transformer,
RidgeCV(alphas=alphas),
)
evaluate(cyclic_cossin_linear_pipeline, X, y, cv=ts_cv)
Mean Absolute Error: 0.125 +/- 0.014 Root Mean Squared Error: 0.166 +/- 0.020
Pipeline(steps=[('columntransformer',
ColumnTransformer(remainder=MinMaxScaler(),
transformers=[('categorical',
OneHotEncoder(handle_unknown='ignore',
sparse_output=False),
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')),
('month_sin',
FunctionTransformer(feature_names_out='one-to-one',
func=<function sin_transformer.<locals>.<lambda> at 0x784...
RidgeCV(alphas=array([1.00000000e-06, 3.16227766e-06, 1.00000000e-05, 3.16227766e-05,
1.00000000e-04, 3.16227766e-04, 1.00000000e-03, 3.16227766e-03,
1.00000000e-02, 3.16227766e-02, 1.00000000e-01, 3.16227766e-01,
1.00000000e+00, 3.16227766e+00, 1.00000000e+01, 3.16227766e+01,
1.00000000e+02, 3.16227766e+02, 1.00000000e+03, 3.16227766e+03,
1.00000000e+04, 3.16227766e+04, 1.00000000e+05, 3.16227766e+05,
1.00000000e+06])))])
В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьтесь ноутбуку. На GitHub HTML-представление не отображается; попробуйте открыть эту страницу с помощью nbviewer.org.
Параметры
Обученные атрибуты
Параметры
Обученные атрибуты
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')
Параметры
Обученные атрибуты
11 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
['month']
Параметры
Обученные атрибуты
1 признак
| month |
['month']
Параметры
Атрибуты обученной модели
1 признак
| month |
['weekday']
Параметры
Атрибуты обученной модели
1 признак
| weekday |
['weekday']
Параметры
Атрибуты обученной модели
1 признак
| weekday |
['hour']
Параметры
Обученные атрибуты
1 признак
| hour |
['hour']
Параметры
Обученные атрибуты
1 признак
| hour |
['year', 'temp', 'feel_temp', 'humidity', 'windspeed']
Параметры
Обученные атрибуты
5 признаков
| year |
| temp |
| feel_temp |
| humidity |
| windspeed |
22 признака
| categorical__season_fall |
| categorical__season_spring |
| categorical__season_summer |
| categorical__season_winter |
| categorical__holiday_False |
| categorical__holiday_True |
| categorical__workingday_False |
| categorical__workingday_True |
| categorical__weather_clear |
| categorical__weather_misty |
| categorical__weather_rain |
| month_sin__month |
| month_cos__month |
| weekday_sin__weekday |
| weekday_cos__weekday |
| hour_sin__hour |
| hour_cos__hour |
| remainder__year |
| remainder__temp |
| remainder__feel_temp |
| remainder__humidity |
| remainder__windspeed |
Параметры
Обученные атрибуты
Качество нашей модели линейной регрессии с таким простым конструированием признаков немного выше, чем при использовании исходных порядковых временных признаков, но ниже, чем при использовании временных признаков, закодированных методом one-hot. В конце этой записной книжки мы подробнее рассмотрим возможные причины такого неудовлетворительного результата.
Периодические сплайновые признаки
Можно попробовать альтернативное кодирование периодических признаков, связанных со временем, с помощью сплайновых преобразований, используя достаточно большое число сплайнов. В результате число расширенных признаков будет больше, чем при синусно-косинусном преобразовании:
from sklearn.preprocessing import SplineTransformer
def periodic_spline_transformer(period, n_splines=None, degree=3):
if n_splines is None:
n_splines = period
n_knots = n_splines + 1 # periodic and include_bias is True
return SplineTransformer(
degree=degree,
n_knots=n_knots,
knots=np.linspace(0, period, n_knots).reshape(n_knots, 1),
extrapolation="periodic",
include_bias=True,
)
Теперь визуализируем влияние такого расширения признаков на синтетические данные о часах, выполнив небольшую экстраполяцию за пределы hour=23:
hour_df = pd.DataFrame(
np.linspace(0, 26, 1000).reshape(-1, 1),
columns=["hour"],
)
splines = periodic_spline_transformer(24, n_splines=12).fit_transform(hour_df)
splines_df = pd.DataFrame(
splines,
columns=[f"spline_{i}" for i in range(splines.shape[1])],
)
pd.concat([hour_df, splines_df], axis="columns").plot(x="hour", cmap=plt.cm.tab20b)
_ = plt.title("Periodic spline-based encoding for the 'hour' feature")

Благодаря использованию параметра extrapolation="periodic" мы видим, что кодирование признаков остается гладким при экстраполяции за полночь.
Теперь можно построить прогнозирующий конвейер, используя эту альтернативную стратегию создания периодических признаков.
Для порядковых значений можно использовать меньше сплайнов, чем дискретных уровней. Благодаря этому сплайновое кодирование становится эффективнее кодирования one-hot, сохраняя при этом большую часть выразительности:
cyclic_spline_transformer = ColumnTransformer(
transformers=[
("categorical", one_hot_encoder, categorical_columns),
("cyclic_month", periodic_spline_transformer(12, n_splines=6), ["month"]),
("cyclic_weekday", periodic_spline_transformer(7, n_splines=3), ["weekday"]),
("cyclic_hour", periodic_spline_transformer(24, n_splines=12), ["hour"]),
],
remainder=MinMaxScaler(),
verbose_feature_names_out=False,
)
cyclic_spline_linear_pipeline = make_pipeline(
cyclic_spline_transformer,
RidgeCV(alphas=alphas),
)
evaluate(cyclic_spline_linear_pipeline, X, y, cv=ts_cv)
Mean Absolute Error: 0.097 +/- 0.011 Root Mean Squared Error: 0.132 +/- 0.013
Pipeline(steps=[('columntransformer',
ColumnTransformer(remainder=MinMaxScaler(),
transformers=[('categorical',
OneHotEncoder(handle_unknown='ignore',
sparse_output=False),
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')),
('cyclic_month',
SplineTransformer(extrapolation='periodic',
knots=array([[ 0.],
[ 2.],
[ 4.],
[ 6.],
[ 8.],
[10.],
[12.]]),
n_knots...
RidgeCV(alphas=array([1.00000000e-06, 3.16227766e-06, 1.00000000e-05, 3.16227766e-05,
1.00000000e-04, 3.16227766e-04, 1.00000000e-03, 3.16227766e-03,
1.00000000e-02, 3.16227766e-02, 1.00000000e-01, 3.16227766e-01,
1.00000000e+00, 3.16227766e+00, 1.00000000e+01, 3.16227766e+01,
1.00000000e+02, 3.16227766e+02, 1.00000000e+03, 3.16227766e+03,
1.00000000e+04, 3.16227766e+04, 1.00000000e+05, 3.16227766e+05,
1.00000000e+06])))])
В среде Jupyter повторно выполните эту ячейку, чтобы отобразить представление HTML, или доверьте блокноту. На GitHub представление HTML отобразить невозможно; попробуйте открыть эту страницу на nbviewer.org.
Параметры
Обученные атрибуты
Параметры
Обученные атрибуты
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')
Параметры
Обученные атрибуты
11 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
['month']
Параметры
Обученные атрибуты
6 признаков
| month_sp_0 |
| month_sp_1 |
| month_sp_2 |
| month_sp_3 |
| month_sp_4 |
| month_sp_5 |
['weekday']
Параметры
Обученные атрибуты
3 признака
| weekday_sp_0 |
| weekday_sp_1 |
| weekday_sp_2 |
['hour']
Параметры
Обученные атрибуты
12 признаков
| hour_sp_0 |
| hour_sp_1 |
| hour_sp_2 |
| hour_sp_3 |
| hour_sp_4 |
| hour_sp_5 |
| hour_sp_6 |
| hour_sp_7 |
| hour_sp_8 |
| hour_sp_9 |
| hour_sp_10 |
| hour_sp_11 |
['year', 'temp', 'feel_temp', 'humidity', 'windspeed']
Параметры
Обученные атрибуты
5 признаков
| year |
| temp |
| feel_temp |
| humidity |
| windspeed |
37 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
| month_sp_0 |
| month_sp_1 |
| month_sp_2 |
| month_sp_3 |
| month_sp_4 |
| month_sp_5 |
| weekday_sp_0 |
| weekday_sp_1 |
| weekday_sp_2 |
| hour_sp_0 |
| hour_sp_1 |
| hour_sp_2 |
| hour_sp_3 |
| hour_sp_4 |
| hour_sp_5 |
| hour_sp_6 |
| hour_sp_7 |
| hour_sp_8 |
| hour_sp_9 |
| hour_sp_10 |
| hour_sp_11 |
| year |
| temp |
| feel_temp |
| humidity |
| windspeed |
Параметры
Обученные атрибуты
Сплайновые признаки позволяют линейной модели эффективно использовать периодические признаки, связанные со временем, и уменьшить ошибку примерно с 14% до 10% от максимального спроса. Это сопоставимо с результатом, который мы получили для признаков, закодированных методом one-hot.
Качественный анализ влияния признаков на предсказания линейной модели
Здесь мы хотим визуализировать влияние выбранных способов конструирования признаков на форму предсказаний, зависящую от времени.
Для этого мы используем произвольное разбиение данных по времени, чтобы сравнить предсказания для некоторого набора отложенных точек данных.
naive_linear_pipeline.fit(X.iloc[train_0], y.iloc[train_0]) naive_linear_predictions = naive_linear_pipeline.predict(X.iloc[test_0]) one_hot_linear_pipeline.fit(X.iloc[train_0], y.iloc[train_0]) one_hot_linear_predictions = one_hot_linear_pipeline.predict(X.iloc[test_0]) cyclic_cossin_linear_pipeline.fit(X.iloc[train_0], y.iloc[train_0]) cyclic_cossin_linear_predictions = cyclic_cossin_linear_pipeline.predict(X.iloc[test_0]) cyclic_spline_linear_pipeline.fit(X.iloc[train_0], y.iloc[train_0]) cyclic_spline_linear_predictions = cyclic_spline_linear_pipeline.predict(X.iloc[test_0])
Чтобы получить качественное представление о результатах, рассмотрим предсказания за последние 96 часов (4 дня) тестовой выборки:
last_hours = slice(-96, None)
fig, ax = plt.subplots(figsize=(12, 4))
fig.suptitle("Predictions by linear models")
ax.plot(
y.iloc[test_0].values[last_hours],
"x-",
alpha=0.2,
label="Actual demand",
color="black",
)
ax.plot(naive_linear_predictions[last_hours], "x-", label="Ordinal time features")
ax.plot(
cyclic_cossin_linear_predictions[last_hours],
"x-",
label="Trigonometric time features",
)
ax.plot(
cyclic_spline_linear_predictions[last_hours],
"x-",
label="Spline-based time features",
)
ax.plot(
one_hot_linear_predictions[last_hours],
"x-",
label="One-hot time features",
)
_ = ax.legend()

По приведённому выше графику можно сделать следующие выводы:
- Исходные порядковые признаки, связанные со временем создают проблемы, поскольку не учитывают естественную периодичность: в конце каждого дня мы наблюдаем резкий скачок предсказаний, когда значение признака часа меняется с 23 на 0. Подобные артефакты можно ожидать и в конце каждой недели или каждого года.
- Как и ожидалось, тригонометрические признаки (синус и косинус) не создают разрывов в полночь, однако модели линейной регрессии не удаётся эффективно использовать эти признаки для правильного моделирования внутридневных изменений. Эту проблему можно потенциально решить, используя тригонометрические признаки для высших гармоник или дополнительные тригонометрические признаки для естественного периода с разными фазами.
- Признаки на основе периодических сплайнов одновременно решают обе эти проблемы: они повышают выразительность линейной модели, позволяя ей сосредоточиться на определённых часах благодаря использованию 12 сплайнов. Кроме того, параметр
extrapolation="periodic"обеспечивает плавное представление междуhour=23иhour=0. - Признаки, закодированные методом one-hot, ведут себя подобно признакам на основе периодических сплайнов, но дают более резкие изменения: например, они лучше моделируют утренний пик в будние дни, поскольку этот пик длится меньше часа. Однако далее мы увидим, что то, что может быть преимуществом для линейных моделей, не обязательно является таковым для более выразительных моделей.
Также можно сравнить количество признаков, полученных в результате каждого конвейера конструирования признаков:
naive_linear_pipeline[:-1].transform(X).shape
(17379, 19)
one_hot_linear_pipeline[:-1].transform(X).shape
(17379, 59)
cyclic_cossin_linear_pipeline[:-1].transform(X).shape
(17379, 22)
cyclic_spline_linear_pipeline[:-1].transform(X).shape
(17379, 37)
Это подтверждает, что стратегии кодирования one-hot и сплайнами создают для временного представления гораздо больше признаков, чем альтернативные подходы. Благодаря этому последующая линейная модель получает больше гибкости (степеней свободы) и может избегать недообучения.
Наконец, мы видим, что ни одна из линейных моделей не может аппроксимировать фактический спрос на аренду велосипедов, особенно в периоды пиков, которые могут быть очень резкими в часы пик в рабочие дни, но гораздо более сглаженными в выходные: наиболее точные линейные модели на основе сплайнов или кодирования one-hot склонны прогнозировать пики поездок на работу на велосипеде даже в выходные и недооценивать связанные с поездками на работу события в будние дни.
Эти систематические ошибки предсказания указывают на недообучение и могут объясняться отсутствием взаимодействий между признаками, например между «workingday» и признаками, производными от «hours». Эта проблема будет рассмотрена в следующем разделе.
Моделирование парных взаимодействий с помощью сплайнов и полиномиальных признаков
Линейные модели автоматически не учитывают эффекты взаимодействия между входными признаками. Не помогает и то, что некоторые признаки нелинейны по отдельности, как в случае признаков, построенных с помощью SplineTransformer (или кодирования one-hot, или разбиения на интервалы).
Однако можно использовать класс PolynomialFeatures для часов, закодированных сплайнами с крупным шагом, чтобы явно моделировать взаимодействие «workingday»/»hours», не вводя слишком много новых переменных:
from sklearn.pipeline import FeatureUnion
from sklearn.preprocessing import PolynomialFeatures
hour_workday_interaction = make_pipeline(
ColumnTransformer(
[
("cyclic_hour", periodic_spline_transformer(24, n_splines=8), ["hour"]),
(
"workingday",
FunctionTransformer(
lambda x: x == "True", feature_names_out="one-to-one"
),
["workingday"],
),
],
verbose_feature_names_out=False,
),
PolynomialFeatures(degree=2, interaction_only=True, include_bias=False),
)
Затем эти признаки объединяются с признаками, уже вычисленными в предыдущем конвейере на основе сплайнов. Явное моделирование этого парного взаимодействия позволяет добиться заметного улучшения качества:
cyclic_spline_interactions_pipeline = make_pipeline(
FeatureUnion(
[
("marginal", cyclic_spline_transformer),
("interactions", hour_workday_interaction),
],
verbose_feature_names_out=True,
).set_output(transform="pandas"),
RidgeCV(alphas=alphas),
)
evaluate(cyclic_spline_interactions_pipeline, X, y, cv=ts_cv)
Mean Absolute Error: 0.078 +/- 0.009 Root Mean Squared Error: 0.104 +/- 0.009
Pipeline(steps=[('featureunion',
FeatureUnion(transformer_list=[('marginal',
ColumnTransformer(remainder=MinMaxScaler(),
transformers=[('categorical',
OneHotEncoder(handle_unknown='ignore',
sparse_output=False),
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')),
('cyclic_month',
SplineTransformer(extrapolation='periodic',
knots=array([[ 0.],
[ 2....
RidgeCV(alphas=array([1.00000000e-06, 3.16227766e-06, 1.00000000e-05, 3.16227766e-05,
1.00000000e-04, 3.16227766e-04, 1.00000000e-03, 3.16227766e-03,
1.00000000e-02, 3.16227766e-02, 1.00000000e-01, 3.16227766e-01,
1.00000000e+00, 3.16227766e+00, 1.00000000e+01, 3.16227766e+01,
1.00000000e+02, 3.16227766e+02, 1.00000000e+03, 3.16227766e+03,
1.00000000e+04, 3.16227766e+04, 1.00000000e+05, 3.16227766e+05,
1.00000000e+06])))])
В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьте блокноту. На GitHub HTML-представление не отображается; попробуйте открыть эту страницу с помощью nbviewer.org.
Параметры
Обученные атрибуты
Параметры
Атрибуты, полученные при обучении
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')
Параметры
Атрибуты, полученные при обучении
11 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
['month']
Параметры
Атрибуты, полученные при обучении
6 признаков
| month_sp_0 |
| month_sp_1 |
| month_sp_2 |
| month_sp_3 |
| month_sp_4 |
| month_sp_5 |
['weekday']
Параметры
Обученные атрибуты
3 признака
| weekday_sp_0 |
| weekday_sp_1 |
| weekday_sp_2 |
['hour']
Параметры
Обученные атрибуты
12 признаков
| hour_sp_0 |
| hour_sp_1 |
| hour_sp_2 |
| hour_sp_3 |
| hour_sp_4 |
| hour_sp_5 |
| hour_sp_6 |
| hour_sp_7 |
| hour_sp_8 |
| hour_sp_9 |
| hour_sp_10 |
| hour_sp_11 |
['year', 'temp', 'feel_temp', 'humidity', 'windspeed']
Параметры
Обученные атрибуты
5 признаков
| year |
| temp |
| feel_temp |
| humidity |
| windspeed |
37 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
| month_sp_0 |
| month_sp_1 |
| month_sp_2 |
| month_sp_3 |
| month_sp_4 |
| month_sp_5 |
| weekday_sp_0 |
| weekday_sp_1 |
| weekday_sp_2 |
| hour_sp_0 |
| hour_sp_1 |
| hour_sp_2 |
| hour_sp_3 |
| hour_sp_4 |
| hour_sp_5 |
| hour_sp_6 |
| hour_sp_7 |
| hour_sp_8 |
| hour_sp_9 |
| hour_sp_10 |
| hour_sp_11 |
| year |
| temp |
| feel_temp |
| humidity |
| windspeed |
Параметры
Обученные атрибуты
['hour']
Параметры
Атрибуты после обучения
8 признаков
| hour_sp_0 |
| hour_sp_1 |
| hour_sp_2 |
| hour_sp_3 |
| hour_sp_4 |
| hour_sp_5 |
| hour_sp_6 |
| hour_sp_7 |
['workingday']
Параметры
Атрибуты после обучения
1 признак
| workingday |
9 признаков
| hour_sp_0 |
| hour_sp_1 |
| hour_sp_2 |
| hour_sp_3 |
| hour_sp_4 |
| hour_sp_5 |
| hour_sp_6 |
| hour_sp_7 |
| workingday |
Параметры
Атрибуты после обучения
| Имя | Тип | Значение |
|---|---|---|
| feature_names_in_ feature_names_in_: ndarray формы (`n_features_in_`,) Имена признаков, обнаруженных во время :term:`fit`. Определяется, только если `X` содержит имена признаков, представленные строками. .. versionadded:: 1.0 | ndarray[object](9,) | ['hour_sp_0','hour_sp_1','hour_sp_2',...,'hour_sp_6','hour_sp_7', 'workingday'] |
| n_features_in_ n_features_in_: int Количество признаков, обнаруженных во время :term:`fit`. .. versionadded:: 0.24 | int | 9 |
| n_output_features_ n_output_features_: int Общее количество выходных полиномиальных признаков. Количество выходных признаков вычисляется перебором всех комбинаций входных признаков подходящего размера. | int | 45 |
| powers_ powers_: ndarray формы (`n_output_features_`, `n_features_in_`) `powers_[i, j]` — показатель степени j-го входного признака в i-м выходном признаке. | ndarray[int64](45, 9) | [[1,0,0,...,0,0,0], [0,1,0,...,0,0,0], [0,0,1,...,0,0,0], ..., [0,0,0,...,1,1,0], [0,0,0,...,1,0,1], [0,0,0,...,0,1,1]] |
45 признаков
| hour_sp_0 |
| hour_sp_1 |
| hour_sp_2 |
| hour_sp_3 |
| hour_sp_4 |
| hour_sp_5 |
| hour_sp_6 |
| hour_sp_7 |
| workingday |
| hour_sp_0 hour_sp_1 |
| hour_sp_0 hour_sp_2 |
| hour_sp_0 hour_sp_3 |
| hour_sp_0 hour_sp_4 |
| hour_sp_0 hour_sp_5 |
| hour_sp_0 hour_sp_6 |
| hour_sp_0 hour_sp_7 |
| hour_sp_0 workingday |
| hour_sp_1 hour_sp_2 |
| hour_sp_1 hour_sp_3 |
| hour_sp_1 hour_sp_4 |
| hour_sp_1 hour_sp_5 |
| hour_sp_1 hour_sp_6 |
| hour_sp_1 hour_sp_7 |
| hour_sp_1 workingday |
| hour_sp_2 hour_sp_3 |
| hour_sp_2 hour_sp_4 |
| hour_sp_2 hour_sp_5 |
| hour_sp_2 hour_sp_6 |
| hour_sp_2 hour_sp_7 |
| hour_sp_2 workingday |
| hour_sp_3 hour_sp_4 |
| hour_sp_3 hour_sp_5 |
| hour_sp_3 hour_sp_6 |
| hour_sp_3 hour_sp_7 |
| hour_sp_3 workingday |
| hour_sp_4 hour_sp_5 |
| hour_sp_4 hour_sp_6 |
| hour_sp_4 hour_sp_7 |
| hour_sp_4 workingday |
| hour_sp_5 hour_sp_6 |
| hour_sp_5 hour_sp_7 |
| hour_sp_5 workingday |
| hour_sp_6 hour_sp_7 |
| hour_sp_6 workingday |
| hour_sp_7 workingday |
82 признака
| marginal__season_fall |
| marginal__season_spring |
| marginal__season_summer |
| marginal__season_winter |
| marginal__holiday_False |
| marginal__holiday_True |
| marginal__workingday_False |
| marginal__workingday_True |
| marginal__weather_clear |
| marginal__weather_misty |
| marginal__weather_rain |
| marginal__month_sp_0 |
| marginal__month_sp_1 |
| marginal__month_sp_2 |
| marginal__month_sp_3 |
| marginal__month_sp_4 |
| marginal__month_sp_5 |
| marginal__weekday_sp_0 |
| marginal__weekday_sp_1 |
| marginal__weekday_sp_2 |
| marginal__hour_sp_0 |
| marginal__hour_sp_1 |
| marginal__hour_sp_2 |
| marginal__hour_sp_3 |
| marginal__hour_sp_4 |
| marginal__hour_sp_5 |
| marginal__hour_sp_6 |
| marginal__hour_sp_7 |
| marginal__hour_sp_8 |
| marginal__hour_sp_9 |
| marginal__hour_sp_10 |
| marginal__hour_sp_11 |
| marginal__year |
| marginal__temp |
| marginal__feel_temp |
| marginal__humidity |
| marginal__windspeed |
| interactions__hour_sp_0 |
| interactions__hour_sp_1 |
| interactions__hour_sp_2 |
| interactions__hour_sp_3 |
| interactions__hour_sp_4 |
| interactions__hour_sp_5 |
| interactions__hour_sp_6 |
| interactions__hour_sp_7 |
| interactions__workingday |
| interactions__hour_sp_0 hour_sp_1 |
| interactions__hour_sp_0 hour_sp_2 |
| interactions__hour_sp_0 hour_sp_3 |
| interactions__hour_sp_0 hour_sp_4 |
| interactions__hour_sp_0 hour_sp_5 |
| interactions__hour_sp_0 hour_sp_6 |
| interactions__hour_sp_0 hour_sp_7 |
| interactions__hour_sp_0 workingday |
| interactions__hour_sp_1 hour_sp_2 |
| interactions__hour_sp_1 hour_sp_3 |
| interactions__hour_sp_1 hour_sp_4 |
| interactions__hour_sp_1 hour_sp_5 |
| interactions__hour_sp_1 hour_sp_6 |
| interactions__hour_sp_1 hour_sp_7 |
| interactions__hour_sp_1 workingday |
| interactions__hour_sp_2 hour_sp_3 |
| interactions__hour_sp_2 hour_sp_4 |
| interactions__hour_sp_2 hour_sp_5 |
| interactions__hour_sp_2 hour_sp_6 |
| interactions__hour_sp_2 hour_sp_7 |
| interactions__hour_sp_2 workingday |
| interactions__hour_sp_3 hour_sp_4 |
| interactions__hour_sp_3 hour_sp_5 |
| interactions__hour_sp_3 hour_sp_6 |
| interactions__hour_sp_3 hour_sp_7 |
| interactions__hour_sp_3 workingday |
| interactions__hour_sp_4 hour_sp_5 |
| interactions__hour_sp_4 hour_sp_6 |
| interactions__hour_sp_4 hour_sp_7 |
| interactions__hour_sp_4 workingday |
| interactions__hour_sp_5 hour_sp_6 |
| interactions__hour_sp_5 hour_sp_7 |
| interactions__hour_sp_5 workingday |
| interactions__hour_sp_6 hour_sp_7 |
| interactions__hour_sp_6 workingday |
| interactions__hour_sp_7 workingday |
Параметры
Обученные атрибуты
Моделирование нелинейных взаимодействий признаков с помощью ядер
Предыдущий анализ показал необходимость моделировать взаимодействия между "workingday" и "hours". Еще один пример такого нелинейного взаимодействия, которое мы хотели бы моделировать, — влияние дождя, которое, например, может быть разным в рабочие дни, выходные и праздничные дни.
Чтобы моделировать все такие взаимодействия, можно сначала развернуть все маргинальные признаки с помощью сплайнов, а затем одновременно выполнить полиномиальное разложение для всех признаков. Однако это привело бы к квадратичному росту числа признаков, что может вызвать переобучение и затруднить вычисления.
В качестве альтернативы можно использовать метод Нюстрёма для вычисления приближенного полиномиального разложения ядра. Попробуем этот вариант:
from sklearn.kernel_approximation import Nystroem
cyclic_spline_poly_pipeline = make_pipeline(
cyclic_spline_transformer,
Nystroem(kernel="poly", degree=2, n_components=300, random_state=0),
RidgeCV(alphas=alphas),
)
evaluate(cyclic_spline_poly_pipeline, X, y, cv=ts_cv)
Mean Absolute Error: 0.053 +/- 0.002 Root Mean Squared Error: 0.076 +/- 0.004
Pipeline(steps=[('columntransformer',
ColumnTransformer(remainder=MinMaxScaler(),
transformers=[('categorical',
OneHotEncoder(handle_unknown='ignore',
sparse_output=False),
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')),
('cyclic_month',
SplineTransformer(extrapolation='periodic',
knots=array([[ 0.],
[ 2.],
[ 4.],
[ 6.],
[ 8.],
[10.],
[12.]]),
n_knots...
RidgeCV(alphas=array([1.00000000e-06, 3.16227766e-06, 1.00000000e-05, 3.16227766e-05,
1.00000000e-04, 3.16227766e-04, 1.00000000e-03, 3.16227766e-03,
1.00000000e-02, 3.16227766e-02, 1.00000000e-01, 3.16227766e-01,
1.00000000e+00, 3.16227766e+00, 1.00000000e+01, 3.16227766e+01,
1.00000000e+02, 3.16227766e+02, 1.00000000e+03, 3.16227766e+03,
1.00000000e+04, 3.16227766e+04, 1.00000000e+05, 3.16227766e+05,
1.00000000e+06])))])
В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьте выполнение записной книжки. На GitHub HTML-представление не отображается. Попробуйте открыть эту страницу с помощью nbviewer.org.
Параметры
Обученные атрибуты
Параметры
Обученные атрибуты
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')
Параметры
Обученные атрибуты
11 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
['month']
Параметры
Обученные атрибуты
6 признаков
| month_sp_0 |
| month_sp_1 |
| month_sp_2 |
| month_sp_3 |
| month_sp_4 |
| month_sp_5 |
['weekday']
Параметры
Обученные атрибуты
3 признака
| weekday_sp_0 |
| weekday_sp_1 |
| weekday_sp_2 |
['hour']
Параметры
Обученные атрибуты
12 признаков
| hour_sp_0 |
| hour_sp_1 |
| hour_sp_2 |
| hour_sp_3 |
| hour_sp_4 |
| hour_sp_5 |
| hour_sp_6 |
| hour_sp_7 |
| hour_sp_8 |
| hour_sp_9 |
| hour_sp_10 |
| hour_sp_11 |
['year', 'temp', 'feel_temp', 'humidity', 'windspeed']
Параметры
Обученные атрибуты
5 признаков
| year |
| temp |
| feel_temp |
| humidity |
| windspeed |
37 features
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
| month_sp_0 |
| month_sp_1 |
| month_sp_2 |
| month_sp_3 |
| month_sp_4 |
| month_sp_5 |
| weekday_sp_0 |
| weekday_sp_1 |
| weekday_sp_2 |
| hour_sp_0 |
| hour_sp_1 |
| hour_sp_2 |
| hour_sp_3 |
| hour_sp_4 |
| hour_sp_5 |
| hour_sp_6 |
| hour_sp_7 |
| hour_sp_8 |
| hour_sp_9 |
| hour_sp_10 |
| hour_sp_11 |
| year |
| temp |
| feel_temp |
| humidity |
| windspeed |
Параметры
Обученные атрибуты
300 features
| nystroem0 |
| nystroem1 |
| nystroem2 |
| nystroem3 |
| nystroem4 |
| nystroem5 |
| nystroem6 |
| nystroem7 |
| nystroem8 |
| nystroem9 |
| nystroem10 |
| nystroem11 |
| nystroem12 |
| nystroem13 |
| nystroem14 |
| nystroem15 |
| nystroem16 |
| nystroem17 |
| nystroem18 |
| nystroem19 |
| nystroem20 |
| nystroem21 |
| nystroem22 |
| nystroem23 |
| nystroem24 |
| nystroem25 |
| nystroem26 |
| nystroem27 |
| nystroem28 |
| nystroem29 |
| nystroem30 |
| nystroem31 |
| nystroem32 |
| nystroem33 |
| nystroem34 |
| nystroem35 |
| nystroem36 |
| nystroem37 |
| nystroem38 |
| nystroem39 |
| nystroem40 |
| nystroem41 |
| nystroem42 |
| nystroem43 |
| nystroem44 |
| nystroem45 |
| nystroem46 |
| nystroem47 |
| nystroem48 |
| nystroem49 |
| nystroem50 |
| nystroem51 |
| nystroem52 |
| nystroem53 |
| nystroem54 |
| nystroem55 |
| nystroem56 |
| nystroem57 |
| nystroem58 |
| nystroem59 |
| nystroem60 |
| nystroem61 |
| nystroem62 |
| nystroem63 |
| nystroem64 |
| nystroem65 |
| nystroem66 |
| nystroem67 |
| nystroem68 |
| nystroem69 |
| nystroem70 |
| nystroem71 |
| nystroem72 |
| nystroem73 |
| nystroem74 |
| nystroem75 |
| nystroem76 |
| nystroem77 |
| nystroem78 |
| nystroem79 |
| nystroem80 |
| nystroem81 |
| nystroem82 |
| nystroem83 |
| nystroem84 |
| nystroem85 |
| nystroem86 |
| nystroem87 |
| nystroem88 |
| nystroem89 |
| nystroem90 |
| nystroem91 |
| nystroem92 |
| nystroem93 |
| nystroem94 |
| nystroem95 |
| nystroem96 |
| nystroem97 |
| nystroem98 |
| nystroem99 |
| nystroem100 |
| nystroem101 |
| nystroem102 |
| nystroem103 |
| nystroem104 |
| nystroem105 |
| nystroem106 |
| nystroem107 |
| nystroem108 |
| nystroem109 |
| nystroem110 |
| nystroem111 |
| nystroem112 |
| nystroem113 |
| nystroem114 |
| nystroem115 |
| nystroem116 |
| nystroem117 |
| nystroem118 |
| nystroem119 |
| nystroem120 |
| nystroem121 |
| nystroem122 |
| nystroem123 |
| nystroem124 |
| nystroem125 |
| nystroem126 |
| nystroem127 |
| nystroem128 |
| nystroem129 |
| nystroem130 |
| nystroem131 |
| nystroem132 |
| nystroem133 |
| nystroem134 |
| nystroem135 |
| nystroem136 |
| nystroem137 |
| nystroem138 |
| nystroem139 |
| nystroem140 |
| nystroem141 |
| nystroem142 |
| nystroem143 |
| nystroem144 |
| nystroem145 |
| nystroem146 |
| nystroem147 |
| nystroem148 |
| nystroem149 |
| nystroem150 |
| nystroem151 |
| nystroem152 |
| nystroem153 |
| nystroem154 |
| nystroem155 |
| nystroem156 |
| nystroem157 |
| nystroem158 |
| nystroem159 |
| nystroem160 |
| nystroem161 |
| nystroem162 |
| nystroem163 |
| nystroem164 |
| nystroem165 |
| nystroem166 |
| nystroem167 |
| nystroem168 |
| nystroem169 |
| nystroem170 |
| nystroem171 |
| nystroem172 |
| nystroem173 |
| nystroem174 |
| nystroem175 |
| nystroem176 |
| nystroem177 |
| nystroem178 |
| nystroem179 |
| nystroem180 |
| nystroem181 |
| nystroem182 |
| nystroem183 |
| nystroem184 |
| nystroem185 |
| nystroem186 |
| nystroem187 |
| nystroem188 |
| nystroem189 |
| nystroem190 |
| nystroem191 |
| nystroem192 |
| nystroem193 |
| nystroem194 |
| nystroem195 |
| nystroem196 |
| nystroem197 |
| nystroem198 |
| nystroem199 |
| nystroem200 |
| nystroem201 |
| nystroem202 |
| nystroem203 |
| nystroem204 |
| nystroem205 |
| nystroem206 |
| nystroem207 |
| nystroem208 |
| nystroem209 |
| nystroem210 |
| nystroem211 |
| nystroem212 |
| nystroem213 |
| nystroem214 |
| nystroem215 |
| nystroem216 |
| nystroem217 |
| nystroem218 |
| nystroem219 |
| nystroem220 |
| nystroem221 |
| nystroem222 |
| nystroem223 |
| nystroem224 |
| nystroem225 |
| nystroem226 |
| nystroem227 |
| nystroem228 |
| nystroem229 |
| nystroem230 |
| nystroem231 |
| nystroem232 |
| nystroem233 |
| nystroem234 |
| nystroem235 |
| nystroem236 |
| nystroem237 |
| nystroem238 |
| nystroem239 |
| nystroem240 |
| nystroem241 |
| nystroem242 |
| nystroem243 |
| nystroem244 |
| nystroem245 |
| nystroem246 |
| nystroem247 |
| nystroem248 |
| nystroem249 |
| nystroem250 |
| nystroem251 |
| nystroem252 |
| nystroem253 |
| nystroem254 |
| nystroem255 |
| nystroem256 |
| nystroem257 |
| nystroem258 |
| nystroem259 |
| nystroem260 |
| nystroem261 |
| nystroem262 |
| nystroem263 |
| nystroem264 |
| nystroem265 |
| nystroem266 |
| nystroem267 |
| nystroem268 |
| nystroem269 |
| nystroem270 |
| nystroem271 |
| nystroem272 |
| nystroem273 |
| nystroem274 |
| nystroem275 |
| nystroem276 |
| nystroem277 |
| nystroem278 |
| nystroem279 |
| nystroem280 |
| nystroem281 |
| nystroem282 |
| nystroem283 |
| nystroem284 |
| nystroem285 |
| nystroem286 |
| nystroem287 |
| nystroem288 |
| nystroem289 |
| nystroem290 |
| nystroem291 |
| nystroem292 |
| nystroem293 |
| nystroem294 |
| nystroem295 |
| nystroem296 |
| nystroem297 |
| nystroem298 |
| nystroem299 |
Параметры
Обученные атрибуты
Мы видим, что эта модель почти не уступает по производительности деревьям с градиентным бустингом: средняя ошибка составляет около 5% от максимального спроса.
Обратите внимание: хотя последний шаг этого конвейера — модель линейной регрессии, промежуточные этапы, такие как извлечение признаков с помощью сплайнов и аппроксимация ядра методом Нюстрёма, являются в высокой степени нелинейными. В результате составной конвейер гораздо выразительнее простой модели линейной регрессии с исходными признаками.
Для полноты картины мы также оцениваем сочетание унитарного кодирования и аппроксимации ядра:
one_hot_poly_pipeline = make_pipeline(
ColumnTransformer(
transformers=[
("categorical", one_hot_encoder, categorical_columns),
("one_hot_time", one_hot_encoder, ["hour", "weekday", "month"]),
],
remainder="passthrough",
verbose_feature_names_out=False,
),
Nystroem(kernel="poly", degree=2, n_components=300, random_state=0),
RidgeCV(alphas=alphas),
).set_output(transform="pandas")
evaluate(one_hot_poly_pipeline, X, y, cv=ts_cv)
Mean Absolute Error: 0.082 +/- 0.006 Root Mean Squared Error: 0.111 +/- 0.011
Pipeline(steps=[('columntransformer',
ColumnTransformer(remainder='passthrough',
transformers=[('categorical',
OneHotEncoder(handle_unknown='ignore',
sparse_output=False),
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')),
('one_hot_time',
OneHotEncoder(handle_unknown='ignore',
sparse_output=False),
['hour', 'weekday',
'month'])],
verbose_feature...
RidgeCV(alphas=array([1.00000000e-06, 3.16227766e-06, 1.00000000e-05, 3.16227766e-05,
1.00000000e-04, 3.16227766e-04, 1.00000000e-03, 3.16227766e-03,
1.00000000e-02, 3.16227766e-02, 1.00000000e-01, 3.16227766e-01,
1.00000000e+00, 3.16227766e+00, 1.00000000e+01, 3.16227766e+01,
1.00000000e+02, 3.16227766e+02, 1.00000000e+03, 3.16227766e+03,
1.00000000e+04, 3.16227766e+04, 1.00000000e+05, 3.16227766e+05,
1.00000000e+06])))])
В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьте этой записной книжке. На GitHub HTML-представление отобразить невозможно; попробуйте загрузить эту страницу с помощью nbviewer.org.
Параметры
Обученные атрибуты
Параметры
Обученные атрибуты
Index(['season', 'holiday', 'workingday', 'weather'], dtype='str')
Параметры
Обученные атрибуты
11 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
['hour', 'weekday', 'month']
Параметры
Обученные атрибуты
43 признака
| hour_0 |
| hour_1 |
| hour_2 |
| hour_3 |
| hour_4 |
| hour_5 |
| hour_6 |
| hour_7 |
| hour_8 |
| hour_9 |
| hour_10 |
| hour_11 |
| hour_12 |
| hour_13 |
| hour_14 |
| hour_15 |
| hour_16 |
| hour_17 |
| hour_18 |
| hour_19 |
| hour_20 |
| hour_21 |
| hour_22 |
| hour_23 |
| weekday_0 |
| weekday_1 |
| weekday_2 |
| weekday_3 |
| weekday_4 |
| weekday_5 |
| weekday_6 |
| month_1 |
| month_2 |
| month_3 |
| month_4 |
| month_5 |
| month_6 |
| month_7 |
| month_8 |
| month_9 |
| month_10 |
| month_11 |
| month_12 |
['year', 'temp', 'feel_temp', 'humidity', 'windspeed']
5 признаков
| year |
| temp |
| feel_temp |
| humidity |
| windspeed |
59 признаков
| season_fall |
| season_spring |
| season_summer |
| season_winter |
| holiday_False |
| holiday_True |
| workingday_False |
| workingday_True |
| weather_clear |
| weather_misty |
| weather_rain |
| hour_0 |
| hour_1 |
| hour_2 |
| hour_3 |
| hour_4 |
| hour_5 |
| hour_6 |
| hour_7 |
| hour_8 |
| hour_9 |
| hour_10 |
| hour_11 |
| hour_12 |
| hour_13 |
| hour_14 |
| hour_15 |
| hour_16 |
| hour_17 |
| hour_18 |
| hour_19 |
| hour_20 |
| hour_21 |
| hour_22 |
| hour_23 |
| weekday_0 |
| weekday_1 |
| weekday_2 |
| weekday_3 |
| weekday_4 |
| weekday_5 |
| weekday_6 |
| month_1 |
| month_2 |
| month_3 |
| month_4 |
| month_5 |
| month_6 |
| month_7 |
| month_8 |
| month_9 |
| month_10 |
| month_11 |
| month_12 |
| year |
| temp |
| feel_temp |
| humidity |
| windspeed |
Параметры
Обученные атрибуты
300 признаков
| nystroem0 |
| nystroem1 |
| nystroem2 |
| nystroem3 |
| nystroem4 |
| nystroem5 |
| nystroem6 |
| nystroem7 |
| nystroem8 |
| nystroem9 |
| nystroem10 |
| nystroem11 |
| nystroem12 |
| nystroem13 |
| nystroem14 |
| nystroem15 |
| nystroem16 |
| nystroem17 |
| nystroem18 |
| nystroem19 |
| nystroem20 |
| nystroem21 |
| nystroem22 |
| nystroem23 |
| nystroem24 |
| nystroem25 |
| nystroem26 |
| nystroem27 |
| nystroem28 |
| nystroem29 |
| nystroem30 |
| nystroem31 |
| nystroem32 |
| nystroem33 |
| nystroem34 |
| nystroem35 |
| nystroem36 |
| nystroem37 |
| nystroem38 |
| nystroem39 |
| nystroem40 |
| nystroem41 |
| nystroem42 |
| nystroem43 |
| nystroem44 |
| nystroem45 |
| nystroem46 |
| nystroem47 |
| nystroem48 |
| nystroem49 |
| nystroem50 |
| nystroem51 |
| nystroem52 |
| nystroem53 |
| nystroem54 |
| nystroem55 |
| nystroem56 |
| nystroem57 |
| nystroem58 |
| nystroem59 |
| nystroem60 |
| nystroem61 |
| nystroem62 |
| nystroem63 |
| nystroem64 |
| nystroem65 |
| nystroem66 |
| nystroem67 |
| nystroem68 |
| nystroem69 |
| nystroem70 |
| nystroem71 |
| nystroem72 |
| nystroem73 |
| nystroem74 |
| nystroem75 |
| nystroem76 |
| nystroem77 |
| nystroem78 |
| nystroem79 |
| nystroem80 |
| nystroem81 |
| nystroem82 |
| nystroem83 |
| nystroem84 |
| nystroem85 |
| nystroem86 |
| nystroem87 |
| nystroem88 |
| nystroem89 |
| nystroem90 |
| nystroem91 |
| nystroem92 |
| nystroem93 |
| nystroem94 |
| nystroem95 |
| nystroem96 |
| nystroem97 |
| nystroem98 |
| nystroem99 |
| nystroem100 |
| nystroem101 |
| nystroem102 |
| nystroem103 |
| nystroem104 |
| nystroem105 |
| nystroem106 |
| nystroem107 |
| nystroem108 |
| nystroem109 |
| nystroem110 |
| nystroem111 |
| nystroem112 |
| nystroem113 |
| nystroem114 |
| nystroem115 |
| nystroem116 |
| nystroem117 |
| nystroem118 |
| nystroem119 |
| nystroem120 |
| nystroem121 |
| nystroem122 |
| nystroem123 |
| nystroem124 |
| nystroem125 |
| nystroem126 |
| nystroem127 |
| nystroem128 |
| nystroem129 |
| nystroem130 |
| nystroem131 |
| nystroem132 |
| nystroem133 |
| nystroem134 |
| nystroem135 |
| nystroem136 |
| nystroem137 |
| nystroem138 |
| nystroem139 |
| nystroem140 |
| nystroem141 |
| nystroem142 |
| nystroem143 |
| nystroem144 |
| nystroem145 |
| nystroem146 |
| nystroem147 |
| nystroem148 |
| nystroem149 |
| nystroem150 |
| nystroem151 |
| nystroem152 |
| nystroem153 |
| nystroem154 |
| nystroem155 |
| nystroem156 |
| nystroem157 |
| nystroem158 |
| nystroem159 |
| nystroem160 |
| nystroem161 |
| nystroem162 |
| nystroem163 |
| nystroem164 |
| nystroem165 |
| nystroem166 |
| nystroem167 |
| nystroem168 |
| nystroem169 |
| nystroem170 |
| nystroem171 |
| nystroem172 |
| nystroem173 |
| nystroem174 |
| nystroem175 |
| nystroem176 |
| nystroem177 |
| nystroem178 |
| nystroem179 |
| nystroem180 |
| nystroem181 |
| nystroem182 |
| nystroem183 |
| nystroem184 |
| nystroem185 |
| nystroem186 |
| nystroem187 |
| nystroem188 |
| nystroem189 |
| nystroem190 |
| nystroem191 |
| nystroem192 |
| nystroem193 |
| nystroem194 |
| nystroem195 |
| nystroem196 |
| nystroem197 |
| nystroem198 |
| nystroem199 |
| nystroem200 |
| nystroem201 |
| nystroem202 |
| nystroem203 |
| nystroem204 |
| nystroem205 |
| nystroem206 |
| nystroem207 |
| nystroem208 |
| nystroem209 |
| nystroem210 |
| nystroem211 |
| nystroem212 |
| nystroem213 |
| nystroem214 |
| nystroem215 |
| nystroem216 |
| nystroem217 |
| nystroem218 |
| nystroem219 |
| nystroem220 |
| nystroem221 |
| nystroem222 |
| nystroem223 |
| nystroem224 |
| nystroem225 |
| nystroem226 |
| nystroem227 |
| nystroem228 |
| nystroem229 |
| nystroem230 |
| nystroem231 |
| nystroem232 |
| nystroem233 |
| nystroem234 |
| nystroem235 |
| nystroem236 |
| nystroem237 |
| nystroem238 |
| nystroem239 |
| nystroem240 |
| nystroem241 |
| nystroem242 |
| nystroem243 |
| nystroem244 |
| nystroem245 |
| nystroem246 |
| nystroem247 |
| nystroem248 |
| nystroem249 |
| nystroem250 |
| nystroem251 |
| nystroem252 |
| nystroem253 |
| nystroem254 |
| nystroem255 |
| nystroem256 |
| nystroem257 |
| nystroem258 |
| nystroem259 |
| nystroem260 |
| nystroem261 |
| nystroem262 |
| nystroem263 |
| nystroem264 |
| nystroem265 |
| nystroem266 |
| nystroem267 |
| nystroem268 |
| nystroem269 |
| nystroem270 |
| nystroem271 |
| nystroem272 |
| nystroem273 |
| nystroem274 |
| nystroem275 |
| nystroem276 |
| nystroem277 |
| nystroem278 |
| nystroem279 |
| nystroem280 |
| nystroem281 |
| nystroem282 |
| nystroem283 |
| nystroem284 |
| nystroem285 |
| nystroem286 |
| nystroem287 |
| nystroem288 |
| nystroem289 |
| nystroem290 |
| nystroem291 |
| nystroem292 |
| nystroem293 |
| nystroem294 |
| nystroem295 |
| nystroem296 |
| nystroem297 |
| nystroem298 |
| nystroem299 |
Параметры
Обученные атрибуты
При использовании линейных моделей признаки, закодированные методом one-hot, показывали результаты, сопоставимые с признаками на основе сплайнов. Однако при использовании низкорангового приближения нелинейного ядра это уже не так: это можно объяснить тем, что сплайновые признаки более гладкие и позволяют приближению ядра находить более выразительную функцию принятия решений.
Теперь качественно рассмотрим прогнозы моделей на основе ядер и градиентно-бустинговых деревьев, которые должны лучше моделировать нелинейные взаимодействия между признаками:
gbrt.fit(X.iloc[train_0], y.iloc[train_0]) gbrt_predictions = gbrt.predict(X.iloc[test_0]) one_hot_poly_pipeline.fit(X.iloc[train_0], y.iloc[train_0]) one_hot_poly_predictions = one_hot_poly_pipeline.predict(X.iloc[test_0]) cyclic_spline_poly_pipeline.fit(X.iloc[train_0], y.iloc[train_0]) cyclic_spline_poly_predictions = cyclic_spline_poly_pipeline.predict(X.iloc[test_0])
Снова увеличим последний фрагмент тестовой выборки, охватывающий 4 дня:
last_hours = slice(-96, None)
fig, ax = plt.subplots(figsize=(12, 4))
fig.suptitle("Predictions by non-linear regression models")
ax.plot(
y.iloc[test_0].values[last_hours],
"x-",
alpha=0.2,
label="Actual demand",
color="black",
)
ax.plot(
gbrt_predictions[last_hours],
"x-",
label="Gradient Boosted Trees",
)
ax.plot(
one_hot_poly_predictions[last_hours],
"x-",
label="One-hot + polynomial kernel",
)
ax.plot(
cyclic_spline_poly_predictions[last_hours],
"x-",
label="Splines + polynomial kernel",
)
_ = ax.legend()

Прежде всего отметим, что деревья могут естественным образом моделировать нелинейные взаимодействия между признаками, поскольку по умолчанию деревьям решений разрешено расти глубже двух уровней.
Здесь видно, что сочетание сплайновых признаков и нелинейных ядер работает достаточно хорошо и почти может сравниться по точности с деревьями регрессии с градиентным бустингом.
Напротив, временные признаки, закодированные методом one-hot, не показывают столь хороших результатов с низкоранговой моделью на основе ядер. В частности, она значительно сильнее конкурирующих моделей завышает оценки спроса в часы низкого спроса.
Также видно, что ни одна из моделей не может успешно предсказать некоторые пиковые значения аренды в часы пик в рабочие дни. Для дальнейшего повышения точности прогнозов, возможно, потребуется доступ к дополнительным признакам. Например, полезно было бы знать географическое распределение парка велосипедов в любой момент времени или долю велосипедов, выведенных из эксплуатации из-за необходимости обслуживания.
Наконец, количественно рассмотрим ошибки прогнозирования этих трёх моделей с помощью диаграмм рассеяния фактического и прогнозируемого спроса:
from sklearn.metrics import PredictionErrorDisplay
fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(13, 7), sharex=True, sharey="row")
fig.suptitle("Non-linear regression models", y=1.0)
predictions = [
one_hot_poly_predictions,
cyclic_spline_poly_predictions,
gbrt_predictions,
]
labels = [
"One hot +\npolynomial kernel",
"Splines +\npolynomial kernel",
"Gradient Boosted\nTrees",
]
plot_kinds = ["actual_vs_predicted", "residual_vs_predicted"]
for axis_idx, kind in enumerate(plot_kinds):
for ax, pred, label in zip(axes[axis_idx], predictions, labels):
disp = PredictionErrorDisplay.from_predictions(
y_true=y.iloc[test_0],
y_pred=pred,
kind=kind,
scatter_kwargs={"alpha": 0.3},
ax=ax,
)
ax.set_xticks(np.linspace(0, 1, num=5))
if axis_idx == 0:
ax.set_yticks(np.linspace(0, 1, num=5))
ax.legend(
["Best model", label],
loc="upper center",
bbox_to_anchor=(0.5, 1.3),
ncol=2,
)
ax.set_aspect("equal", adjustable="box")
plt.show()

Эта визуализация подтверждает выводы, сделанные по предыдущему графику.
Все модели занижают оценки высокого спроса (в часы пик в рабочие дни), однако градиентный бустинг делает это в меньшей степени. В среднем градиентный бустинг хорошо прогнозирует периоды низкого спроса, тогда как конвейер полиномиальной регрессии с one-hot-кодированием, по-видимому, систематически завышает спрос в этом диапазоне. В целом прогнозы деревьев с градиентным бустингом ближе к диагонали, чем прогнозы моделей на основе ядер.
Заключительные замечания
Отметим, что результаты моделей на основе ядер можно было бы немного улучшить, используя больше компонент (более высокоранговое приближение ядра), ценой увеличения времени обучения и прогнозирования. При больших значениях n_components качество признаков, закодированных методом one-hot, даже сравнялось бы с качеством сплайновых признаков.
Регрессор Nystroem + RidgeCV также можно было бы заменить на MLPRegressor с одним или двумя скрытыми слоями; результаты были бы весьма похожими.
В этом практическом примере использовался набор данных с почасовой дискретизацией. Однако циклические признаки на основе сплайнов могут очень эффективно моделировать время суток или день недели при более мелкой временной дискретизации (например, при измерениях каждую минуту, а не каждый час), не увеличивая число признаков. Представление времени с помощью one-hot-кодирования такой гибкости не обеспечивает.
Наконец, в этом блокноте мы использовали RidgeCV, поскольку этот метод очень эффективен с вычислительной точки зрения. Однако он моделирует целевую переменную как случайную величину с гауссовским распределением и постоянной дисперсией. Для задач регрессии с положительными значениями, вероятно, уместнее использовать распределение Пуассона или гамма-распределение. Этого можно добиться, используя GridSearchCV(TweedieRegressor(power=2), param_grid({"alpha": alphas})) вместо RidgeCV.
Общее время выполнения скрипта: (0 минут 17.468 секунд)
Связанные примеры
© 2007–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/auto_examples/applications/plot_cyclical_feature_engineering.html