☰
LSTM气温预测源码包拆解:从数据到可视化避坑指南
2026/10/18 20:50:40 网站建设 项目流程

简介:这份资源面向高校学生与Python初学者,提供一套基于LSTM的气温预测及可视化完整实现方案,可用于期末大作业、课程设计或入门深度学习实战。项目以Python语言编写,涵盖数据采集、模型构建、训练与预测全流程,并配有可视化展示,代码注释详尽,新手也能看懂,下载后简单部署即可运行。压缩包共16个文件,约723KB,包含8个py源码文件、4个pyc编译文件、2个xlsx数据表与2个md说明文档,源码按爬虫、数据集、模型、训练、预测等模块拆分,结构清晰,便于按需查阅与二次修改。目前已有179人学习下载,具备一定参考热度。读者可获得一套可直接运行的完整项目代码、配套文档说明与数据文件,既能作为高分大作业提交,也能借此理解LSTM时序预测的建模思路与可视化实现方法,具有较高的实际应用与学习价值。

1. 从一份气温预测源码包说起:LSTM 到底能不能跑通

去年帮一个学弟看他的期末大作业,题目是「基于 LSTM 的气温预测及可视化」,他跑出来的预测曲线几乎是一条直线,RMSE 高得离谱,答辩前三天还在改。我拿到源码包一看,问题不在 LSTM 本身,而在数据反归一化和时间窗切分上——这两个地方一错,模型再深也是白搭。这份基于 LSTM 的气温预测及可视化 Python 源码加文档说明,就是围绕这类真实痛点组织的:它把数据爬取、序列建模、训练、预测、可视化串成一条完整链路,适合正在做 Python 期末大作业、课程设计的学生,也适合想快速搭一个时序预测 Demo 的开发者。下面我按「资源结构 → 数据与模型 → 训练与可视化 → 避坑 → 进阶」的顺序,把这份包拆开讲清楚,让你拿到手就能判断能不能用、怎么改。

2. 源码包结构拆解:每个文件在链路里干什么

2.1 目录清单与模块职责

拿到压缩包解压后,根目录下能看到这些文件:weather_spider.py、data.py、dataset.py、model.py、train.py、predict.py、demo.py、config.py、README.md,以及一个data目录和__pycache__缓存目录。这个结构是典型的「数据采集 → 数据处理 → 模型定义 → 训练 → 推理 → 演示」分层,不是把所有逻辑塞进一个脚本里,对期末大作业来说,这种分层本身就是加分项,因为答辩时老师能看出你理解模块边界。

文件职责关键依赖
weather_spider.py抓取历史气温数据,落盘到data/requests、BeautifulSoup
data.py读取原始数据、清洗、缺失值处理pandas、numpy
dataset.py构造时间窗样本,划分训练/测试集torch.utils.data
model.py定义 LSTM 网络结构torch.nn
train.py训练循环、损失计算、模型保存torch
predict.py加载模型、单步/多步预测torch
demo.py可视化预测结果与真实值对比matplotlib
config.py超参数、路径、窗口大小集中管理无

config.py单独存在这件事很关键。很多同学的作业把学习率、窗口大小、批次大小散落在各个文件里,改一个参数要翻五个地方,最后自己都记不清哪个生效。这份包把配置抽出来,你调参时只动一个文件,复现实验记录也方便。

2.2 环境依赖与首次运行

README 里一般会写依赖,但为了确保能跑,我建议先建虚拟环境再装包。Python 版本用 3.8 到 3.10 之间比较稳,太新的版本有时和旧版 PyTorch 有兼容问题。

# 创建虚拟环境,避免污染全局包 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate # 安装核心依赖,版本按 README 为准,这里给常见组合 pip install torch numpy pandas matplotlib requests beautifulsoup4 scikit-learn

装完后先跑weather_spider.py抓数据,或者直接用data/里已有的 CSV。注意:如果爬虫目标站点结构变了,抓取会失败,这时候不要死磕爬虫,直接用包内自带数据先把训练跑通,确认模型链路没问题,再回头修爬虫。这是排错的基本顺序——先隔离变量,再定位问题。

提示:__pycache__目录是 Python 自动生成的字节码缓存,删掉不影响运行,提交作业时建议删掉,保持包干净。

3. 数据管道与 LSTM 建模:窗口切分和归一化是命门

3.1 时间序列窗口构造

气温预测本质是拿过去 N 天的温度预测未来 M 天的温度。dataset.py里通常用滑动窗口把一维序列切成(样本数, 窗口长度, 特征数)的三维张量。窗口长度就是config.py里的seq_len,比如设为 7,表示用过去 7 天预测第 8 天。

# dataset.py 核心逻辑示意 import numpy as np import torch from torch.utils.data import Dataset class TempDataset(Dataset): def __init__(self, series, seq_len=7, pred_len=1): self.series = series # 归一化后的一维数组 self.seq_len = seq_len # 输入窗口长度 self.pred_len = pred_len # 预测步长 def __len__(self): # 可切出的样本总数 return len(self.series) - self.seq_len - self.pred_len + 1 def __getitem__(self, idx): # 输入窗口 x = self.series[idx : idx + self.seq_len] # 预测目标 y = self.series[idx + self.seq_len : idx + self.seq_len + self.pred_len] # LSTM 要求输入是 (seq_len, feature),这里 feature=1 return torch.FloatTensor(x).unsqueeze(-1), torch.FloatTensor(y)

