☰
天气预测毕设Python源码包:爬虫、五模型与可视化全流程
2026/10/10 11:46:48 网站建设 项目流程

简介:这是一套基于Python的天气预测与天气可视化毕业设计项目,面向计算机相关专业学生,尤其适合正在完成毕业设计、课程设计或期末大作业,以及希望进行项目实战练习的学习者。项目已通过导师指导并获评99分,代码完整可运行,从全国天气数据爬取、历史数据清洗与探索,到气温预测建模和可视化界面展示均有覆盖;预测部分包含3层MLP、LSTM、随机森林、线性回归、决策树等多种模型,并附带GUI正式版与最简版,方便对照学习和测试。资源压缩包共38个文件,约12.17MB,以.py脚本、.ipynb分析笔记、.h5/.pkl/.joblib训练模型、.csv历史天气数据及.docx设计说明文档为主,另含预测效果图和界面截图,便于直观理解模型表现。目前已有99人学习下载,适合直接运行调试,还可配套完成设计报告与答辩演示,整体完整度高。

1. 天气预测毕设项目:一份能跑通全流程的 Python 源码包

做天气预测毕业设计最怕的不是模型不收敛,而是答辩前几天才发现爬虫、数据、模型、界面四个环节总有一个跑不通。这个基于 Python 的天气预测和天气可视化项目,把全国天气爬虫、临沧四年历史气温数据、五套预测模型、Tkinter 桌面程序和可视化图表打包成一整套源码,顺着跑一遍就能覆盖“数据采集 → 特征工程 → 模型训练 → 结果展示”的完整链路。线性回归、决策树、随机森林、3 层 MLP、LSTM 五个模型都有独立脚本,模型文件、标准化器、CSV 数据、文档手册一应俱全。适合正在做毕业设计、课程设计、期末大作业的同学,也适合想系统练 Python 数据分析和深度学习的初学者——哪怕只挑其中一个模型跑通,也能撑起一次完整答辩。

2. 数据采集与清洗:从全国天气爬虫到临沧四年历史数据

2.1 爬虫层怎么搭:城市编码字典与请求参数

我一般会先看数据从哪来,因为模型的预测上限由数据质量决定。全国天气信息爬取.py 依赖 all_county_dict.json 和 city_dict_1.json 两个字典文件,前者按省维护县域编码,后者维护常用城市编码,本质都是城市名到天气网站编码的映射。常见做法是拿城市编码拼接 URL 请求天气页面,解析出温度、天气现象后落成结构化记录。这个项目把爬虫独立成一个脚本,方便你替换成自己城市的编码再跑一遍。

import requests import json import time # city_dict_1.json: {城市名: 城市编码} with open('city_dict_1.json', 'r', encoding='utf-8') as f: city_dict = json.load(f) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml' } def fetch_weather_by_city(city_name): code = city_dict.get(city_name) if not code: raise ValueError(f'字典中找不到城市编码: {city_name}') url = f'http://www.weather.com.cn/weather/{code}.shtml' resp = requests.get(url, headers=headers, timeout=10) resp.encoding = 'utf-8' return resp.text if __name__ == '__main__': html = fetch_weather_by_city('临沧') print(html[:300])

fetch_weather_by_city 先从字典里取城市编码,拼出目标 URL,再用 requests.get 带自定义请求头发起访问,最后把响应内容按 utf-8 解码返回。先打印前 300 个字符是为了确认页面结构,再决定下一步用哪种方式提取温度字段。

headers 里的 User-Agent 必须模拟真实浏览器,很多天气站点会拦截裸的 Python requests;timeout 设 10 秒是防止某个城市请求卡死拖住整个爬虫;resp.encoding 手动指定 utf-8,是因为部分站点返回的编码声明与内容不一致,靠自动判断容易乱码。

拿到 HTML 之后的解析,常见做法是正则或 BeautifulSoup 提取温度、天气现象,日常的解析逻辑就写在同一个脚本里。需要注意控制请求频率,建议每次请求之间 sleep 0.5 到 1 秒,别有一次性把几千个城市全打一遍的冲动——毕设选两三个城市做演示完全够用。

