Skip to main content
Ctrl+K
scikit-learn homepage
  • 安装
  • 用户指南
  • API
  • 示例
  • 社区
    • 入门
    • 解除历史记录
    • 术语表
    • 发展
    • FAQ
    • 支持
    • 相关项目
    • 路线图
    • 治理
    • 关于我们
  • GitHub
  • 安装
  • 用户指南
  • API
  • 示例
  • 社区
    • 入门
    • 解除历史记录
    • 术语表
    • 发展
    • FAQ
    • 支持
    • 相关项目
    • 路线图
    • 治理
    • 关于我们
  • GitHub

Section Navigation

  • 发布亮点
    • scikit-learn 1.6的发布亮点
    • scikit-learn 1.5的发布亮点
    • scikit-learn 1.4的发布亮点
    • scikit-learn 1.3的发布亮点
    • scikit-learn 1.1的发布亮点
    • scikit-learn 1.0的发布亮点
    • scikit-learn 0.24发布亮点
    • scikit-learn 0.23的发布亮点
  • 双聚类
    • 光谱双集群算法的演示
    • 光谱协同聚集算法的演示
    • 使用谱协同集群算法对文档进行二集群
  • 校准
    • 分类器校准的比较
    • 概率校准曲线
    • 分类器的概率校准
  • 分类
    • 分类器比较
    • 具有协方差椭圆体的线性和二次鉴别分析
    • 用于分类的正态、Ledoit-Wolf和OAS线性鉴别分析
    • 识别手写数字
  • 聚类
    • 手写数字数据上的K-Means集群演示
    • 硬币图像上的结构化Ward分层集群演示
    • 均值漂移集群算法的演示
    • 集群绩效评估中的机会调整
    • 有结构和不有结构的集聚
    • 具有不同指标的聚集性集群
    • K-Means++初始化的示例
    • 二分K均值和常规K均值性能比较
    • 比较BIRCH和MiniBatchKMeans
    • 在玩具数据集上比较不同的聚类算法
    • 在玩具数据集上比较不同的分层链接方法
    • K-Means和MiniBatchKMeans集群算法的比较
    • DBSCAN集群算法演示
    • HDSCAN集群算法演示
    • OPTICS分簇算法演示
    • 亲和力传播分簇算法演示
    • k均值假设的证明
    • k均值初始化影响的实证评估
    • 特征聚集
    • 特征聚集与单变量选择
    • 分层集群:结构化与非结构化病房
    • 归纳集群
    • 在线学习面部部分词典
    • 图分层聚集树图
    • 按地区划分希腊硬币的图片
    • 在KMeans聚类中使用轮廓分析选择聚类数
    • 用于图像分割的光谱集群
    • 2D数字嵌入上的各种聚集性聚集
    • 载体量化示例
  • 协方差估计
    • Ledoit-Wolf与OAS估计
    • 稳健协方差估计和Mahalanobis距离相关性
    • 稳健与经验协方差估计
    • 收缩协方差估计:LedoitWolf vs OAS和最大似然
    • 稀疏逆协方差估计
  • 交叉分解
    • 比较交叉分解方法
    • 主成分回归与偏最小平方回归
  • 数据集示例
    • 绘制随机生成的多标签数据集
  • 决策树
    • 决策树回归
    • 绘制在虹膜数据集上训练的决策树的决策面
    • 具有成本复杂性修剪的后修剪决策树
    • 了解决策树结构
  • 分解
    • 使用FastICA的盲源分离
    • Iris数据集LDA和PCA 2D投影的比较
    • Faces数据集分解
    • 因子分析(旋转)以可视化模式
    • 2D点云上的FastICA
    • 使用字典学习的图像去噪
    • 增量PCA
    • 核PCA
    • 使用概率PCA和因子分析(FA)进行模型选择
    • Iris数据集的主成分分析(PCA)
    • 使用预先计算的字典进行稀疏编码
  • 开发估算器
    • __sklearn_is_fitted__ 作为开发人员API
  • 集成方法
    • 梯度提升中的分类特征支持
    • 使用堆叠组合预测因子
    • 比较随机森林和柱状图梯度增强模型
    • 比较随机森林和多输出Meta估计量
    • 使用AdaBoost进行决策树回归
    • Gradient Boosting中的提前停止
    • 树木森林的重要性
    • 使用树木集合的特征转换
    • 梯度增强树的梯度中的功能
    • 梯度提升袋外估计
    • 梯度增强回归
    • 梯度提升正规化
    • 使用完全随机树哈希特征转换
    • 隔离森林示例
    • 单调约束
    • 多类AdaBoosted决策树
    • 随机森林的OOB错误
    • 绘制个人和投票回归预测图
    • 在虹膜数据集中绘制树木集合的决策面
    • 梯度Boosting回归的预测区间
    • 单一估计量与装袋:偏差方差分解
    • 两级AdaBoost
    • 可视化VotingClassifier的概率预测
  • 基于现实世界数据集的示例
    • 压缩感知:使用L1先验进行断层扫描重建(Lasso)
    • 使用特征脸和SVM的人脸识别示例
    • 基于核主成分分析的图像去噪
    • 时间序列预测的滞后特征
    • 模型复杂性影响
    • 真实数据集上的离群值检测
    • 预测延迟
    • 物种分布建模
    • 与时间相关的特征工程
    • 非负矩阵分解和潜在Dirichlet分配的主题提取
    • 股票市场结构可视化
    • 维基百科主特征量
  • 特征选择
    • F检验与互信息的比较
    • 基于模型的顺序特征选择
    • 流水线ANOVA SVM
    • 递归特征消除
    • 单变量特征选择
  • 冻结的估计者
    • 例子利用 FrozenEstimator
  • 高斯混合模型
    • 变异Bayesian高斯混合物的浓度先验型分析
    • 高斯混合物的密度估计
    • GMM检查方法
    • GMM协方差
    • 高斯混合模型椭圆体
    • 高斯混合模型选择
    • 高斯混合模型顺曲线
  • 机器学习的高斯过程
    • 高斯过程回归(GPT)估计数据噪音水平的能力
    • 核岭回归与高斯过程回归的比较
    • 使用高斯过程回归(GPT)预测Mona Loa数据集的二氧化碳水平
    • 高斯过程回归:基本入门示例
    • 虹膜数据集上的高斯过程分类(GSK)
    • 离散数据结构上的高斯过程
    • 异或数据集上的高斯过程分类(GSK)插图
    • 不同核的先验和后验高斯过程的说明
    • 高斯过程分类(GSK)的等概率线
    • 利用高斯过程分类(GSK)进行概率预测
  • 广义线性模型
    • 比较线性Bayesian回归量
    • 利用Bayesian Ridge回归进行曲线匹配
    • 多项和一对二回归的决策边界
    • 随机梯度下降的早停止
    • 使用预先计算的格拉姆矩阵和加权样本来匹配弹性网络
    • HuberRegressor与Ridge在具有强异常值的数据集上
    • 使用多任务Lasso进行联合特征选择
    • 逻辑回归中的L1罚分和稀疏性
    • 基于L1的稀疏信号模型
    • 通过信息标准选择Lasso模型
    • Lasso型号选择:AIC-BIC /交叉验证
    • 密集和稀疏数据上的套索
    • Lasso、Lasso-LARS和Elastic Net路径
    • 逻辑函数
    • 使用多项逻辑+ L1的MNIST分类
    • 20个新群体的多类稀疏逻辑回归
    • 非负最小平方
    • 使用随机梯度下降的一类支持者与一类支持者
    • 普通最小二乘和岭回归
    • 正交匹配追踪
    • 绘制岭系数作为正规化的函数
    • 在iris数据集上绘制多类新元
    • 多项和样条插值
    • 分位数回归
    • L1-逻辑回归的正规化路径
    • 作为L2正则化函数的脊系数
    • 鲁棒线性估计量匹配
    • 使用RASAC的鲁棒线性模型估计
    • 新元:分离超平面的最大裕度
    • 新元:处罚
    • 新元:加权样本
    • 新元:凸损失函数
    • Theil-Sen回归
  • 检查
    • 线性模型系数解释中的常见陷阱
    • 机器学习无法推断因果效应
    • 排列重要性与随机森林特征重要性(Millennium)
    • 具有多重共线或相关特征的排列重要性
  • 核近似
    • 具有多项核逼近的可扩展学习
  • 流形学习
    • 多种学习方法的比较
    • 分割球面上的流形学习方法
    • 手写数字的流形学习:局部线性嵌入,Isomap...
    • 多维标度
    • 瑞士滚动和瑞士洞减少
    • t-SNE:各种困惑度值对形状的影响
  • 杂项
    • 部分相关的高级绘图
    • 比较玩具数据集异常值检测的异常检测算法
    • 核岭回归与SVR的比较
    • 管道
    • NPS估计器和复杂管道
    • 基于RBS核的显式特征图逼近
    • 使用多输出估计器完成面
    • 介绍 set_output API
    • 保序回归
    • 元数据路由
    • 多标签分类
    • 随机投影嵌入的Johnson-Lindenstrauss界
    • 具有显示对象的可视化
  • 缺失值插补
    • 在构建估计器之前输入缺失值
    • 使用IterativeImputer的变体输入缺失值
  • 模型选择
    • 平衡模型复杂性和交叉验证分数
    • 比较随机搜索和网格搜索用于超参数估计
    • 网格搜索和连续减半的比较
    • 混淆矩阵
    • 具有交叉验证的网格搜索自定义改装策略
    • 基于cross_val_score和GridSearchCV的多度量评估演示
    • 模型正规化对训练和测试误差的影响
    • 嵌套与非嵌套交叉验证
    • 绘制交叉验证预测
    • 绘制学习曲线并检查模型的可扩展性
    • 事后调整决策函数的截止点
    • pr曲线
    • 具有交叉验证的接收器工作特性(ROC)
    • 用于文本特征提取和评估的样本管道
    • 使用网格搜索进行模型统计比较
    • 连续减半迭代
    • 用排列测试分类分数的重要性
    • 不足与过度贴合
    • 在scikit-learn中可视化交叉验证行为
  • 多类方法
    • 多类训练元估计器概述
  • 多输出方法
    • 使用分类器链的多标签分类
  • 最近邻居
    • TSNE的大约最近邻居
    • 缓存最近的邻居
    • 比较使用和不使用邻居成分分析的最近邻居
    • 利用邻居成分分析降低维度
    • 物种分布的核密度估计
    • 核密度估计
    • 最近的重心分类
    • 最近邻分类
    • 最近邻回归
    • 邻里要素分析插图
    • 利用本地异常值因子(LOF)进行新颖性检测
    • 使用局部离群因子(LOF)进行离群点检测
    • 简单的1D核密度估计
  • 神经网络
    • 比较MLP分类器的随机学习策略
    • 数字分类的受限制Boltzmann Machine功能
    • 多层感知器中的变化规则化
    • MNIST上MLP权重的可视化
  • 管道和复合估计量
    • 具有异类数据源的列Transformer
    • 混合类型的列Transformer
    • 级联多种特征提取方法
    • 回归模型中目标转换的效果
    • 流水线:链接PCA和逻辑回归
    • 使用Pipeline和GridSearchCV选择降维
  • 预处理
    • 比较不同缩放器对数据的影响与离群值
    • 比较目标编码器与其他编码器
    • 特征缩放的重要性
    • 将数据映射到正态分布
    • 目标编码器的内部交叉拟合
  • 半监督分类
    • Iris数据集上半监督分类器与支持机的决策边界
    • 不同阈值对自我训练的影响
    • 标签传播圈:学习复杂结构
    • 标签传播数字:主动学习
    • 标签传播数字:展示性能
    • 文本数据集的半监督分类
  • 支持向量机
    • 带非线性核(RBS)的一类支持者
    • 具有不同支持机核的地块分类边界
    • 在虹膜数据集中绘制不同的支持者分类器
    • 在LinearSRC中绘制支持载体
    • RBF SVM参数
    • 支持机边缘示例
    • SV打破平局示例
    • 具有自定义内核的支持者
    • SVM-Anova:具有单变量特征选择的支持者
    • SV:最大裕度分离超平面
    • 支持者:分离不平衡类别的超平面
    • 支持者:加权样本
    • 缩放SVCs的正规化参数
    • 使用线性和非线性核的支持量回归(SVR)
  • 使用文本文档
    • 使用稀疏特征对文本文档进行分类
    • 基于k-means的文本聚类
    • 收件箱Hasher和DictVectorizer比较
  • 示例
  • 基于现实世界数据集的示例
  • 与时间相关的特征工程

