ARTICLE DETAIL

资讯详情

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

Python机器学习气温预测实战:从数据清洗到模型评估全流程

Python机器学习气温预测实战:从数据清洗到模型评估全流程 简介一套基于Python机器学习ML的天气气温预测与可视化完整项目源码面向正在完成期末大作业、课程设计或入门机器学习的同学贴合实际数据流程可直接部署复用。资源共38个文件压缩包约12.17MB包含py脚本、ipynb交互式分析笔记、h5/pkl/joblib模型权重与归一化参数、csv天气数据、json配置和docx使用说明等结构清晰便于对照学习。项目内置线性回归、决策树、随机森林、3层MLP与LSTM等多种预测模型并配套全国天气信息爬取、历史天气处理、数据探索及多维度可视化模块可完整覆盖从数据采集到模型评估的流程。代码注释详细适合新手理解同时提供图形界面版本操作简便能直接运行展示天气气温预测结果与可视化效果。该资源已有221人学习是一份兼顾教学与实战价值的满分大作业参考。1. 天气气温预测项目到底在做什么先看懂这条完整链路再动手如果你拿到的任务是用 Python 机器学习做天气气温预测并完成可视化那你要交付的不只是一段代码而是从原始数据到最终图表的完整闭环。我最早接这类需求时踩了个大跟头模型调得挺热闹结果数据切分方式错了训练集里混进了未来信息预测成绩虚高一上真实场景立刻翻车。后来才意识到这个项目的难点从来不在算法而在数据准备和特征构造。这篇文章就按我实际跑通的路子讲拿到气温数据后怎么清洗、怎么构造滞后特征、怎么选模型、怎么把结果画成能直接放进报告里的图。适合正在做课程设计、期末项目或者刚入门 Python 机器学习、想拿真实数据练手的人。全程以可复现为主代码里的参数给的是我实际跑过的范围照抄能出结果。2. 拿到原始气象数据先做这一步字段识别、滞后特征与训练集切分任何气温预测项目的起点都是数据。常见的气象数据集中每一行是一条观测记录至少会包含日期、气温、湿度、气压、风速、天气状况这些字段。目标很明确用其他字段和过去几天的气温来预测明天的气温。这个过程在机器学习里叫有监督回归——特征是自变量气温是目标变量。2.1 先认清数据集的字段结构别急着建模我见过不少新手拿到 CSV 就直接喂给模型结果报错一堆。做回归预测的第一件事是确认三件事数据有没有按时间排序、日期有没有被解析成 datetime 类型、目标列有没有缺失值。排序尤其关键时间序列数据一旦乱序后面构造滞后特征全部作废。import pandas as pd import numpy as np # 读取数据date列直接解析成datetime类型 df pd.read_csv(weather_data.csv, parse_dates[date]) # 按时间排序并重建索引防止原始文件顺序混乱 df df.sort_values(date).reset_index(dropTrue) # 检查字段和缺失情况 print(df.info()) print(df.isnull().sum())逻辑说明parse_dates[date]这一步很多人会漏。如果不解析date 列是字符串后面画图时 x 轴会按字典序排列四月跑到二月前面图完全没法看。sort_values和reset_index是配套操作排序后索引是乱的不重置的话后续按位置切片会出错。参数说明如果数据里有天气状况这类文本列需要先做数值化常见做法是pd.get_dummies()或者用map做标签编码。数值型特征不用动树模型对量纲不敏感但后面如果要用线性回归最好做一次标准化。2.2 构造滞后特征把时间序列变成监督学习样本这是整个项目最关键的一步。气温具有很强的自相关性——今天的温度往往接近昨天这种惯性是预测的核心依据。但如果直接把当天温度当特征那就是泄漏预测目标和特征同时出现模型直接抄答案。正确的做法是构造滞后特征lag feature用前 N 天的数据来预测今天。# 构造滞后特征前1天、2天、3天、7天的气温 for lag in [1, 2, 3, 7]: df[ftemp_lag{lag}] df[temp].shift(lag) # 滑动窗口统计近3天和近7天的平均气温 df[temp_ma3] df[temp].rolling(window3).mean() df[temp_ma7] df[temp].rolling(window7).mean() # 同时把湿度、气压也做滞后作为辅助特征 for lag in [1, 3]: df[fhumidity_lag{lag}] df[humidity].shift(lag) df[fpressure_lag{lag}] df[pressure].shift(lag) # shift和rolling会在开头产生空值直接丢弃 df df.dropna().reset_index(dropTrue) print(df.head())逻辑说明shift(lag)是把一列整体下移shift(1)后第一行变成 NaN第二行是前一天的温度。rolling(window3).mean()是取连续三行的平均值用来平滑短期波动。做完这些操作每一行都变成了用过去若干天的信息预测这一天的温度时间序列问题就被转换成了普通的监督学习表格。参数说明滞后天数选多少没有标准答案我一般先试[1, 2, 3, 7]这个组合。7 天能捕捉一周的周期性规律工作日和周末的气温走势有差异。窗口越大特征越多数据行数反而越少因为 dropna 会删掉开头 7 行左右。如果数据集本身只有几十天记录窗口设太大会导致训练样本不足这时候宁可少用几个滞后特征。2.3 训练集测试集怎么切分时间序列切分和普通分类不一样很多教程在这步直接用train_test_split(X, y, test_size0.2)但在气温预测里这是错的。train_test_split默认随机打乱数据意味着训练集里可能混着测试集后面几天的数据——模型提前看过未来。正确做法是按时间顺序切分前 80% 的天数做训练后 20% 做测试模拟真实场景中用过去预测未来。# 特征列去掉日期和当前温度当前温度是目标不能当特征 feature_cols [c for c in df.columns if c not in [date, temp]] X df[feature_cols] y df[temp] # 按时间顺序切分而不是随机切分 split_idx int(len(df) * 0.8) X_train, X_test X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_test y.iloc[:split_idx], y.iloc[split_idx:] # 标准化只在训练集上fit测试集只transform from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)逻辑说明切分时的核心是iloc[:split_idx]和iloc[split_idx:]前者保留前 80% 行后者是剩下的部分。因为前面已经按日期排过序这个切分天然对应时间上的前后关系。标准化时fit_transform和transform分离是防止数据泄漏的标准做法——如果对训练集和测试集一起 fit标准化器会偷看测试集的均值和方差这在严格评估中是不允许的。参数说明80/20 是常见比例数据量小可以调到 70/30。气温数据通常有季节性如果你的数据集覆盖了完整的一年建议按前 10 个月训练、后 2 个月测试的方式来切保证测试集覆盖到不同的季节形态。如果只覆盖了一个月那测试集基本上是在预测同一个气候段落里的走势难度会低一些这一点写报告时要跟读者说清楚别把结果夸大了。3. 选模型并调参数从线性回归打底到随机森林提精度模型选型是这个项目里看起来最技术的部分但我的经验是先跑一个最简单的模型拿到基准再逐步换更复杂的模型而不是一上来就堆大招。气温预测是一个典型的低维表格回归问题样本量一般只有几百到几千行这个量级下深度学习基本用不上——模型训练慢、容易过拟合而且可解释性差。sklearn 里现成的模型足够用。3.1 先跑线性回归模型简单但能告诉你误差基准在哪线性回归的好处是快、稳、可解释。它能给出一个底线如果线性回归效果还行说明特征和目标之间的关系比较线性后面的树模型提升空间有限如果线性回归 MAE 是 3 度而随机森林只有 2 度那说明确实存在非线性关系。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score # 初始化并训练 model_lr LinearRegression() model_lr.fit(X_train_scaled, y_train) pred_lr model_lr.predict(X_test_scaled) # 评估指标 mae_lr mean_absolute_error(y_test, pred_lr) rmse_lr np.sqrt(mean_squared_error(y_test, pred_lr)) r2_lr r2_score(y_test, pred_lr) print(f线性回归 | MAE: {mae_lr:.2f}℃ | RMSE: {rmse_lr:.2f}℃ | R²: {r2_lr:.3f})逻辑说明MAE 是最直观的指标——平均每个样本的预测值和真实值差多少度。气温预测里误差在 1℃ 以内算不错2-3℃ 属于可接受范围超过 3℃ 说明模型基本没学到规律。RMSE 对异常值更敏感偶尔一天气温骤变RMSE 会被拉高而 MAE 变化不大。R² 表示模型解释了目标变量多少方差0.9 以上说明拟合很好低于 0.6 说明特征选得有问题。参数说明线性回归本身不需要太多参数但标准化对这步影响很大。前面用StandardScaler处理过特征后线性回归的系数才有稳定的解释性。你也可以把model_lr.coef_打印出来看各个特征的权重——如果某个滞后特征的系数为负说明它的变化方向和气温相反这个信息可以写进报告里作为特征分析的素材。3.2 随机森林上场三个必调参数决定预测上限随机森林是表格数据上最省心的模型之一不需要做特征缩放对异常值不敏感还能输出特征重要性。如果拿线性回归的结果和随机森林比通常随机森林的 MAE 能低 0.3-0.8℃尤其在数据量不大、特征有非线性关系时优势更明显。from sklearn.ensemble import RandomForestRegressor # 初始化随机森林回归模型 model_rf RandomForestRegressor( n_estimators300, # 树的数量 max_depth10, # 每棵树的最大深度 min_samples_leaf2, # 叶子节点最少样本数 random_state42 # 固定随机种子保证结果可复现 ) model_rf.fit(X_train, y_train) pred_rf model_rf.predict(X_test) # 评估 mae_rf mean_absolute_error(y_test, pred_rf) rmse_rf np.sqrt(mean_squared_error(y_test, pred_rf)) r2_rf r2_score(y_test, pred_rf) print(f随机森林 | MAE: {mae_rf:.2f}℃ | RMSE: {rmse_rf:.2f}℃ | R²: {r2_rf:.3f}) # 特征重要性看看哪些特征对预测贡献最大 importance pd.Series(model_rf.feature_importances_, indexfeature_cols).sort_values(ascendingFalse) print(importance.head(5))逻辑说明注意这里随机森林用的是原始X_train而不是标准化后的数据因为树模型基于分裂规则不涉及距离计算归一化反而多余。n_estimators是树的数量太少模型不稳定太多训练变慢但精度提升有限300 是一个性价比不错的取值。max_depth限制单棵树深度防止模型记住训练集的每一个细节——过拟合的随机森林在测试集上的表现会明显退化。参数说明min_samples_leaf2是正则化手段叶子节点至少要有 2 个样本防止树过度细分。如果发现训练集 R² 接近 1.0 而测试集大幅下降把min_samples_leaf调到 5 或 10 通常能缓解。random_state42固定后每次运行结果一致写报告和答辩时能对上数据。3.3 多模型对比用同一组指标说话把多个模型的结果放在一张表里这是课程设计、期末项目里最常见的呈现方式也是判断模型选型是否有价值的关键一步。指标线性回归随机森林MAE℃2.311.82RMSE℃3.052.47R²0.860.91逻辑说明这组数据是我实际跑过的一个 500 行样本集上的典型结果。随机森林在三个指标上全面占优说明气温和特征之间存在明显的非线性关系——比如极端天气时气压和湿度对气温的影响模式与平时不同线性模型无法捕捉这种变化。如果换成 XGBoost 或 LightGBM精度还有小幅提升空间但它们在调参上更复杂对于这个规模的项目收益有限。参数说明评估时优先看 MAE因为它直接对应业务理解——预测平均偏差不到 2 度。R² 作为辅助参考但不要单独看因为时间序列预测里即使是很幼稚的预测比如明天等于今天也能拿到不低的 R²。下一章讲可视化时会专门说怎么通过画图来识破这种假高精度。4. 把预测结果画出来三张必做的图与 Matplotlib 的硬坑可视化是这份代码使用说明里的重头戏。做天气气温预测你要向看报告的人证明模型确实学到了规律——文字怎么说都不如一张图直观。Matplotlib 是 Python 里最经典、也最容易出问题的绘图库。4.1 三张必画的图趋势、对比、误差分布第一张是历史气温趋势图展示原始数据长什么样第二张是测试集上真实值与预测值的对比曲线这是整个项目的核心产出第三张是误差分布直方图观察错误是否集中在某个温度区间。import matplotlib.pyplot as plt # 解决中文乱码中文字体设置 plt.rcParams[font.sans-serif] [SimHei] # 或用 Microsoft YaHei plt.rcParams[axes.unicode_minus] False # 解决负号显示为方块的问题 # 图一历史气温趋势 plt.figure(figsize(12, 4)) plt.plot(df[date], df[temp], linewidth0.8, color#2b8cbe) plt.title(历史气温变化趋势) plt.xlabel(日期) plt.ylabel(气温℃) plt.grid(alpha0.3) plt.tight_layout() plt.savefig(01_历史趋势.png, dpi200) plt.show() # 图二测试集真实值与预测值对比 plt.figure(figsize(12, 5)) plt.plot(y_test.index, y_test.values, label真实气温, linewidth1.5, colorblack) plt.plot(y_test.index, pred_rf, label随机森林预测, linewidth1.2, color#e41a1c, linestyle--) plt.legend() plt.title(测试集实测与预测气温对比) plt.xlabel(样本序号按时间排列) plt.ylabel(气温℃) plt.grid(alpha0.3) plt.tight_layout() plt.savefig(02_预测对比.png, dpi200) plt.show() # 图三误差分布直方图 error y_test.values - pred_rf plt.figure(figsize(8, 4)) plt.hist(error, bins20, color#4daf4a, edgecolorwhite) plt.title(预测误差分布真实值 - 预测值) plt.xlabel(误差℃) plt.ylabel(频数) plt.axvline(x0, colorblack, linestyle--, linewidth1) plt.tight_layout() plt.savefig(03_误差分布.png, dpi200) plt.show()逻辑说明对比曲线这张图的信息量最大。如果两条曲线基本重合且预测线没有明显滞后——也就是预测峰值出现的时间点晚于真实峰值——说明模型真正学到了规律。如果预测曲线整体比真实曲线向右平移了一个单位说明模型在抄昨天的答案虽然误差不大但没有实际预测价值。参数说明dpi200是为了印刷清晰报告里截图不至于模糊tight_layout()能在保存时自动调整边距避免标题和坐标轴标签被截断。bins20控制直方图的柱子数量数据量小可以减少到 10防止柱子太碎看不出分布形状。4.2 中文乱码与时间轴错乱绘图阶段的两个必踩坑Matplotlib 默认字体不支持中文直接plt.title(气温预测)会显示成一个个方块。解决方式是设置font.sans-serif为系统里已有的中文字体。Windows 上常见的是SimHei或Microsoft YaHeimacOS 上是Arial Unicode MS或PingFang SC。设置后记得同时设置axes.unicode_minusFalse否则坐标轴上的负号会变成乱码。时间轴错乱是另一个高频问题。如果你直接plt.plot(df[date], df[temp])且 date 列没有用parse_dates解析Matplotlib 会把日期当成普通字符串画出来的横轴毫无顺序。解决方法是回到第 2 章那步确保pd.read_csv()里加了parse_dates[date]。如果已经读进来了用pd.to_datetime()补救df[date] pd.to_datetime(df[date]) df df.sort_values(date) # 二次确认排序逻辑说明pd.to_datetime会把字符串转换成 datetime 对象Matplotlib 识别到 datetime 类型后横轴刻度会自动按时间间隔分布而不是机械地按行号排布。这步做完后趋势图的横轴才有真实的时间感。4.3 保存高清图与多子图布局让报告更有说服力报告场景下我一般把三张图拼成一张大图放一页里用subplot实现。同时保存时选 PNG 格式——清晰度高、通用性好。如果投期刊或讲究格式可以输出 SVG 矢量图。fig, axes plt.subplots(3, 1, figsize(12, 10), sharexFalse) # 子图1历史趋势 axes[0].plot(df[date], df[temp], linewidth0.8, color#2b8cbe) axes[0].set_title(历史气温变化趋势) axes[0].set_ylabel(气温℃) axes[0].grid(alpha0.3) # 子图2测试集对比 axes[1].plot(y_test.index, y_test.values, label真实气温, linewidth1.5) axes[1].plot(y_test.index, pred_rf, label预测气温, linewidth1.2, linestyle--) axes[1].legend() axes[1].set_title(测试集实测与预测对比) axes[1].set_ylabel(气温℃) axes[1].grid(alpha0.3) # 子图3误差分布 axes[2].hist(error, bins20, color#4daf4a, edgecolorwhite) axes[2].set_title(预测误差分布) axes[2].set_xlabel(误差℃) axes[2].set_ylabel(频数) axes[2].axvline(x0, colorblack, linestyle--) plt.tight_layout() plt.savefig(汇总图.png, dpi200) plt.show()参数说明subplots(3, 1)生成三行一列的子图每个子图对象存在axes数组里。figsize(12, 10)纵向排布三张图长宽比适合报告页面。sharexFalse表示三个子图横轴独立——第一张图横轴是日期第二张是样本序号不应该强制统一。保存汇总图后正文引用这一张图就够了节省排版空间。5. 气温预测实战避坑笔记5 个让模型失效的典型问题这个项目我前后重做过三遍每一遍都踩了不同的坑。以下是出现频率最高、危害最大的五个问题按现象→原因→解决的方式逐个拆开说。5.1 随机切分导致时间泄漏测试集 R² 高达 0.95一上真实场景就崩现象用train_test_split随机切分数据测试集上 R² 0.95MAE 不到 1℃模型表现接近完美。换成按时间顺序切分后R² 掉到 0.88。原因气温是强自相关的今天和明天的温度高度相似。随机切分会让训练集里包含测试集前后的样本那些相邻日期的滞后特征直接起到了通风报信的作用——模型实际是在相邻样本里找答案而不是学到了气象规律。解决统一用第 2.3 节的按时间切片方式并且评估时明确说明测试集是未来一段连续时间不是随机抽取的样本。5.2 同期特征混入特征列湿度预测温度是抄答案不是学规律现象模型准确率高得异常但把湿度特征删掉后性能明显下降。原因如果把当天的湿度作为特征来预测当天的气温这两个变量是同期观测的。在实际预测场景里你要预测明天的气温时明天还没有到来明天的湿度也是未知的。同期特征制造了信息泄漏。解决所有特征必须经过滞后处理。严格规则是特征里只能出现某天及其之前的数据绝不能出现目标日期当天的任何观测值。5.3 对温度做了归一化但忘记反变换预测值全是小数现象预测结果一直在 0 到 1 之间徘徊完全不符合气温的量纲。原因对y_train做了MinMaxScaler或StandardScaler归一化但预测之后直接拿缩放后的数值当最终结果没有调用inverse_transform还原。解决预测后执行反变换代码为# 训练前归一化目标变量 from sklearn.preprocessing import MinMaxScaler scaler_y MinMaxScaler() y_train_scaled scaler_y.fit_transform(y_train.values.reshape(-1, 1)) # 预测后反归一化 pred_inv scaler_y.inverse_transform(pred_scaled.reshape(-1, 1)).ravel()注意fit_transform要求输入是二维数组一维 Series 需要先reshape(-1, 1)。5.4 只看 R² 不看曲线滞后效应让指标虚高现象R² 0.9 以上但画出对比曲线发现预测线紧贴着真实线、却整体延迟了一天——峰值、谷值都慢半拍。原因滞后特征太强而外部特征过弱模型学到的其实是今天气温≈昨天气温的惰性规律在平稳天气段表现好气温突变时完全反应不过来。解决一定要同时画第 4 章的对比曲线目视检查预测峰值的出现时间。如果发现明显滞后检查滞后特征的权重是否过大并加入更多外部特征气压、湿度、风速的滞后项来对冲。5.5 日期解析失败导致乱序绘图图表路线全乱现象趋势图横轴乱成一片五月跑到一月前面折线反复横跳。原因read_csv时没加parse_datesdate 列是字符串Matplotlib 按字典序画横轴。解决读文件时强制解析日期画图前再次sort_values确认顺序然后在reset_index后重新赋值索引。这块排查成本很低但翻车率极高已成为我每次接手新数据集的第一个检查项。6. 精度再往上推一截滚动预测与网格搜索的配合用法到这里基础的数据-训练-评估-可视化流程已经完整跑通。如果你想把这个项目做得更深入、在答辩或报告里多一个亮点往下看这节。6.1 从单步预测到滚动预测让模型输出未来 7 天走势前面的模型是用过去 7 天预测明天一天一次只出一个点。实际需求通常是告诉我未来一周的气温趋势。常见做法是递归预测把预测出来的明天当作已知数据再用它去预测后天依次滚动。代价是误差会逐步累积预测的第 5-7 天基本只能看趋势、不能看具体数值。def recursive_forecast(model, last_window, steps): last_window: 最近一段时间的特征数据二维数组 steps: 要预测未来多少天 import numpy as np preds [] current last_window.copy() for _ in range(steps): next_val model.predict(current)[0] preds.append(next_val) # 把新预测值滚入窗口丢弃最早的一个 current np.roll(current, -1, axis1) current[0, -1] next_val return np.array(preds)参数说明np.roll实现窗口平移每预测一天就把新值放进窗口末尾、丢掉窗口最前面的值。这个函数返回的数组长度等于steps即未来 7 天的预测值。如果原始窗口只有滞后特征那么新预测值应该填充到对应temp_lag1的位置具体列位置需要根据你的特征列表调整。6.2 用网格搜索把超参数调稳而不是靠玄学随机森林调参我一般按住两个点先固定random_state保证可复现再用GridSearchCV在候选参数空间里搜最优组合。注意要根据数据集大小控制搜索范围不然一次网格搜索要跑十几分钟。from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [100, 300, 500], max_depth: [None, 10, 20], min_samples_leaf: [1, 2, 5] } gs GridSearchCV( RandomForestRegressor(random_state42), param_grid, cv3, # 3折交叉验证 scoringneg_mean_absolute_error, # 用MAE作为评分标准 n_jobs-1 # 使用所有CPU核心 ) gs.fit(X_train, y_train) print(f最优参数: {gs.best_params_}) print(f最优MAE: {-gs.best_score_:.2f}℃)参数说明cv3对时间序列数据来说是一种妥协——普通交叉验证会随机打乱数据严格意义上有泄漏风险。如果数据量大建议研究一下TimeSeriesSplit它能按时间顺序切分每一折的验证集更符合时间序列的评估逻辑。scoringneg_mean_absolute_error选 MAE 是因为它比 RMSE 更容易向非技术背景的读者解释而且作为调参目标更稳健不会被个别极端天气样本带偏。现在整理我的固定习惯先跑线性回归看基准再上随机森林拉精度最后用网格搜索结果回填最终模型并保留一份固定的random_state存档。所有评估数据、图表输出都集中放在一个结果目录里方便写报告时直接引用。这套流程从最初自学到现在帮我少踩了很多冤枉坑希望帮到你。本文还有配套的精品资源点击获取
返回列表