ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于LSTM的气温预测及可视化:一套可复用的时间序列预测模板

基于LSTM的气温预测及可视化:一套可复用的时间序列预测模板 简介面向Python课程设计与期末大作业场景一份基于LSTM的气温预测与可视化完整项目源码与文档说明均包含在内。项目覆盖数据采集、预处理、模型构建、训练评估、预测及可视化全流程适合希望参考完整实现思路、快速搭建同类任务的学习者。压缩包共16个文件以8个Python脚本为主分别承担爬虫采集、数据处理、模型定义、训练、预测和演示等职责另含2个Excel数据表、2个Markdown说明文档及少量pyc缓存整体仅723KB目录结构清晰。目前已有179人学习下载是典型的高分期末大作业方案。代码注释完善新手也能读懂关键逻辑配合说明文档和演示脚本下载后简单部署即可运行也可作为课程设计、答辩展示或二次开发的参考基础。1. 基于LSTM的气温预测及可视化从期末大作业到能直接改的时间序列模板基于LSTM的气温预测及可视化 python 源码本质是一条完整的数据采集—序列构建—模型训练—预测回测—可视化管线。说它适合当期末大作业不只是因为注释齐全更因为它把 LSTM 时序预测最容易翻车的环节摆在了明面上数据怎么滑窗、训练集怎么切、预测怎么画才不骗自己。我拆这份资源最直观的感受它不是让你跑通 demo 就完事而是给你一个能换数据集、能改参数、能加特征的最小框架。适合正在做课程设计的学生以及第一次接触 LSTM 时间序列、想拿温度数据练手的开发者。如果你想要一个黑匣子预测脚本它反而显得啰嗦想借这个项目理解时序模型它的结构值得照着拆一遍。2. 项目结构与数据链路五个 .py 模块分别干了什么2.1 文件清单与模块职责把压缩包里的 .py 对号入座打开压缩包第一眼看到的是一堆散落的 .py 文件没有 src 目录、没有包结构典型的期末大作业风格。解压后你会看到 t-main 和 ClimateAnalysis 两个目录README.md 放在最外层。但这套源码的命名其实很有规律weather_spider.py 管采集dataset.py 和 data.py 管数据加工model.py 管网络定义train.py 管训练predict.py 和 demo.py 管预测与展示config.py 管全局参数。我先给一张模块对照表后面逐段讲。文件职责输入 → 输出config.py全局参数所有模块读取的默认配置weather_spider.py天气数据爬取URL → 原始天气表data.py数据预处理原始 CSV → 清洗后的气温序列dataset.py滑窗数据集气温序列 → (x, y) 样本对model.pyLSTM 网络定义样本维度 → 模型实例train.py训练主循环模型 数据 → 权重文件predict.py预测与评价权重 测试数据 → 预测值demo.py一键演示直接运行 → 出图提示pycache是 Python 运行时生成的缓存目录可以不管它真正要动的文件是 config.py、model.py、train.py 三个。模块划分的合理性在于数据、模型、训练、预测四层互相解耦。我在实际跑的时候改的最多的是 config.py 和 dataset.py因为换数据集、调滑窗长度基本只碰这两个文件不需要动 model 和 train 的逻辑。这比把爬虫、训练、画图全塞进一个 main.py 的做法好维护得多。另外 README.md 里有环境依赖清单建议先跟一遍再动代码常见坑是缺 numpy、pandas、torch、matplotlib、requests、beautifulsoup4 这几个包缺哪个 pip install 哪个就行。2.2 weather_spider.py爬虫的请求方式与字段清洗weather_spider.py 负责把历史天气数据抓下来。常见做法是请求天气网站的月视图页面解析 HTML 表格里的日期、最高温、最低温字段最后落成一个 CSV。这个项目里的关键函数一般是 fetch_weather 和 parse_weather_table 这样的结构requests 负责拿页面BeautifulSoup 负责解析我用一个简化版本说明它做的事import requests from bs4 import BeautifulSoup import pandas as pd def fetch_weather(city_code, year, month): # 构造天气网站的月视图 URL url fhttps://example-weather.com/{city_code}/{year}{month:02d}.html headers {User-Agent: Mozilla/5.0} resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 return resp.text def parse_weather_table(html): soup BeautifulSoup(html, html.parser) rows [] # 定位天气数据表格行提取日期、最高温、最低温 for tr in soup.select(table tr): cells [td.get_text(stripTrue) for td in tr.select(td)] if len(cells) 3: rows.append({date: cells[0], tmax: cells[1], tmin: cells[2]}) return pd.DataFrame(rows) if __name__ __main__: df parse_weather_table(fetch_weather(101010100, 2023, 6)) df.to_csv(weather_raw.csv, indexFalse, encodingutf-8-sig)逻辑说明fetch_weather 用 requests 拿页面parse_weather_table 用 BeautifulSoup 解析表格最后存成 CSV。编码用 utf-8-sig 是关键Excel 打开不会乱码requests 的 timeout 参数一定要设否则某个请求卡住整个循环就断了。参数说明city_code 是气象站城市编码timeout10 表示超过 10 秒放弃这次请求encoding 指定了解析编码不同天气站的页面编码可能是 gbk这个值要根据实际页面调整。爬虫这块是整条链路里最脆的环节因为天气网站的页面结构说改就改选择器一变parse_weather_table 里那条 select 规则就失效。我一般会先打印一页 HTML 确认选择器再跑全量别一上来就循环抓三年数据抓错格式再回头排查很浪费时间。如果你不想依赖爬虫也可以把别人整理好的历史气温 CSV 直接丢进 data/ 目录只要列名和 data.py 里读的字段一致就行。2.3 dataset.py 与 data.py滑窗切分是 LSTM 数据的核心data.py 负责把 CSV 里的温度和日期字段整理成干净的序列比如把最高温列转成 float、把日期解析成 datetime、处理缺失值。这里有一个顺序问题清洗必须在滑窗之前因为如果有缺失值滑窗切出来的样本里就会有 NaNLSTM 拿 NaN 训练loss 大概率会变成 nan。我见过不少人跳过 data.py 直接手动读 CSV结果训练出来模型权重全是 NaN回头排查才发现温度列里混了空字符串。dataset.py 和 data.py 的分工是data.py 输出原始序列dataset.py 输出可以直接丢给 DataLoader 的样本对这样换数据集只改 data.py换滑窗策略只改 dataset.py。import numpy as np import torch from torch.utils.data import Dataset class WeatherDataset(Dataset): def __init__(self, series, seq_len7): # series: 一维 numpy 数组seq_len: 用过去多少天预测下一天 self.seq_len seq_len self.x, self.y self._build_samples(series) def _build_samples(self, series): x, y [], [] for i in range(len(series) - self.seq_len): x.append(series[i:i self.seq_len]) y.append(series[i self.seq_len]) return np.array(x).reshape(-1, self.seq_len, 1), np.array(y) def __len__(self): return len(self.y) def __getitem__(self, idx): return torch.tensor(self.x[idx], dtypetorch.float32), \ torch.tensor(self.y[idx], dtypetorch.float32)逻辑说明_build_samples 用滑窗把一条长序列切成长度为 seq_len 的输入和单个目标值完成从原始时序到监督学习样本的转换。这里的 feature_dim 是 1因为只用了气温一个特征如果你后续加入湿度、气压feature_dim 会变成 3 甚至更多滑窗切出来的每个样本就是 (seq_len, feature_dim) 的二维矩阵。参数说明seq_len7 表示用一周的历史预测明天这个值不是越大越好——气温序列有较强的周期性和自相关性seq_len 太小模型学不到趋势太大反而引入噪声常见取值是 7、14、30我一般会用 7 和 14 做对比实验。这里有个隐蔽问题滑窗样本之间高度重叠第 i 个样本和第 i1 个样本只差一天的数据如果不做随机打乱训练时梯度会非常平滑模型容易过拟合到最近几天的模式。所以 train.py 里 DataLoader 的 shuffleTrue 不是可选项是必须项。训练集和测试集的划分也要在切片之前完成最好的顺序是清洗 → 划分 → 滑窗先切分再滑窗才能保证测试集的样本不泄漏训练集信息。如果你先滑窗再切分滑窗产生的边缘样本会在两个集合之间串门评估结果就是虚高的。3. 模型设计与训练hidden_size、seq_len 和早停怎么配合3.1 model.py网络结构、hidden_size 与层数的选择model.py 里的 LSTM 结构很标准输入是连续数值序列所以不需要 Embedding 层直接从全连接映射接 LSTM 再接输出层。核心是下面这段import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, seq_len7): super().__init__() self.lstm nn.LSTM(input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, 1) def forward(self, x): # x 形状: (batch, seq_len, input_size) out, _ self.lstm(x) # out: (batch, seq_len, hidden_size) last out[:, -1, :] # 取最后一个时间步的隐状态 return self.fc(last) # (batch, 1)逻辑说明输入 (batch, seq_len, 1) 经过 LSTM 后输出每个时间步的隐状态我们只取最后一个时间步的隐状态再过一个全连接层得到预测值。这里 batch_firstTrue 让张量形状更直观避免在 batch 和 seq_len 两个维度上绕晕。参数说明hidden_size64 是隐状态维度决定模型容量num_layers2 是堆叠的 LSTM 层数两层就能捕捉一定的层次特征单层往往欠拟合三层以上在小数据集上基本只会过拟合。input_size1 对应单变量温度。这里我要说一个常被新手忽略的点LSTM 的初始隐状态 h0 和 c0 默认是零PyTorch 的 nn.LSTM 会在内部自动处理所以 forward 里没有手动传 (h0, c0)。但如果你要做多步预测比如一次预测未来 7 天就必须自己维护隐状态并在每个预测步之间传递否则等于每次都从零开始预测会严重失真。项目里的 predict.py 走的是单步回测路线所以没暴露这个问题后面第 4 章会展开。补充一下model.py 里通常还会写一个独立的推理分支但核心结构不变。如果你在答辩时被问到为什么用两层不用三层可以直接回答数据集只有几百天的日温记录样本量小层数加深带来的参数增量没有足够数据支撑两层是容量和泛化之间的平衡点。这个回答比我试出来的显得专业得多。3.2 train.py训练循环、学习率与早停机制train.py 是整条管线的中枢。标准流程是读配置 → 加载数据 → 建模型 → 定损失函数和优化器 → 循环训练 → 每轮记录 loss → 保存最好权重。我用项目里常见的写法做一个可运行的训练主循环import torch import torch.nn as nn from torch.utils.data import DataLoader def train(model, train_loader, epochs, lr0.001, devicecpu): criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lrlr) model.to(device) best_loss float(inf) for epoch in range(epochs): model.train() total_loss 0.0 for x, y in train_loader: x, y x.to(device), y.to(device) optimizer.zero_grad() pred model(x).squeeze(-1) loss criterion(pred, y) loss.backward() optimizer.step() total_loss loss.item() * x.size(0) avg_loss total_loss / len(train_loader.dataset) print(fEpoch {epoch1:03d} | Loss {avg_loss:.6f}) # 保存训练过程中 loss 最低的权重 if avg_loss best_loss: best_loss avg_loss torch.save(model.state_dict(), best_model.pth) return model逻辑说明每一轮把数据按 batch 喂给模型计算 MSE 损失反向传播更新权重最后用平均 loss 判断要不要覆盖保存 best_model.pth。MSELoss 是回归任务标配温度预测的误差平方和损失能直接反映预测值和真实值的偏离程度。参数说明lr0.001 是 Adam 的常见起点epochs 一般设 50 到 100batch_size 在 DataLoader 里通常设 32 或 64。注意这里没有做学习率衰减项目里的原版同样简单所以如果你的 loss 在后期震荡不降可以在 Adam 外面包一层 torch.optim.lr_scheduler.StepLR每 20 轮把学习率乘 0.5。早停是另一个值得补的点。温度预测这种小数据集训练到后面 loss 曲线通常是快速下降 → 平台期 → 微微过拟合靠 epoch 上限硬撑并不明智。我在复现时习惯在训练循环里加一个 patience 计数器连续 10 轮验证 loss 不降就提前终止最优权重已经保存在 best_model.pth 里。加早停的代码很短就是维护一个 best_loss 和一个 counterbest_loss float(inf) patience 10 counter 0 for epoch in range(epochs): # ... 正常训练代码得到 valid_loss ... if valid_loss best_loss: best_loss valid_loss counter 0 torch.save(model.state_dict(), best_model.pth) else: counter 1 if counter patience: print(fEarly stop at epoch {epoch 1}) break逻辑说明valid_loss 来自验证集训练集最后 20% 那一段当它连续 patience 轮没有刷新最优值时触发 break每轮结束都要把 best_loss 和 counter 更新一遍。参数说明patience10 表示容忍 10 轮不进步一般取 5 到 15数据越小声越小。这个机制配合 best_model.pth 的保存逻辑能保证你最终加载的是历史上验证 loss 最低的那个权重而不是最后一个 epoch 的权重。3.3 config.py把超参数集中到一处调参不靠改代码这个项目做得比较聪明的一点是 config.py 把散落各处的超参数集中到了一起。我建议你不要忽视这个文件期末大作业答辩时老师大概率会问你的参数为什么这么设config.py 就是你回答的依据。# config.py DATA_PATH data/weather.csv # 原始气温数据路径 SEQ_LEN 7 # 滑窗长度用 7 天预测下一天 BATCH_SIZE 32 # 每批样本数 EPOCHS 80 # 最大训练轮数 LR 0.001 # 学习率 HIDDEN_SIZE 64 # LSTM 隐状态维度 NUM_LAYERS 2 # LSTM 堆叠层数 TRAIN_RATIO 0.8 # 训练集占比前 80% 做训练 DEVICE cpu # 训练设备有 GPU 改成 cuda逻辑说明config.py 本身不参与计算它只是把 train.py、dataset.py、predict.py 都要读的公共参数集中到一处import config 之后各个模块拿到的 SEQ_LEN、HIDDEN_SIZE 就是同一个值避免改一个参数要翻三个文件的尴尬。参数说明TRAIN_RATIO0.8 意味着用前 80% 的时间段训练、后 20% 做验证这个切法对时序数据是必须的——不能随机抽样否则未来信息会泄漏进训练集。SEQ_LEN7 和 HIDDEN_SIZE64 是最值得调的两个旋钮SEQ_LEN 影响模型看到的历史窗口HIDDEN_SIZE 影响模型容量。如果你的数据是小时级的SEQ_LEN 可能要 24 或者 48如果是日温7 到 14 就够。DEVICE 字段在无 GPU 环境下保持 cpu有 CUDA 显卡时改成 cuda 能快一个数量级前提是你的 torch 是带 CUDA 的版本。调参的顺序我一般是先固定其他参数只改 SEQ_LEN对比 7 和 14 两组再固定它改 HIDDEN_SIZE对比 32、64、128最后看 loss 曲线决定 EPOCHS。一次只动一个变量别同时改三个不然 loss 变好了你都不知道是谁的功劳。把每次实验的 MAE、RMSE、训练时间记录在一个表格里答辩时拿出来非常加分。4. 预测与可视化回测策略、出图规范与两个指标4.1 predict.py单步回测与多步预测的区别predict.py 做的事可以分两步加载训练好的权重对测试集做滑窗回测然后把预测值和真实值一起返回。回测的含义是用第 1 到 7 天的温度预测第 8 天再用第 2 到 8 天预测第 9 天……每一步的输入里都包含真实值所以叫teacher forcing的单步回测。import numpy as np def predict(model, test_loader, devicecpu): model.eval() preds, trues [], [] with torch.no_grad(): for x, y in test_loader: x x.to(device) pred model(x).squeeze(-1) preds.append(pred.cpu().numpy()) trues.append(y.numpy()) return np.concatenate(preds), np.concatenate(trues)逻辑说明model.eval() 关闭 dropout 和 batch normalization 的训练行为no_grad() 关闭梯度计算这两行是推理的必要设置少了任何一行预测结果都会不稳或浪费显存。参数说明test_loader 的数据不能 shuffle因为预测结果要和真实值按时间顺序对齐画图一旦打乱你画出来的真实值和预测值就对不上号了。这里的 test_loader 是用 WeatherDataset 包住测试集序列后创建的和 train_loader 同源。这里要提醒一个很多人栽过的坑单步回测的误差是每步都用真实值纠正的误差它天然偏乐观。真正要检验模型能不能做多步预测得另外实现一个循环预测出第 8 天把它当作已知输入去预测第 9 天这样往后推 7 步。项目里的 predict.py 是单步回测我建议你至少手动跑一遍多步预测看看误差是怎么随着步数放大的那才是 LSTM 在气温上的真实表现。多步预测的核心代码是把预测值动态拼回输入序列每次只滑动一步def multi_step_predict(model, seed, steps7, devicecpu): # seed: 长度为 seq_len 的一维数组作为预测起点 model.eval() model.to(device) seq seed.copy() preds [] with torch.no_grad(): for _ in range(steps): x torch.tensor(seq).reshape(1, -1, 1).float().to(device) y model(x).squeeze(-1).item() preds.append(y) # 把预测值追加到序列末尾丢掉最前面一天 seq np.append(seq[1:], y) return np.array(preds)逻辑说明先把测试集最后 seq_len 天取出来作为种子序列每次用模型预测下一天再把预测值追加到序列末尾、丢掉最前面一天如此循环 steps 次得到连续的多步预测。参数说明steps 是你要预测的天数设 7 就预测未来一周seed 序列越长初始上下文越充分但多步预测的误差还是会随着步数累积——预测第 1 天误差可能 1 度第 7 天可能就 3 度了这个放大趋势本身就值得写进报告。4.2 demo.py把 loss 曲线和预测对比图画出来demo.py 是演示入口运行后应该画出两类图训练 loss 曲线和测试集上真实温度 vs 预测温度的对比曲线。绘图用 matplotlib这是最常用的方案。我给出项目的核心绘图逻辑import matplotlib.pyplot as plt def plot_result(trues, preds, save_pathresult.png): plt.figure(figsize(12, 5)) plt.plot(trues, labelTrue, colorblack, linewidth1) plt.plot(preds, labelPred, colorred, linewidth1, linestyle--) plt.legend() plt.xlabel(Day) plt.ylabel(Temperature (°C)) plt.title(LSTM Temperature Prediction) plt.grid(alpha0.3) plt.savefig(save_path, dpi150, bbox_inchestight) def plot_loss(history, save_pathloss.png): plt.figure(figsize(8, 5)) plt.plot(history, colorblue) plt.xlabel(Epoch) plt.ylabel(MSE Loss) plt.title(Training Loss) plt.grid(alpha0.3) plt.savefig(save_path, dpi150, bbox_inchestight)逻辑说明真实值用黑色实线、预测值用红色虚线温度单位是摄氏度横轴是天数。loss 曲线看收敛性对比曲线看拟合度这两张图是期末大作业报告里必须出现的核心证据。参数说明dpi150 保证论文插图清晰bbox_inchestight 自动裁剪空白边距。保存文件而不是用 plt.show()是因为跑批量实验时不可能每轮都弹窗盯着看。如果你想把图做得更专业可以把训练集和测试集的预测分别画在两个子图里训练集用浅色、测试集用深色一眼就能看出模型在没见过的数据上掉不掉链子。这个细节在答辩时很讨喜。另外demo.py 里通常还会把真实温度和预测温度的温度差单独画一个误差子图超出某个阈值比如 2 度的部分就是模型的高风险日这类日子多出现在气温骤变的换季期。4.3 评价指标MAE 与 RMSE 怎么看才算懂光画图不够报告里得给数字。气温预测最常用的两个回归指标是 MAE平均绝对误差和 RMSE均方根误差它们都能在 predict.py 里顺手算出来。def evaluate(preds, trues): mae np.mean(np.abs(preds - trues)) rmse np.sqrt(np.mean((preds - trues) ** 2)) print(fMAE: {mae:.3f} °C | RMSE: {rmse:.3f} °C) return mae, rmse逻辑说明MAE 告诉你平均每天预测偏了多少度RMSE 因为对误差取了平方会放大那些偏离大的样本所以 RMSE 通常大于等于 MAE。两个指标结合看如果 RMSE 比 MAE 大很多说明模型在个别极端温度突然降温、升温上预测得很差这是时序模型的常见病。参数说明温度的 MAE 在 1 到 2 摄氏度之间算不错超过 3 度说明模型基本在猜这个数值没有绝对标准要看数据本身的波动幅度冬天的日温波动 10 度以上MAE 2 度就算可接受。我一般会把 mae、rmse 和两张图画进同一个对比文件里作为训练日志的一部分。这样每次实验结束打开 outputs 目录就能横向比较不同参数组合的预测质量不用重新跑一遍。如果你把多步预测的误差也按步数画成一条误差增长曲线那这份日志的说服力会比单看两个数字强很多。5. 避坑与常见问题五个坑的现场还原与修复5.1 loss 变成 NaN先查数据再查学习率现象训练跑到某个 epochloss 打印出来是 nan模型权重全废后续所有 epoch 都在 nan 里打转。原因大概率是数据里有缺失值或非数值字符NaN 一旦进入 loss 反向传播梯度也会变成 NaN之后每一步训练都在污染权重少数情况下是学习率过大导致梯度爆炸也会在某个 step 直接冲破数值上限。解决进模型之前做一次数据清洗用 pandas 的 dropna 删掉缺失行或者用前后两天的均值填充再把温度序列做归一化Min-Max 或 StandardScaler让输入落在 0 到 1 附近LSTM 的激活函数才不会饱和。数据清洗永远在滑窗之前做这个顺序不能反。如果清洗完还是 nan把 lr 从 0.001 降到 0.0001 再试。5.2 预测曲线整体滞后一拍模型在抄昨天的答案现象画出来的预测线几乎就是真实线往右平移了一天真实值涨它跟着涨真实值跌它跟着跌但永远慢半拍。原因这是时序预测最典型的现象。模型学到的最优策略是复制上一天的温度因为气温是强自相关的昨天的温度对今天有很强的预测力模型走捷径比真的学习温度变化规律更划算。单步回测评估方式还会让这个问题被指标掩盖——滞后一拍的 RMSE 可能并不难看。解决引入更多特征让模型无法偷懒加入月份、季节编码、前一天的温度差一阶差分或者直接改用多步预测损失来训练。我在复现气温项目时把温度差作为额外特征加进去之后滞后现象明显缓解因为模型不能再靠复制上一天来蒙混过关了。5.3 训练集完美、测试集崩掉泄漏和过拟合在捣鬼现象训练集上 loss 低到 0.1测试集上 MAE 飙到 4 度两张曲线肉眼可见地分家。原因通常有三个。一是切分前没有按时序切而是随机抽样了导致测试集里混进了和训练集重叠的信息你看到的训练集完美其实是模型见过测试集的假象二是模型容量太大hidden_size 设到 128 以上数据量只有几百天必然过拟合三是滑窗样本重叠太多训练集内部高度的自相似让模型记住了样本而不是规律。解决确认 train_test_split 是按时序切而不是 shuffle 切检查顺序是清洗 → 划分 → 滑窗还是反了降低 hidden_size 到 32 或 64训练时打开 shuffleTrue 并把 dropout 加到 0.2 左右。改完这三处测试集指标通常能拉回来一大截。5.4 CPU 训练慢到怀疑人生先做烟雾测试再谈精度现象笔记本没有独立显卡DEVICEcpu几十个 epoch 跑下来要等十几分钟甚至更久。原因CPU 上的矩阵运算本来就不擅长 LSTM 这种串行结构seq_len 长、hidden_size 大、epoch 多三者叠加时间成本直线上升。很多人为了省时间直接跳到小 epoch结果模型没收敛就交上去了。解决先减小 EPOCHS 到 10、SEQ_LEN 到 3 做烟雾测试确认整条链路能跑通、loss 在正常下降再恢复正式参数。batch_size 适当调大到 64能提高 CPU 的向量化利用率。如果实在没耐心把 seq_len、hidden_size 都减半跑一轮当基线后面再逐步加回来好过一直在等一个大参数集的完整训练。5.5 matplotlib 中文和负号变方块字体配置两行搞定现象图例写成温度直接变方块坐标轴的负号也变成奇怪的框图拿不出手。原因matplotlib 默认字体里没有中文字形黑体、宋体这类系统字体需要手动指定负号问题则是默认的 Unicode 负号渲染在某些字体下会失败。解决绘图脚本开头强制指定字体import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # Windows 黑体 plt.rcParams[axes.unicode_minus] False # 解决负号显示问题逻辑说明第一行指定黑体作为默认无衬线字体第二行关掉 Unicode 负号渲染改用 ASCII 负号。如果你在 Linux 服务器上跑SimHei 不存在改成 WenQuanYi Zen Hei 或者干脆把图例写成英文别在字体上耗时间。这两个参数是 matplotlib 中文图表的老坑项目里如果没写你接手时一定要补上。补完之后重新运行 demo.py中文和负号就都正常了。6. 进阶改造从单变量到多变量再加一个多步预测自检6.1 加特征比换模型更划算既然爬虫已经能拿到原始天气页顺手把湿度、气压、风速一起解析出来model.py 的 input_size 改成 4滑窗样本最后一维变成 (seq_len, 4)其余代码几乎不用动。答辩时我做了多变量温度预测比我跑通了 LSTM好讲得多。注意先各自归一化再拼接不然气压 1000 上下的数值范围会把 -10 到 40 的温度特征淹没。6.2 换成 GRU 做对比改两行就能出实验数据PyTorch 里把 nn.LSTM 换成 nn.GRU 只需改 model.py 的构造和 forward 各一行train.py 完全共用。GRU 参数更少、收敛更快在小数据集上常和 LSTM 相当。用第 4 章同一套 evaluate 流程算两组 MAE、RMSE画一张对比柱状图实验对比章节就有了。import torch.nn as nn class GRUPredictor(nn.Module): def __init__(self, input_size4, hidden_size64, num_layers2): super().__init__() self.gru nn.GRU(input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.gru(x) return self.fc(out[:, -1, :])逻辑说明GRU 只有隐状态 h没有 cell state 的 c所以 forward 里只解包一个状态值其余结构和 LSTM 完全一致。参数说明input_size4 对应湿度、气压、风速加温度四个特征epochs 可以适当缩短因为 GRU 收敛更快。6.3 多步预测自检数字可以骗人曲线不会我每改一次参数都会跑一个自检用训练好的模型做 7 步滚动预测把预测曲线画出来看趋势是否合理。如果第 3 天起预测开始平坦化甚至震荡回头检查特征和滑窗长度别急着调层数。从那次被滞后一拍坑过之后我养成了习惯任何时序模型先画多步预测曲线再报指标数字。数字可以骗人曲线不会。这个习惯帮我避掉了不少评估上的雷希望也能帮到你。本文还有配套的精品资源点击获取
返回列表