2.2 临沧历史天气:Lingcang202001-202312.csv 的字段与清洗

临沧历史天气爬取.py 生成的数据文件 Lingcang202001-202312.csv,跨度从 2020 年 1 月到 2023 年 12 月。这个范围选得比较聪明:四年覆盖不同季节和年份的温差异常,后续做训练集和验证集切分时,有足够的连续序列供滞后特征和滚动窗口使用。文件字段不复杂,核心就五列。

字段示例类型用途
date2023-07-15字符串/日期时间轴与特征提取
high28℃字符串预测目标:最高气温
low19℃字符串次目标:最低气温
weather多云字符串天气现象,可转为类别特征
wind西南风2级字符串风的信息,暂可不参与建模
import pandas as pd df = pd.read_csv('Lingcang202001-202312.csv', encoding='utf-8') print('行列数:', df.shape) print(df.head()) # 统一列名,把温度列的 ℃ 去掉并转成数值 df.columns = ['date', 'high', 'low', 'weather', 'wind'] for col in ['high', 'low']: df[col] = pd.to_numeric(df[col].astype(str).str.replace('℃', ''), errors='coerce') # 检查缺失值 print(df.isna().sum()) # 删除温度缺失的样本 df = df.dropna(subset=['high', 'low']) df.to_csv('Lingcang_clean.csv', index=False, encoding='utf-8')

读取后先打印行列数和前几行,确认表头实际情况;接着把列名改成 date、high、low、weather、wind,方便后续所有代码统一调用。pd.to_numeric 配合 str.replace 去掉温度里的 ℃ 符号,errors='coerce' 会把无法转换的值变成 NaN,最后 dropna 删除这些脏样本并另存一份干净数据。

encoding='utf-8' 是读取时指定,Windows 下如果源文件被 Excel 二次编辑过,可能需要改成 encoding='gbk';errors='coerce' 非常实用——它不会因为某一行温度写了“-”或“暂无”就中断程序,而是把异常值转成 NaN 留给 dropna 统一处理。

数据清洗是毕设里最容易被老师追问的环节。我建议在文档说明里写清楚这三个决定:表头为什么统一成英文、缺失值为什么直接删而不是填充、温度为什么转成数值。答辩时能说出这几个理由,比堆十个模型更让老师认可。

2.3 数据探索:建模前先看分布和相关性

数据探索.py 做的事情是建模前必走的流程:看缺失、看分布、看相关性。很多人拿到 CSV 直接丢进模型,结果训练集与测试集分界处预测值断崖,最后才发现是数据里有重复日期或者某个月整段缺失。先用代码扫一遍,能省下后面大量排查时间。

import pandas as pd import matplotlib.pyplot as plt import seaborn as sns df = pd.read_csv('Lingcang_clean.csv', encoding='utf-8') df['date'] = pd.to_datetime(df['date']) df['month'] = df['date'].dt.month df['year'] = df['date'].dt.year # 1) 覆盖范围检查 print('时间跨度:', df['date'].min(), '->', df['date'].max()) print('每月样本数:\n', df.groupby(['year', 'month']).size().head()) # 2) 温度分布 plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) sns.histplot(df['high'], bins=30, kde=True) # 3) 月度箱线图 plt.subplot(1, 2, 2) sns.boxplot(x='month', y='high', data=df) plt.tight_layout() plt.savefig('explore_high.png', dpi=150)

先把 date 转成 datetime 类型,提取 month 和 year 作为分组字段。打印时间跨度和每月样本数,能一眼看出是否存在整月缺失;直方图看最高气温是否近似正态分布,箱线图看每个月的温度中位数和异常点,哪个季节方差大一目了然。

sns.histplot 的 kde=True 会在直方图上叠加核密度曲线,方便对比分布形态;bins=30 对四年数据来说密度够细;boxplot 的 x='month' 会把 1 到 12 月分别成组,异常点用点状标出。这也就是后面特征工程里为什么把 month 单独作为一列的直观依据——月份的周期性在图上非常清楚。

3. 五套温度预测模型:从线性回归到 LSTM 的选型与实现

3.1 特征工程:把日期变成模型能学到的数字