备注

Go to the end 下载完整的示例代码。或者通过浏览器中的MysterLite或Binder运行此示例

与时间相关的特征工程#

该笔记本电脑引入了不同的策略,以利用时间相关功能来执行高度依赖于业务周期(天、周、月)和年度季节周期的自行车共享需求回归任务。

在此过程中,我们介绍了如何使用 sklearn.preprocessing.SplineTransformer 阶级及其 extrapolation="periodic" 选项.

# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause

自行车共享需求数据集的数据探索#

我们首先从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",
)
Average hourly bike demand during the week

预测问题的目标是按小时计算的自行车租赁的绝对数量:

df["count"].max()
np.int64(977)

让我们重新调整目标变量(每小时自行车租赁数量)来预测相对需求,以便平均绝对误差更容易解释为最大需求的一小部分。

备注

本笔记本中使用的模型的匹配方法都最小化了估计条件平均值的均方误差。然而,绝对误差将估计条件中位数。

然而,当在讨论中报告测试集的性能指标时,我们选择关注平均绝对误差而不是(根)均方误差,因为它更直观地解释。然而,请注意,在这项研究中,一个指标的最佳模型也是另一个指标的最佳模型。

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",
)
plot cyclical feature engineering

输入特征数据帧是描述天气条件的变量的时间注释每小时日志。它包括数字和类别变量。请注意,时间信息已经扩展为几个补充列。

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

