简介面向计算机相关专业课程设计与期末大作业场景提供一套基于 Python 机器学习的天气预测与可视化完整项目。包含数据采集、处理、建模、预测与可视化展示的典型流程适合正在完成课程设计、期末大作业的学生也适合需要项目实战练习的入门学习者。压缩包共 24 个文件包含 4 个 Python 源码文件、4 个 CSV 数据集、1 个训练好的 pkl 模型、1 个 HTML 可视化页面、12 张结果截图及说明文档整体仅 1.42 MB结构清晰、轻量易用。项目已经过严格调试下载后可直接运行可快速得到天气预测结果与可视化图表便于对照源码理解机器学习项目从数据到落地的完整链路。目前已有 249 人学习下载适合作为课程设计、期末大作业或入门实战的参考资料能显著节省从零搭建和调试的时间。1. 用 Python 机器学习做天气预测和天气可视化到底交付什么计算机、大数据甚至地理信息专业里“基于 Python 机器学习的天气预测与天气可视化”是一道特别经典的课程设计题。它拆开就是三件事把历史气象数据洗成能喂给模型的形态用机器学习算法拟合出温度或降水的变化规律再把规律画成老师一眼能看懂的趋势图、对比图和相关性图。这门课设真正考察的不是预测准不准这一个点而是数据处理、特征工程、模型训练和可视化表达这条链路的完整度完整跑通并自圆其说才是拿到高分的关键。这篇笔记适合两类人一类是准备复现或改造这个大作业的在校生另一类是刚接触 sklearn 生态、想找个有数据又有结果的入门项目练手的开发者。我按实际带过这类项目的流程来讲模型选型、清洗思路、特征构造、训练评估、可视化与避坑一次过完。2. 做天气预测先定模型线性回归、随机森林还是 LSTM2.1 机器学习与经典时序模型的选择为什么课程设计默认走 sklearn 路线天气数据天生带时间序列属性所以很多人在动手前会纠结到底用时间序列模型还是机器学习模型如果你去做纯预测方向ARIMA、Prophet、指数平滑都能用statsmodels 也提供完整实现。但“机器学习(ml)”这个大作业的常见做法是把预测改造成监督学习问题用预测日之前能观测到的历史特征去回归“明天的平均气温”或“明天是否下雨”然后用 sklearn 里的模型完成训练和评估。这样处理的最大好处是评估闭环方便——mean_absolute_error、r2_score、classification_report这些指标直接拿来就能写进报告答辩时有据可查。为什么课程设计普遍不直接上时间序列模型一方面是时间序列模型的分解、定阶、平稳性检验讲起来很绕几千行数据也未必能讲出花来另一方面随机森林、梯度提升这类树模型对多变量特征的兼容性更强日期、温湿度、气压、风速、滞后项一股脑放进去它自己会挑效果往往不输经典时序法。至于机器学习入门书里常说的线性回归在这个题目里更适合当“基准线”用——先跑一个线性回归看分数再上随机森林看提升报告里就有对比素材。LSTM 是很多人会惦记的选项我一般不建议在这个数据量上直接选。日常气象记录噪声大且周期复杂LSTM 需要凑足够长的窗口和较多训练轮次才能压住过拟合几千行的数据量很容易学成“复读昨天”。课程设计的真实诉求是稳定可演示而不是秀复杂度随机森林或梯度提升在单机 CPU 上几十秒出结果稳定性远好于调参靠玄学的深度学习。模型选择上没有绝对对错但追求“完成度优先”时顺序一般是线性回归做基线随机森林或梯度提升做主模型LSTM 只作为论文里一段“更复杂的备选方案”来提。2.2 拿到项目源码包先看什么数据格式与目录划分这一类“源码全部数据”的压缩包解压后常见的目录划分是 data 目录放原始 CSVscripts 目录放预处理、建模、可视化脚本根目录放一个总入口和依赖列表。大作业评分时源码结构完整程度直接影响第一印象。规范化一点的结构长这样weather_project/ ├── data/ │ └── weather_data.csv # 历史气象观测数据 ├── scripts/ │ ├── data_preprocess.py # 读取、清洗、特征工程 │ ├── train_model.py # 训练与评估 │ ├── visualize.py # 绘图 │ └── main.py # 串联整个流程 └── requirements.txt如果你想快速复现全部逻辑写进一个main.py也能跑但拆成四个文件、每个文件只干一件事后面排查问题会省很多时间。先看数据的这一步最常见的操作是先把 CSV 读进来确认列名和日期格式import pandas as pd df pd.read_csv(data/weather_data.csv, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) print(df.info()) # 看列名、各列类型、有没有空值 print(df.head()) # 看前五行确认样式和单位 print(df[date].min(), df[date].max()) # 确认时间跨度parse_dates[date]是把日期字符串解析成 pandas 的 datetime 类型这决定了后面按时间排序、取月份、画时间轴是否顺畅。一个很容易踩的细节是不同来源的 CSV 列名差异很大有的叫temperature有的叫temp有的干脆是中文列名“平均气温”。我通常会先做一个列名映射表统一改成小写英文字段名后面所有代码只用一套字段名绘图时再用另一个字典映射回中文标题。这样代码一旦写好换一个数据源只是改映射表的事不用满文件找字符串替换。天气数据常见的字段包括日期、最高/最低/平均气温、相对湿度、气压、风速、降水量、天气现象编码。做 95 分大作业通常不需要所有列都上场按任务选 6~8 个有物理意义的字段就行。列数太多反而让特征重要性图变得杂乱答辩时解释不清。3. 天气预测的数据处理与模型训练把 CSV 跑通成一条预测流水线3.1 机器学习中的数据处理缺失值、异常点、日期编码与滞后特征天气数据从网站上抓下来或从气象站导出最常遇到三类问题缺测、异常值、日期不连续。先说缺失值。部分站点的自动观测设备会整夜掉线导致某一天湿度或风速是空值。直接用dropna删行会浪费有效样本尤其是删在冬季季节分布就歪了。常规做法是先用前向填充把少量缺口补掉再结合前后几天的移动均值兜底import numpy as np # 数值列先前向填充再后向填充处理连续缺测 num_cols [temp_mean, humidity, pressure, wind_speed] df[num_cols] df[num_cols].ffill() df[num_cols] df[num_cols].bfill() # 异常值超过城市历史合理范围的用前后7天移动均值替换 df.loc[df[temp_mean] 45, temp_mean] np.nan df.loc[df[temp_mean] -30, temp_mean] np.nan df[temp_mean] df[temp_mean].fillna( df[temp_mean].rolling(7, min_periods1, centerTrue).mean() )ffill()是向前填充缺失值bfill()是把剩余头部空值补掉。温度设 45°C 作为上限、-30°C 作为下限是因为城市气象站点历史数据基本不可能超出这个范围超了就视为观测异常。换成降水量的场景上下限就不适用了要按物理意义单独设。这类“领域知识截断”不需要写得多复杂但要在注释里说明依据答辩被问到时能答上来。日期列本身不能直接喂给 sklearn需要拆成模型能理解的数值特征。气候有明显的年周期和季节模式所以月份、一年中的第几天、星期几都是有信息量的date_feat df[date].dt df[month] date_feat.month df[day_of_year] date_feat.dayofyear # 刻画季节周期1~365 df[weekday] date_feat.weekday # 星期几0~6 df[is_weekend] df[weekday].isin([5, 6]).astype(int)day_of_year是这里最值得解释的一列温度按季节起伏7 月 15 日和 1 月 15 日的温度差可能达到二十几度用这个数值能直接表达“现在处在哪个季节位置”比单独用月份更好使。is_weekend在气温预测上用不太上但如果扩展到用电负荷或出行场景就很有用这也是报告里可以写的“跨领域特征迁移”亮点。气温有很强的连续性昨天 30°C今天大概率接近 30°C这种“记忆性”要靠滞后特征带进模型# 滞后特征用前一天、前两天、前七天的平均气温 for lag in [1, 2, 3, 7]: df[ftemp_lag{lag}] df[temp_mean].shift(lag) # 滑动统计特征近7天均值和标准差描述最近一段整体冷暖 df[temp_ma7] df[temp_mean].rolling(7).mean() df[temp_std7] df[temp_mean].rolling(7).std() df df.dropna().reset_index(dropTrue)shift(lag)是把整列往下移几行让第 i 行拿到“前几天”的值。滞后 1、2、3 天抓的是短期惯性滞后 7 天抓的是“跟上周同期比”滑动标准差能反映天气剧烈变化期。这些特征构造完因为前 7 天没有足够的历史数据dropna会把开头几行删掉这是正常的不需要担心。最后构造预测目标也就是监督学习里的标签我们预测“明天平均气温”df[target_temp] df[temp_mean].shift(-1) # 用真实明天温度做标签 df df.dropna(subset[target_temp]).reset_index(dropTrue)shift(-1)是向上移让第 i 行挂上第 i1 天的真实温度作为答案。这里就出现一个铁律target_temp绝对不能出现在特征列表里否则模型等于考试时偷看了答案。为了保险后面选特征时我会统一排除掉它。3.2 按时间划分训练集与评估别再随机切分了很多机器学习入门资料会教你train_test_split(test_size0.2, random_state42)但在天气预测里随机切分是个坑。天气数据相邻日子高度相关随机切会把 7 月 5 日放进训练集、7 月 6 日放进测试集模型等于“记住了昨天”测试分数虚高。答辩时如果老师换一段新数据让你现场预测立刻现形。正确做法是按时间顺序拿前 80% 当训练集后 20% 当测试集from sklearn.ensemble import RandomForestRegressor from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, r2_score feature_cols [c for c in df.columns if c not in (date, target_temp)] X df[feature_cols] y df[target_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:] models { LinearRegression: LinearRegression(), RandomForest: RandomForestRegressor(n_estimators200, max_depth8, random_state42), } for name, model in models.items(): model.fit(X_train, y_train) pred model.predict(X_test) print(name, MAE:, mean_absolute_error(y_test, pred).round(2), R2:, r2_score(y_test, pred).round(3))split_idx是按行数比例直接切出一个位置前 80% 是历史较早的数据后 20% 是连续的未来数据测试集里每天的数据在时间上都晚于训练集。n_estimators200表示构建 200 棵决策树max_depth8控制每棵树深度限制深度是防止树模型在几千行数据上把训练集背下来。random_state42固定随机种子保证你重跑结果一致这也是大作业里很重要的小习惯——同一次提交两次运行结果不能差太多。运行完通常能看到线性回归 MAE 在 2~3°C随机森林 MAE 能压到 1.5~2°CR2 在 0.85 以上就算不错。如果随机森林的 MAE 反而比线性回归高优先检查特征里是否混进了不该有的未来信息或者滞后特征没有对齐。3.3 分类任务顺便预测“明天是否下雨”报告亮点只做温度回归也能拿分但很多高分作业会再加一个分类任务用同样的历史特征预测明天是否降水。这样报告里能同时展示回归指标和分类指标答辩内容更丰富。训练代码套路一样只是目标列变成 0/1 标签评估用准确率和 F1df[rain_label] (df[precipitation] 0).astype(int) from sklearn.ensemble import GradientBoostingClassifier from sklearn.metrics import accuracy_score, f1_score y_cls df[rain_label] y_train_cls, y_test_cls y_cls.iloc[:split_idx], y_cls.iloc[split_idx:] clf GradientBoostingClassifier(random_state42) clf.fit(X_train, y_train_cls) pred_cls clf.predict(X_test) print(Accuracy:, accuracy_score(y_test_cls, pred_cls).round(3)) print(F1:, f1_score(y_test_cls, pred_cls).round(3))precipitation 0是把降水量转成是否下雨的二分类标签。分类任务比回归任务更容易出现“样本不均衡”比如全年下雨天数只占三成模型会偷懒全预测“不下雨”也能拿到 70% 准确率。所以分类评估一定要同时看 F1别只看 accuracy。这段代码跑完后报告里可以放一张表格回归任务 MAE/R2 一行分类任务 Accuracy/F1 一行整页报告就有了左右两个支撑点。4. 天气可视化落地用四张图把数据趋势和预测误差讲清楚4.1 数据趋势、季节分布与特征相关性先让原始数据自己说话可视化是这份大作业最直接的面子工程。老师翻报告先看图再看代码最后才问模型。我见过模型一般但图很清爽的拿高分也见过模型调得很好但图上全是乱码的翻车案例。可视化部分不需要花哨四张图足够趋势图、月度箱线图、相关性热力图、预测对比图。画图前先确定 Matplotlib 能用中文字体这一行配置值得放在文件开头import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei, Noto Sans CJK SC, PingFang SC] matplotlib.rcParams[axes.unicode_minus] False第一张图是历史温度趋势。几千个数据点连成的折线会像毛线团一样乱所以要叠加一条 30 天滑动平均线让长期趋势一眼可见df[temp_ma30] df[temp_mean].rolling(30, min_periods1).mean() fig, ax plt.subplots(figsize(12, 4)) ax.plot(df[date], df[temp_mean], lw0.8, alpha0.7, label日均温) ax.plot(df[date], df[temp_ma30], lw2, color#d62728, label30日滑动平均) ax.set_xlabel(日期) ax.set_ylabel(温度(°C)) ax.legend() ax.set_title(历史日均温与30日滑动平均整体趋势) plt.tight_layout() plt.savefig(viz_trend.png, dpi150) plt.show()rolling(30, min_periods1)表示每 30 天取均值前 29 天数据不够时就按已有天数算避免开头一大段空缺。红色滑动平均线把季节周期直接拖出来答辩时指着它说“数据有明显的年周期所以构造了 day_of_year 特征”就把特征工程和可视化串起来了。第二张图是逐月箱线图表达“一年十二个月温度怎么分布”顺带能看出异常点month_labels [1月,2月,3月,4月,5月,6月, 7月,8月,9月,10月,11月,12月] fig, ax plt.subplots(figsize(12, 5)) df.boxplot(columntemp_mean, bymonth, axax, patch_artistTrue) ax.set_xticklabels(month_labels) ax.set_title(各月份日均温分布) ax.set_xlabel(月份) ax.set_ylabel(温度(°C)) plt.suptitle() # 去掉 pandas 自动加的标题 plt.tight_layout() plt.show()箱体中间的线是中位数箱体范围是四分位距上下须是正常的波动范围须之外的点就是离群值。这张图能看出夏季箱体短、冬季箱体长之类的季节性细节写报告时只需一句话“温度波动幅度随季节变化说明模型需要捕捉异方差性的特征”这就能体现统计学素养。第三张图是特征相关性热力图做特征有效性论证用import seaborn as sns corr df[feature_cols [target_temp]].corr() fig, ax plt.subplots(figsize(10, 8)) sns.heatmap(corr, annotFalse, cmapRdBu_r, center0, squareTrue, axax) ax.set_title(特征与预测目标的相关性矩阵) plt.tight_layout() plt.show()看这张图时重点看target_temp这一行、最后一列temp_lag1和它的相关性应该接近 0.8说明“昨天的温度是预测明天温度最重要的依据”day_of_year相关性在 0.5 左右说明季节信息有效。这个相关性和模型的特征重要性可以互相印证报告里写出来非常加分。4.2 预测对比与误差分布答辩现场讲效果的两张关键图预测效果的展示要克制。把上千个测试集预测点全部画出来两条线挤成一根黑带什么都看不清。通常只截取测试集里后 120 天展示既能看出跟随趋势又不至于密到没信息import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(y_test.index, y_test.values, label真实值, color#1f77b4, lw1.2) plt.plot(y_test.index, pred, label随机森林预测值, color#d62728, lw1.2, alpha0.8) plt.legend() plt.title(测试集后 120 天真实值与预测值对比) plt.xlabel(样本序号按时间排序) plt.ylabel(温度(°C)) plt.tight_layout() plt.show()pred变量用的是上一章随机森林在测试集上的预测结果。如果前面循环里同时训练了多个模型这里可以把预测结果按字典存起来像pred_dict[RandomForest]画图时直接取。对比图的核心是展示“跟随得怎么样”夏天峰值的相位是否对得上、冬天气温突然跳水时预测有没有滞后盯着这几处讲答辩老师会觉得你真的看懂了模型行为。最后一张是误差分布直方图把抽象的 MAE 变成直观的图形errors y_test.values - pred fig, ax plt.subplots(figsize(8, 4)) ax.hist(errors, bins40, color#1f77b4, alpha0.8) ax.axvline(0, colorblack, lw1) ax.set_xlabel(预测误差真实值 - 预测值单位 °C) ax.set_ylabel(样本数) ax.set_title(测试集预测误差分布) plt.tight_layout() plt.show()误差集中在 0 附近且左右对称说明模型没有系统性偏差如果直方图整体左偏说明模型系统性高估了温度右偏则是系统性低估。看到明显偏移时可以尝试把特征里加入“月份与滞后1天的交互特征”这类交叉项往往能修正。这张图写进报告时配一句“误差标准差约为 X°C说明模型在大多数日期能控制在 ±2°C 以内”就够分量了。5. 天气预测大作业避坑5 个常见问题与排查5.1 数据泄漏模型分数高得不像话先检查这两处现象测试集上 MAE 小于 0.5°CR2 接近 1比所有正常模型的成绩都好换一段新数据却立刻拉胯。原因两类典型泄漏。一是用train_test_split默认随机切分相邻日期被拆到训练集和测试集两边模型等于背下了前一天的答案二是构造特征时把“预测目标当天”的观测值放进了特征比如用明天的湿度预测明天的温度或者把target_temp本身留在了feature_cols里。解决先改成按时间顺序用iloc手动切分再加入一行断言代码自检assert target_temp not in feature_cols随后打印feature_cols列表逐个核对每个特征的生成方式。核心标准只有一条预测 T1 日特征里只能出现 T 日及更早能观测到的量T1 日的任何数据都不能进特征。养成把特征和目标分成两个独立 DataFrame 的习惯也能减少这类事故。5.2 Matplotlib 中文乱码与图形方块现象同样的代码在自己电脑上标题正常显示换一台电脑或放到 Linux 服务器上再跑所有中文标题变成一个个方块。原因Matplotlib 默认使用 DejaVu Sans 字体这个字体不包含中文字形目标机器上又没有安装 SimHei、微软雅黑这类中文字体。解决先按第 4 章的rcParams配置指定中文字体列表列表里多写几个备选字体哪个存在用哪个import matplotlib matplotlib.rcParams[font.sans-serif] [Noto Sans CJK SC, SimHei, Microsoft YaHei, PingFang SC] matplotlib.rcParams[axes.unicode_minus] False如果目标机器上连中文字体都没装先装字体再画图# Ubuntu / Debian sudo apt install fonts-noto-cjk # CentOS / 带 yum 的发行版 sudo yum install wqy-zenhei5.3 日期解析失败导致排序错乱、画图串位现象df[date]显示为 object 类型sort_values(date)后顺序仍然不对画出的折线图横轴乱跳。原因CSV 里的日期格式五花八门有2024-01-05、2024/1/5、20240105等多种形态read_csv只把字符串读进来了没有解析成 datetime按字符串排序就得不到正确的时间顺序。解决导入时指定parse_dates或读取后统一用pd.to_datetime转换df[date] pd.to_datetime(df[date], errorscoerce) print(df[date].isna().sum()) # 解析失败的行数应为 0 df df.dropna(subset[date])errorscoerce会把解析失败的值变成 NaT方便统计哪些行格式不对。处理完后再sort_values和reset_index后续所有按时间切分、取月份的操作才有正确基础。5.4 滞后特征构造错误导致预测结果变成“复读机”现象模型 R2 很高但预测曲线始终比真实曲线晚一天看着像是把“昨天的温度”直接搬出来了。原因滞后特征构造顺序出了问题。比如先用shift(-1)构造目标再做shift(1)构造滞后特征行错位后模型学到的其实是“用前天推昨天”特征与目标的对应关系整体偏移了一天。解决固定一个构造顺序先做滞后特征最后再构造目标列构造过程中可以用这行代码自检print(df[[date, temp_mean, temp_lag1, target_temp]].tail(10))以行尾数据为准temp_lag1应该是前一天的temp_meantarget_temp应该是后一天的temp_mean。三个并排打印一眼就能发现错位。这个排错技巧虽然简单但确实是我在实际项目里最常用的后悔药。5.5 环境依赖不一致带到演示现场跑不起来现象自己电脑跑得顺畅拷到答辩用的机器或服务器上报ModuleNotFoundError: sklearn或者RandomForestRegressor got an unexpected keyword argument。原因目标环境缺少依赖库或者 sklearn、pandas 版本不一致旧 API 在新版本里被移除了。解决提前生成并提交一份固定的依赖文件在新机器上按文件安装pip freeze requirements.txt然后在干净环境里安装并重跑python -m pip install -r requirements.txt哪怕文件名不叫requirements.txt交作业时附上依赖清单并在 README 里写一行安装命令也是一个实打实的工程加分项。提前用一台陌生电脑跑通全流程比答辩现场翻车强得多。6. 模型保存与滚动推理让预测变成持续可用的最后一步课程设计做到能画图、有指标已经能交差了。但想接近“95 分以上”那档建议补上两件小事模型持久化和多日滚动预测。使用joblib保存训练好的模型既避免答辩现场耗时重新训练也避免随机重建带来的结果波动import joblib joblib.dump(model, weather_model_rf.pkl) loaded_model joblib.load(weather_model_rf.pkl)保存后再加载出来的模型预测结果必须与保存前完全一致这是模型可复现性的底线。另一件值得做的是连续预测未来 3 天。很多同学只写了“用今天的数据预测明天”老师一问“那后天呢”就卡壳。连续预测的核心是滚动推理每推完一天要把预测值回填到历史序列里作为下一天的滞后特征。参考实现如下# 取出最近 7 天的真实温度作为滚动预测的初始历史 history_temp df[temp_mean].tail(7).tolist() last_date df[date].max() future_dates [last_date pd.Timedelta(daysi) for i in range(1, 4)] for step, fdate in enumerate(future_dates, start1): row_data { temp_lag1: history_temp[-1], temp_lag2: history_temp[-2], temp_lag3: history_temp[-3], temp_lag7: history_temp[-7], temp_ma7: float(np.mean(history_temp[-7:])), temp_std7: float(np.std(history_temp[-7:])), month: fdate.month, day_of_year: fdate.dayofyear, weekday: fdate.weekday(), is_weekend: 1 if fdate.weekday() 5 else 0, } # 湿度、气压、风速这类特征没有未来值用最近一天的观测值代替 row_data[humidity] df[humidity].iloc[-1] row_data[pressure] df[pressure].iloc[-1] row_data[wind_speed] df[wind_speed].iloc[-1] pred_temp loaded_model.predict(pd.DataFrame([row_data]))[0] print(f第 {step} 天{fdate.date()}预测温度: {pred_temp:.1f} °C) # 预测值回填为新的历史值供下一步使用 history_temp.append(pred_temp)这段代码演示的是一个重要习惯预测的未来值不能只输出完就丢掉要作为下一天的历史数据回填否则模型输入会停留在昨天的状态。湿度、气压这类没有未来观测值的特征用最新观测值代替是权宜之计答辩时直接说“短期内有惯性最近的观测值是当前已知的最佳估计”这样的解释比回避问题更有说服力。我还习惯把随机森林的特征重要性画成横向条形图放进报告这一步对拿高分帮助很大importance pd.Series(model.feature_importances_, indexfeature_cols) importance.sort_values().plot.barh(figsize(8, 6)) plt.title(Random Forest 特征重要性) plt.xlabel(重要性) plt.tight_layout() plt.show()这张图通常显示temp_lag1最重要其次是temp_lag2和day_of_year正好验证了相关性和领域认知。答辩时指着图说“模型主要依赖昨天温度和季节位置符合气象学常识”比背公式更有说服力。我有一回答辩只放了一张误差表被老师追问“那你觉得模型凭什么相信明天温度会更热”时答得磕磕绊绊后来形成习惯每次做这类项目都先画一张特征重要性图把模型行为讲清楚再讲指标真的省了很多嘴皮子功夫。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?