模型本身不认“2023年5月1日”这种写法,第一步是把日期拆成数值特征。这个项目里的特征组合是年、月、日、星期、季节,再加前一天最高温、前两天最高温和过去七天平均最高温。季节不是标准库自带的,常见做法是用月份整除 3 映射。如果你熟悉 Python 入门阶段的 pandas 操作,这些代码可以直接复用。

import pandas as pd import numpy as np def build_features(df, target='high'): df = df.copy() df['date'] = pd.to_datetime(df['date']) df['year'] = df['date'].dt.year df['month'] = df['date'].dt.month df['day'] = df['date'].dt.day df['dayofweek'] = df['date'].dt.dayofweek df['season'] = (df['month'] % 12) // 3 # 滞后特征与滚动均值 df['high_lag1'] = df[target].shift(1) df['high_lag2'] = df[target].shift(2) df['high_rolling7'] = df[target].rolling(window=7).mean() df['high_diff'] = df[target].diff() return df.dropna().reset_index(drop=True) df = pd.read_csv('Lingcang_clean.csv', encoding='utf-8') df = build_features(df) print(df[['date', 'year', 'month', 'season', 'high_lag1', 'high_rolling7']].head())

shift 把前一天、前两天的温度拿过来作为今天预测的输入,rolling 计算近 7 天滑动平均,diff 计算温度相较前一天的差值。这三个序列特征对气温这种强惯性数据非常有效,因为今天的温度往往接近昨天,也接近近一周的平均水平。dropna 会删掉数据开头因 shift 和 rolling 产生的空值行。

window=7 表示滑动窗口为 7 天,如果做月尺度预测可以改成 30,天尺度预测 7 天更合适;season 用 (month % 12) // 3 计算,1 到 3 月是 0,4 到 6 月是 1,正好对应冬春夏秋四个季节。

特征工程必须在切分训练集之前完成,但滞后特征不能跨训练/测试边界。我一般先整体构造特征,再按时间顺序切分,这样能避免数据泄露——如果用随机切分,模型会偷看到未来的温度,测试分数虚高,答辩时一复现就露馅。

3.2 sklearn 三件套:线性回归、决策树与随机森林

机器学习气温预测模型.py 里同时训练了线性回归、决策树和随机森林,三个模型共用一个特征矩阵。这样做的目的是对比:线性回归看基线水平,决策树看非线性拟合能力,随机森林看集成带来的提升。三个模型分别存成 pkl 文件,models 目录下的 linear_regression_model.pkl、decision_tree_model.pkl、random_forest_model.pkl 就是训练产物。

from sklearn.linear_model import LinearRegression from sklearn.tree import DecisionTreeRegressor from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error import joblib X = df[['year', 'month', 'day', 'dayofweek', 'season', 'high_lag1', 'high_lag2', 'high_rolling7', 'high_diff']] y = df['high'] # 按时间顺序切分,不随机打乱 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 = { 'linear_regression': LinearRegression(), 'decision_tree': DecisionTreeRegressor(max_depth=8, random_state=42), 'random_forest': RandomForestRegressor(n_estimators=200, max_depth=10, random_state=42, n_jobs=-1) } for name, model in models.items(): model.fit(X_train, y_train) pred = model.predict(X_test) print(f'{name}: MAE = {mean_absolute_error(y_test, pred):.2f} ℃') joblib.dump(model, f'models/{name}_model.pkl')

这里刻意用 iloc 按行号切分而不是 train_test_split,因为气温序列必须保持时间顺序,随机切分会把未来数据混进训练集。三个模型依次训练、评估、序列化,随机森林 n_jobs=-1 让模型并行利用所有 CPU 核心,训练速度明显加快。

DecisionTreeRegressor 的 max_depth=8 限制树深,防止单棵树过拟合;RandomForestRegressor 的 n_estimators=200 表示 200 棵树,max_depth=10 做剪枝,这两个参数在这个数据规模下够用;random_state=42 固定随机种子,保证复现时结果一致。线性回归不需要额外参数,它就是评估下限用的。

3.3 3 层 MLP:用 Keras 搭一个前馈网络

