随机森林回归实现气温预测:从特征工程到模型调优

发布时间:2026/9/30 13:03:37
随机森林回归实现气温预测:从特征工程到模型调优
1. 从业务需求到算法选型为什么是随机森林1.1 气温预测的本质是回归问题先给第一次接触气象预测的朋友说句大实话气温预测看起来是个很天气学的活儿但在工程落地层面它本质上就是一个典型的回归任务。我们有大量历史观测数据包含温度、湿度、气压、风速、风向等字段目标是预测未来某个时间点的气温数值。这个输入输出结构天然就是机器学习里的监督学习而且是连续值输出所以回归算法是主战场。我当初接到这个项目时脑子里蹦出的第一个想法并不是随机森林而是LSTM。毕竟气温是时间序列时序模型听起来更正统。但真拿到小时级气象数据一看情况变了单站气象数据的数据量其实不大几万个样本撑死了特征维度也就十几列而且存在明显的周期性日周期、年周期和滞后相关性今天的温度跟前几天的温度强相关。拿这么点数据去训深度学习模型很容易过拟合还费显卡。这时候随机森林这类基于决策树的集成方法反而是最稳的选择。随机森林做回归的思路说起来很直接训练出一堆决策树让每棵棵去学习特征和目标之间的关系最后把所有树的预测结果取平均。单棵树容易过拟合但几百棵树一起投票噪声就被平均掉了泛化能力反而上去了。这就是集成学习中Bagging的核心思想——用多个弱模型组合出一个强模型。1.2 随机森林凭什么能打我在多个气象预测项目里对比过线性回归、SVR、XGBoost、随机森林最后经常回到随机森林原因是它的三大特性在气象场景里特别贴合。第一抗非线性能力强。气温和湿度、气压之间的关系根本不是线性的夏天高温高湿让人觉得闷热冬天湿度大风寒效应明显这种复杂的交互关系用线性模型很难刻画但决策树天生就能找到非线性切分点。第二对特征尺度不敏感。气象数据里风速可能是0到20 m/s气压是990到1030 hPa温度是-20到40℃量纲差异极大。你用神经网络或者SVR不做标准化基本没法训但随机森林是基于特征值排序做切分的完全不需要归一化直接把原始数据丢进去就能训。这省掉了巨大的数据预处理工作量。第三训练效率极高。随机森林的训练是可以完全并行化的每棵树独立生长树之间没有依赖关系。我用一台八核CPU处理几万条样本几十秒就能训完做参数调优时跑网格搜索也不会等到怀疑人生。还要提一个容易被忽略的点随机森林的特征重要性。气象预测项目最烦的就是不知道哪些因素影响大而随机森林训练完直接能给出一份特征重要性排名。这个排名能帮你反推气象规律比如前天温度对今天温度的影响权重很高、相对湿度次之、气压又次之这种结论可以直接写进项目汇报里非常有说服力。1.3 与其它常见算法方案对比做技术选型时不能只知道随机森林好还得知道在什么情况下它比其他方案好。我整理了一个对比表方便大家按场景选用。模型非线性特征缩放数据量要求训练速度可解释性适合场景线性回归弱需要少极快最强趋势分析、基线模型SVR中需要中慢一般小样本高维特征随机森林强不需要中快强中小规模表格数据回归XGBoost强不需要中较快中竞赛、追求精度的场景LSTM强需要大慢弱长序列、海量样本随机森林的定位很清晰在中小型气象数据上它是精度和成本平衡得最好的方案。XGBoost精度通常能稍微高一点但它需要调的正则化参数更多对新手不友好LSTM在数据量不足时反而容易崩。所以项目要求快速落地、结果可靠、能解释清楚时我通常会从随机森林开始跑出一个基线结果再决定要不要上更强的模型。2. 数据准备气象数据从哪里来怎么处理2.1 数据来源与字段结构做气温预测的第一步不是建模是搞数据。我这次项目用的是某个公开气象站的逐小时观测数据大概覆盖了两年的观测记录总共一万七千多条样本字段包括temperature气温目标变量单位℃humidity相对湿度%pressure海平面气压hPawind_speed风速m/swind_direction风向角度precipitation降水量mmtimestamp观测时间这类数据在各大气象数据平台上都能下载到优先选逐小时或逐三小时的数据粒度太粗比如日均值会把日周期变化抹掉模型预测出来的结果会很平没有实际参考价值。数据拿到手第一步永远是做探索性分析。我习惯先打印出数据的统计描述看一眼有没有明显的异常值。正常气温应该在-20到40℃之间湿度在0到100之间气压在950到1050之间。如果出现超出物理常识的值基本就是传感器故障或者录入错误这种脏数据对模型的破坏力极大必须清洗。2.2 时间特征与滞后特征的构造气象数据的精髓在于周期性。一年有四季轮回一天有昼夜交替这些周期性信息不会自己出现在数据表里需要我们从时间戳里手工提取。我常用的特征有hour小时0-23捕捉日内温度曲线month月份1-12捕捉季节性变化dayofyear年内第几天让模型知道当前处于一年中的哪个位置dayofweek星期几有些地区周末和工作日的气温模式会有细微差异season季节用1、2、3、4代表春夏秋冬除了时间特征滞后特征这个坑最容易踩也最值得做。气温有很强的惯性今天的温度跟前一天、前两天甚至前面24小时的温度都是高度相关的。所以我把目标列做了平移生成temp_lag_1前1小时温度、temp_lag_24前24小时温度、temp_lag_168前一周同时刻温度之类的特征。还有个非常有效但不一定有人告诉你的技巧滚动窗口统计量也叫做移动平均。比如过去3小时的平均气温、过去24小时的最高气温这类特征刻画的是气温的趋势和累积效应比单点滞后特征更平滑能显著提升模型在天气突变场景下的稳定性。# 滞后特征构造 df[temp_lag_1] df[temperature].shift(1) df[temp_lag_24] df[temperature].shift(24) df[temp_lag_168] df[temperature].shift(168) # 滚动窗口特征 df[temp_mean_3h] df[temperature].rolling(3).mean() df[temp_max_24h] df[temperature].rolling(24).max()这里提醒一句构造滞后特征后一定要删除NaN行因为前168行根本没有历史数据可以参照不删掉训练时会报错或者被自动丢弃。2.3 缺失值与异常值处理策略气象观测数据再规范也免不了有缺失。传感器故障、传输中断、设备维护都会产生空值。我这次数据集里湿度字段缺了三百多行占比不到2%处理方式用的是前后线性插值也就是用缺失点附近的有效值画一条直线把洞填上。对气象数据来说这个方案足够好因为气象要素的短时间变化是连续的线性插值的误差极小。异常值处理要用物理常识统计学双保险。先按物理常识过滤气温不可能低于-60℃也不可能高于60℃湿度不可能小于0。再用统计学方法比如计算3倍标准差区间超出区间的样本标记为异常。但注意气象里的异常跟业务数据里的异常不一样——夏天突然来一场冷空气气温两小时内降10℃这在统计上可能是异常但在物理意义上是真实发生的天气过程。所以这类样本不能直接删要结合前后时间点判断只有孤立的、不可解释的突变才需要处理。我做数据清洗时的做法是先画一个时间序列图肉眼扫一遍全貌再看具体的突变点人工判断哪些是真实天气过程哪些是传感器噪声。这一步看起来很土但非常有效比任何自动化异常检测算法都靠谱。3. 随机森林回归模型实现3.1 环境准备与依赖配置建模环境我用的是Python 3.9 scikit-learn 1.2.2。没有用更高版本是因为有些老代码兼容性问题但如果你是新项目装最新版也没问题。核心依赖就三个numpy1.23.5 pandas1.5.3 scikit-learn1.2.2 matplotlib3.6.2scikit-learn的随机森林回归器封装在sklearn.ensemble.RandomForestRegressor里不需要自己从零实现算法。新手容易在这里产生误区——以为用机器学习必须手写算法其实工程实践里99%的场景都是调库。重点要理解的是算法原理和参数含义而不是重复造轮子。3.2 特征与标签的定义逻辑这一步是所有建模工作里最需要想清楚的地方。特征矩阵X放的是我们用于预测的全部信息标签y放的是我们想预测的目标。我要预测的是下一个时刻的气温那么X就是从历史数据里构造出的所有气象特征和时间特征y就是向后平移一位的temperature序列。这里有个重要的工程细节必须强调时间序列预测的数据集划分不能直接用train_test_split的随机切分。随机切分会把时间顺序打乱导致模型偷看未来——训练集里混着后面时间的数据测试集里混着前面时间的数据。这样的模型评估结果虚高一旦上线做真实的未来预测性能立刻崩掉。正确做法是按时间顺序截断比如前80%的数据做训练后20%做测试保证训练集时间永远在测试集之前。from sklearn.model_selection import train_test_split # 正确的做法按时间顺序划分 train_size int(len(df) * 0.8) train_data, test_data df.iloc[:train_size], df.iloc[train_size:] X_train, y_train train_data[features], train_data[target] X_test, y_test test_data[features], test_data[target]3.3 核心代码实现与参数说明模型本身的实现代码量少得惊人但每一行背后都有讲究。我把完整的建模代码放下注释里写清楚每个参数的含义。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score import numpy as np # 初始化随机森林回归器 model RandomForestRegressor( n_estimators300, # 决策树数量多棵树平均降低方差 max_depth20, # 限制单棵树深度防止过拟合 min_samples_split5, # 内部节点再划分的最小样本数 min_samples_leaf2, # 叶节点最小样本数强制叶子不能太纯 max_featuressqrt, # 每次分裂随机抽取的特征数量让树之间差异更大 random_state42, # 固定随机种子保证结果可复现 n_jobs-1 # 使用全部CPU核心并行训练 ) model.fit(X_train, y_train) # 预测 y_train_pred model.predict(X_train) y_test_pred model.predict(X_test)参数选择我一开始就用了一个相对合理的组合并没有直接跑全量网格搜索。这里解释一下关键参数的设计意图n_estimators300并不是越多越好超过某个阈值后精度提升曲线趋平但训练时间线性增长。300是我测试下来精度和时间的平衡点。max_depth20不限制深度的话单棵树会一直分裂到完全拟合训练集必过拟合。限制到20层让每棵树都保留一定程度的粗糙感。min_samples_leaf2叶子节点至少保留2个样本防止模型记住训练集中的个别极端个案。max_featuressqrt每次分裂只随机取一部分特征做最优切分强制各棵树长得不一样这是Bagging策略的精髓所在。训练完先看训练集和测试集的误差对比print(训练集 R2:, r2_score(y_train, y_train_pred)) print(测试集 R2:, r2_score(y_test, y_test_pred)) print(测试集 MAE:, mean_absolute_error(y_test, y_test_pred)) print(测试集 RMSE:, np.sqrt(mean_squared_error(y_test, y_test_pred)))我这组数据的典型结果是训练集R2约0.98测试集R2约0.92。训练集远高于测试集说明模型存在轻微的过拟合但测试集0.92的精度对于气温预测来说已经相当能打了。很多人一看到训练集精度高就慌其实小幅过拟合在随机森林里是常态只要测试集指标达到业务要求就不必过度干预。3.4 手工调参的经验与顺序调参最容易犯的错误是一上来就网格搜索。我建议新手先手工调理解每个参数的敏感度再去跑自动化搜索。我自己总结的调参顺序是第一步先固定一个较大的n_estimators比如500让模型精度充分饱和。如果这个值太小后面的调参结论都会被随机噪声干扰看不准。第二步调max_depth。从10开始每次加5观察测试集R2的变化。太浅拟合不足太深过拟合找到那个再加深就涨不动的拐点。第三步调min_samples_leaf。这个参数控制正则化强度从1开始往大调。气象数据里偶尔有极端天气样本把叶子节点最小样本数调大比如5-10能有效抑制模型对极端值的过度响应。第四步最后微调max_features。默认sqrt一般就很好但样本特征特别多时可以试试log2或者一个固定的浮点数比例。这套流程跑下来通常能把测试集精度在默认参数基础上提升1到2个百分点对气象项目来说这个提升已经非常可观。4. 模型评估与结果解读4.1 评估指标体系怎么选气象预测模型不太看准确率因为气温是连续值讨论预测准不准用的都是回归指标。我核心看四个MAE平均绝对误差预测值和真实值差的绝对值平均。它直观单位是℃适合向非技术合作方汇报。RMSE均方根误差先求误差平方再平均再开方它会放大大的误差的惩罚适合发现模型在极端天气下的失控情况。R2决定系数衡量模型对目标变量变异的解释能力越接近1越好。预测偏差Bias预测值的均值减去真实值的均值判断模型是系统性偏高还是偏低。只看R2不看RMSE是很多新人会犯的错误。R2高只能说明模型整体拟合得好但气温预测应用里最怕的是极端高温或低温预报偏差过大。RMSE能把这种风险暴露出来因为平方操作会让大误差样本在指标里占据统治地位。4.2 训练集与测试集结果对比分析我在项目里的实际结果拿出来给大家做个参考指标训练集测试集R20.9810.923MAE0.42℃0.76℃RMSE0.68℃1.12℃训练集MAE只有0.42℃测试集是0.76℃误差放大不到一倍这个过拟合程度是可以接受的。测试集RMSE 1.12℃意味着平均来看预测值和真实值的偏差大概在1℃这个量级。对气温预测场景来说1℃左右的误差在业务上是很实用的精度毕竟天气预报行业的常规要求也就是2℃以内。还要特别看一眼Bias。我计算测试集的预测均值减真实均值结果是0.13℃说明模型整体略微高估气温。这种情况在夏季晴热天气下比较常见模型没完全学到高温日地表升温加速的规律预测值整体偏低一点点。了解这个系统性偏差后可以在业务端做个简单的偏差校正。4.3 特征重要性分析随机森林最讨喜的副产品就是特征重要性。训练完后直接从模型里取出来看importances model.feature_importances_ feature_names X_train.columns for name, imp in sorted(zip(feature_names, importances), keylambda x: x[1], reverseTrue): print(f{name}: {imp:.4f})根据我的特征清单排名通常会是这样temp_lag_1前1小时温度重要性极高占到0.35以上。这符合物理直觉气温是连续变化的一小时前的气温对当前气温有极强的指示性。temp_lag_24前24小时温度重要性第二梯度表明日周期规律非常显著。humidity和pressure重要性中等它们决定了冷暖空气的博弈结果。season和hour重要性也靠前因为气温的年周期和日周期是硬规律。风速风向类特征重要性相对靠后但这不代表它们没用只是在这个数据集的变异性里温度和湿度字段已经包含了大部分预测信息。这个重要性排名可以反哺特征工程——如果某个你辛辛苦苦构建的特征重要性几乎为零下一次迭代就可以放心砍掉减少模型复杂度和过拟合风险。4.4 预测结果可视化验证表格指标再漂亮最后都要用图来说话。我把测试集连续500个小时的真实气温和预测气温画在同一张折线图里。观察到的典型现象是大部分时段预测线和真实线严丝合缝但在几个冷空气过境的拐点处预测曲线有明显滞后——真实气温已经开始骤降预测值还保持着之前的水平过了两三个小时才跟上。这种现象揭示了一个本质规律随机森林本质上是一个非线性插值器它对历史样本覆盖范围内的模式预测得极好但面对没有见过的突变型样本它的外推能力是有限的。这个短板无法通过调参完全解决需要在业务上接受它或者在特征工程层面增加气压变化率过去6小时温差这类趋势特征帮模型提前捕捉突变信号。5. 常见问题与调优实录5.1 时间序列预测中数据泄漏的坑我在项目分享里最想强调的坑就是这个。很多初学者拿着气象数据直接用train_test_split(X, y, test_size0.2, random_state42)切分结果测试集R2能到0.96沾沾自喜一部署上线立刻腰斩。原因我刚才说过随机切分让训练集和测试集共享了同一时间段的信息模型相当于看到过未来。比如测试集里某一天的数据混进了训练集那么这一天的特征模式比如前一天温度在训练时已经见过预测准确率当然虚高。正确做法除了按时间截断还有一个严格的要求所有特征构造过程都必须在训练集上独立进行。如果我在全量数据上先做滚动均值再做切分那么测试集样本的滚动均值其实已经用了它未来窗口的数据这又是一层数据泄漏。正确的姿势是先切分再在训练集部分做特征工程然后把计算好的平滑参数或滞后值应用到测试集。5.2 极端气温预测偏差问题做气温预测高温热浪和寒潮是最容易被扣分的场景。我测试集里误差异常大的样本基本都集中在两类情况强冷空气爆发式南下时的断崖式降温以及盛夏午后热对流触发前的异常升温。随机森林在这两类情景下的预测误差能到3℃甚至更高。我的解决思路有两个层次。第一层是特征补充加入温度变化率特征比如过去6小时温度变化斜率让模型显式感知温度变化的方向和速度。第二层是数据层面的把历史同期极端天气样本做重采样让模型在训练时看到更多极端样本不至于把这些重要模式当成噪声忽略掉。实测下来加了温度变化率特征后测试集极端样本的MAE从2.4℃降到了1.8℃改善非常明显这个效果比盲目调参显著得多。5.3 树的数量与过拟合博弈新手问我最多的一个问题是n_estimators该设多大。我见过有人把树设到5000训练时间翻了几倍精度却一点没涨。随机森林的误差曲线是一个凹函数刚开始随着树增多误差快速下降到达某个临界点后曲线趋平再增加树只是浪费算力。我用200、300、500、800做过对比测试300是一个甜点位500收益已经很微弱。更值得关注的是过拟合监控。我每次训练都会打印训练集和测试集的R2差距如果差距超过5个百分点就需要加强正则化。优先调min_samples_leaf这个参数比max_depth温和不会因为设得太狠导致欠拟合。5.4 网格搜索调参的合理姿势手工调参到后期可以用GridSearchCV做一次精细化搜索把前面试出来的参数范围缩小让机器在这个小范围里找到局部最优。我常用的参数网格长这样from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [200, 300, 400], max_depth: [15, 20, 25], min_samples_leaf: [2, 5, 8], } grid_search GridSearchCV( RandomForestRegressor(random_state42, n_jobs-1), param_grid, cv3, scoringneg_mean_absolute_error, verbose1 ) grid_search.fit(X_train, y_train)注意这里的scoring用了MAE而不是默认的R2。因为气象业务里最关心的就是平均差多少度用MAE做优化目标跟业务指标更对齐。交叉验证折数设为3因为气象数据的时序性没法做标准的K折我用的是按时间切块的3折验证保证每一折的验证集都在训练集后面。5.5 模型部署与推理提速实战模型训练好了要落地还有一个经常被忽略的环节推理速度。随机森林预测单条数据很快但如果你要预测未来一个月逐小时的气温也就是720个点递归调用模型预测720次总耗时可能在几秒到十几秒之间体感就有点慢了。提升推理速度有几个实操技巧。第一个是压缩模型减少n_estimators因为推理时每棵树都要单独跑一遍。如果精度损失可接受从300棵砍到100棵推理速度直接变3倍。第二个是用joblib保存模型推理时只加载一次不要在循环里反复加载。第三个是大批量预测时使用模型自带的向量化预测接口不要一行一行调predict把整个待预测序列一次传入。import joblib # 保存模型 joblib.dump(model, temperature_rf_model.pkl) # 加载模型 model joblib.load(temperature_rf_model.pkl) # 批量预测 future_features build_future_features(...) y_future_pred model.predict(future_features)存储这块多说一句joblib对包含大量数组的模型对象做了特殊优化比pickle文件小得多、加载快得多实践中优先用它。我在实际项目中还踩过一个坑模型保存的时候用的是相对路径换了台服务器部署后加载不到文件。后来一律用绝对路径配合项目目录结构解耦才彻底解决这个问题。做这个项目最大的体会是模型精度高不高只是评估模型的一个维度真正考验人的是数据质量把控、特征工程设计和业务场景理解。随机森林帮我把从数据到可用模型的路径压缩到半天以内让我有充足精力去打磨特征和排查数据泄漏问题。如果你也是第一次做气象预测项目我建议你按这个顺序走一遍先保证数据干净再花力气构造滞后特征然后跑通随机森林基线和评估最后根据特征重要性和误差分布反向优化。这套流程踩过的坑我都替你踩了照着做至少能少走三天的弯路。