由于只有3个 "heavy_rain" 事件,我们不能使用此类别来训练具有交叉验证的机器学习模型。相反,我们通过将它们折叠到 "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天的间隔。我们还限制训练集大小,以使CV折叠的性能更加稳定。

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" 使得具有类别d类型的特征被认为是类别特征。作为参考,我们根据dype从金字塔中提取分类特征。内部树为这些功能使用专用的树拆分规则。

数值变量不需要预处理,为了简单起见,我们只尝试这个模型的默认超参数:

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=model_prop is not None,
    )
    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}"
    )


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

我们看到我们设置了 max_iter 大到足以提前停止。

该模型的平均误差约为最大需求的4%至5%。这对于没有任何超参数调整的第一次试验来说非常好!我们只需要明确分类变量即可。请注意,与时间相关的特征按原样传递,即不处理它们。但对于基于树的模型来说,这不是什么大问题,因为它们可以学习有序输入特征和目标之间的非单调关系。

线性回归模型的情况并非如此,正如我们将在下文中看到的那样。

天真的线性回归#

与线性模型一样,类别变量需要进行一热编码。为了保持一致性,我们使用 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(),
    ),
    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

看到被选中的人是肯定的 alpha_ 在我们的规定范围内。

性能不佳:平均误差在最大需求的14%左右。这比梯度增强模型的平均误差高出三倍多。我们可以怀疑,周期性时间相关特征的原始编码(只是最小-最大缩放)可能会阻止线性回归模型正确利用时间信息:线性回归不会自动建模输入特征和目标之间的非单调关系。必须在输入中设计非线性项。