3层MLP气温预测.py 对应 models 目录下的 MPL_temperature_prediction_model.h5——注意文件名里 MPL 是笔误,实际是 MLP,加载时按文件名读就行。MLP 在 sklearn 三件套基础上加了隐藏层,能学特征之间的非线性组合。为什么是 3 层而不是 10 层?因为特征只有 9 个,数据不到 1400 条,层数再多就会把噪声也背下来,训练误差很低但验证误差反而升高。

import numpy as np import joblib from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping from sklearn.preprocessing import StandardScaler X = df[['year', 'month', 'day', 'dayofweek', 'season', 'high_lag1', 'high_lag2', 'high_rolling7', 'high_diff']].values y = df['high'].values scaler = StandardScaler() X_scaled = scaler.fit_transform(X) joblib.dump(scaler, 'models/mlp_scaler.joblib') split_idx = int(len(df) * 0.8) X_train, X_test = X_scaled[:split_idx], X_scaled[split_idx:] y_train, y_test = y[:split_idx], y[split_idx:] model = Sequential([ Dense(64, activation='relu', input_shape=(X_train.shape[1],)), Dropout(0.2), Dense(32, activation='relu'), Dense(1) ]) model.compile(optimizer='adam', loss='mse', metrics=['mae']) early = EarlyStopping(monitor='val_loss', patience=20, restore_best_weights=True) model.fit(X_train, y_train, epochs=200, batch_size=32, validation_split=0.1, callbacks=[early], verbose=1) model.save('models/MPL_temperature_prediction_model.h5')

MLP 与 sklearn 模型最大的差别是输入要标准化。这里用 StandardScaler 对全部特征做 z-score 归一化,再把标准化器存成 mlp_scaler.joblib,后面 GUI 加载模型做预测时,新输入的日期特征也要用同一个 scaler 转换。网络结构是 64-32-1,中间插了一个 Dropout 层防止过拟合。

Dropout(0.2) 表示每轮训练随机丢弃 20% 的神经元,测试时不丢弃,这是抑制过拟合的常用手段;EarlyStopping 的 patience=20 指验证集损失连续 20 轮不下降就提前终止训练,restore_best_weights=True 会把模型权重回滚到验证集最好的那一步;epochs=200 只是上限,实际通常跑不到。

3.4 LSTM:把气温序列当时间序列来学

LSTM 与前四个模型不一样,它不吃“今天特征预测今天温度”这种扁平结构,而是吃“前 7 天的温度序列预测第 8 天温度”这种时间窗口。LSTM气温预测.py 里的核心工作是把一维温度序列重排成 (样本数, 时间步数, 特征数) 的三维数组。这个 reshape 是新手最容易出错的地方,直接把一维数组喂进去,Loss 会一直不下降。

import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense def create_sequences(data, look_back=7): X, y = [], [] for i in range(len(data) - look_back): X.append(data[i:i + look_back]) y.append(data[i + look_back]) return np.array(X), np.array(y) values = df['high'].values.astype(float) look_back = 7 X, y = create_sequences(values, look_back) split_idx = int(len(X) * 0.8) X_train, X_test = X[:split_idx], X[split_idx:] y_train, y_test = y[:split_idx], y[split_idx:] # LSTM 输入形状: (样本数, 时间步, 特征数) X_train = X_train.reshape((X_train.shape[0], X_train.shape[1], 1)) X_test = X_test.reshape((X_test.shape[0], X_test.shape[1], 1)) model = Sequential([ LSTM(64, activation='tanh', input_shape=(look_back, 1)), Dense(32, activation='relu'), Dense(1) ]) model.compile(optimizer='adam', loss='mse', metrics=['mae']) model.fit(X_train, y_train, epochs=100, batch_size=32, validation_split=0.1, verbose=1) model.save('models/LSTM_temperature_prediction_model.h5')

create_sequences 用一个滑动窗口把连续温度切成长度为 look_back 的输入块,窗口每向右移动一格产生一个样本。reshape 时必须用 X_train.shape[0] 和 X_train.shape[1] 动态获取维度,硬编码样本数会在数据长度变化时报错——这是我实际踩过的坑,改数据集规模后第一件事就是这里。

