拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python随机森林时间序列预测:从CSV到可复现结果

Python随机森林时间序列预测:从CSV到可复现结果 简介这份资源面向计算机、电子信息工程、数学等专业的大学生及算法入门者提供一套可直接运行的随机森林RF时间序列预测完整方案适用于课程设计、期末大作业与毕业设计等场景。压缩包共3个文件包含2个csv数据文件与1个py源码文件整体约46KB数据文件用于模型训练与验证源码文件承载核心预测逻辑。代码采用参数化编程参数可灵活调整编程思路清晰并配有保姆级注释几乎一行一注释便于零基础读者理解随机森林在时间序列预测中的建模流程与调参方法。作者为某大厂资深算法工程师具备八年Matlab与Python算法仿真经验擅长智能优化算法、神经网络预测与信号处理等方向。目前已有193人学习下载读者可借此掌握从数据读取、特征构造到模型训练与结果输出的完整链路并在此基础上迁移至自己的数据集开展实验。1. 用 Python 跑通 RF 时间序列预测从一份 CSV 到可复现的预测结果手上只有一份按时间排列的 CSV字段是日期加一个数值列想预测未来 30 天走势又不想一上来就搭 LSTM 调参调到怀疑人生——这是我被问得最多的一类需求。随机森林Random Forest简称 RF在这里是个被低估的起点它不假设线性、不要求平稳、对缺失和异常有一定容忍度训练几秒出结果特征工程做对了精度并不难看。这篇笔记就围绕「Python 实现 RF 时间序列预测完整源码和数据」这个方向把数据长什么样、特征怎么造、模型怎么训、预测怎么回推成日期一步步写清楚。适合会一点 Python、能跑通 pandas 和 sklearn、但没系统做过时间序列的从业者也适合想给 LSTM 找个靠谱 baseline 的人。RF 本身是监督学习它不认识「时间」这个概念你喂它一行特征、它吐一个值。所以时间序列用 RF 的核心动作只有一个把一条时间轴切成「用过去 N 个点预测下一个点」的监督样本。这一步做对了后面全是常规的回归流程这一步做错了模型精度再高也是自欺欺人。下面从数据准备讲到滚动预测中间穿插我踩过的坑源码和数据按这个结构自己就能拼出来。2. 数据准备与 RF 做时间序列的底层逻辑2.1 为什么 RF 能用在时间序列上时间序列预测的传统路线是 ARIMA 那一套靠自相关和差分把序列变成平稳过程再建模。RF 走的是另一条路它不建模序列的统计结构而是把「预测」当成一个普通回归问题输入是一组历史特征输出是目标值。只要你能把历史信息整理成特征列RF 就能学。这带来两个直接好处。第一非线性关系它能抓比如销量在周末突然翻倍、温度低于某个阈值后能耗陡增这类分段、阈值型模式树模型天然擅长。第二多变量扩展容易除了历史滞后值你还能塞进星期几、月份、节假日标记、外部变量RF 对特征尺度不敏感不用标准化。代价也要说清楚。RF 的输出是叶子节点均值的平均预测值会被「拉平」对趋势的 extrapolation 能力很弱——它没见过比训练集更高的值就基本预测不出新高。所以纯 RF 适合做短期、有周期性、波动范围稳定的预测要做长期带明显趋势的预测得先差分或者去趋势把残差交给 RF。2.2 数据格式与最小数据集一份能直接跑的时间序列数据最少两列一列时间戳一列数值。时间戳要能解析成 datetime数值列不能有非数字字符。常见的坑是 CSV 里日期格式混乱、数值列混进了千分位逗号或者「-」占位符。import pandas as pd import numpy as np # 读取数据parse_dates 直接把时间列解析成 datetime df pd.read_csv(data.csv, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) # 检查缺失与异常 print(df.isna().sum()) print(df[value].describe()) # 数值列清洗去掉千分位、转 float df[value] ( df[value].astype(str) .str.replace(,, , regexFalse) .replace({-: np.nan, : np.nan}) .astype(float) ) # 按时间重采样到固定频率缺失用线性插值补 df df.set_index(date).asfreq(D) df[value] df[value].interpolate(methodlinear) df df.reset_index()这段代码做了四件事解析时间、排序、清洗数值、补齐时间频率。asfreq(D)是关键一步它把不规则的日期对齐到每天缺失的日期变成 NaN再用interpolate补上。如果跳过这步后面造滞后特征时「前一天」可能实际隔了三天特征就失真了。参数上asfreq的频率要和业务周期匹配日数据用D小时数据用h工作日数据用B。插值方法短缺口用linear长缺口或者有明显季节性的考虑用同期的值填充而不是线性插值。2.3 把时间序列转成监督学习样本这是整个流程的核心转换。假设用过去 7 个点预测第 8 个点就要构造 7 列滞后特征加 1 列目标。def make_supervised(series, n_lags): 把一维序列转成滞后特征矩阵 series: 一维数组 n_lags: 用过去多少个点做特征 df pd.DataFrame({y: series}) for i in range(1, n_lags 1): df[flag_{i}] df[y].shift(i) df df.dropna().reset_index(dropTrue) return df data make_supervised(df[value].values, n_lags7) print(data.head()) print(data.shape)shift(i)把序列往下挪 i 行lag_1就是前一天的值lag_7是七天前的值。dropna去掉因为 shift 产生的头部空行。n_lags的选择直接决定模型能看到多长的历史太小抓不到周期太大引入冗余和噪声。经验做法是先看自相关图ACF滞后阶数取自相关系数还比较显著的位置日数据带周周期n_lags至少给到 7 或 14。注意shift之后一定要dropna否则头部 NaN 会污染训练。另外别用shift(-1)当特征那是未来信息会造成数据泄露。3. 特征工程让 RF 从「能跑」到「能用」3.1 时间特征与周期编码光有滞后值RF 学不到「周末效应」和「月度规律」因为它不知道 lag_7 对应的是星期几。把时间维度拆成特征补进去是提升精度最划算的一步。def add_time_features(df, date_coldate): df df.copy() df[dayofweek] df[date_col].dt.dayofweek # 0周一 df[month] df[date_col].dt.month df[day] df[date_col].dt.day df[is_weekend] (df[dayofweek] 5).astype(int) # 周期性编码把 dayofweek 映射到圆上避免 6 和 0 被当成距离很远 df[dow_sin] np.sin(2 * np.pi * df[dayofweek] / 7) df[dow_cos] np.cos(2 * np.pi * df[dayofweek] / 7) return dfdayofweek是 0 到 6 的整数但周一和周日实际只差一天直接当数值喂给模型会误导。用 sin/cos 把它映射到单位圆上周期性就保住了。月份同理可以加month_sin、month_cos。is_weekend这种二值标记对树模型很友好分裂一次就能把周末单独切出来。3.2 滚动统计特征滞后值是「点」滚动统计是「段」。均值、标准差、最大最小能刻画近期水平和波动对突变型序列特别有用。def add_rolling_features(df, colvalue, windows(7, 14, 30)): df df.copy() for w in windows: df[froll_mean_{w}] df[col].shift(1).rolling(w).mean() df[froll_std_{w}] df[col].shift(1).rolling(w).std() df[froll_max_{w}] df[col].shift(1).rolling(w).max() df[froll_min_{w}] df[col].shift(1).rolling(w).min() return df关键在shift(1)先挪一天再算滚动保证当前行的统计量只用到昨天及以前的数据。如果忘了这个 shiftroll_mean_7里就包含了当天的值而当天值又和预测目标高度相关模型会「偷看答案」离线指标虚高上线就崩。这是时间序列特征工程里最隐蔽的泄露血泪经验。窗口选择上7 对应周周期14 和 30 覆盖中短期趋势。窗口越多特征越多但 RF 对冗余特征不敏感代价只是训练慢一点可以先全加再靠特征重要性筛。3.3 特征重要性筛选与差分处理特征堆到几十列后用 RF 自带的feature_importances_看哪些真正有用。from sklearn.ensemble import RandomForestRegressor X data.drop(columns[y]) y data[y] model RandomForestRegressor(n_estimators300, random_state42, n_jobs-1) model.fit(X, y) imp pd.Series(model.feature_importances_, indexX.columns).sort_values(ascendingFalse) print(imp.head(15))通常lag_1、lag_7、roll_mean_7会排在前列。如果发现某个时间特征重要性接近 0可以删掉减少噪声。但别一刀切只留前 5 个树模型靠特征组合工作删太狠反而掉精度一般保留累计重要性到 95% 的那批。如果序列有明显上升或下降趋势RF 预测会系统性偏低预测不出新高。处理办法是先对序列做一阶差分让 RF 学「变化量」预测完再累加回去。diff_series df[value].diff().dropna().values # 用 diff_series 走上面的 make_supervised 流程 # 预测出的是差分值最后用 cumsum 还原差分适合趋势强的数据纯周期波动数据不用差分直接建模更稳。4. 训练、验证与滚动预测的完整实现4.1 时间序列不能随机划分这是新手最容易翻车的地方。train_test_split默认随机打乱会把未来数据混进训练集验证指标好看得离谱实际预测一塌糊涂。时间序列必须按时间顺序切前面一段训练后面一段验证。def time_split(X, y, test_ratio0.2): n len(X) split int(n * (1 - test_ratio)) return X[:split], X[split:], y[:split], y[split:] X_train, X_test, y_train, y_test time_split(X, y, test_ratio0.2)切完之后训练集内部还可以做「滚动验证」用前 70% 训练、预测接下来 10%再往前滚多次评估取平均比单次切分更能反映稳定性。4.2 训练 RF 与关键参数from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error rf RandomForestRegressor( n_estimators500, # 树的数量越多越稳500 起步 max_depthNone, # 不限制深度让树充分生长 min_samples_leaf2, # 叶子最少样本防过拟合 max_features0.5, # 每次分裂考虑一半特征增加多样性 random_state42, n_jobs-1 ) rf.fit(X_train, y_train) pred rf.predict(X_test) mae mean_absolute_error(y_test, pred) rmse np.sqrt(mean_squared_error(y_test, pred)) print(fMAE{mae:.3f}, RMSE{rmse:.3f})参数逐个说。n_estimators是树的数量RF 的方差随树增多而下降500 到 1000 是常见区间再多收益递减。max_depth时间序列数据量通常不大不限制深度让树长满靠min_samples_leaf控制过拟合。min_samples_leaf设 2 到 5太小会记住噪声太大欠拟合。max_features回归任务默认是全部特征设成 0.5 或sqrt能提升树之间的差异性往往精度更好。评估指标上MAE 直观和原数据同量纲RMSE 对大误差更敏感。两个都看如果 RMSE 远大于 MAE说明存在个别预测偏差很大的点去查那些点对应的日期是不是有异常事件。4.3 递归滚动预测未来 N 步预测未来时没有真实的滞后值可用只能用模型自己的预测值回填这叫递归预测。def forecast(rf, history, n_steps, n_lags, feat_builder): history: 历史值列表 n_steps: 预测步数 feat_builder: 把历史窗口转成特征行的函数 preds [] buf list(history) for _ in range(n_steps): x_row feat_builder(buf, n_lags) y_hat rf.predict(x_row)[0] preds.append(y_hat) buf.append(y_hat) # 把预测值当真实值继续用 return preds递归预测的误差会累积第 1 步的小偏差进入第 2 步的特征逐步放大。所以步数越多预测越不可信一般控制在 30 步以内。如果要做更长改用「直接多步」策略——为每个预测步单独训一个模型用不同长度的历史窗口避免误差传递代价是训练 N 个模型。feat_builder要保证和训练时的特征顺序、列名完全一致否则predict会报错或者静默出错。稳妥做法是把训练时的列顺序存下来预测时按同样顺序重排。5. 避坑与排查RF 时间序列最常见的 5 个翻车点5.1 指标好到不真实上线就崩现象验证集 R² 0.98MAE 小得离谱实际预测完全不对。 原因特征里混入了未来信息。最常见的是滚动统计忘了shift(1)或者用了shift(-1)造特征或者随机划分导致未来数据进了训练集。 解决逐列检查特征生成逻辑凡是涉及「当前行」的统计量都要先 shift划分一律用时间顺序切分用「预测值只能依赖 t 时刻之前的数据」这条规则过一遍所有特征。5.2 预测曲线被拉平抓不到峰值现象预测值总在均值附近晃真实数据的尖峰一个都预测不出来。 原因RF 输出是叶子均值的平均天然平滑加上平方误差损失对极端值惩罚大模型倾向保守。 解决对目标做变换比如对数变换压缩峰值或者改用分位数回归RandomForestRegressor不支持可换GradientBoostingRegressor的 quantile loss也可以把「是否超过阈值」单独做成分类任务辅助。5.3 滞后阶数选错周期抓不住现象模型对周内规律毫无反应周一和周日预测值几乎一样。 原因n_lags太小比如只给了 3模型看不到 7 天前的信息或者时间特征没加模型不知道今天是星期几。 解决先画 ACF/PACF 确定显著滞后阶数日数据至少给到 7 或 14同时补上dayofweek的 sin/cos 编码让模型显式知道周期位置。5.4 缺失值处理不当插值引入假规律现象某段缺失用线性插值补完后模型在那段区间预测异常准其他区间却很差。 原因线性插值造出了一条完美直线模型学到了这个假模式但真实数据不是这样。 解决短缺口1-2 个点线性插值可以长缺口要么用同期均值填要么把缺失作为特征标记出来加一列is_missing让模型自己判断这段不可信。5.5 特征顺序在预测时错位现象训练时指标正常预测时结果离谱或者直接报维度错误。 原因训练用的 DataFrame 列顺序和预测时构造的不一致sklearn 按位置取特征顺序错了模型就乱套。 解决训练完把X.columns.tolist()存下来预测时用x_row x_row[train_columns]强制对齐或者干脆全程用 numpy 数组并固定列索引。6. 把 RF 当 baseline验证思路与一个提精度的技巧RF 最大的价值不是它精度最高而是它足够快、足够稳能给你一个「这条序列到底好不好预测」的基准线。我的习惯是任何时间序列任务先花二十分钟跑一个 RF baseline记下 MAE 和 RMSE再去上 LSTM、Transformer。如果复杂模型比 RF 只好一点点那多半是数据本身噪声大换模型解决不了问题该回去查数据质量。验证 RF 结果是否可信我一般看三件事。第一把预测值和真实值画在同一张图上重点看拐点处模型是提前、滞后还是完全没反应——完全没反应说明特征里缺了驱动那个拐点的信息。第二看残差是否还有自相关如果残差里还有明显周期说明模型没学干净可以再加滞后或周期特征。第三做一次「打乱时间顺序」的对照实验把 y 随机打乱后重新训练如果精度没怎么掉说明你的特征里根本没有时间信息模型在瞎猜。# 残差自相关快速检查 resid y_test.values - pred from statsmodels.graphics.tsaplots import plot_acf plot_acf(resid, lags30) # 残差应落在置信区间内否则还有结构没提取一个实用的提精度技巧是「目标变换 加权」。对波动大的序列先对 y 取对数再训练预测完np.exp还原能明显改善峰值预测。另外如果近期数据比历史数据更重要可以给训练样本加时间权重越近的样本权重越高RF 的fit支持sample_weight参数把权重设成指数衰减即可。weights np.exp(np.linspace(-1, 0, len(X_train))) # 越靠后权重越大 rf.fit(X_train, y_train, sample_weightweights)这个技巧在业务数据分布随时间漂移时特别管用代价是模型对历史规律的记忆变弱得根据数据稳定性调衰减速度。我自己做时间序列RF 永远是第一个跑通的模型不是因为它最好而是因为它最快告诉我「这份数据值不值得继续投入」。如果 RF 都做不出像样的结果先别急着换模型回去看数据。希望帮到你。本文还有配套的精品资源点击获取
返回列表