ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python随机森林时间序列预测实战:滑动窗口与特征工程完整源码

Python随机森林时间序列预测实战:滑动窗口与特征工程完整源码 简介本资源面向计算机、电子信息工程、数学等专业的大学生及算法入门者提供一套基于Python的随机森林RF时间序列预测完整实现方案可用于课程设计、期末大作业与毕业设计等场景。压缩包共3个文件包含1个py源码与2个csv数据集整体约46KB源码采用参数化编程参数可灵活调整并配有保姆级注释几乎一行一注释便于零基础读者理解建模思路与代码逻辑。数据集可直接用于训练与验证帮助读者快速跑通从数据读取、特征构造到模型训练与预测的完整流程。作者为某大厂资深算法工程师具备八年Matlab与Python算法仿真经验擅长智能优化算法、神经网络预测与信号处理等方向。目前已有193人学习下载适合希望以最小成本掌握RF时间序列预测实战方法、并可直接复用到自身课题中的读者参考。1. 拿到这份 RF 时间序列预测源码先别急着跑时间序列预测这件事很多人第一反应是上 LSTM、Transformer觉得模型越深越准。但真到了工业场景——比如设备传感器读数、门店日销、电力负荷——你会发现随机森林Random Forest简称 RF这种老派方法经常把深度学习按在地上摩擦。原因不复杂样本量往往只有几百到几千条特征工程做得好树模型对噪声和非平稳性的鲁棒性反而更强训练还快调参也不玄学。这份资源就是围绕用 Python 实现 RF 时间序列预测打包的完整源码加数据核心是把单变量时间序列通过滑动窗口重构成监督学习问题再喂给随机森林做回归。它适合两类人一类是刚学完sklearn想找个完整项目练手的另一类是手头有业务时序数据、想快速搭个能上线的基线模型的从业者。下面我按数据怎么变成特征 → 模型怎么搭 → 坑在哪 → 怎么验证的顺序拆一遍代码可以直接抄。2. 把时间序列转成监督学习样本滑动窗口与滞后特征2.1 为什么 RF 不能直接吃时间序列随机森林本质上是一个回归器它要求输入是(n_samples, n_features)的二维表格每一行是一个独立样本每一列是一个特征。而原始时间序列是一维的、有序的、前后依赖的。你直接把一列数值丢进去模型学到的只是这个值大概在什么范围完全丢失了时间维度的信息。所以第一步必须做重构用过去 N 个时刻的值预测下一个时刻的值。这就是滑动窗口sliding window的思路。假设窗口长度lag3序列是[10, 12, 11, 13, 14]那么重构后就是特征1(t-3)特征2(t-2)特征3(t-1)目标(t)1012111312111314这样每一行就成了一个独立样本RF 就能正常训练了。窗口长度lag是最关键的参数它决定了模型能看到多长的历史。2.2 重构代码与参数说明import numpy as np import pandas as pd def series_to_supervised(data, n_in1, n_out1, dropnanTrue): 将时间序列重构为监督学习格式 data: 一维或二维数组形状 (n_timesteps, n_features) n_in: 用过去多少个时刻作为输入特征 n_out: 预测未来多少个时刻 dropnan: 是否删除因移位产生的 NaN 行 df pd.DataFrame(data) cols [] # 生成滞后特征列名如 var1(t-1), var1(t-2) for i in range(n_in, 0, -1): cols.append(df.shift(i)) # 生成预测目标列 for i in range(0, n_out): cols.append(df.shift(-i)) agg pd.concat(cols, axis1) if dropnan: agg.dropna(inplaceTrue) return agg.values # 示例单变量序列用过去 3 个点预测当前点 raw np.array([10, 12, 11, 13, 14, 15, 14, 16, 18, 17], dtypefloat).reshape(-1, 1) supervised series_to_supervised(raw, n_in3, n_out1) print(supervised)这段代码的逻辑是shift(i)把序列整体下移 i 位shift(-i)上移 i 位。n_in3时前三列分别是 t-3、t-2、t-1 的值最后一列是 t 的值。dropnanTrue会删掉因为移位产生空值的头尾行。参数怎么定n_in一般取 3 到 20取决于你的数据周期性和采样频率。日数据可以试 7 或 14小时数据可以试 24 或 48。n_out1是最常见的单步预测如果要预测未来多步把n_out调大但注意目标列会变成多列RF 需要改成多输出或分别建模。提示n_in不是越大越好。窗口太长会引入过多噪声特征还会让有效样本数减少因为要 drop 掉更多行小数据集上尤其明显。2.3 训练集/测试集切分的时序陷阱时间序列绝对不能随机打乱切分这是血泪经验。随机切分会让未来的数据混进训练集造成数据泄露测试集指标虚高上线就翻车。正确做法是按时间顺序切from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error # 假设 supervised 已经生成最后一列是目标 X supervised[:, :-1] y supervised[:, -1] # 按 80/20 顺序切分不 shuffle split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] model RandomForestRegressor( n_estimators200, # 树的数量越多越稳但越慢 max_depthNone, # 不限制深度让树充分生长 min_samples_leaf2, # 叶节点最少样本防止过拟合 random_state42, n_jobs-1 # 用满所有 CPU 核心 ) model.fit(X_train, y_train) pred model.predict(X_test) print(MAE:, mean_absolute_error(y_test, pred)) print(RMSE:, np.sqrt(mean_squared_error(y_test, pred)))n_estimators从 100 起步200 到 500 通常够用再往上收益递减。min_samples_leaf是防过拟合的关键小数据集建议设 2 到 5。n_jobs-1让训练并行化大数据集上能省不少时间。3. 特征工程与模型调参让 RF 真正发挥威力3.1 纯滞后特征不够得加滚动统计量只用原始滞后值RF 能学到的模式有限。实践中我一般会额外构造几类特征滚动均值、滚动标准差、差分值、以及时间戳衍生特征星期几、月份、是否节假日。这些特征能把趋势和季节性显式地喂给模型。def add_rolling_features(df, col, windows[3, 7, 14]): 为指定列添加滚动均值和滚动标准差特征 windows: 滚动窗口大小列表 for w in windows: df[f{col}_roll_mean_{w}] df[col].rolling(windoww).mean() df[f{col}_roll_std_{w}] df[col].rolling(windoww).std() # 一阶差分捕捉变化趋势 df[f{col}_diff_1] df[col].diff(1) return df # 假设原始数据是 DataFrame列为 value # df add_rolling_features(df, value) # 注意滚动特征会产生 NaN需要和滑动窗口重构一起 dropna滚动窗口大小要和业务周期匹配。日销数据用 7一周和 30一月传感器数据看采样间隔。差分特征对非平稳序列特别有用它把绝对值预测变成变化量预测RF 更容易学到规律。3.2 用时间序列交叉验证代替单次切分单次 80/20 切分的评估结果波动很大换一个切点指标就变。更稳的做法是前向滚动验证walk-forward validation每次用前面一段训练预测紧接着的一段然后窗口向前滑动。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) scores [] for train_idx, test_idx in tscv.split(X): X_tr, X_te X[train_idx], X[test_idx] y_tr, y_te y[train_idx], y[test_idx] m RandomForestRegressor(n_estimators200, min_samples_leaf2, random_state42, n_jobs-1) m.fit(X_tr, y_tr) p m.predict(X_te) scores.append(mean_absolute_error(y_te, p)) print(各折 MAE:, scores) print(平均 MAE:, np.mean(scores))TimeSeriesSplit保证每次训练集都在测试集之前不会泄露未来信息。n_splits5表示切 5 折数据量小可以设 3。看各折分数的方差比看平均值更重要——方差大说明模型不稳定换个时间段就失效。3.3 调参优先级先min_samples_leaf再max_featuresRF 的超参数里对时间序列预测影响最大的是min_samples_leaf和max_features。max_depth一般不用限制让树长满靠叶节点样本数控制复杂度。max_features控制每棵树分裂时考虑的特征比例默认是1.0全部特征特征多的时候可以降到0.5或sqrt。参数作用小数据集建议大数据集建议n_estimators树的数量100~300300~800min_samples_leaf叶节点最小样本2~51~3max_features分裂特征比例1.00.5~1.0max_depth树最大深度NoneNone 或 20~30调参用GridSearchCV配合TimeSeriesSplit但要注意计算量。我一般先粗调min_samples_leaf试 1、2、3、5再调max_features试 0.5、0.8、1.0n_estimators最后加。4. 避坑与排查RF 时序预测最容易翻车的五个地方4.1 指标好看但预测曲线滞后一截现象MAE 很小但把预测值和真实值画在一起预测曲线整体比真实曲线晚一个时间步像追着跑。原因这是滞后特征建模的固有特性。模型学到的最强信号就是上一时刻的值当序列变化剧烈时它只能用历史值去逼近天然滞后。如果n_in太小或差分特征缺失滞后会更明显。解决加入差分特征或让模型预测变化量而非绝对值增大n_in让模型看到更长历史对强趋势序列先做差分再建模预测后再还原。4.2 训练集 R² 接近 1测试集一塌糊涂现象训练集上拟合完美测试集 MAE 是训练集的几倍。原因要么是随机切分导致数据泄露要么是min_samples_leaf1让每棵树记住了噪声。时间序列的样本间相关性高过拟合特别隐蔽。解决强制用TimeSeriesSplit把min_samples_leaf提到 2 以上减少n_in砍掉冗余滞后特征检查是否有未来信息混入特征比如用了全局均值编码。4.3 滚动特征引入 NaN 导致样本大量丢失现象加了rolling(30)之后训练样本从几千条掉到几百条。原因滚动窗口前 29 行是 NaNdropna把它们全删了。窗口越大损失越多。解决小数据集别用大窗口或者用min_periods参数允许部分计算也可以对 NaN 做前向填充但要评估填充是否引入偏差。4.4 多步预测直接套单步模型现象想预测未来 7 天把n_out7结果模型输出一列根本对不上。原因series_to_supervised在n_out1时目标列会变成多列但RandomForestRegressor只接受单列目标。直接跑会报维度错误或静默出错。解决多步预测要么用MultiOutputRegressor包一层要么对每个步长单独建一个模型直接多步策略要么用递归预测预测一步把结果当输入再预测下一步。递归策略简单但误差会累积直接多步更稳但计算量大。4.5 特征重要性全是滞后 1其他特征没用现象feature_importances_显示t-1占了 90% 以上其他特征几乎为零。原因时间序列自相关性极强t-1和t的相关性最高树模型自然优先用它。这不一定是坏事但说明其他特征没提供增量信息。解决如果业务上需要模型对突变更敏感可以尝试去掉t-1强制模型用更早的滞后和滚动特征或者对目标做差分让模型学变化量而非绝对值。5. 验证与进阶残差诊断和特征重要性落地模型跑通只是开始真正决定能不能上线的是验证环节。我习惯在预测完成后做两件事画残差图和看特征重要性排序。残差图能暴露很多问题。如果残差围绕零随机分布说明模型没有系统性偏差如果残差呈现周期性波动说明还有季节性没被提取如果残差方差随时间增大说明序列存在异方差可能需要对数变换。import matplotlib.pyplot as plt residuals y_test - pred fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].plot(residuals) axes[0].set_title(Residuals over time) axes[0].axhline(0, colorr, linestyle--) axes[1].hist(residuals, bins30) axes[1].set_title(Residual distribution) plt.tight_layout() plt.show() # 特征重要性 importances model.feature_importances_ for i, imp in enumerate(sorted(importances, reverseTrue)[:10]): print(fTop {i1} importance: {imp:.4f})特征重要性排序要和业务理解对照。如果排第一的是某个你根本没预期的特征得查查是不是数据泄露如果所有重要性都很平均说明特征冗余度高可以精简。另一个实用技巧是预测区间。RF 本身不给置信区间但可以用每棵树的预测值分布来近似让所有树分别预测取分位数。# 获取每棵树的预测 all_preds np.array([tree.predict(X_test) for tree in model.estimators_]) lower np.percentile(all_preds, 5, axis0) upper np.percentile(all_preds, 95, axis0) print(90% 预测区间宽度均值:, np.mean(upper - lower))这个区间不是严格的统计置信区间但能反映模型的不确定性——区间宽说明这棵树们意见分歧大预测不可靠业务上可以据此设置告警阈值。最后说个我自己的习惯每次跑完模型我都会把预测值、真实值、残差、预测区间拼成一张宽表存下来按时间排序。上线后拿这张表和新数据对比能快速定位是模型退化还是数据源出了问题。从那以后我每次做时序项目都强制走一遍残差诊断和特征重要性核对再急也不跳过。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进