Python随机森林气温预测实战:从特征工程到模型调优
发布时间:2026/9/30 15:12:46 作者:尧图编辑部 阅读量:1,286

气象站里报出来的“晴 26°C”背后其实是一套复杂的物理方程加无数传感器修正。但如果你手头只有一份历史气象表格又想在几行代码内搭个能用的气温预测模型那随机森林绝对是性价比最高的起点。去年我做本地站点高温趋势预测时第一版基线就是用 Python 加随机森林回归算法完成的从数据清洗到参数调优只花了一个下午精度已经能控制在平均误差 1.5°C 以内。这篇文章就把完整链路拆开讲为什么选随机森林、特征怎么做、代码怎么跑、以及我在实际项目里踩过的几个大坑。适合刚接触机器学习回归任务、想用真实气象数据练手的同学也欢迎做气候数据分析的朋友来交流。1. 项目概述与模型选型思路1.1 为什么选中随机森林做气温预测气温预测本质上是个回归问题输入一批历史气象观测值输出一个连续温度值。但这个回归关系很不“线性”湿度高不一定降温但配合大风和阴雨天体感温度会骤降气压、前一日温差、季节周期都在同时起作用。传统线性回归在这种多因子非线性的场景下往往只能拟合一个大趋势很难捕捉局部突变。随机森林的做法则完全不同。它训练多棵决策树每棵树在样本和特征上做随机采样bootstrap 抽样和特征随机抽样最终把多棵树的预测结果取平均作为输出。这种 bagging 策略最直接的收益是方差降低单棵决策树很容易学到诡异的极端噪声但几十棵上百棵树平均下来个别的误判就被淹没了。集成之后模型对异常值也不敏感某个传感器记录了一个离谱的瞬时风速最多影响一棵子树的切分整体预测不会被打偏。有人问为什么不用支持向量机或者 XGBoost。SVR 非线性核的选择和惩罚系数 C 调整起来很看经验网格搜索稍不留意就陷入局部最优XGBoost 精度上限确实更高但需要处理学习率、树深度、正则化、早停一大堆超参数对刚入门的人来说包袱太重。随机森林在中等规模的气象历史数据上几千到几万条样本不需要特征归一化不用考虑缺失值编码默认参数跑出来已经能当 baseline后续想替换成更强的模型也有充足的对比参照。当然它有一个硬伤模型无法外推。因为预测值是训练集中样本的加权平均如果测试期出现了百年一遇的极端高温训练集里的最高温只有 38°C模型永远不可能预测出 40°C。这一点在后面的问题排查章节我会再展开。1.2 模型适用场景与数据需求这个模型不是什么天气场景都能搞定它的舒适区是短期趋势预测尤其是几小时到三天的逐时或逐日气温。我在项目中主要用它做两件事一是给农业大棚的通风决策提供温度参考二是对城市热岛效应做一个基于历史观测的统计估算不追求物理可解释性只求预测趋势稳。数据需求方面最低要求是一份包含“日期 气温”的两列数据这样只能提取日期本身的季节周期预测精度相对有限。理想情况下希望历史数据至少覆盖连续 2 到 3 年并且包含这些字段日期、气温或最高最低温、相对湿度、气压、风速、降水量。年份越完整模型能学到的季节周期和年际差异越可靠。样本量上并不夸张每天一条记录三年也就一千条左右随机森林完全吃得消如果是逐小时数据几万条也跑得动。数据来源方面有公开气象数据集也有从天气接口导出的历史记录但需要注意授权和合规问题。自己用小型气象站采集的数据最稳妥注意传感器校准即可。1.3 项目技术栈与整体流程整个项目我用的就是最常见的 Python 数据科学生态pandas 做数据整理numpy 做数值运算scikit-learn 中的 RandomForestRegressor 做模型训练matplotlib 画特征重要性和预测对比图。不需要深度学习框架也不需要 GPU普通笔记本就能流畅跑完。流程上大致分五步数据获取与清洗、特征工程、训练集测试集划分、随机森林训练与调参、模型评估和特征分析。下面两章就是按这个顺序来的先解决数据问题再进入建模。2. 数据准备与特征工程2.1 数据获取与清洗实操我用的示例数据是一份模拟气象站观测表字段包括 date、temp、humidity、pressure、wind_speed、precipitation其中 temp 就是我们要预测的目标变量。拿到手第一步是解析日期并排序这是所有时间序列任务的前提。import pandas as pd import numpy as np df pd.read_csv(weather.csv, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) print(df.head())接着做缺失值检查。气象观测数据经常有传感器掉线导致空值。我习惯的做法是如果缺失比例低于 5%用前后两天均值做线性插值如果连续缺失超过 3 天则直接删掉这一段避免插值制造出人工数据。对异常值我用了最简单的业务规则气温在 -40 到 45°C 之外的视为异常湿度必须在 0 到 100%气压在 850 到 1080 hPa。超出范围的记录不是删除而是标记为缺失再插值因为删除会使时间不连续。这里容易忽略的一点是日期索引必须连续否则后面生成滞后特征时会出现“前一天”错位。我通常用pd.date_range重排一个完整日期列表再左连接原始数据缺失日期记 NaN。2.2 特征工程把时间变成模型能理解的语言随机森林虽然不需要归一化但特征设计直接影响模型上限。我在这类项目中重点构造三组特征。第一组是时间特征。直接把月份作为一个整数传给模型模型会把它当作数值对待但 1 月和 12 月之间的“距离”并不是 11一个月更像类别变量。因此我同时拆出 year、month、day、dayofyear、weekday。dayofyear一年中的第几天配合正弦余弦变换后能连续地表示季节变化因为 1 月 1 日和 12 月 31 日的季节应该很接近但直接数值上差了 364。用下面的代码把周期编码成二维坐标df[dayofyear] df[date].dt.dayofyear df[season_sin] np.sin(2 * np.pi * df[dayofyear] / 365.25) df[season_cos] np.cos(2 * np.pi * df[dayofyear] / 365.25)第二组是滞后特征这是时间序列预测的灵魂。气温具有很强的自相关性今天的温度大概率跟前一天甚至前两天的温度接近。所以我构造了目标变量的一阶滞后和二阶滞后以及一个七日滑动平均表示近期冷热基调df[temp_lag1] df[temp].shift(1) df[temp_lag2] df[temp].shift(2) df[temp_rolling7] df[temp].rolling(7).mean()第三组是其他气象因子的滞后值。注意是滞后值不是同时刻值。预测当天气温时当天湿度是拿不到的未来信息只能用前一天的湿度、气压、风速作为特征。很多新手在这里栽跟头我会在第四章重点展开。2.3 训练集与测试集划分的特殊性气温预测严格说是时间序列任务直接调用 sklearn 的train_test_split并设置随机种子是不可取的因为随机划分会把未来的数据混进训练集模型等于“偷看”了答案测试指标虚高上线后立刻翻车。正确做法是按时间顺序切分用前 80% 的数据训练后 20% 的数据测试。在样本量约 2000 条时我选择前 1600 条训练后 400 条测试。如果需要交叉验证也要使用TimeSeriesSplit它保证了训练集永远是测试集之前的样本。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) # tscv.split(X) 会按时间顺序生成连续递增的训练集和测试块这个细节决定了模型的真正泛化能力也和后面调参的网格搜索方式直接挂钩。3. 基于sklearn构建随机森林气温预测模型3.1 主流程代码实现与评估指标我把特征和目标变量整理成矩阵。需要提醒的是构造滞后特征后要丢掉前几行因为这些行的滞后值是 NaN。数据准备好后模型训练本身非常简洁# 假设 df 已经包含特征列和目标列 feature_cols [season_sin, season_cos, humidity_lag1, pressure_lag1, wind_speed_lag1, temp_lag1, temp_lag2, temp_rolling7] X df[feature_cols].dropna() y df.loc[X.index, temp] train_size int(len(X) * 0.8) X_train, X_test X.iloc[:train_size], X.iloc[train_size:] y_train, y_test y.iloc[:train_size], y.iloc[train_size:] from sklearn.ensemble import RandomForestRegressor model RandomForestRegressor(n_estimators200, max_depth10, min_samples_leaf3, n_jobs-1, random_state42) model.fit(X_train, y_train) y_pred model.predict(X_test)评估阶段我一口气看三个指标MAE、RMSE 和 R²。MAE 是平均绝对误差直观反映了预测温差RMSE 对大误差更敏感如果某天预测差 5°CRMSE 会比 MAE 高不少R² 表示模型解释了测试集多少方差越接近 1 越好。from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score mae mean_absolute_error(y_test, y_pred) rmse mean_squared_error(y_test, y_pred, squaredFalse) r2 r2_score(y_test, y_pred) print(fMAE: {mae:.2f}°C, RMSE: {rmse:.2f}°C, R2: {r2:.3f})我当时的基线结果大约是 MAE 1.4°CR² 0.91。对一个只基于历史统计、不考虑物理方程的模型来说这个精度已经足够支撑日常温度参考了。3.2 参数调优用网格搜索找到合理超参数随机森林默认参数能跑但距离最优还有空间。主要调四个参数n_estimators、max_depth、min_samples_split 和 min_samples_leaf。n_estimators 控制树的数量太少则方差大太多则训练变慢且后期收益极低max_depth 限制每棵树能连续切多深防止学到过于复杂的噪声min_samples_leaf 要求叶子节点至少包含多少样本相当于给预测值做平滑。调参时最关键的一个坑是交叉验证方式。如果直接GridSearchCV(cv5)默认会做分层 K 折这对分类问题尚可但在时间序列场景中会让模型用未来的数据验证过去导致参数偏好失真。所以必须传一个 TimeSeriesSplit 实例给cvfrom sklearn.model_selection import GridSearchCV param_grid { n_estimators: [100, 200, 300], max_depth: [8, 10, 12], min_samples_leaf: [2, 3, 5], max_features: [sqrt, 0.5] } grid GridSearchCV( RandomForestRegressor(random_state42), param_grid, cvtscv, scoringneg_mean_absolute_error, n_jobs-1, verbose1 ) grid.fit(X_train, y_train) print(grid.best_params_)这里 scoring 我选了 neg_mean_absolute_error它更贴近业务关注的平均温差而不是像 R² 那样容易被极端样本主导。GridSearchCV 在 TimeSeriesSplit 上大约会跑几十组参数在几万条样本内也就十几分钟完全可以接受。运行完毕后最好的三个参数组合我通常会对比它们的测试集 MAE。印象中 max_depth 从默认的无限制降到 8 到 10 后验证误差反而下降说明决策树深度过大确实存在过拟合。min_samples_leaf 也不宜过小否则树会记住个别特殊天气。3.3 特征重要性解释与应用sklearn 的随机森林训练后有一个feature_importances_属性可以直接查看每个特征对预测的贡献。我用它做初步筛选importance pd.Series(model.feature_importances_, indexfeature_cols) print(importance.sort_values(ascendingFalse))在我跑的模型里排在前几名的通常是 temp_lag1、temp_rolling7、season_cos 和 pressure_lag1。这很符合气象直觉短期气温有很强的惯性近期冷暖趋势比遥远的历史平均值更起作用气压场变化又是冷暖空气活动的直接信号。不过要注意默认的基于基尼系数的特征重要性有一个偏差它偏向数值型高基数特征而且对特征之间的共线性很敏感。所以当我想要更严谨的特征排序时会用permutation_importance做一次验证。它的原理是把某个特征的值随机打乱观察模型误差下降多少下降越多说明该特征越重要。这样得到的排序更可靠只是计算成本略高。from sklearn.inspection import permutation_importance perm_imp permutation_importance(model, X_test, y_test, n_repeats10, random_state42, scoringneg_mean_absolute_error)如果两个方法的结果出现巨大差异我会检查特征之间是否有强相关性比如“前一天温度”和“七日滑动平均”本质上高度相关保留其一即可否则既增加计算量又让解释变得复杂。4. 常见问题与排查技巧4.1 数据泄漏指标漂亮却没法落地我在复现别人代码时见过最典型的错误用当天的湿度、当天风速、当天气压去预测当天气温。训练时模型发现温度跟同刻气压高度相关R² 能飞到 0.98测试集也表现完美。可一旦需要做真正的预测这些所谓的特征在“当天”还没发生时根本拿不到模型瞬间失去了常用特征预测值退化成历史平均直接没法用。排查方法很简单列出模型用到的所有特征逐一问自己“在预测目标时间点之前我是否已经知道这个值”如果答案是“预测当天上午需要当天下午的风速”那就是泄漏。正确的做法是统一把特征全部替换成滞后版本比如前一天湿度、前一天风速、前三日平均气压。4.2 极端气温捕捉不到随机森林没有外推能力这是它在气象预测中最明显的短板。如果测试集包含一段热浪期间温度超过了训练集中出现过的最大值模型预测值就会被压缩在训练集的历史极值附近误差会瞬间扩大。我遇到的一次情况是训练集最高温 37°C测试期某天实际 39°C模型只预测了 35.8°C。后来我做了两个调整一是在训练阶段加入一个“极端天气标记”比如气压变化率异常和气温超过历史 95 分位的标记帮模型提前识别异常情景二是将预测目标从原始气温改为距平值气温减去同期历史平均这样模型预测的是“偏离通常态的幅度”在一定程度上缓解了对极端值记忆不足的问题。当然要彻底解决还是得引入物理模型或分位数随机森林后者能给出预测区间而不是单点值。4.3 模型训练慢且内存占用高随机森林虽然比深度学习轻量但盲目加大参数也会跑不动。有人一上来就设 n_estimators1000而且不限制 max_depth结果一棵树就长出几千个节点内存直接耗尽。实操上树的数量在 100 到 300 之间已经是收益递减区。我用 200 棵和 500 棵做过对比MAE 差距不到 0.05°C但训练时间翻了近三倍。同时一定设置max_depth和min_samples_leaf这能显著减小树体积。别忘了n_jobs-1让所有 CPU 核一起工作。如果数据量超过十万条我会先把样本做一次降采样或者改用 HistGradientBoosting但这是后话。4.4 问题速查表现象可能原因排查与解决训练集 R² 极高但测试集很差过拟合或特征泄漏限制 max_depth、增大 min_samples_leaf检查特征是否包含未来信息预测值总是接近历史平均随机森林外推能力弱或滞后特征不足增加趋势特征、改用距平预测、尝试分位数随机森林交叉验证结果波动很大时间序列分段不合理或样本量太少改用 TimeSeriesSplit检查训练与测试期的天气系统差异特征重要性排序不合理特征共线性或基尼重要性偏差计算特征相关性矩阵用 permutation_importance 交叉验证首次预测几天后就明显漂移递归预测导致误差累积改用直接多输出策略或每个预测步单独训练一个模型我个人在实际操作中最大的体会是随机森林在气温预测里最大的价值不是“精度碾压”而是它作为基线模型稳定、易用、可解释。调半天参数其实 1°C 的平均误差已经被固定在那里再往下压需要换更强的模型。但在那之前先用随机森林把特征思路和数据链路理顺能帮你省掉大量返工时间。最后再分享一个小技巧训练完成后一定要把模型用 pickle 或 joblib 保存下来同时把“特征列顺序”一起保存因为模型在预测时会按照训练时的列顺序解释输入列顺序一变预测结果就全乱了。这个细节我替你们踩过坑。