look_back=7 是时间窗口,代表用前一周的记忆预测明天;LSTM(64) 的 64 是隐藏单元数,越大记忆容量越大但越容易过拟合;activation='tanh' 是 LSTM 的标准激活函数,不要改成 relu,否则梯度不稳定;input_shape=(look_back, 1) 里的 1 是特征数,这里只用最高温单变量做序列预测。

4. 可视化与 GUI:把预测结果变成能演示的桌面程序

4.1 气温可视化.ipynb:先用图把四年趋势讲清楚

毕业设计里的可视化不只是加分项,是必交项。气温可视化.ipynb 用 matplotlib 画的图,核心目的不是好看,而是让老师 30 秒内看懂数据特征。最常见的三张图是:四年最高/最低温叠加折线图、月均气温柱状图、每年温度分布箱线图。这些图做好之后可以直接放进论文和答辩 PPT。

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('Lingcang_clean.csv', encoding='utf-8') df['date'] = pd.to_datetime(df['date']) plt.figure(figsize=(14, 5)) plt.plot(df['date'], df['high'], label='最高气温', linewidth=0.7, alpha=0.8) plt.plot(df['date'], df['low'], label='最低气温', linewidth=0.7, alpha=0.8) plt.xlabel('日期') plt.ylabel('温度 (℃)') plt.title('临沧 2020-2023 日最高/最低气温变化') plt.legend() plt.grid(alpha=0.3) plt.savefig('temp_trend.png', dpi=150) plt.show() # 月度平均 df['month'] = df['date'].dt.month monthly = df.groupby('month')[['high', 'low']].mean() monthly.plot(kind='bar', figsize=(10, 4)) plt.title('各月平均气温') plt.savefig('monthly_mean.png', dpi=150)

先画全量折线图,四年日数据直接用 linewidth=0.7 压低线宽,避免线条糊成一团;然后按月分组算平均气温画柱状图,展示季节规律。这两张图已经足够支撑 PPT 里的数据介绍页,后面模型的预测对比图可以另画。

alpha=0.8 控制透明度,多条线叠在一起时防止互相遮挡;dpi=150 是导出分辨率,论文插图 150 够用,打印的话建议 300;groupby('month') 把 1 到 12 月聚合后取均值,每个月都有足够样本,不会出现某个月只有几天数据导致的抖动。

4.2 GUI 最简版与正式版:从能跑到能答辩

GUI最简版.py 和 GUI正式版.py 都在根目录,两者差别很明显。最简版只有一个输入框和一个按钮,输入日期调用预训练模型输出温度,用来验证整套预测链路是否畅通;正式版加了模型下拉选择、日期起止范围、结果表格、预测折线图,等于把前面所有模块串成一个可交互的演示系统。

功能点GUI 最简版GUI 正式版
输入方式单个日期框起始日期 + 天数
模型选择固定模型下拉选择五套模型
结果展示文本框数字表格 + 折线图
依赖单模型单脚本全模型池 + scaler
答辩效果能跑通链路可演示可截图
import tkinter as tk from tkinter import ttk, messagebox import joblib import numpy as np class WeatherApp: def __init__(self, root): self.root = root root.title('临沧气温预测系统') # 模型清单:显示名 -> (模型路径, scaler路径) self.model_pool = { '随机森林': ('models/random_forest_model.pkl', None), '线性回归': ('models/linear_regression_model.pkl', None), '决策树': ('models/decision_tree_model.pkl', None), '3层MLP': ('models/MPL_temperature_prediction_model.h5', 'models/mlp_scaler.joblib'), 'LSTM': ('models/LSTM_temperature_prediction_model.h5', 'models/lstm_scaler.joblib'), } self.loaded_models = {} self.create_widgets() def predict_one_day(self, model, scaler, date_str): # 把日期字符串转成特征向量 ...

GUI 的核心是把模型路径和 scaler 路径做成字典池,按用户选择动态加载。模型首次选定后缓存在 self.loaded_models 里,避免每次预测都重新读一次几个小时训练好的网络。predict_one_day 内部会复用 build_features 的逻辑,将日期字符串转成特征数组后再预测。

