简介:面向高校机器学习课程设计与期末大作业场景的一套基于Python的天气预测与可视化项目源码,适合需要快速完成高分项目或学习完整建模流程的读者。代码工程化程度较高,下载后无需修改即可运行,能省去从零搭建的麻烦。压缩包共23个文件、约1.42MB,核心包括4个Python脚本,覆盖数据清洗、模型训练、预测推理与可视化展示;另含4个CSV格式的训练/验证/测试数据集、1个训练好的pkl模型文件、可交互的HTML天气图表以及12张结果图片,便于核对输出与撰写实验报告。项目已有1099人学习使用,说明其结构清晰、上手门槛较低。从中可完整走通天气数据获取、预处理、模型训练与评估、结果可视化的机器学习全流程,也可在此基础上替换数据集或调整算法,作为自己的课程设计或毕设基础。
1. 天气预测项目用Python怎么做,关键不在模型而在数据链
看到“机器学习 Python 天气预测 可视化”这个组合,多数人第一反应是直接调LSTM。但这类大作业项目的评分逻辑,不是预测分数刷得多高,而是把数据获取、特征构造、模型对比、结果可视化整条链路走完。我见过不少拿到95分以上的项目代码量不大,每一环却都有清晰证据:缺失值怎么补,滞后特征为什么取7天,基线模型和LSTM差多少,可视化能不能一眼看出偏差。下面按这条链路把做法、代码和参数讲清楚,新手能照抄,熟手能查漏。
2. 天气预测的Python数据预处理:把原始观测变成能喂模型的时间序列
2.1 先统一数据格式:用pandas把气象记录读成时间索引
天气预测在机器学习里属于时间序列预测,和普通表格分类有个本质区别:样本之间不独立。今天的最高温和昨天高度相关,所以不能像图像分类那样随机切分训练集和测试集,这也是天气预测Python项目里最常见的丢分原因。常见做法是要求数据里至少包含日期、最高温、最低温、湿度、气压这几个字段。以一份CSV为例,先做格式统一。
import pandas as pd df = pd.read_csv("weather_data.csv", encoding="utf-8") df["date"] = pd.to_datetime(df["date"]) df.set_index("date", inplace=True) df = df.sort_index() # 务必按时间排序 print(df.tail())代码逻辑:pd.to_datetime把字符串日期转成统一的datetime类型,即使原文件里混用“2024-01-01”和“2024/1/1”也能正常解析。set_index之后,DataFrame的索引成为时间轴,方便后续做滞后、滚动和按日期切片。sort_index这一步容易被忽略,原始记录一旦乱序,后面用shift构造特征时会悄悄把未来数据泄漏进训练集,模型指标虚高但真实效果很差。
如果本机还没装pandas,先在终端执行pip install pandas。这里有个常见环境坑:命令行里下载成功,但运行脚本时提示ModuleNotFoundError,多半是vscode的python解释器没有指向安装包的虚拟环境,而不是包本身装错。
2.2 缺失值和异常值处理:天气数据不能简单填0
气象观测经常有缺测和突变值。缺测的常见做法是前向填充,因为天气是连续变化过程,用前一个时刻的值填补比填0或填均值都更贴近真实状态。异常值则用滑动窗口识别,比如温度在一两天内跳变超过3个标准差,大概率是传感器故障或录入错误。
df["high_tmp"] = df["high_tmp"].ffill() df["low_tmp"] = df["low_tmp"].bfill() window = 7 mean = df["high_tmp"].rolling(window, center=True).mean() std = df["high_tmp"].rolling(window, center=True).std() df["anomaly"] = (df["high_tmp"] - mean).abs() > 3 * std df.loc[df["anomaly"], "high_tmp"] = mean[df["anomaly"]] df.drop(columns=["anomaly"], inplace=True)ffill和bfill分别沿时间轴向下、向上填充,两者配合能覆盖数据段中间的单个空洞。rolling(window, center=True)会以当前行为中心,各取前后3天构成一个7天窗口,均值和标准差能反映局部正常波动范围。3 * std是常用的3西格玛判异阈值,超过就认为是离群点,用窗口均值替换。这一段的答辩价值在于:评委问“为什么用填充而不是删除”,答案可以落到“时间序列删除行会破坏时间步的连续性,影响后面构造lag特征”。
2.3 特征工程:滞后特征和滑窗统计让线性模型也能看见历史
机器学习模型不会主动理解“昨天”和“上周同日”的含义,需要把时间上的关系显式做成特征。常见做法是把目标列的历史值拆成滞后特征和窗口统计特征,lag_7对应一周前同一天的天气,roll_mean_3代表近3天温度趋势。
| 特征名 | 构造方式 | 含义 |
|---|---|---|
| lag_1 | high_tmp.shift(1) | 昨天最高温 |
| lag_7 | high_tmp.shift(7) | 一周前最高温 |
| roll_mean_3 | high_tmp.rolling(3).mean() | 近3天平均温度 |
| roll_max_3 | high_tmp.rolling(3).max() | 近3天温度峰值 |
| delta_h | humidity.diff() | 湿度变化量 |
df["lag_1"] = df["high_tmp"].shift(1) df["lag_7"] = df["high_tmp"].shift(7) df["roll_mean_3"] = df["high_tmp"].rolling(3).mean() df["roll_max_3"] = df["high_tmp"].rolling(3).max() df["delta_h"] = df["humidity"].diff(1) df = df.dropna()为什么滞后取7而不是随便取?温度存在以一周为周期的天气过程,lag_7能让模型看到上周同一天的气温水平;取到30天时,信号会被噪声掩盖,特征数量增加但效果反而下降。shift之后前7行必然是NaN,dropna会顺带把训练集头部删掉。这里要注意,切分训练集和验证集必须发生在dropna之后,否则验证集第一条会用到未来时刻的数据。天气预测项目里的“机器学习三大假设”在时间序列上天然不成立,样本独立同分布这一条就被打破,所以处理顺序就是证据链的一部分。
3. 天气预测的机器学习模型怎么选:用线性回归当基线,用LSTM做进阶
3.1 线性回归先跑通主干,记录MAE和RMSE作为基准
模型选型上,天气预测项目通常有两个方向:一是线性回归、随机森林这类非时序模型加滞后特征,二是LSTM这类循环网络直接消费窗口序列。对比实验不能一上来就甩LSTM,而是先用线性回归打底,把它的表现当作“下限”,后面LSTM的提升才有说服力。
代码里选择最高温作为预测目标,特征用上一章的lag_1、lag_7、roll_mean_3、delta_h。
from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error from sklearn.model_selection import train_test_split import numpy as np features = ["lag_1", "lag_7", "roll_mean_3", "delta_h"] X = df[features].values y = df["high_tmp"].values X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, shuffle=False) lr = LinearRegression() lr.fit(X_tr, y_tr) pred = lr.predict(X_te) print("MAE:", mean_absolute_error(y_te, pred)) print("RMSE:", np.sqrt(mean_squared_error(y_te, pred)))训练集和验证集切分时shuffle=False是硬性要求,时间序列不能随机打乱。MAE可以直观解释成“平均误差1.8度”,RMSE对离群误差更敏感,当RMSE明显大于MAE时,说明模型在极端天气日子的偏差被放大。这两项指标是报告里必须同时出现的,也是评委最常看的第一组数字。
3.2 LSTM训练的关键参数:归一化、时间步长、学习率
如果只做线性回归,项目完成度大概在80分上下。进阶做法是用LSTM捕捉更长的周期依赖,LSTM的输入是三维张量,形状为(样本数,时间步长,特征数)。时间步取7,就是拿过去7天的数据预测明天的最高温。
from sklearn.preprocessing import MinMaxScaler from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense scaler = MinMaxScaler(feature_range=(0, 1)) scaled = scaler.fit_transform(df[["high_tmp", "humidity"]]) def make_sequences(data, steps=7): Xs, ys = [], [] for i in range(steps, len(data)): Xs.append(data[i-steps:i]) ys.append(data[i, 0]) # high_tmp 在第0列 return np.array(Xs), np.array(ys) X_seq, y_seq = make_sequences(scaled, 7) split = int(len(X_seq) * 0.8) X_train, X_test = X_seq[:split], X_seq[split:] y_train, y_test = y_seq[:split], y_seq[split:] model = Sequential([ LSTM(64, activation="tanh", input_shape=(7, 2)), Dense(1) ]) model.compile(optimizer="adam", loss="mse") history = model.fit(X_train, y_train, epochs=50, batch_size=32, validation_data=(X_test, y_test), verbose=0)三个参数值得细说。时间步长steps=7不是越大越好,温度以一周为周期变化最强,取15虽然信息更多,但也会把更多无关波动带进来。batch_size=32在几千条数据量下稳定,训练loss出现明显震荡时先降到16。learning_rate使用adam默认的0.001,如果loss下降太慢,改成Adam(learning_rate=0.005),但调大后要观察是否发散。归一化用的是MinMaxScaler,把所有特征压到0到1之间,这是因为LSTM的激活函数tanh对输入尺度敏感,原始温度值直接喂进去会导致梯度饱和。
另外一个容易被忽略的点:LSTM输入不止最高温,还拼了湿度列。湿度能帮助模型区分晴雨状态,比只用单变量做序列预测稳定很多。
提示:几千条气象数据用CPU训练LSTM也只需要1分钟左右,不需要为这个项目专门配置GPU环境。
3.3 用验证集loss的梯度变化判断何时停止
训练时如果只盯着训练集loss,会陷入过拟合。设置validation_data后,每个epoch结束都会算出验证集loss,训练集loss持续下降、验证集loss开始反弹,就是过拟合的信号。常见做法是加一个EarlyStopping回调,让模型在验证loss不再下降时自动停下。
from tensorflow.keras.callbacks import EarlyStopping es = EarlyStopping(monitor="val_loss", patience=10, restore_best_weights=True) history = model.fit(X_train, y_train, epochs=200, batch_size=32, validation_data=(X_test, y_test), callbacks=[es], verbose=1) print("最终训练轮数:", len(history.history["loss"]))patience=10表示验证loss连续10个epoch不下降就停止,restore_best_weights=True会把权重回滚到验证loss最低的那个时刻。训练日志里能看到loss从下降变平缓的过程,这个动态曲线本身就是“梯度收敛”的直观证据。答辩时如果说一句“模型在第52轮收敛,后面开始过拟合,所以用了早停”,这个细节对评分的正向影响比想象中大。
4. 天气预测结果可视化:用matplotlib画对比图,用pyecharts做交互
4.1 matplotlib画真实值与预测值曲线,一眼定位偏差时段
可视化是整个项目的门面,也是“数据分析和可视化”评分项的主体。最基础也最必要的一张图,是测试集里真实温度和预测温度的两条折线。先解决中文显示问题,再画图。
import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False plt.figure(figsize=(12, 5)) plt.plot(y_test[-100:], label="真实温度", linewidth=2) plt.plot(pred[-100:], label="预测温度", linestyle="--") plt.xlabel("样本序号") plt.ylabel("最高温(℃)") plt.title("机器学习天气预测:模型预测与真实值对比") plt.legend() plt.grid(alpha=0.3) plt.show()macOS系统需要把SimHei换成PingFang SC,Linux可以改WenQuanYi Zen Hei,否则中文标签会变成方块。取最后100条样本是为了让图不糊成一片,建议另出一张全量测试集对比图放进报告。更进一档的做法是用axvspan把误差最大的三天用色块标出来,答辩时直接回答“哪天差得多、为什么差得多”,这个设计比模型本身更容易拿印象分。
4.2 误差分布直方图与特征相关性热力图放进报告
只画时间序列对比图还不够,误差分布和特征相关性是必配的两张分析图。误差分布回答“误差集中在哪个区间”,相关性热力图回答“特征之间是不是冗余、和目标是不是相关”。
import seaborn as sns import matplotlib.pyplot as plt errors = y_test - pred plt.figure(figsize=(8, 4)) sns.histplot(errors, bins=30, kde=True) plt.xlabel("预测误差 (℃)") plt.title("天气预测误差分布") plt.show() corr_cols = ["high_tmp", "low_tmp", "humidity", "pressure", "lag_1", "lag_7"] plt.figure(figsize=(8, 6)) sns.heatmap(df[corr_cols].corr(), annot=True, cmap="coolwarm", fmt=".2f") plt.title("气象特征相关性热力图") plt.show()热力图里藏着两个答辩考点:一是high_tmp和lag_1相关系数很高,说明气温是一个强自相关过程,这正是LSTM能比线性回归多学东西的前提;二是humidity和目标温度负相关时,可以解释为湿度高对应阴雨天气,云层削弱了太阳辐射。报告里写上一句这种解释,技术深度会明显脱离“代码搬运”的档次。
4.3 用pyecharts搭一个天气预测可视化页面
如果课程名称里有“可视化项目”或“可视化大屏”要求,用pyecharts比matplotlib更讨巧。pyecharts输出HTML页面,天然支持鼠标悬浮、区域缩放和组件联动,打开就是一张可交互的图表面板。下面是一个折线图的最小生成示例。
from pyecharts.charts import Line from pyecharts import options as opts line = ( Line() .add_xaxis(list(range(len(y_test[-30:])))) .add_yaxis("真实温度", [round(float(v), 2) for v in y_test[-30:]]) .add_yaxis("预测温度", [round(float(v), 2) for v in pred[-30:]]) .set_global_opts( title_opts=opts.TitleOpts(title="天气预测可视化"), yaxis_opts=opts.AxisOpts(name="温度(℃)"), ) ) line.render("weather_forecast.html")render在本地生成HTML文件,浏览器打开即可,不需要启动额外服务。图中数据乱掉时,先打印len(y_test)和len(pred),确认两个序列长度一致再渲染。针对这个场景,图型选型可以参考:预测对比用折线图,误差分布用直方图,特征相关性用热力图,天气现象空间分布才考虑3D地图。前三种是通用且稳定的组合,不要为了炫技硬上地图。
5. 天气预测项目提分技巧:误差分段统计与未来7天预报演示
完成上面四步,项目已经到90分左右,离95分还差两个关键操作:按温度区间做误差分析,以及演示未来7天预报。
5.1 按温度区间统计误差,主动暴露模型短板
把测试集按真实最高温分成低温、常温、高温三段,分别计算MAE。这样既能在答辩时讲清“模型在哪种天气下误差大”,又能顺势提出后续改进方向,属于主动暴露短板但又不减分的策略。
| 温度区间 | 样本数 | MAE | RMSE |
|---|---|---|---|
| 低于5℃ | 18 | 2.3 | 3.1 |
| 5℃到20℃ | 34 | 1.1 | 1.5 |
| 高于20℃ | 12 | 0.8 | 1.2 |
bins = pd.cut(y_te, bins=[-50, 5, 20, 50], labels=["低温", "常温", "高温"]) for level in ["低温", "常温", "高温"]: mask = bins == level mae = mean_absolute_error(y_te[mask], pred[mask]) print(level, "MAE:", round(mae, 2))5.2 未来7天预报:滚动预测比一步预测更完整
前面做的都是已知测试集上的“回测”,还要补一个未来7天的预报演示。常见做法是滚动预测,用最后7天数据预测明天,再把预测值拼进窗口,循环7次得到未来一周结果。
future = scaled[-7:].copy() results = [] for _ in range(7): x_input = future[-7:].reshape(1, 7, 2) y_pred = model.predict(x_input, verbose=0)[0, 0] results.append(y_pred) append_row = future[-1].copy() append_row[0] = y_pred future = np.vstack([future, append_row]) results = scaler.inverse_transform(np.array(results).reshape(-1, 1))这里inverse_transform只还原最高温一列,因为输入给scaler的是两列,还原时保持形状为(7, 1)即可。湿度列在滚动过程中用最后一天的实际值补位,7天之内可以接受。把这个输出结果和对应日期整理成表格截图放进报告,完成度明显高于只画一张折线图。
5.3 最后的小技巧:把预测结果导出成JSON,前端直接消费
把未来7天预测结果连同日期导出为JSON文件,整个项目就串成“数据清洗—建模—预测—展示”的闭环。答辩时打开浏览器加载这个JSON,展示页面会自动刷新内容,操作效果比现场跑代码利索得多。
import json data = { "date": [str(d.date()) for d in df.index[-7:]], "pred": [round(float(v), 2) for v in results.ravel()] } with open("forecast.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False)最后配合一张浏览器打开的截图写进报告的“工程化”章节,说明“预测结果通过JSON接口传给前端,后续换数据源不需要改展示层”,这就是从“能跑”到“像产品”的分界。
本文还有配套的精品资源,点击获取