例如, "hour" 该功能阻止线性模型认识到,早上从6点增加到8点应该会对自行车租赁数量产生强烈的积极影响,而晚上从18点增加到20点类似幅度的增加应该会对自行车租赁的预测数量产生强烈的负面影响。

时间步骤类别#

由于时间特征是以离散的方式编码的(“小时”特征中有24个唯一值),因此我们可以决定使用一热编码将这些特征视为分类变量,从而忽略小时值的顺序所暗示的任何假设。

对时间特征使用一次性编码为线性模型提供了更大的灵活性,因为我们在每个离散时间级别引入了一个额外的特征。

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(),
    ),
    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

该模型的平均错误率为10%,比使用时间特征的原始(有序)编码要好得多,这证实了我们的直觉,即线性回归模型受益于额外的灵活性,不会以单调的方式处理时间进程。

然而,这引入了大量的新功能。如果一天中的时间用一天开始后的分钟而不是小时来表示,那么一次性编码将引入1440个特征而不是24个特征。这可能会导致一些显着的过度装配。为了避免这种情况,我们可以使用 sklearn.preprocessing.KBinsDiscretizer 而是重新分类细粒度有序或数字变量的级别数量,同时仍然受益于一热编码的非单调表达优势。

最后,我们还观察到,单热编码完全忽略了小时级别的顺序,而这可能是一个有趣的归纳偏差,需要保留到一定程度。在下文中,我们尝试探索局部保留时间特征相对顺序的平滑、非单调编码。

