简介:基于LSTM的气温预测及可视化源码包,是面向Python开发者和计算机专业学生的毕设/课设参考项目,用于解决气温时间序列建模与预测问题。压缩包共16个文件,包含8个Python脚本、4个pyc编译缓存、2个xlsx气温数据文件及2个Markdown说明文档,整体仅721KB,轻量易读,已有280人学习,适合需要完整可运行项目的开发者。实现上覆盖数据爬取(bs4)、预处理、模型构建、训练评估与可视化,使用Keras搭建LSTM网络,借助过去5天特征预测下一天气温。项目采用北京、上海、广州、郑州四城历史气温数据进行验证,每个样本的特征维度为1,输入按时间序列格式整理,核心代码注释清晰。附有详细项目说明、配置脚本与README文档,便于理解从爬虫抓取到模型评估的完整流程,是课程设计或毕业设计的高质量参考,也能辅助入门者快速上手时间序列预测。
1. 从爬虫数据到 LSTM 输入:气温预测项目到底在解决什么问题
气温预测这种问题,第一反应是查天气预报,但作为深度学习练手项目,它真正的难点不在“预测准”,而在把网页上半结构化数据变成能喂给循环神经网络的张量。这个基于 LSTM 的气温预测项目,抓取了中国天气网北京、上海、广州、郑州四个城市 2011 年以来的逐日气温,用 Keras 搭建单层 LSTM,以过去 5 天的温度预测第 6 天的温度。它把数据爬取、清洗、时序转换、模型训练、结果可视化串成了一条完整链路,适合正在做 Python 课程设计、毕业设计,或者刚接触 LSTM 时间序列预测的开发者。更值得琢磨的是它的数据组织方式:每个时间点特征维度为 1,但 LSTM 要求输入是三维的,这中间怎么转换,是多数新手卡住的地方。
2. 天气数据爬取与预处理:bs4 解析与时间序列构造
2.1 项目结构与模块职责
解压源码包后,核心文件分工清晰:weather_spider.py负责从中国天气网抓取历史气温;data.py和dataset.py负责读取、清洗和把普通数值列表转换成 LSTM 需要的监督学习格式;model.py定义 LSTM 网络结构;train.py执行训练并保存权重;predict.py加载模型做预测;demo.py调用 matplotlib 完成可视化。config.py统一管理城市列表、抓取 URL、时间步长、批大小等参数,改一个文件就能切换到其他城市。
这种按职责拆分的写法本身就有参考价值。课程设计常用的做法是把所有代码堆在一个main.py里,但一旦要调整模型结构或换数据源,牵一发动全身。这里把爬虫、数据处理、模型、训练、预测分成独立模块,每个模块都能单独运行验证,调试体验好很多。
2.2 用 bs4 抓取中国天气网历史数据
爬虫部分使用了requests获取页面,再用bs4的BeautifulSoup解析 HTML 表格。中国天气网的天气详情页会把逐日温度放在<ul class="t clearfix">或者<table>中,不同年份的 URL 规则不同,所以config.py里需要维护一个 URL 模板。
import requests from bs4 import BeautifulSoup import time def fetch_weather(city_code, year): url = f"http://www.weather.com.cn/weather/{city_code}/{year}.shtml" headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"} resp = requests.get(url, headers=headers, timeout=10) resp.encoding = "utf-8" soup = BeautifulSoup(resp.text, "html.parser") # 解析每个日期的最高温度,中国天气网常见结构为 li 列表 days = soup.select("ul.t li") result = [] for item in days: date = item.h1.get_text(strip=True) high = item.p.span.get_text(strip=True) # 最高温,单位℃ low = item.p.i.get_text(strip=True) # 最低温 result.append((date, float(high), float(low))) return result这段代码的核心是选择器匹配。ul.t li取决于目标页面结构,不同浏览器 F12 看到的 class 名可能不一样,所以第一次运行失败时不要急着改超时,先打印soup.title确认页面是否被反爬拦截。time.sleep(0.5)建议加在循环请求里,避免高频访问被服务端拒绝。
2.3 构造 LSTM 监督学习样本
抓下来的是一串连续日期和温度,LSTM 不能直接吃这种裸序列。它需要的是「用前 5 天预测第 6 天」的样本对。这一步在dataset.py中完成,把原始数组转换成有监督的(X, y)结构。
import numpy as np def create_sequence(data, lookback=5): X, y = [], [] for i in range(len(data) - lookback): X.append(data[i:i + lookback]) y.append(data[i + lookback]) return np.array(X), np.array(y) # 示例:data 是连续 100 天的最高温列表 temps = [f[1] for f in weather_list] # 提取最高温 X, y = create_sequence(temps, lookback=5) print("X shape:", X.shape) # (95, 5) print("y shape:", y.shape) # (95,)这里是第一个容易出错的地方:create_sequence输出的X形状是(样本数, 5),但 Keras 的 LSTM 层要求输入是(样本数, 时间步长, 特征数),所以后面必须做X = X.reshape((X.shape[0], X.shape[1], 1))。项目描述里的(batch_size, dimension)就是这个意思,其中dimension等于时间步长 5。特征数 1 代表每个时刻只用一个温度值,如果之后想加入湿度、降水作为附加特征,只需要把特征数改成 2 或更多,并把temps换成多列矩阵。
2.4 数据归一化与训练集划分
气温序列数值范围较大,直接输入 LSTM 会导致梯度不稳定。常见做法是用MinMaxScaler把数据压缩到 0 到 1 区间,训练完成后再反归一化回真实温度。归一化必须在划分训练集之前进行,否则测试集的信息会泄漏到训练集里,导致评估结果虚高。
from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(0, 1)) temps = np.array(temps).reshape(-1, 1) scaled = scaler.fit_transform(temps) X, y = create_sequence(scaled.flatten(), lookback=5) split = int(len(X) * 0.8) X_train, X_test = X[:split], X[split:] y_train, y_test = y[:split], y[split:] # 转换为 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))fit_transform是在全部数据上计算的,这在实际项目中讨论很多。严格来说,应该先用训练集fit,再用同一 scaler 去transform测试集。如果数据分布稳定,先全局归一化问题不大,但为了毕设答辩时不被人追问,建议改成scaler.fit(X_train_raw)再分别 transform。
3. 基于 Keras 的 LSTM 模型构建与训练参数调整
3.1 单层 LSTM 还是堆叠 LSTM
气温预测是一维单变量时间序列,数据量通常只有几千天,没有必要堆叠多层 LSTM。项目采用的是单层 LSTM + Dense 输出层,这也是此类小规模时序任务最稳妥的结构。层数越多,参数量越大,越容易过拟合,训练时间也越长。对于课程设计,单层 LSTM 把超参数调明白,比强行加层更有说服力。
model.py中的代码如下:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam def build_model(lookback=5, n_features=1): model = Sequential() model.add(LSTM(units=64, input_shape=(lookback, n_features), return_sequences=False)) model.add(Dropout(0.2)) model.add(Dense(units=32, activation="relu")) model.add(Dense(units=1)) model.compile(optimizer=Adam(learning_rate=0.001), loss="mean_squared_error", metrics=["mae"]) return model关键参数在input_shape=(lookback, n_features),这里lookback=5是时间步长,n_features=1是特征维度。return_sequences=False表示只返回最后一个时间步的输出,因为我们要预测的是未来一个点,而不是输出一个序列。Dropout 层防止过拟合,在训练数据只有几千条时尤其重要。如果发现训练损失下降慢,可以把learning_rate从 0.001 提高到 0.005 试试。
3.2 训练流程与早停机制
训练时不能只看 loss 曲线,还要关注验证集误差。常见做法是预留 20% 数据做验证,并配合EarlyStopping在验证损失不再下降时自动停止,保存最佳权重。train.py中体现为:
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint early_stop = EarlyStopping(monitor="val_loss", patience=15, restore_best_weights=True) checkpoint = ModelCheckpoint("best_model.h5", monitor="val_loss", save_best_only=True) history = model.fit(X_train, y_train, validation_split=0.2, epochs=100, batch_size=32, callbacks=[early_stop, checkpoint], verbose=1)patience=15表示连续 15 个 epoch 验证损失没有改善就停止。batch_size=32对于这个数据量是合适的,如果数据特别少,可以降到 16。训练过程中可以打印每个 epoch 的loss和val_loss,观察两者差距:如果训练 loss 很低但 val_loss 很高,说明过拟合,需要增加 Dropout 比例或减少units。
3.3 超参数配置表
项目把常用参数集中在config.py,方便实验对比。实际调参时可以参考这张表:
| 参数 | 推荐值 | 调整方向 |
|---|---|---|
| lookback 时间步长 | 5(本项目默认) | 温度连续性短,增大到 7/10 可捕获周周期 |
| LSTM units | 64 | 数据量少用 32,数据丰富用 128 |
| Dropout | 0.2 | 过拟合时加到 0.3-0.5 |
| batch_size | 32 | 小数据集用 16,太大梯度不稳定 |
| learning_rate | 0.001 | 不收敛时试 0.0005,收敛太慢试 0.005 |
| epochs | 100 | 早停生效后实际运行可能 40-60 就停 |
其中lookback最值得实验。气温变化有昼夜和天气系统周期,5 天只是一个直观选择。你可以改成 7 天、10 天对比测试集 MAE,用实验数据说话,这在答辩时是加分项。
4. 预测结果可视化与误差评估
4.1 反归一化还原温度
模型输出的是归一化后的 0~1 区间数值,画图前必须还原成真实摄氏温度。predict.py里有一段关键代码:
from tensorflow.keras.models import load_model model = load_model("best_model.h5") pred_scaled = model.predict(X_test) # 反归一化:缩放器保存的是整个序列的 min/max pred = scaler.inverse_transform(pred_scaled) y_test_origin = scaler.inverse_transform(y_test.reshape(-1, 1))注意scaler.inverse_transform的输入形状必须是(样本数, 1),如果直接传一维数组会报错。这里y_test在构造时已经是二维,但pred_scaled是二维的,所以先 reshape 再反变换。保存权重时不保存 scaler,所以最好在训练结束后把 scaler 对象用pickle或joblib存下来,预测脚本才能正确还原。
4.2 绘制预测值与真实值对比图
可视化是项目中直接肉眼可评估的部分。demo.py使用 matplotlib 绘制两条曲线,并计算评估指标:
import matplotlib.pyplot as plt from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np mae = mean_absolute_error(y_test_origin, pred) rmse = np.sqrt(mean_squared_error(y_test_origin, pred)) print(f"MAE: {mae:.2f} °C, RMSE: {rmse:.2f} °C") plt.figure(figsize=(12, 5)) plt.plot(y_test_origin, label="True Temperature", linewidth=1.5) plt.plot(pred, label="LSTM Predicted", linewidth=1.5) plt.legend() plt.xlabel("Days") plt.ylabel("Temperature (°C)") plt.title("LSTM Temperature Prediction") plt.grid(alpha=0.3) plt.show()MAE能直接反映平均误差几摄氏度,RMSE对极端误差更敏感。如果 MAE 在 1~2°C 以内,对于逐日最高温预测已经是不错的结果;超过 3°C 就要检查是不是测试集里包含跨季数据,模型没有捕捉到季节突变。可视化时不要只画测试集,可以把训练集尾部连上测试集一起画,直观看出预测曲线相对真实值的滞后程度。
4.3 常见误差场景排查
预测曲线如果整体比真实值滞后一天,这通常不是模型问题,而是时间序列预测的固有现象。因为模型总是基于过去 5 天做一步预测,遇到气温骤升骤降时,反应会慢半拍。排查顺序是先看训练集划分是否乱序,再看归一化是否在整个序列上做。另一个高频问题是预测值几乎平直,只有小幅波动,这说明模型学到了温度均值但没学到波动细节,对策是增大 LSTM units,或者把 lookback 缩短到 3 天试试。
5. 进阶技巧:多步预测、模块级验证与踩坑记录
5.1 从单步预测扩展到多步预测
项目默认只预测下一天,如果要做未来 3 天或 7 天预测,有两种常见方案。递推法是把预测结果当作真实值继续输入模型,循环预测多步;直接法是把输出层改为多个神经元,比如Dense(3)同时输出未来三天。递推法实现简单但误差会累积,直接法对数据量要求更高。
# 递推法示例:预测未来 3 天 current_seq = X_test[-1] # 形状 (5, 1) steps = 3 out = [] for _ in range(steps): p = model.predict(current_seq.reshape(1, 5, 1), verbose=0) out.append(p[0, 0]) current_seq = np.append(current_seq[1:], p.reshape(1, 1), axis=0)这段代码演示了如何把 LSTM 的输出重新拼回输入序列。注意append后要保证current_seq始终是(5, 1)形状。递推法的优点是模型不用重新训练,缺点是第二步开始就用预测值代替真实值,误差会逐层放大。对这个项目来说,做 3 天递推可以接受,做 7 天时预测曲线容易趋平。
5.2 用前向验证检验模型稳定性
课程设计答辩时,老师常问“你的模型在没见过的年份上表现如何”。前向验证是不错的验证手段:用 2011-2018 年训练,2019 年测试;再换 2011-2019 训练,2020 年测试,看指标是否稳定。这个项目的爬虫脚本支持按年份抓取,所以做这个实验成本很低。
# 伪代码:按年份滑动验证 for test_year in [2019, 2020, 2021]: train_years = range(2011, test_year) train_df = fetch_and_merge(train_years) # 自定义合并函数 test_df = fetch_weather(city_code, str(test_year)) # 重新训练并评估这种验证方式能暴露模型对极端年份的适应能力。如果某一年夏季特别热或冬季特别冷,LSTM 的误差会比其他年份大,这是正常现象,但提前知道这个边界比答辩时被问住要好得多。
5.3 实际运行中的典型坑
项目跑通后有几个高频问题值得记录。第一,weather_spider.py抓取时遇到<p class="tem"><span>-2℃</span></p>这样的结构,get_text()会返回带℃和换行的字符串,需要replace("℃", "").strip()再转 float。第二,Keras 版本差异:旧项目用from keras.models import Sequential,新环境可能报错,统一改为from tensorflow.keras即可。第三,如果predict.py加载模型时报Unknown metric: mae,这是因为自定义指标未注册,把compile里的metrics=["mae"]改成字符串"mean_absolute_error"能兼容更多版本。把这些经验写进项目说明里,比堆叠功能更能体现工程能力。
本文还有配套的精品资源,点击获取