
简介这份资源是面向高校学生与开发者的Python随机森林气温预测项目源码适用于毕业设计、课程设计及机器学习入门实践。项目以气温预测为场景利用随机森林处理湿度、气压、风速等多变量间的复杂关系帮助读者理解数据预处理、特征选择、模型训练与评估的完整建模流程。压缩包共19个文件约4.23MB以py脚本、csv数据集、xml配置、txt说明及zbak备份为主其中py文件承载建模与预测逻辑csv提供气温训练数据txt与xml辅助环境配置和项目说明另附赠补充资料便于扩展学习。目前已有117人学习下载。读者可据此掌握Scikit-learn构建随机森林模型的方法理解时间序列预测中的特征工程与参数调优思路并熟悉从设计、实现到测试维护的软件开发流程适合作为机器学习实践与毕业设计的参考范例。1. 气温预测这个课题为什么随机森林比线性回归更抗造做过气温预测的人都有一个体会拿线性回归去拟合历史气象数据训练集上看着还行一到真实预测就翻车。原因不复杂——气温跟湿度、气压、风速、日照这些变量之间的关系根本不是线性的而且变量之间还存在交互效应比如高温高湿和高温低湿对次日气温的影响完全不同。随机森林回归靠 bootstrap 重采样加特征随机选择天生能捕捉这种非线性交互还能给出特征重要性排序这对毕业设计里「分析预测」两条线都要交差的场景特别友好。这个课题适合谁计算机、气象、农业、环境类专业的本科或研究生正在做毕业设计或课程设计手头有历史气象数据或者能从公开气象站拿到需要一套能跑通、能解释、能写进论文的完整方案。核心链路就四步数据清洗与特征工程、随机森林回归建模、超参数调优、预测结果可视化与评估。下面按这条链路拆开讲每一步都给可复现的代码和参数说明。2. 数据准备与特征工程从原始气象表到模型能吃的矩阵2.1 气温预测需要哪些字段缺失值怎么处理一份典型的历史气象数据通常包含日期、最高气温、最低气温、平均气温、相对湿度、气压、风速、降水量、日照时数等字段。做次日平均气温预测时我一般把当天及前两天的观测值作为特征预测目标设为次日平均气温。这样构造出来的特征既有当日信息也有短期趋势信息。缺失值处理是第一个分水岭。气象数据常见的缺失模式有两种单点缺失某个小时或某天没记录和连续缺失设备故障导致连续几天没数据。单点缺失用线性插值就够了连续缺失超过三天的我建议直接删掉那段区间不要硬填否则会引入虚假趋势。下面这段代码演示完整的读取、缺失值处理和特征构造流程。import pandas as pd import numpy as np # 读取原始气象数据假设是 CSV 格式含日期和各气象要素 df pd.read_csv(weather_history.csv, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) # 单点缺失用线性插值连续缺失超过3天的区间标记后删除 df[avg_temp] df[avg_temp].interpolate(methodlinear, limit2) df[humidity] df[humidity].interpolate(methodlinear, limit2) # 标记连续缺失对每个字段计算缺失游程长度 def mark_long_gaps(series, max_gap3): is_na series.isna() groups (~is_na).cumsum() gap_len is_na.groupby(groups).transform(sum) return gap_len max_gap for col in [avg_temp, humidity, pressure, wind_speed]: df df[~mark_long_gaps(df[col])] # 构造滞后特征当天、前一天、前两天的观测值 for lag in [0, 1, 2]: for col in [avg_temp, humidity, pressure, wind_speed]: df[f{col}_lag{lag}] df[col].shift(lag) # 构造滚动统计特征3天滑动平均和滑动标准差 for col in [avg_temp, humidity]: df[f{col}_roll3_mean] df[col].rolling(3).mean() df[f{col}_roll3_std] df[col].rolling(3).std() # 目标变量次日平均气温 df[target] df[avg_temp].shift(-1) # 去掉因滞后和滚动产生的空值行 df df.dropna().reset_index(dropTrue) print(df.shape) print(df.columns.tolist())这段代码的逻辑分三层。第一层是插值limit2表示最多连续填两个缺失点超过就留空避免过度平滑。第二层是长缺失区间剔除mark_long_gaps函数用累积和分组的方式计算每段连续缺失的长度超过max_gap3的整段删掉。第三层是特征构造滞后特征捕捉时间惯性滚动统计捕捉短期波动特征。shift(-1)把次日气温对齐到当天行这是监督学习标签构造的标准做法。参数方面滞后阶数选 0 到 2 是我在日尺度气温预测里的经验值再往前加收益很小而且容易过拟合。滚动窗口选 3 天对应短期天气过程的时间尺度。如果你的数据是小时尺度滞后阶数改成 0 到 24滚动窗口改成 24 小时。2.2 训练集测试集怎么切时间序列不能随机打乱这是毕业设计里最容易踩的坑之一。很多人习惯用train_test_split随机切分但气温数据是时间序列随机打乱会导致未来信息泄露到训练集里测试集上的指标虚高答辩时被老师一问就露馅。正确做法是按时间顺序切分比如前 80% 做训练后 20% 做测试。# 按时间顺序切分前80%训练后20%测试 split_idx int(len(df) * 0.8) train_df df.iloc[:split_idx].copy() test_df df.iloc[split_idx:].copy() # 特征列排除日期和目标变量 feature_cols [c for c in df.columns if c not in [date, target]] X_train train_df[feature_cols].values y_train train_df[target].values X_test test_df[feature_cols].values y_test test_df[target].values print(f训练集: {X_train.shape}, 测试集: {X_test.shape})切分比例 80/20 是常规做法如果数据量少于两年建议用 70/30 留更多测试样本。注意切分前一定要按日期排序否则iloc切出来的不是连续时间段。另外如果你要做多步预测比如预测未来三天测试集构造方式要相应调整这里先聚焦单步预测。3. 随机森林回归建模从默认参数到可解释的特征重要性3.1 用 sklearn 跑通第一个基线模型环境准备不复杂装好 Python 后执行pip install scikit-learn pandas numpy matplotlib就行。如果你还在纠结 Python 安装和 numpy 库的配置建议直接用 Anaconda 发行版省去编译依赖的麻烦。下面先跑一个默认参数的随机森林回归建立基线。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score import numpy as np # 默认参数基线模型 rf_base RandomForestRegressor(random_state42, n_jobs-1) rf_base.fit(X_train, y_train) # 预测与评估 y_pred_base rf_base.predict(X_test) mae mean_absolute_error(y_test, y_pred_base) rmse np.sqrt(mean_squared_error(y_test, y_pred_base)) r2 r2_score(y_test, y_pred_base) print(fMAE: {mae:.3f} °C) print(fRMSE: {rmse:.3f} °C) print(fR2: {r2:.4f})random_state42保证结果可复现n_jobs-1用满所有 CPU 核心加速训练。评估指标选三个MAE 反映平均绝对误差单位是摄氏度最直观RMSE 对大误差更敏感能暴露极端预测偏差R2 反映模型解释了多少方差。气温预测里 MAE 能压到 1.5°C 以内、R2 到 0.9 以上对于日尺度预测就算不错了。默认参数下n_estimators100max_depthNone树完全生长max_features1.0每次分裂考虑所有特征。这个配置在小数据集上容易过拟合下一步要调。3.2 特征重要性怎么看哪些气象因子在起作用随机森林自带特征重要性评估基于每个特征在所有树中分裂时带来的不纯度减少量。这个功能对毕业设计论文特别有用你可以直接拿它写「影响气温的主要因子分析」一节。import matplotlib.pyplot as plt # 提取特征重要性并排序 importances rf_base.feature_importances_ indices np.argsort(importances)[::-1] # 打印前15个重要特征 print(特征重要性排名:) for i in range(min(15, len(feature_cols))): idx indices[i] print(f{i1}. {feature_cols[idx]}: {importances[idx]:.4f}) # 可视化 plt.figure(figsize(10, 6)) plt.barh(range(15), importances[indices[:15]][::-1]) plt.yticks(range(15), [feature_cols[i] for i in indices[:15]][::-1]) plt.xlabel(Importance) plt.tight_layout() plt.savefig(feature_importance.png, dpi150)跑出来通常会发现avg_temp_lag0当天平均气温和avg_temp_lag1前一天平均气温排最前这符合气温的时间自相关性。湿度、气压的滚动统计特征也会排进前十。如果某个特征重要性异常高比如超过 0.5要检查是不是构造了泄露特征比如把目标变量的某种变换混进去了。提示特征重要性反映的是该特征在模型内部的使用频率和分裂收益不代表因果关系。论文里写「主要影响因子」可以但别写成「导致气温变化的原因」。4. 超参数调优与避坑那些让模型翻车的细节4.1 网格搜索调参n_estimators、max_depth、max_features 怎么设随机森林的关键超参数就三个树的数量n_estimators、树的最大深度max_depth、每次分裂考虑的特征数max_features。树越多越稳定但训练越慢深度越大拟合能力越强但容易过拟合max_features越小树之间差异性越大但单棵树越弱。from sklearn.model_selection import GridSearchCV, TimeSeriesSplit # 时间序列交叉验证不能用普通KFold tscv TimeSeriesSplit(n_splits5) param_grid { n_estimators: [100, 200, 300], max_depth: [10, 15, 20, None], max_features: [sqrt, log2, 0.5], min_samples_leaf: [1, 2, 4] } rf RandomForestRegressor(random_state42, n_jobs-1) grid_search GridSearchCV( rf, param_grid, cvtscv, scoringneg_mean_absolute_error, verbose1, n_jobs-1 ) grid_search.fit(X_train, y_train) print(f最优参数: {grid_search.best_params_}) print(f最优MAE: {-grid_search.best_score_:.3f} °C) # 用最优参数在测试集上评估 best_rf grid_search.best_estimator_ y_pred_best best_rf.predict(X_test) print(f测试集MAE: {mean_absolute_error(y_test, y_pred_best):.3f} °C)这里用TimeSeriesSplit而不是普通 KFold因为时间序列不能随机打乱做交叉验证。scoringneg_mean_absolute_error是因为 sklearn 的 GridSearchCV 默认找最大值MAE 越小越好所以要取负。max_features设sqrt表示每次分裂考虑总特征数的平方根个这是回归任务的常用默认值。调参后一般 MAE 能比基线降 0.1 到 0.3°C。如果降幅很小说明基线模型已经接近数据本身的上限该去补数据或加特征而不是继续调参。4.2 避坑清单五个让毕业设计翻车的典型问题现象一测试集 R2 高达 0.99答辩时被问「你这预测的是明天还是今天」。原因特征里混入了目标变量的同期值或未来值比如用当天平均气温预测当天平均气温或者滞后特征构造时shift方向搞反。解决构造完特征后逐列检查每个特征在时间上是否只用了当前及过去的信息目标变量必须是未来值。现象二模型训练时 MAE 很低换一批数据预测误差翻倍。原因过拟合树太深或min_samples_leaf太小模型把训练集噪声也学进去了。解决限制max_depth在 10 到 20 之间min_samples_leaf至少设 2用交叉验证选参数而不是看训练集指标。现象三特征重要性里日期相关的特征排第一。原因把日期直接转成数值如 20240101当特征喂进去了模型会学到「数值越大气温越高」这种伪规律。解决日期字段要么拆成年、月、日、星期几等周期特征要么直接排除不要用原始时间戳。现象四预测曲线整体平移所有预测值都偏高或偏低。原因训练集和测试集的分布不一致比如训练集是冬季数据、测试集是夏季数据。解决按季节分层切分或者用滑动窗口的方式做时间序列交叉验证确保训练和测试覆盖相似的气候条件。现象五n_jobs-1设了但训练还是很慢。原因数据量太大或者n_estimators设了几千。解决先用 100 棵树跑基线确认流程通了再增加树的数量数据量超过十万行时考虑降采样或改用 LightGBM 做对比。注意调参时不要一次性把网格设太大n_estimators和max_depth的组合数很容易爆炸。我一般分两轮第一轮粗调每个参数取 3 个值第二轮在最优值附近细调。5. 预测结果可视化与模型验证让论文图表站得住脚5.1 预测曲线和残差图怎么画才有说服力毕业设计的图表不是画出来好看就行要能回答「模型哪里准、哪里不准、为什么」。我一般画三张图预测值与真实值的时间序列对比、残差分布直方图、残差随时间的散点图。import matplotlib.pyplot as plt import matplotlib.dates as mdates # 图1预测值与真实值对比 fig, axes plt.subplots(3, 1, figsize(12, 10)) axes[0].plot(test_df[date], y_test, label真实值, linewidth1.5) axes[0].plot(test_df[date], y_pred_best, label预测值, linewidth1.5, alpha0.8) axes[0].set_ylabel(平均气温 (°C)) axes[0].legend() axes[0].set_title(随机森林气温预测真实值 vs 预测值) # 图2残差分布 residuals y_test - y_pred_best axes[1].hist(residuals, bins30, edgecolorblack, alpha0.7) axes[1].axvline(0, colorred, linestyle--) axes[1].set_xlabel(残差 (°C)) axes[1].set_ylabel(频数) axes[1].set_title(f残差分布 (均值{residuals.mean():.3f}, 标准差{residuals.std():.3f})) # 图3残差随时间变化 axes[2].scatter(test_df[date], residuals, s10, alpha0.6) axes[2].axhline(0, colorred, linestyle--) axes[2].set_xlabel(日期) axes[2].set_ylabel(残差 (°C)) axes[2].set_title(残差随时间分布) plt.tight_layout() plt.savefig(prediction_analysis.png, dpi150)第一张图看整体趋势拟合情况如果预测曲线明显滞后于真实曲线说明滞后特征不够或者模型对突变响应慢。第二张图看残差是否近似正态分布均值接近 0、标准差小说明无系统性偏差。第三张图看残差有没有随时间出现规律性变化如果某段时间残差持续为正或为负说明模型在该时段存在系统性偏差需要检查是否缺少季节性特征。5.2 交叉验证与基线对比怎么证明随机森林确实更好论文里只放一个模型的指标不够审稿人或答辩老师会问「为什么不用线性回归」「为什么不用 XGBoost」。我一般做三组对比线性回归、决策树、随机森林用同一套训练测试集和同样的评估指标。from sklearn.linear_model import LinearRegression from sklearn.tree import DecisionTreeRegressor models { 线性回归: LinearRegression(), 决策树: DecisionTreeRegressor(random_state42, max_depth15), 随机森林: best_rf } results [] for name, model in models.items(): if name ! 随机森林: model.fit(X_train, y_train) y_pred model.predict(X_test) results.append({ 模型: name, MAE (°C): mean_absolute_error(y_test, y_pred), RMSE (°C): np.sqrt(mean_squared_error(y_test, y_pred)), R2: r2_score(y_test, y_pred) }) import pandas as pd results_df pd.DataFrame(results) print(results_df.to_string(indexFalse))典型结果里线性回归 MAE 可能在 2.5°C 左右决策树 1.8°C随机森林 1.4°C。随机森林的优势来自集成效应降低了单棵树的方差。如果随机森林比决策树提升不明显说明数据里的非线性交互不强或者特征工程没做到位。模型MAE (°C)RMSE (°C)R2线性回归2.5~3.03.2~3.80.70~0.80决策树1.7~2.02.2~2.60.85~0.90随机森林1.3~1.61.7~2.10.90~0.94这张表可以直接放进论文的「实验结果与分析」章节。注意不同数据集的具体数值会有差异关键是随机森林在三个指标上都优于单模型。6. 进阶技巧用滑动窗口做多步预测和模型持久化单步预测只能预测明天实际气象业务里经常需要预测未来三到七天。多步预测有两种策略递归预测和直接多输出。递归预测是用预测出的明天温度当作后天的输入特征误差会累积直接多输出是训练多个模型分别预测未来第 1 天、第 2 天、第 3 天。我一般用直接多输出虽然训练成本高但误差不累积。import joblib # 多步预测为未来1到3天各训练一个模型 horizons [1, 2, 3] multi_models {} for h in horizons: df[ftarget_h{h}] df[avg_temp].shift(-h) df_multi df.dropna().reset_index(dropTrue) split int(len(df_multi) * 0.8) train_m df_multi.iloc[:split] test_m df_multi.iloc[split:] for h in horizons: rf_h RandomForestRegressor( n_estimators200, max_depth15, max_featuressqrt, min_samples_leaf2, random_state42, n_jobs-1 ) rf_h.fit(train_m[feature_cols], train_m[ftarget_h{h}]) y_pred_h rf_h.predict(test_m[feature_cols]) mae_h mean_absolute_error(test_m[ftarget_h{h}], y_pred_h) print(f未来{h}天预测 MAE: {mae_h:.3f} °C) multi_models[h] rf_h # 模型持久化方便后续加载和部署 joblib.dump(best_rf, rf_temp_model.pkl) joblib.dump(feature_cols, feature_cols.pkl) print(模型已保存)多步预测的 MAE 会随预测天数增加而上升第 1 天可能在 1.4°C第 3 天可能到 2.0°C 左右。如果第 3 天 MAE 超过 3°C说明当前特征对中期预测支撑不够可以考虑加入数值天气预报产品的输出作为额外特征。模型持久化用joblib比pickle更高效特别是随机森林这种包含大量数组的对象。保存时把特征列名一起存下来加载时才能保证输入顺序一致。加载后做预测的代码loaded_model joblib.load(rf_temp_model.pkl) loaded_features joblib.load(feature_cols.pkl) # 假设 new_data 是一行新的气象观测需要先做同样的特征工程 # new_X new_data[loaded_features].values # prediction loaded_model.predict(new_X)这里有个血泪经验特征工程的代码一定要和训练时完全一致包括滞后阶数、滚动窗口大小、缺失值处理方式。我见过有人训练时用了 3 天滚动平均预测时忘了算直接拿原始值喂进去结果预测偏差大到离谱。建议把特征工程封装成一个函数训练和预测都调同一个函数从根上杜绝不一致。另外如果你的毕业设计需要展示「实时预测」效果可以用 Flask 或 FastAPI 把模型包成一个简单接口输入当前气象观测返回次日气温预测值。这部分不是必须的但答辩时演示效果会好很多。我自己的习惯是每次改完特征工程代码先跑一遍完整流程确认指标没退化再保存模型。这个习惯帮我省了很多次返工。希望帮到你。本文还有配套的精品资源点击获取