三角特征#

作为第一次尝试,我们可以尝试使用具有匹配周期的sin和cos变换来编码这些周期特征中的每一个。

每个有序时间特征被转换成2个特征,这2个特征以非单调的方式一起编码等效信息,更重要的是,在周期范围的第一个值和最后一个值之间没有任何跳跃。

from sklearn.preprocessing import FunctionTransformer


def sin_transformer(period):
    return FunctionTransformer(lambda x: np.sin(x / period * 2 * np.pi))


def cos_transformer(period):
    return FunctionTransformer(lambda x: np.cos(x / period * 2 * np.pi))

让我们通过对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")
Trigonometric encoding for the 'hour' feature

让我们使用将小时编码为颜色的2D散点图,以更好地了解这种表示如何将一天中的24小时映射到2D空间,类似于某种24小时版本的模拟时钟。请注意,由于sin/cos表示的周期性,“第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)
plot cyclical feature engineering

我们现在可以使用以下策略构建特征提取管道:

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(),
)
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

采用这种简单特征工程的线性回归模型的性能比使用原始有序时间特征好一些,但比使用单一热编码时间特征差一些。我们将在本笔记本的最后进一步分析造成这一令人失望结果的可能原因。

周期样条曲线特征#

我们可以尝试使用具有足够多样条线数量的样条线变换来对周期性时间相关特征进行替代编码,因此与sin/cos变换相比,扩展特征数量更多:

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,
    )

再次,让我们通过小时=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")
Periodic spline-based encoding for the 'hour' feature

由于使用了 extrapolation="periodic" 参数,我们观察到当外推到午夜之后时,特征编码保持平滑。

我们现在可以使用这种替代周期性特征工程策略构建预测管道。

对于这些有序值,可以使用比离散级别更少的样条线。这使得基于样条曲线的编码比单热编码更有效,同时保留了大部分表现力:

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(),
)
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

样条曲线特征使线性模型能够成功利用周期性时间相关特征,并将误差从最大需求的~14%降低到~10%,这与我们观察到的一次性编码特征类似。

特征对线性模型预测影响的定性分析#

在这里,我们想要可视化特征工程选择对预测的时间相关形状的影响。

为了做到这一点,我们考虑一个任意的基于时间的分割,以比较一系列数据点上的预测。

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()
Predictions by linear models

