ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Python机器学习的气温预测项目实战全流程解析

基于Python机器学习的气温预测项目实战全流程解析 简介这份基于Python机器学习ML的天气气温预测与可视化项目是一套适合期末大作业、课程设计场景的完整源代码包面向有一定Python基础、希望获得高分案例参考的学生同时包含详细代码注释降低阅读门槛。资源共38个文件压缩包大小12.17MB包含10个Python脚本、4个Jupyter Notebook、多个训练好的模型文件h5/pkl与scalerjoblib以及天气CSV数据、城市JSON配置、GUI版本源码和使用说明文档。脚本覆盖数据爬取、模型训练、测试预测与界面展示Notebook便于分步骤复现算法流程。目前已有222人学习下载尤适合需要快速完成课程作业的新手参考。包内还提供全国与临沧历史天气爬取脚本、多模型对比3层MLP、LSTM、线性回归、决策树等并附有正式版与最简版GUI及图文手册部署简单可直接运行并用于答辩演示。1. 从「气温预测」到「能跑通的ML项目」标题背后的完整闭环很多人第一次看到这个标题以为它只是一份跑个模型、画两张图的课程作业其实把源代码和使用说明完整过一遍你会发现它是一整套数据工程闭环从CSV原始数据开始清洗、特征工程、建模、评估、可视化每一步都有代码支撑。基于python机器学习(ml)的天气气温预测本质上是一个时间序列回归任务——用历史气温和外部特征预测未来平均气温难度不高但流程完整性要求很高。这个方向适合两类人一类是做课程设计或本科毕业设计的同学需要能讲清楚、能现场演示的完整项目另一类是刚入门机器学习的从业者想找一条覆盖数据处理、模型选型、结果可视化的标准应用流程而不是反复刷理论却不知道真实项目长什么样。它解决的核心问题是把气温预测从一句口号变成可运行、可评估、可展示的代码。2. 数据准备与清洗气温数据不会主动告诉你它有多脏2.1 数据源怎么选本地CSV优先别去爬网页常见做法是准备一份历史日值气象数据字段至少要有date、temp_mean、temp_max、temp_min如果还能带上humidity、pressure、wind_speed更好。公开气象数据渠道并不少城市级别的日气象记录导出成CSV后放到项目根目录就能用。我一般不建议在这个环节写爬虫去抓网页反爬带来的IP问题、页面结构调整后选择器失效都会让一个本该聚焦在机器学习上的项目变成爬虫调试现场。对课程设计和入门练手来说一份干净的本地CSV能省掉大量和项目主题无关的时间。读取数据之前先确认环境里装了pandas、numpy、scikit-learn、matplotlib这几样基础库版本不需要太新能跑通即可。pandas读取数据后第一件事不是建模而是用几行命令看清数据长相import pandas as pd df pd.read_csv(weather.csv, encodingutf-8) print(df.shape) print(df.head()) print(df.dtypes) print(df.describe())df.shape告诉你数据有多少行多少列df.dtypes能看出date是不是object字符串类型、气温列是不是数值类型df.describe()输出的min和max如果出现85度这类数值说明数据里混入了坏点。这一步不要跳过后面所有特征工程都建立在字段类型正确的前提上。如果读文件报编码错误把encoding换成gbk试一次再不行就用utf-8-sig这算是气象CSV最常见的打开方式。2.2 日期、缺失值和异常值三步把数据洗干净数据清洗的固定流程是四步转日期格式、按时间排序、填充缺失、剔除异常。顺序不能乱尤其是排序必须在填充之前做否则interpolate插值会按行号而不是按时间插出来的结果只是“看起来连续”实际时间关系是错的。df[date] pd.to_datetime(df[date]) df df.sort_values(date).drop_duplicates(subsetdate) df[temp_mean] df[temp_mean].interpolate(methodtime) df df[(df[temp_mean] -35) (df[temp_mean] 45)] df df.reset_index(dropTrue)pd.to_datetime把日期字符串变成时间类型这样后面才可以做滞后特征和滚动窗口sort_values按时间排序之后drop_duplicates能去掉同一天的重复记录。interpolate(methodtime)会按时间间隔做线性插值这是气象数据缺测最常用的处理方式不建议用全局均值填充全局均值会把冬天填出夏天的数字。最后的区间过滤针对的是传感器故障-35到45这个范围覆盖了中国绝大多数有人居住的地区超过45度的气温基本可以判定是坏点直接剔掉比留着拉偏模型更安全。如果某段数据连续缺失超过一周interpolate也救不回来这种情况下宁可直接删掉那段区间也不要让插值创造出一段“假气象记录”。气象数据缺失往往是设备故障造成的不是随机缺失机器学习的假设在这里不成立所以处理手段要保守。2.3 特征工程把日历变成模型能懂的数值机器学习模型能处理的只有数值日期本身不能直接喂给RandomForestRegressor。这一步真正的核心是把周期性编码出来。气温有明显的年周期和星期周期特征通常拆成两部分第一部分是日历特征第二部分是滞后项和滚动均值。气温有惯性今天的温度大概率接近昨天和上周同一天所以滞后特征要构造temp_lag_1、temp_lag_7、temp_lag_14分别表示一天前、一周前、两周前的平均气温。加上季节、月份这类周期特征模型才能区分“这是冬天还是夏天”。def build_features(df): df[year] df[date].dt.year df[month] df[date].dt.month df[day] df[date].dt.day df[weekday] df[date].dt.weekday df[season] df[month] % 12 // 3 for lag in [1, 2, 3, 7, 14]: df[ftemp_lag_{lag}] df[temp_mean].shift(lag) df[temp_roll_mean_7] df[temp_mean].rolling(window7).mean().shift(1) return df.dropna()season由month计算得到12、1、2月是03、4、5月是1以此类推比手写if-else判断快得多。shift(lag)取的是前lag天的数据生成之后必须dropna否则前14行会带NaNsklearn训练时会直接报错。temp_roll_mean_7是近7天滑动平均注意后面还跟着一个shift(1)原因是滚动均值计算时包含了当天温度如果不shift模型就“偷看了今天的答案”这是最典型的数据泄露场景。到这里一份能直接喂给sklearn的数据就准备好了X是特征列y是temp_mean。源码交付时把这个函数封装好别人只需要调用build_features就够了。3. 用Python机器学习建模从基线到对比让预测结果有依据3.1 气温预测是回归任务不是分类任务很多人一上来就问要不要上LSTM、Transformer其实先要想清楚问题类型。平均气温是一个连续数值不是“热、冷、适中”三档分类所以损失函数用MSE或MAE预测层不做非线性映射评估用回归指标这是机器学习里最标准的回归问题。单层LSTM这类时序网络对这个体量的数据帮助不大一是调参成本高二是带滞后特征的传统模型已经能拿到足够好的误差课程设计场景最怕的是模型复杂度上去了但解释跟不上。回归问题的另一个特点是特征尺度差异大气温是几十的量级气压是上千的量级如果用线性模型必须做标准化。但随机森林这类树模型不受影响所以作为入门基线非常合适。3.2 跑通最小基线随机森林十分钟上手推荐随机森林做第一版模型原因是它对特征尺度不敏感气温、湿度、气压不在一个量纲上用线性回归还得先做标准化随机森林不用。其次它对异常值和缺失容忍度高fit之前不需要做精细的分布变换。对折腾数据的人来说能少一步是一步。from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error feature_cols [year, month, day, weekday, season, temp_lag_1, temp_lag_2, temp_lag_3, temp_lag_7, temp_lag_14, temp_roll_mean_7] X df[feature_cols] y df[temp_mean] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, shuffleFalse, random_state42 ) model RandomForestRegressor(n_estimators200, max_depth10, random_state42) model.fit(X_train, y_train) y_pred model.predict(X_test) print(MAE :, mean_absolute_error(y_test, y_pred)) print(RMSE:, mean_squared_error(y_test, y_pred, squaredFalse))n_estimators先给200训练完看验证误差再翻倍翻倍后误差变化不到0.1就说明收益饱和max_depth先给10防止树太深记住训练集噪声。这个模型里最关键的不是这两个参数而是train_test_split里的shuffleFalse。气象数据是时间序列只能拿前面的日期训练、后面的日期验证。如果shuffleTrue训练集会混入未来数据模型等于开卷考试测试误差再好看也没有参考价值部署到新数据上立刻翻车。3.3 模型怎么选线性回归、随机森林、梯度提升的取舍与调参基线跑通之后我一般会做一次横向对比这也是源码里比较加分的部分。常见做法是拿LinearRegression、RandomForestRegressor、GradientBoostingRegressor三个模型对比分别代表线性模型、Bagging集成、Boosting集成三个方向。模型训练速度调参重点典型使用场景LinearRegression最快基本不用调做基线对比、检验线性关系RandomForestRegressor快n_estimators、max_depth、min_samples_leaf结构化数据默认首选GradientBoostingRegressor慢learning_rate、max_depth、n_estimators对精度有更高要求时统一对比脚本如下from sklearn.linear_model import LinearRegression from sklearn.ensemble import GradientBoostingRegressor models { LinearRegression: LinearRegression(), RandomForest: RandomForestRegressor(n_estimators200, max_depth10, random_state42), GradientBoosting: GradientBoostingRegressor(n_estimators200, learning_rate0.05, max_depth4, random_state42), } for name, model in models.items(): model.fit(X_train, y_train) pred model.predict(X_test) mae mean_absolute_error(y_test, pred) rmse mean_squared_error(y_test, pred, squaredFalse) print(f{name:20s} MAE{mae:.3f}°C RMSE{rmse:.3f}°C)实际跑气温数据时随机森林和梯度提升互有胜负谁赢取决于数据量和滞后特征的强度。需要提醒的是GradientBoostingRegressor如果保持learning_rate默认0.1n_estimators拉到200训练集误差会非常低但验证集反而变差这是典型的过拟合。把learning_rate降到0.01到0.1之间配合提前停止才是稳妥做法。LinearRegression在这个任务里通常MAE偏高但它的价值是作为下限参考——如果随机森林连线性回归都没明显超过要先检查特征工程而不是继续调参。3.4 评估指标MAE、RMSE和R²各看什么气温预测最直观的指标是MAE单位就是摄氏度误差2.5意味着平均偏差2.5度。RMSE对大误差更敏感如果RMSE明显大于MAE说明存在少数极端日子的预测误差很大这时要回头检查是不是寒潮、热浪这类异常天气样本没处理。至于R²在时间序列回归里容易被滞后特征抬得很高气温惯性极强昨天的温度对今天的预测贡献巨大R²接近0.98不代表模型对天气突变有解释能力只能说明它学会了“今天和昨天差不多”。所以汇报时我会同时列MAE、RMSE和R²三个数字并提醒看的人重点关注MAE这个数字最容易翻译成业务语言。4. 从数字到图表Matplotlib和Pyecharts的可视化落地4.1 可视化要回答的真实问题趋势、分布、误差可视化在这个项目里不是画得好看而是要回答三个具体问题预测值跟真实值是不是贴合、误差集中在什么范围、模型认为哪个特征最重要。常见的源码里可视化部分至少包含三个图真实值-预测值对比折线图、残差散点图、特征重要性柱状图。展示思路是从Matplotlib静态图起步再把关键图导出成Pyecharts交互HTML用于答辩和汇报。4.2 Matplotlib真实值、预测值与残差图第一步先解决中文乱码Windows下用SimHeiLinux服务器上没有中文字体就直接用英文坐标标签避免显示成方框。然后是x轴横坐标太密集的问题这是个高频问题测试集有几百个日期全部画进去就是一条黑带。处理非常简单只画最近90天。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False test_df X_test.copy() test_df[real] y_test.values test_df[pred] y_pred test_df test_df.tail(90) plt.figure(figsize(12, 5)) plt.plot(test_df.index, test_df[real], label真实气温, linewidth1) plt.plot(test_df.index, test_df[pred], label预测气温, linewidth1, linestyle--) plt.title(气温预测真实值 vs 预测值最近90天) plt.xlabel(日期) plt.ylabel(平均气温 (°C)) plt.legend() plt.grid(alpha0.3) plt.show()tail(90)只保留最后90个样本折线图看起来清楚很多。figsize(12,5)让横向宽度够长避免曲线挤在一起linewidth1适合长序列太粗会盖住真实值。残差图用散点residual y_test.values - y_pred plt.figure(figsize(12, 4)) plt.scatter(X_test.index, residual, s4, alpha0.6) plt.axhline(y0, colorred, linestyle--) plt.title(残差分布) plt.xlabel(日期) plt.ylabel(误差 (°C)) plt.grid(alpha0.3) plt.show()残差如果在0轴附近随机震荡说明模型没有明显的系统性偏差如果某段时间残差连续为正说明模型系统性低估了那次寒潮或热浪。看到这个图才知道要不要额外构造“近期温度变化率”这类特征去捕捉突变。4.3 Pyecharts交互式HTML让汇报更有说服力Matplotlib在Jupyter里能看但导出给导师看的静态图不够直观。Pyecharts把同样的数据渲染成一个带缩放、悬停提示的HTML页面浏览器打开即可看不需要额外装插件展示性和通用性都比静态图好很多。from pyecharts.charts import Line from pyecharts import options as opts dates X_test.index.strftime(%Y-%m-%d).tolist() line ( Line() .add_xaxis(dates) .add_yaxis(真实气温, y_test.tolist(), is_smoothTrue) .add_yaxis(预测气温, y_pred.round(2).tolist(), is_smoothTrue) .set_global_opts( title_optsopts.TitleOpts(title气温预测真实值 vs 预测值), tooltip_optsopts.TooltipOpts(triggeraxis), datazoom_opts[opts.DataZoomOpts(range_start60, range_end100)], ) ) line.render(temperature_forecast.html)add_xaxis需要传入列表所以dates用strftime把时间索引转成字符串y_pred先round(2)再转列表避免悬停提示里出现一长串小数。range_start60让页面一打开就聚焦最后40%的数据正好是测试集最关键的预测段。render会在项目目录下生成一个独立html文件答辩前拖进浏览器鼠标滑过就能看到每天的真实值和预测值这种细节在验收时比一堆print输出有说服力得多。5. 避坑笔记跑气象ML项目最常见的五个翻车现场5.1 训练集和测试集随机拆分把时间顺序打乱了现象模型MAE只有0.5验证图完美贴合但换一批新数据预测结果离谱。原因train_test_split默认shuffleTrue随机拆分让模型在训练时看到了未来的气温本质上是在作弊。时间序列数据一旦混入未来信息评估指标就失去参考价值。解决拆分时显式传入shuffleFalse让训练集永远是验证集之前的时间段。更严谨的做法是用TimeSeriesSplit做交叉验证而不是普通的KFold这个问题在气象、金融、销量预测里都会出现属于机器学习应用流程里最容易忽略的一环。5.2 数据里有80度的“传感器故障值”现象训练出的模型在冬天预测值普遍偏高MAE始终压不下去。原因极端异常值把回归目标整体拉偏模型为了拟合那个80度样本不得不把权重向高温区调整结果就是普通日子的预测也被带偏。气象数据里的异常值不服从随机噪声假设它通常是设备故障、传输错误造成的系统性错误。解决在特征工程之前做温度合理区间过滤用-35到45的硬区间比用3倍标准差更安全因为气温数据的季节性很强正态性假设本身不成立。过滤之后再算统计量会发现标准差和均值都稳定很多。5.3 lag特征“偷看未来”预测结果像复读机现象预测曲线和真实曲线几乎重合但整体向右平移了一天看起来就像把昨天的真实值挪到了今天。原因构造temp_lag_1时有人把shift(1)写成了不shift等于用当天的温度预测当天模型自然抄答案。滚动均值也有同样问题rolling(window7).mean()计算时包含了当天值不shift的话就泄露了。解决所有滞后特征统一shift(k)k必须大于等于1滚动均值末尾也要加shift(1)确保模型只能看到过去不能看到现在。这是时间序列特征工程里最重要的一条铁律比选什么模型都关键。5.4 模型保存后再预测报“feature mismatch”现象训练阶段完全正常用joblib.load加载模型之后对新数据predict报错说特征数或特征名对不上。原因sklearn的模型只保存参数不保存训练时的列顺序第二次构造DataFrame时列顺序变了模型按位置读取时错位。这个问题一旦换环境或换数据集就会出现属于源码交付时最常见的bug。解决训练完立刻把feature_cols一起保存预测前用reindex按原顺序对齐特征列。import joblib # 训练结束时 joblib.dump(model, models/temp_model.pkl) joblib.dump(feature_cols, models/feature_cols.pkl) # 预测新数据时 loaded_features joblib.load(models/feature_cols.pkl) new_df new_df.reindex(columnsloaded_features) pred joblib.load(models/temp_model.pkl).predict(new_df)这段代码看起来简单但能省掉你在答辩现场对着报错信息发呆的十分钟。模型文件命名也建议带上日期或版本比如temp_model_v1.pkl比model.pkl更容易做后续迭代管理。5.5 中文乱码和Pyecharts白屏现象Matplotlib图上中文全变方框Pyecharts渲染出的html打开是空白。原因系统缺少SimHei这类中文字体或者Pyecharts版本升级后旧博客里的API写法不兼容。这两个问题几乎每个用python画图的人都会撞上属于视觉呈现环节的经典坑。解决Matplotlib侧在绘图前设置plt.rcParams[font.sans-serif] [SimHei]和axes.unicode_minusFalseWindows下基本能解决如果服务器上没有中文字体最简单的办法是坐标轴和标题都改用英文。Pyecharts侧先pip show pyecharts确认版本再按当前版本对应的set_global_opts写法调用别直接复制两三年前的博客代码。另一个办法是锁定旧版本比如pyecharts就是1.9.x新项目不急着升级。这个坑不解决图表的可读性会大打折扣再好的预测结果也展示不出来。6. 验证与收尾让这套源码看起来像一份合格的交付6.1 用TimeSeriesSplit做交叉验证而不是K折常规KFold用在时间序列上同样会造成未来数据混入训练集正确做法是按时间顺序切分每一折的训练集始终在当前验证集之前。实现很简单from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_idx, val_idx in tscv.split(X): X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y.iloc[train_idx], y.iloc[val_idx]这样五次评估都来自“用过去预测未来”最后取平均值比单次固定测试集更有说服力。答辩时被问到“为什么不用KFold”这个点就是加分项。6.2 模型保存与使用说明的对齐一个能交付的项目不能只在Jupyter里跑通。我一般会把模型用joblib保存到models目录并在使用说明里写清楚三件事环境依赖、运行顺序、参数含义。预测新数据时确保特征列用保存过的feature_cols重新对齐这一步比调参更能避免交付后被打回。我做完这类项目后有一个习惯把源数据版本、特征列表、最终模型参数整理成一张表放进使用说明然后删掉所有输出重新跑一遍脚本确认新环境里执行顺序没问题。别人照着说明十分钟能复现这份代码才算真正完整。如果你也打算往这个方向走希望这篇笔记能帮你少踩几个我当年踩过的坑希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进