这段代码里seq_len和pred_len直接决定样本数量和预测难度。seq_len太小,模型看不到足够历史,预测会滞后;太大,样本数骤减,训练容易过拟合。我一般先用 7 或 14 试,看验证集损失再调。unsqueeze(-1)是把一维序列变成(seq_len, 1),因为nn.LSTM要求输入维度是(batch, seq, feature),少这一维会直接报维度错误,这是新手最常见的翻车点之一。

3.2 归一化与反归一化

气温数值范围不大,但归一化仍然必要,因为 LSTM 对输入尺度敏感。常见做法是 Min-Max 归一化到[0,1],或者用训练集的均值和标准差做标准化。关键原则:归一化参数只能用训练集算,然后应用到测试集,否则就是数据泄露,答辩时被问到会很难解释。

# data.py 中的归一化处理 from sklearn.preprocessing import MinMaxScaler scaler = MinMaxScaler(feature_range=(0, 1)) train_scaled = scaler.fit_transform(train_values.reshape(-1, 1)) # 注意:测试集用 transform,不是 fit_transform test_scaled = scaler.transform(test_values.reshape(-1, 1)) # 预测完成后必须反归一化,否则画出来的曲线是 0 到 1 之间的 pred_real = scaler.inverse_transform(pred_scaled)

反归一化这一步如果漏掉,可视化出来的预测值全在 0 到 1 之间,和真实气温(比如 15 到 35 度)完全对不上,曲线看起来「很平」,这就是我开头说的那个学弟踩的坑。predict.py和demo.py里一定要确认有inverse_transform调用。

3.3 LSTM 网络结构定义

model.py里的网络通常不复杂:一层或两层 LSTM,接一个全连接层输出预测值。层数不是越多越好,气温这种单变量序列,两层 LSTM 已经够用,再深容易过拟合且训练慢。

# model.py 网络定义 import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size=1, hidden_size=64, num_layers=2, output_size=1): super().__init__() self.hidden_size = hidden_size self.num_layers = num_layers # batch_first=True 让输入格式为 (batch, seq, feature) self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): # h0、c0 初始化为全零 h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) out, _ = self.lstm(x, (h0, c0)) # 只取最后一个时间步的输出做预测 out = self.fc(out[:, -1, :]) return out

hidden_size控制记忆容量,64 或 128 是常见起点;num_layers是堆叠层数;batch_first=True这个参数必须和dataset.py的输出维度对齐,否则训练时维度报错。out[:, -1, :]表示只拿序列最后一步的隐藏状态去预测,因为 LSTM 已经把历史信息压缩到了最后一步。

4. 训练、预测与可视化:把曲线画对才算完

4.1 训练循环与损失监控

train.py的核心是标准 PyTorch 训练循环:前向传播、算损失、反向传播、更新参数。损失函数用 MSELoss,优化器用 Adam,学习率从config.py读。

# train.py 训练主循环 import torch import torch.nn as nn from model import LSTMModel from dataset import TempDataset from torch.utils.data import DataLoader model = LSTMModel() criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) loader = DataLoader(TempDataset(train_scaled), batch_size=32, shuffle=True) for epoch in range(100): model.train() total_loss = 0 for x, y in loader: optimizer.zero_grad() pred = model(x) loss = criterion(pred.squeeze(), y.squeeze()) loss.backward() optimizer.step() total_loss += loss.item() # 每 10 轮打印一次,观察是否收敛 if (epoch + 1) % 10 == 0: print(f"Epoch {epoch+1}, Loss: {total_loss/len(loader):.6f}")

lr=0.001是 Adam 的常用默认值,如果损失震荡不降,先降到 0.0005 试;如果下降太慢,可以升到 0.005,但别超过 0.01,否则容易发散。shuffle=True对时间序列要谨慎——如果样本之间有强时序依赖,打乱可能破坏顺序信息,不过这里每个样本已经是独立窗口,打乱通常没问题。训练时一定要打印损失,不打印你就不知道模型是没学还是过拟合,这是黑匣子调试的基本习惯。

4.2 预测与反归一化

predict.py负责加载保存的模型权重,对测试集做预测,并把结果反归一化回真实温度。

# predict.py 预测流程 model.load_state_dict(torch.load("best_model.pth")) model.eval() with torch.no_grad(): preds = model(test_tensor) # 输出是归一化值 preds = preds.squeeze().numpy() preds_real = scaler.inverse_transform(preds.reshape(-1, 1))

model.eval()会关闭 Dropout 和 BatchNorm 的训练行为,预测时必须加。torch.no_grad()关闭梯度计算,省显存也提速。反归一化用训练时保存的同一个scaler,不能重新 fit,否则尺度对不上。

4.3 可视化对比与指标计算

demo.py用 matplotlib 把真实值和预测值画在同一张图上,同时算 RMSE 和 MAE。图能直观看出模型是滞后、偏高还是偏低。