从上面的图中我们可以得出以下结论:

  • 的 raw ordinal time-related features 存在问题,因为它们没有捕捉到自然周期性:当小时特征从23回到0时,我们观察到每天结束时,预测会出现大幅跳跃。我们可以期待在每周或每年结束时都会出现类似的文物。

  • 果然那 trigonometric features (sine和cos)在午夜时不存在这些不连续性,但线性回归模型未能利用这些特征来正确地建模日内变化。使用高次次

  • 的 periodic spline-based features 一次解决这两个问题:通过使用12个样条线,它们可以专注于特定的时间,从而为线性模型提供了更多的表达力。此外 extrapolation="periodic" 选项强制实现之间的平滑表示 hour=23 和 hour=0 .

  • 的 one-hot encoded features 其行为类似于基于样条曲线的周期性特征,但更尖锐:例如,它们可以更好地模拟工作日的早晨峰值,因为该峰值持续时间短于一小时。然而,我们将在下文中看到,线性模型的优势不一定是更具表达力的模型的优势。

我们还可以比较每个特征工程管道提取的特征数量:

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编码和样条编码策略为时间表示创建了比替代方案更多的特征,这反过来又为下游线性模型提供了更大的灵活性(自由度),以避免欠拟合。

最后,我们观察到,没有一个线性模型可以近似真实的自行车租赁需求,特别是对于工作日高峰时段可能非常尖锐但周末时段可能要平坦得多的高峰:基于样条线或单热编码的最准确线性模型往往会预测与交通相关的自行车租赁的峰值,甚至在周末,并低估了通勤-工作日期间的相关事件。

这些系统性预测错误揭示了一种欠拟的形式,可以通过特征(例如“工作日”和源自“小时”的特征)之间缺乏交互项来解释。此问题将在下一节中解决。

利用样条和多项特征建模成对相互作用#

线性模型不会自动捕获输入要素之间的交互效应。某些要素是边缘非线性的,这并没有帮助,例如由 SplineTransformer (or一次性编码或分类)。

但是,可以使用 PolynomialFeatures 粗粒度样条编码小时的类,以显式地建模“工作日”/“小时”交互作用,而不会引入太多新变量:

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"), ["workingday"]),
        ]
    ),
    PolynomialFeatures(degree=2, interaction_only=True, include_bias=False),
)

然后将这些特征与之前的样条曲线基管道中已经计算的特征相结合。通过显式地建模这种成对交互,我们可以观察到良好的性能改进:

cyclic_spline_interactions_pipeline = make_pipeline(
    FeatureUnion(
        [
            ("marginal", cyclic_spline_transformer),
            ("interactions", hour_workday_interaction),
        ]
    ),
    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

建模与核的非线性特征交互#

之前的分析强调了对之间的相互作用进行建模的必要性 "workingday" 和 "hours" .我们想要建模的这种非线性相互作用的另一个例子可能是降雨的影响,例如,在工作日、周末和假期期间,降雨的影响可能不一样。

为了对所有此类相互作用进行建模,我们可以在基于样条曲线的扩展之后,同时对所有边缘特征使用一次多项扩展。然而,这会创建二次数量的特征,这可能会导致过拟和计算可处理性问题。

或者,我们可以使用Nyström方法来计算近似的多项核展开。让我们尝试后者:

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

我们观察到,该模型几乎可以与梯度增强树的性能相媲美,平均误差约为最大需求的5%。

请注意,虽然该管道的最后一步是线性回归模型,但中间步骤(如样条特征提取和Nyström核近似)是高度非线性的。因此,复合管道比具有原始特征的简单线性回归模型更具表现力。

为了完整性起见,我们还评估了一热编码和内核逼近的组合:

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",
    ),
    Nystroem(kernel="poly", degree=2, n_components=300, random_state=0),
    RidgeCV(alphas=alphas),
)
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