model_pool 字典的键是下拉框显示名,值为模型文件和标准化器路径的元组;None 表示该模型不需要标准化。这个设计让新增模型时只需要改字典,不用动界面逻辑。正式版还有一个细节:预测过程中要禁用按钮,避免用户重复点击导致多个预测线程叠加。

4.3 模型加载与预测的串通:scaler 必须和模型成对使用

这里有个容易被忽略的细节:MLP 和 LSTM 模型的输入必须经过对应的 scaler,而 sklearn 模型不需要。因为神经网络对输入特征尺度敏感,训练时用了标准化特征,预测时就必须用同一个 scaler 转换;随机森林和决策树是树模型,对尺度不敏感,训练时没做标准化,预测时也不能做。混用的话,MLP 的预测值会偏得离谱,而且不会报错——纯靠肉眼从图上才能看出方向对、数值全乱。

def preprocess_input(scaler, feature_array): if scaler is None: return np.array([feature_array]) scaled = scaler.transform([feature_array]) return scaled # 预测单个日期 # 顺序: 年、月、日、星期、季节、lag1、lag2、rolling7、diff features = [2023, 7, 15, 5, 1, 28.5, 29.1, 27.8, 0.4] X = preprocess_input(scaler, features) temp = model.predict(X)[0, 0]

preprocess_input 先判断 scaler 是否为 None,是就直接把特征数组包装成二维矩阵交给树模型;不是则调用 scaler.transform 做标准化。注意这里必须用 transform 而不是 fit_transform,因为 scaler 已经用训练集拟合过了,再 fit 会覆盖均值和方差。返回结果 model.predict 后取 [0, 0],是因为 Keras 输出是二维张量。

feature_array 的顺序必须和训练时完全一致,否则输入维度对不上或数值语义错位。MLP 的 scaler 是 mlp_scaler.joblib,LSTM 用的是 lstm_scaler.joblib,两个网络训练时使用的特征组合不同,不能混用。

5. 避坑指南:天气预测毕设最容易翻车的五个地方

这些坑不是凭空想象,是项目代码结构里能反推出来的典型事故。每条按“现象 → 原因 → 解决”拆开写,方便踩到的时候直接定位。

5.1 现象:LSTM 预测出来几乎是一条直线

整个项目里最阴间的坑:模型训练 loss 降了,但预测曲线几乎不波动,像一条被压平的直线。我见过好几个同学卡在这里,甚至开始怀疑数据有问题。原因通常是序列构造写错了,比如 create_sequences 的窗口内数据顺序没保持,模型学到的是“恒等于最近值”的退化解;另一种常见原因是测试集混入了训练样本,模型在分界处输出突变。解决方法是把 create_sequences 生成的前几个样本打印出来人工检查,确认 X[i] 是 X[i+1] 的过去而不是未来;切分数据用时间顺序,不要用 train_test_split;如果 train loss 降得很低但 test loss 纹丝不动,优先查窗口边界是不是跨了 train/test。

5.2 现象:全国天气爬虫跑到一半报 403 或返回空白页

爬虫跑到第几十个城市突然 403,或者返回的 HTML 里一片空白,这是反爬机制在起作用。requests 默认的 User-Agent 会被识别成脚本;也有人一秒钟请求几十个城市,IP 被临时封禁;还有站点要求请求头带上 Referer。解决方法是把 headers 补全,至少包含 User-Agent 和 Accept,第一次先不加 Cookie;两次请求之间加 time.sleep(1);如果单城市能跑通但批量跑全挂,把并发降到串行,再不行就换数据源,直接下载历史 CSV 而不是实时爬。这个脚本本来就是教学演示用的,重点在链路完整,不在爬取量。

5.3 现象:加载 h5 模型报 Unknown layer 或无法导入