# demo.py 可视化 import matplotlib.pyplot as plt from sklearn.metrics import mean_squared_error, mean_absolute_error import numpy as np rmse = np.sqrt(mean_squared_error(true_real, preds_real)) mae = mean_absolute_error(true_real, preds_real) plt.figure(figsize=(12, 5)) plt.plot(true_real, label="真实气温", color="blue") plt.plot(preds_real, label="预测气温", color="red", linestyle="--") plt.xlabel("时间步") plt.ylabel("气温") plt.title(f"LSTM 气温预测对比 RMSE={rmse:.2f} MAE={mae:.2f}") plt.legend() plt.savefig("result.png", dpi=150) plt.show()

RMSE 对异常值敏感,MAE 更稳健,两个都报出来更专业。如果预测曲线整体滞后真实曲线一个时间步,说明seq_len偏小或模型没学到趋势,可以试着加长窗口或增加 LSTM 层数。如果曲线几乎重合但测试集 RMSE 很低、换一段数据就崩,那多半是过拟合,需要加 Dropout 或减少参数量。

5. 避坑与排查:这几处翻车率最高

5.1 预测曲线是一条直线

现象:可视化出来预测值几乎不变,像一条水平线。原因:漏了反归一化,或者模型输出被squeeze成了标量后广播。解决:检查predict.py里有没有inverse_transform;确认preds形状是(N,1)而不是(1,N),必要时用reshape(-1,1)修正。

5.2 训练损失不下降或变成 NaN

现象:Loss 一直停在某个值,或者几个 epoch 后变成nan。原因:学习率太大、输入没归一化、或者序列里有缺失值没处理。解决:先把学习率降到 0.0001 试;确认data.py里做了缺失值填充(前向填充或均值填充);检查归一化是否在切窗口之前完成。

5.3 维度不匹配报错

现象:RuntimeError: input must have 3 dimensions或expected hidden size。原因:dataset.py返回的x没有unsqueeze(-1),或者batch_first没设成True。解决:统一输入形状为(batch, seq_len, 1);nn.LSTM初始化时加batch_first=True;h0、c0的维度用x.size(0)而不是硬编码。

5.4 爬虫抓不到数据

现象:weather_spider.py运行后data/目录为空或报连接错误。原因:目标页面结构变化、请求头被拦截、或者网络环境限制。解决:先用包内自带 CSV 跑通训练;修爬虫时加User-Agent请求头,检查解析用的标签是否还对得上;不要在没有数据的情况下怀疑模型。

5.5 测试集指标好得离谱

现象:RMSE 小于 0.1,曲线完美重合。原因:归一化时对全量数据做了fit_transform,测试集信息泄露到训练过程。解决:严格按时间顺序切分训练/测试,scaler只在训练集上fit,测试集只transform。这是时序预测里最隐蔽的坑,答辩时被问到「你怎么保证没有未来信息泄露」要能答上来。

6. 进阶技巧:多步预测与超参数搜索怎么落地

单步预测跑通后,真正拉开差距的是多步预测。把pred_len从 1 改成 3 或 7,模型要一次输出未来多天温度,有两种常见做法:一种是直接让全连接层输出pred_len个值,另一种是自回归——把预测出的值再喂回输入窗口。前者训练稳定但误差会累积,后者实现简单但容易漂移。我一般先用直接多输出,因为代码改动小,model.py里把self.fc = nn.Linear(hidden_size, pred_len)即可,dataset.py的__getitem__里y取连续pred_len个点。

超参数方面,seq_len、hidden_size、lr、batch_size这四个最值得搜。不要上重型框架,写个三层循环就够:

# 简易网格搜索,记录每组参数的验证集 RMSE import itertools seq_lens = [7, 14, 21] hidden_sizes = [32, 64, 128] lrs = [0.001, 0.0005] results = [] for seq_len, hidden, lr in itertools.product(seq_lens, hidden_sizes, lrs): # 用当前参数重建 dataset、model,训练若干轮 # 在验证集上算 RMSE,追加到 results results.append((seq_len, hidden, lr, val_rmse)) # 按 RMSE 升序,取前几组 results.sort(key=lambda x: x[3]) for r in results[:5]: print(f"seq_len={r[0]}, hidden={r[1]}, lr={r[2]}, RMSE={r[3]:.3f}")

这个搜索很粗糙,但比拍脑袋调参强。注意每次换seq_len都要重新切数据集,不能复用旧的 DataLoader。验证集要从训练集里再切一段出来,不能直接用测试集选参数,否则测试集就失去了评估意义。

还有一个容易被忽略的点:保存模型时把config.py里的超参数一起存下来,或者写进 README。我见过太多人过两周回来跑自己的代码,忘了当时seq_len设的多少,结果加载权重后维度对不上,只能重训。从那以后我每次保存模型都强制把参数字典一起 dump 成 json,加载时先读参数再建网络,这个习惯帮我省了无数次后悔药。希望这份拆解能帮到你,拿到包先跑通链路,再按自己的数据改窗口和归一化,最后把可视化做扎实,期末大作业的分数不会低。

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

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

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

立即咨询