虽然在使用线性模型时,一次性编码特征与基于样条曲线的特征具有竞争力,但在使用非线性核的低阶逼近时情况不再是这样:这可以通过样条曲线特征更平滑并允许核逼近来找到更具表达力的决策函数这一事实来解释。

现在让我们定性地看看核模型和梯度增强树的预测,这些树应该能够更好地建模特征之间的非线性相互作用:

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()
Predictions by non-linear regression models

首先,请注意,树可以自然地对非线性特征交互进行建模,因为默认情况下,决策树允许生长超过2个级别的深度。

在这里,我们可以观察到样条特征和非线性核的组合效果非常好,几乎可以与梯度增强回归树的准确性相媲美。

相反,单热编码时间特征在低等级核模型中表现不佳。特别是,它们比竞争车型明显高估了低需求时数。

我们还观察到,没有一个模型能够成功预测工作日高峰时段的一些峰值租金。可能需要访问其他功能才能进一步提高预测的准确性。例如,在任何时间点访问车队的地理重新划分或因需要维修而无法移动的自行车比例可能很有用。

最后,让我们使用真实需求与预测需求散点图来更定量地了解这三个模型的预测误差:

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()
Non-linear regression models

这个可视化证实了我们根据之前的情节得出的结论。

所有模型都低估了高需求事件(工作日高峰时间),但梯度提升的影响较小。平均而言,通过梯度提升可以很好地预测低需求事件,而单一的多元回归管道似乎系统性地高估了该制度下的需求。总体而言,梯度增强树的预测比核心模型更接近对角线。

总结发言#

我们注意到,通过使用更多组件(更高等级的核逼近),我们本可以以更长的适应和预测持续时间为代价,为核模型获得稍好的结果。对于较大的 n_components ,单热编码特征的性能甚至会匹配样条线特征。

的 Nystroem + RidgeCV 回归量也可以被替换为 MLPRegressor 有一两个隐藏层,我们会得到非常相似的结果。

我们在本案例研究中使用的数据集是以小时为基础进行采样的。然而,基于循环样条的特征可以通过更细粒度的时间分辨率(例如,每分钟而不是每小时进行测量)非常有效地对一天内的时间或一周内的时间进行建模,而无需引入更多的特征。独热编码时间表示将不提供这种灵活性。

最后,在我们使用的这个笔记本中 RidgeCV 因为从计算的角度来看,它非常高效。然而,它将目标变量建模为具有恒定方差的高斯随机变量。对于正回归问题,使用Poisson或Gamma分布可能更有意义。这可以通过使用来实现 GridSearchCV(TweedieRegressor(power=2), param_grid({"alpha": alphas})) 而不是 RidgeCV .

Total running time of the script: (0分15.488秒)

发布手册
推出FriendyterLite

Download Jupyter notebook: plot_cyclical_feature_engineering.ipynb

Download Python source code: plot_cyclical_feature_engineering.py

Download zipped: plot_cyclical_feature_engineering.zip

相关实例

时间序列预测的滞后特征

Lagged features for time series forecasting

梯度提升中的分类特征支持

Categorical Feature Support in Gradient Boosting

多项和样条插值

Polynomial and Spline interpolation

比较目标编码器与其他编码器

Comparing Target Encoder with Other Encoders

Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io> _

上一页

物种分布建模

下一页

非负矩阵分解和潜在Dirichlet分配的主题提取

On this page
  • 自行车共享需求数据集的数据探索
  • 基于时间的交叉验证
  • 梯度提升
  • 天真的线性回归
  • 时间步骤类别
  • 三角特征
  • 周期样条曲线特征
  • 特征对线性模型预测影响的定性分析
  • 利用样条和多项特征建模成对相互作用
  • 建模与核的非线性特征交互
  • 总结发言
Download source code
Download Jupyter notebook
Download zipped
推出FriendyterLite
发布手册

© Copyright 2007 - 2025, scikit-learn developers (BSD License).