加载 MPL_temperature_prediction_model.h5 或 LSTM_temperature_prediction_model.h5 时,Keras 报错 Unknown layer 或者找不到自定义类,大概率是训练方和加载方的 TensorFlow 版本不一致。老版本保存的模型,在 2.x 下会因层名解析失败而崩溃;我遇到过 2.4 和 2.10 之间互导模型直接报错的情况。解决方法是先对照 requirements.txt 统一版本,再尝试加载;实在不匹配就用 load_model 时传入 custom_objects,但更稳的方案是改训练脚本,用 model.save_weights 和 model.load_weights,只存权重,配合模型结构代码加载,彻底绕开版本兼容问题。

5.4 现象:GUI 点击预测后窗口无响应,或者直接白屏

Tkinter 是单线程 UI 框架,把 model.predict 这种 CPU 密集型操作放在主线程里,界面会一直等待模型计算,看起来就像卡死。实际不是死机,是主线程被预测计算占住了,窗口重绘事件排队等不到执行。解决方法是把预测放到 threading.Thread 里执行,预测完成后再用 queue 把结果送回主线程更新界面;或者更简单的,预测前先禁用预测按钮并把状态文字改成“预测中”,至少让用户知道程序在干活而不是没响应。正式版 GUI 里我一般直接上线程方案,最简版可以用按钮禁用方案。

5.5 现象:models 目录里写的是 MPL 而不是 MLP,加载路径对不上

models 目录下的 MPL_temperature_prediction_model.h5,这个名字是源码包里的原始笔误,实际对应的是多层感知机 MLP。如果你按习惯把路径改成 MLP_temperature_prediction_model.h5,代码里找不到文件,直接报 FileNotFoundError。解决方法是加载路径直接用 models/MPL_temperature_prediction_model.h5,不要手痒改名;如果坚持要改,models 目录、GUI 的 model_pool、3层MLP气温预测.py 三个地方必须同步改,漏一个都会崩。这个小坑特别适合写进答辩的“项目说明”里,反而能体现你对代码细节熟悉。

6. 用 key_predictions 做多步预测:回测与未来七天怎么落地

key_predictions.py 是整个项目里容易被忽略但很实用的小工具。它的作用是用训练好的模型预测未来 N 天的最高气温,并把预测结果按日期打印成表格。前几章的模型都是单步预测,也就是用过去 7 天预测明天;而 key_predictions 把单步预测拼成多步预测——预测出明天的温度后,把它当成新的历史值,再去预测后天,滚动向前。

import joblib import pandas as pd import numpy as np model = joblib.load('models/random_forest_model.pkl') df = pd.read_csv('Lingcang_clean.csv', encoding='utf-8') last_date = pd.to_datetime(df['date'].iloc[-1]) last_high = df['high'].iloc[-1] history = df['high'].iloc[-7:].tolist() predictions = [] for i in range(1, 8): future_date = last_date + pd.Timedelta(days=i) # 用历史窗口构造特征 features = [future_date.year, future_date.month, future_date.day, future_date.dayofweek, (future_date.month % 12) // 3, history[-1], history[-2], np.mean(history[-7:]), history[-1] - history[-2]] pred = model.predict([features])[0] predictions.append(pred) history.append(pred) # 滚动更新 print(f'{future_date.date()}: {pred:.1f} ℃')

这个滚动预测的逻辑与 LSTM 的滑窗思想一致,只是换成了 sklearn 模型。每次循环打印一个日期和预测温度,同时把预测值追加进 history,作为下一次预测的输入。注意 features 的顺序与 3.2 中 X 的列顺序保持一致,这是最容易漏的地方。

验证方法很直接:把数据集最后 30 天当作测试集,模型只使用 30 天之前的数据训练,然后逐日预测这 30 天的最高温,与真实值对比算 MAE。如果 MAE 能控制在 1.5℃ 以内,这个模型就有实际参考意义。我之前用随机森林在这个数据上回测,效果优于线性回归,但比 LSTM 的稳定性稍差——所以正式演示时我会同时展示三个模型的回测误差,而不是只挑表现最好的那个。从那以后我每次做毕设项目,都强制走一遍回测流程:不管用什么模型,先把最后一段数据切出去当验证集,预测完画一张真实值和预测值的对比图。这个动作看起来简单,但在答辩时能让你快速回答“你的模型预测准不准”,比描述 loss 曲线有说服力得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询