ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于LSTM的气温预测实战:数据准备、模型训练与可视化

基于LSTM的气温预测实战:数据准备、模型训练与可视化 简介基于长短期记忆网络LSTM的气温预测与可视化工程聚焦计算机毕设与课程设计场景也适合Python及深度学习初学者。项目抓取北京、上海、广州、郑州四个城市多年气温数据涉及天气数据爬取、预处理、时间序列样本构造、Keras建模、训练评估与可视化对比。每个样本以过去5天气温为输入特征维度设为1预测下一天温度可帮助理解循环神经网络处理时序数据的基本思路。资源共16个文件压缩包约721KB其中8个Python脚本覆盖爬虫、数据处理、模型配置、训练、预测等环节另含2个Excel气温数据文件、2个Markdown说明文档和4个pyc缓存文件。当前已有281人学习。项目配有较详细的文档与注释能帮助新手梳理数据获取、时序建模、超参数调整及结果解读的完整流程适合作为实战练手或答辩参考。1. 基于 LSTM 的气温预测不是拿模型刷榜而是让预测曲线真正跟上温度拐点凌晨 3 点的户外巡检要预判结露风险气象台给的数据却只有日内均温做光伏发电功率推导业务方偏偏要逐小时气温作为输入学校课程设计拿到“基于 LSTM 的气温预测及可视化”这个题目网上搜源码容易搜到跑得通的却很难。这三个场景指向同一个需求用 Python 把历史气温喂进 LSTM训练一个能预测未来 24~72 小时温度曲线的模型再画出能和真实曲线并排对比的图。网上流传的版本大多卡在数据切分、归一化和时间对齐这三个环节翻车概率不低。这篇笔记按一线做法完整落地一次新手能跟着出图熟手能直接拿参数和避坑清单。2. 数据准备这一步耽误了大多数人滑动窗口、归一化与序列切分动手写模型之前有三个问题必须先回答为什么这个任务默认选 LSTM数据集最少要长成什么样窗口和归一化参数怎么定。跳过这三个问题直接套代码后面八成要回头重做。这一章先把选型逻辑说清楚再给出可以直接复制的数据准备代码。2.1 为什么气温序列优先选 LSTM 而不是普通 RNN 或 Transformer气温是一个典型的连续时间序列今天下午 2 点的温度和昨天同一时刻、前天同一时刻都有强相关这种相关随滞后阶数缓慢衰减。普通 RNN 在反向传播时梯度要连乘很多步序列长度超过 20 步左右梯度就开始指数级消失前面的信息根本传不到输出端模型只能学到短距离的惯性外推。LSTM 的遗忘门和输入门相当于给梯度开了一条“高速公路”关键的历史模式可以选择性保留这正是气温这种长周期序列最需要的。Transformer 在这些年很火但对单变量气温预测属于“杀鸡用牛刀”。它的自注意力机制在短序列上效果并不比 LSTM 有明显优势反而是训练耗时更长、显存占用更大而且需要更多数据才能发挥优势。气温序列通常只有几千到几万条样本这个量级下 Transformer 很难展现全局建模能力。从实际工程角度看气象机构用的物理数值模式WRF 这类精度确实高但需要高性能计算资源、复杂的地形数据和长时间积分普通开发者根本跑不动。数据驱动的方案里LSTM 是用最小算力拿到稳定 baseline 的最优选择。我一般会先用单层 LSTM 跑通全流程确认数据没有问题之后再根据效果决定要不要加深网络。许多人一上来就搭三层 LSTM 加两层全连接结果训练半天 loss 不降最后发现是数据切分出了错白白浪费时间。选型这件事先跑通再优化比一开始就追求复杂模型实用得多。2.2 你要准备的最简数据集一列时间、一列温度这个项目的数据集不需要很复杂一个 CSV 文件就够了至少包含两列datetime 和 temp。datetime 是时间戳temp 是气温数值单位用摄氏度。分辨率建议选逐小时也就是每天 24 条记录。小时级数据能覆盖昼夜温差的变化日级数据做出来只能看到一条平滑的年度正弦曲线预测结果几乎没有参考价值。时间跨度上最少要有 12 个月的数据。因为气温序列里最显著的周期是年周期——夏天和冬天的温度分布完全不同模型需要看到完整的季节变化才能学会“当前时间大概该有多热”。只用三个月数据也能训练但模型在换到不同季节的测试集上通常会崩误差放大到无法接受的程度。如果只有半年数据可以退而求其次只预测未来 12 小时但文章里所有的窗口参数都要相应缩小。缺失值处理不要偷懒。气象站偶尔会停机或者上报失败一行一行查不现实常见做法是用 pandas 的 interpolate 做线性插值。连续缺失不超过 3 小时的情况下线性插值的误差可以忽略连续缺失超过 6 小时建议直接把这一整天的数据丢弃否则插值出来的一段平直线会让模型误以为温度恒定。字段里如果还有湿度、气压、风速这些列先留着后面作为多特征输入时用得上。2.3 滑动窗口、归一化与训练/验证切分的正确顺序LSTM 不能直接吃一整年数据它需要一个固定长度的滑动窗口。窗口的含义是用过去多少个时间步的特征预测未来多少个时间步。对逐小时气温数据最常用的配置是 input_steps168也就是过去 168 个小时7 天作为记忆长度output_steps24一次输出未来 24 小时的温度曲线。168 这个数字不是拍脑袋定的7 天刚好覆盖一个完整的周周期——如果数据里有周末和工作日的温度差异比如城市热岛效应受车流影响这个窗口能捕捉到。归一化用 MinMaxScaler把温度映射到 [-1, 1] 区间。两个原因一是 LSTM 内部用 tanh 激活输出范围就是 [-1, 1]输入数据落在同一量级梯度传播更平稳二是气温数值本身跨度不大全国大部分地区年温差在 40 度以内MinMax 缩放后信息损失很小。不要用 StandardScaler 做标准化标准化对正态分布的数据效果好但气温序列有明显的周期性和偏态标准化后模型收敛反而变慢。数据切分有一个容易忽略的顺序问题必须先切分再 fit 归一化器。也就是说只用训练集的统计量去计算 min 和 max测试集用训练集算出的参数做 transform。如果先对全量数据做归一化再切分测试集的数值范围会“泄漏”进训练过程模型相当于提前见过了测试题的答案验证集上的效果虚高换到新数据就现出原形。下面这段代码把正确顺序写死了import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler df pd.read_csv(temperature.csv, parse_dates[datetime]) df df.set_index(datetime).sort_index() # 缺失值插值超过连续3小时的缺口直接丢弃该段 df[temp] df[temp].interpolate(limit3) total len(df) split_idx int(total * 0.8) train_df df.iloc[:split_idx] test_df df.iloc[split_idx:] # 注意fit 只作用在训练集上测试集用同一个 scaler 做 transform scaler MinMaxScaler(feature_range(-1.0, 1.0)) train_scaled scaler.fit_transform(train_df[[temp]].values) test_scaled scaler.transform(test_df[[temp]].values)这段代码里train_df 和 test_df 是按时间顺序硬切开的前 80% 做训练后 20% 做测试。为什么不用 train_test_split 随机切因为气温数据是强自相关的相邻几天的样本高度相似随机切会让测试集里混入与训练集几乎一样的片段误差被大幅低估。按时间切分才能模拟“用过去预测未来”的真实场景这也是这个项目里最容易翻车的地方。滑动窗口切分函数如下输入归一化后的数据输出形状为 (样本数, 168, 1) 和 (样本数, 24) 的数组def create_sequences(data, input_steps168, output_steps24): X, y [], [] for i in range(len(data) - input_steps - output_steps 1): X.append(data[i:i input_steps]) y.append(data[i input_steps:i input_steps output_steps]) return np.array(X), np.array(y) X_train, y_train create_sequences(train_scaled, 168, 24) X_test, y_test create_sequences(test_scaled, 168, 24) # 转成 PyTorch 张量并构造数据加载器 import torch from torch.utils.data import TensorDataset, DataLoader X_train_t torch.tensor(X_train, dtypetorch.float32) y_train_t torch.tensor(y_train, dtypetorch.float32) X_test_t torch.tensor(X_test, dtypetorch.float32) y_test_t torch.tensor(y_test, dtypetorch.float32) train_ds TensorDataset(X_train_t, y_train_t) test_ds TensorDataset(X_test_t, y_test_t) train_loader DataLoader(train_ds, batch_size64, shuffleTrue) test_loader DataLoader(test_ds, batch_size256, shuffleFalse)X 的最后一个维度是 1因为目前只用温度这一个特征。如果后面要加入湿度、气压只需在拼特征时把最后一维变大滑动窗口的切分代码不用改。这里的 batch_size64 对应几千到几万条样本的训练集样本量大可以调到 128小数据集用 32 更稳。注意一个细节DataLoader 里 train_loader 设置 shuffleTruetest_loader 设置 shuffleFalse。训练时打乱的是样本的取出顺序样本内部的时间顺序没有被破坏这样做可以让每个 batch 覆盖不同的季节时段梯度下降更平稳测试集必须保持原有顺序因为后面可视化时要按时间轴把预测结果拼回连续曲线。3. 搭建 LSTM 模型与训练循环网络定义、超参数和断点续跑数据准备好之后进入建模环节。这里用 PyTorch 实现一是它的动态图机制在调试序列模型时特别方便二是这套代码稍加改动就能迁移到其他时间序列任务。Keras 也能做但遇到维度不匹配这类错误时排查成本更高对新手不友好。3.1 网络结构单层 LSTM 加回归头隐藏单元取 64 起步模型结构本身不复杂一个 LSTM 层接收 (batch, steps, features) 的三维输入输出最后时间步的隐藏状态然后接一个全连接回归头把隐藏状态映射到未来 24 个时间步的温度值。这种结构被称为 seq2point和 seq2seq 的区别在于它不逐个解码未来时间步而是直接一次性输出整个预测序列训练更快在单变量温度预测上效果足够。隐藏单元数量 hidden_size 取多少我的经验是单变量气温预测从 64 起步数据量大超过两年逐小时数据可以试 128但不要一上来就上 256。气温序列可学习的规律有限涨跌主要由周期性和惯性决定容量过大的网络会把训练集中的噪声当成规律记下来验证集 loss 反而在训练后期上升。模型定义代码如下import torch.nn as nn class TemperatureLSTM(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers1, output_steps24): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue ) self.reg_head nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Linear(32, output_steps) ) def forward(self, x): # x 形状: (batch, input_steps, input_size) out, _ self.lstm(x) # 取最后一个时间步的隐藏状态 last_hidden out[:, -1, :] return self.reg_head(last_hidden)forward 函数里out[:, -1, :] 取出序列最后一个时间步的输出。为什么不把所有时间步的输出做平均或拼接因为对预测未来气温来说最后一步的隐藏状态已经编码了 LSTM 对整段序列历史的浓缩信息取平均反而会把最近时刻的重要变化冲淡。回归头先降到 32 维再输出 24 个值中间加 ReLU 是为了让模型有能力拟合温度变化中的非线性拐点如果只有一层线性映射模型退化成普通的线性回归LSTM 学到的模式会被浪费。3.2 训练循环MSE 损失、Adam 优化器与早停机制损失函数选 MSELoss也就是均方误差。温度是连续变量MSE 对离群点敏感会迫使模型尽量贴合每一个真实值MAE 虽然在异常值上更稳健但在训练的最后一阶段容易出现小梯度抖动loss 曲线上下跳个不停很难判断是否收敛。优化器选 Adam初始学习率 1e-3。这是时间序列任务里最靠谱的默认组合几乎不用调整就能在几十轮内收敛到不错的水平。学习率不要设成默认的 1e-2。LSTM 的梯度传播路径长学习率过大会让损失在训练初期直接飞掉表现为 loss 输出 nan 或者某个 epoch 之后突然跳到极大值。加上权重衰减 weight_decay1e-5 可以有效抑制过拟合但系数不要超过 1e-4否则模型会欠拟合。梯度裁剪是 LSTM 训练里容易被忽略的一步max_norm1.0 的意思是如果梯度的范数超过 1就按比例缩放回去防止长期依赖路径上的梯度爆炸。训练循环里同时实现早停记录验证集上的最优 loss连续 10 轮没有刷新最优值就停止训练。这个机制比固定轮数训练可靠得多因为不同数据集收敛速度差异很大固定 100 轮要么浪费算力要么欠拟合。保存模型只保存 state_dict不要保存整个 model 对象这样加载时不受 PyTorch 版本兼容性影响。代码如下import torch.optim as optim model TemperatureLSTM() criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) best_val_loss float(inf) patience 10 trigger_times 0 epochs 80 for epoch in range(epochs): model.train() train_loss 0.0 for xb, yb in train_loader: optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() # 梯度裁剪防止 long-term 梯度爆炸 nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() train_loss loss.item() * xb.size(0) train_loss / len(train_loader.dataset) model.eval() val_loss 0.0 with torch.no_grad(): for xb, yb in test_loader: pred model(xb) val_loss criterion(pred, yb).item() * xb.size(0) val_loss / len(test_loader.dataset) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), lstm_temperature.pt) trigger_times 0 else: trigger_times 1 if trigger_times patience: print(fearly stop at epoch {epoch}, best val loss {best_val_loss:.6f}) break if epoch % 5 0 or epoch epochs - 1: print(fepoch {epoch:2d} | train loss {train_loss:.6f} | val loss {val_loss:.6f})loss 累加时我用了 loss.item() * xb.size(0)也就是把一个 batch 的 loss 乘以 batch 大小最后除以数据集总样本数这样才能算出整个数据集上的平均 loss而不是每个 batch 的简单平均。如果直接用 loss.item() 累加再除以 batch 数量算出来的均值会被小 batch 的噪声干扰尤其在最后一个 batch 不足 64 条样本时偏差很明显。训练时注意观察控制台输出正常情况是 train loss 和 val loss 都在下降val loss 在某个点开始震荡或微升早停机制会自动截断。如果从第 1 轮开始 val loss 就不降甚至上升先别调模型——检查上一章的数据切分和归一化有没有做对这个项目里 80% 的训练失败来自数据侧而不是模型侧。3.3 模型加载、逆归一化与结果输出的正确姿势训练完成后预测结果还停留在 [-1, 1] 的归一化空间必须逆变换回真实气温才能做可视化和误差统计。这里有个容易踩的维度问题MinMaxScaler 是按列做变换的如果模型输出形状是 (batch, 24)而 scaler 之前 fit 的数据形状是 (n, 1)直接调用 inverse_transform 会报错或者得到错误的列数。需要先把预测结果 reshape 成与原始数据相同的列结构。加载和逆变换代码如下model TemperatureLSTM() model.load_state_dict(torch.load(lstm_temperature.pt, map_locationcpu)) model.eval() with torch.no_grad(): pred_scaled model(X_test_t).numpy() # 形状 (batch, 24) # 逆归一化前需要把预测结果 reshape 成 (batch * 24, 1) n_samples pred_scaled.shape[0] pred_flat pred_scaled.reshape(-1, 1) pred_temp scaler.inverse_transform(pred_flat).reshape(n_samples, -1) # 真实值同样逆变换回原始温度 y_flat y_test_t.numpy().reshape(-1, 1) true_temp scaler.inverse_transform(y_flat).reshape(n_samples, -1) print(fpredict shape: {pred_temp.shape}, true shape: {true_temp.shape}) print(fsample predict: {pred_temp[0][:5]}, sample true: {true_temp[0][:5]})inverse_transform 的时候牢记一个原则用哪个 scaler 变换的数据就用哪个 scaler 逆变换回去。项目里训练和测试都用了同一个 scaler这没问题但如果你实验中发现测试集的分布和训练集差别太大比如训练集是春夏数据测试集是冬天数据归一化后的测试值会挤在 [-1, 1] 的某个角落模型几乎不可能预测准。这也是为什么训练测试按时间切分时最好保证测试集覆盖完整的季节循环。到这里模型已经能跑出预测结果接下来就是把这个结果画成有说服力的图。4. 可视化别只画一张拟合曲线训练损失、残差和时间轴对齐都要有可视化是这个项目里最容易出效果也最容易出糗的部分。只画一张预测对比图远远不够评审或业务方最关心三件事模型真的收敛了吗、预测误差有多大、预测曲线和真实曲线在时间轴上是严格对齐的吗。这一章按分析顺序给出三组图的做法。4.1 先看 loss 曲线训练有没有病一眼就能看出来模型训练过程中的 train loss 和 val loss 要记录下来画的时候 y 轴用对数刻度。原因很简单MSE 在前 10 个 epoch 可能从 0.1 降到 0.01后 30 个 epoch 才从 0.01 降到 0.001线性轴会把前半段压成一条竖线后半段的细微变化完全看不见。对数轴能同时展现两个阶段。训练循环里如果把每一轮的 loss 都存进一个列表直接就能画import matplotlib.pyplot as plt plt.figure(figsize(10, 4)) plt.plot(train_losses, labeltrain loss, linewidth1.5) plt.plot(val_losses, labelval loss, linewidth1.5) plt.yscale(log) plt.xlabel(epoch) plt.ylabel(MSE) plt.title(Training and Validation Loss) plt.legend() plt.grid(True, whichboth, alpha0.3) plt.tight_layout() plt.savefig(loss_curve.png, dpi150)读这张图有两个要点。第一train loss 和 val loss 之间的 gap 如果从小变大说明模型开始过拟合早停已经触发了gap 一直很小则说明模型容量不足可以适当把 hidden_size 调大再跑一轮。第二val loss 如果在训练中段出现突然跳高又回落大概率是学习率设置偏大梯度在某个 batch 上冲过了头可以尝试调到 5e-4 并配合学习率衰减。这张图本身不能直接证明预测准但它能证明训练过程是健康的排除掉模型没收敛这个干扰因素。4.2 真实 vs 预测对比图时间轴对齐是可视化的重灾区对比图最常翻车的地方在于x轴不是真实时间把不同样本的预测结果错位拼接了。正确的做法是选测试集里连续的一段区间比如从第 100 个样本到第 110 个样本每个样本预测未来 24 小时。为了画出连续的曲线需要把每个样本的预测结果按时间顺序首尾对齐——第 100 个样本的预测覆盖第 100 个输入窗口结束之后的 24 小时第 101 个样本的预测紧接着从第 101 个窗口结束后开始。最简单的对齐方式是只画第一个样本的 24 步预测和对应真实值先跑通再画连续拼接。第一种图适合验证单个样本的效果能看到模型对昼夜周期和温度峰值的拟合程度sample_idx 0 true_line true_temp[sample_idx] pred_line pred_temp[sample_idx] plt.figure(figsize(10, 4)) plt.plot(range(24), true_line, markero, labeltrue temperature, linewidth1.8) plt.plot(range(24), pred_line, markerx, labelpred temperature, linewidth1.8) plt.xlabel(hours ahead) plt.ylabel(temperature (°C)) plt.title(24-hour Temperature Forecast: True vs Predicted) plt.legend() plt.grid(True, alpha0.3) plt.tight_layout() plt.savefig(forecast_compare.png, dpi150)横坐标的含义是“未来第几个小时”0 表示输入窗口结束后的第一个时刻。画图时注意两点一是真实数据要选与预测完全对应的时间段不能错位二是如果预测曲线整体比真实曲线滞后 1~2 个小时这属于惯性预测的典型症状第 5 章会专门讲怎么处理。第二种图是连续多天的还原效果把多个样本的预测拼起来。这里推荐“滑窗重叠拼接”的方式测试集里相邻样本的输入窗口只滑动了一个小时它们的预测结果在时间上高度重叠取这些重叠预测的平均值作为最终输出可以显著降低单次预测的抖动。代码思路是为每个时间步累计收到的预测值取平均画出来就是一条平滑完整的预测温度曲线。4.3 残差分布图判断预测误差是随机噪声还是系统性偏差预测值和真实值之差叫残差。把整个测试集上所有时间步的残差收集起来画直方图能揭示模型的质量。如果残差分布近似以 0 为中心的正态分布说明误差是随机噪声模型已经学到了数据里最主要的规律。如果残差明显整体偏正说明预测系统性地低于真实值模型存在偏差这时靠调参没用要检查是不是归一化时 min/max 取值不对称导致的。residual (true_temp - pred_temp).flatten() plt.figure(figsize(10, 4)) plt.hist(residual, bins50, edgecolorwhite, alpha0.8) plt.xlabel(residual (true - pred, °C)) plt.ylabel(count) plt.title(Residual Distribution on Test Set) plt.axvline(x0, colorred, linestyle--, linewidth1.5) plt.grid(True, alpha0.3) plt.tight_layout() plt.savefig(residual_dist.png, dpi150) # 数值指标一起输出 mae np.mean(np.abs(residual)) rmse np.sqrt(np.mean(residual ** 2)) print(fMAE: {mae:.3f} °C, RMSE: {rmse:.3f} °C)残差直方图之外我习惯再画一张残差随“预测提前量”变化的曲线——把未来第 1 小时的预测误差、第 6 小时的误差、第 12 小时的误差分别算出来画成一条上升曲线。这个信息对业务方特别有用如果提前 6 小时预测误差在 2 度以内提前 24 小时误差涨到 5 度那他们的决策流程就应该把“6 小时内的预测”作为可用区间而不是笼统地说模型预测准或不准。很多项目评分不高问题不是模型差而是没有把误差在不同提前量上的表现讲清楚。5. 气温预测 LSTM 的避坑清单五个高频翻车现场与对症方案下面五条是这类项目里最常踩的坑每一条我都见过不止一次。按“现象 → 原因 → 解决”的方式写排查时可以直接对照。5.1 预测曲线整体比真实曲线滞后一拍现象画出对比图预测曲线几乎就是真实曲线向右平移了 1~3 个小时温度上升时预测还在下降温度到顶了预测才刚追上。原因这是单变量 LSTM 最典型的惯性预测问题。模型只看到温度这一条信号发现“上一时刻的温度”和“下一时刻的温度”相关性极高于是学会了直接复制当前值而不是真正建模温度变化的趋势。尤其在输入特征只有温度时模型没有气压、风速等信息来判断温度即将发生转折。解决一个思路是加入更多气象特征比如湿度、气压、风速这些特征的变化往往领先于温度变化能帮助模型提前“感知”拐点。另一个更直接的做法是把损失函数改成在时间维度上加权对更远的未来时刻施加更大的权重强迫模型不只是拟合近端惯性。如果短期不改模型可以尝试把预测结果做一次线性校正——统计测试集上不同提前量下的滞后偏移量用这个量去补偿输出。补偿是治标加特征是治本。5.2 归一化信息泄漏测试集效果虚高换数据就崩现象训练 loss 和验证 loss 都很好看MAE 只有 0.5 度但把模型放到新的气象站数据上预测误差飙升到 5 度以上。原因代码里先做了全量数据的 MinMaxScaler 再切分训练测试集。测试集的最小最大值参与了缩放参数的估计模型训练时“偷看”了测试集的分布范围所以验证时表现异常好。这是时间序列项目里最常见的数据泄漏比特征泄漏隐蔽得多。解决严格按第 2 章的代码顺序——先按时间切分再 fit 训练集的 scaler用同一个 scaler transform 测试集。检查方法很简单打印 scaler.data_min_ 和 scaler.data_max_确认它们来自训练集而不是全量数据。另外如果你在使用过程中对某一天特别长的极端天气做了手动数据清洗清洗动作也要放在切分之前统一处理否则同样会引入偏差。5.3 数据切分没有保持时间顺序随机打乱导致“未来数据”泄漏现象测试集误差很小但把预测结果按时间画成图后发现曲线在某些位置出现了“锯齿”状跳变预测值频繁地突然靠近真实值又离开。原因用了 sklearn 的 train_test_split 且没有设置 shuffleFalse。气温序列的滑动窗口是重叠的——第 100 个样本的窗口结束于第 268 小时第 101 个样本的窗口结束于第 269 小时这两个样本有 167 个小时是重复的。随机切分后测试集里会混入与训练集窗口高度重叠的样本相当于考试时参考答案就贴在旁边。解决使用 train_test_split 时必须显式设置 shuffleFalse或者干脆用数组索引按比例硬切。切分后检查训练集最大时间戳是否小于测试集最小时间戳加一行断言可以避免后续所有环节的错乱。这个坑排查成本低但破坏力大项目说明里如果看到源码是随机切分的务必改成时间顺序切分再跑。5.4 loss 在训练初期就出现 NaN 或者一直不降现象第一个 epoch 的 loss 直接是 nan或者训练到第 10 轮 loss 还停在初始值附近纹丝不动。原因loss 为 nan 通常是学习率过大导致的梯度爆炸LSTM 在长序列上的梯度范数可以非常大Adam 虽然自适应调整学习率但无法完全防止极端情况。loss 不降的原因则更多样归一化没做导致输入数值在几十到几百的量级LSTM 的 tanh 激活早就饱和了梯度根本传不回去或者网络结构设计成输出层没有经过合适的激活函数输出数值范围与目标差了好几个量级。解决先把学习率降到 1e-4 试跑 5 个 epoch如果 loss 正常下降再逐步调回 1e-3在 backward 之后加上 clip_grad_norm_ 是必须的max_norm 从 1.0 开始调。检查输入张量的数值范围打印 xb.min() 和 xb.max()确认在 [-1, 1] 附近。如果数据没问题把回归头的初始权重调小用 nn.init.xavier_uniform_ 对 Linear 层做初始化能解决一部分梯度问题。5.5 多步预测误差滚雪球越往后预测越不准现象24 步预测里前 6 步误差都在 1 度以内第 12 步开始误差扩大第 24 步误差达到 4~5 度。原因这是多步预测的固有误差累积问题。模型在一步预测后误差会作为下一轮的“输入污染”进入下一轮计算在单步输出的模式里不体现但在输出整个 24 步曲线并逐点评估时就会越来越离谱。气温序列虽然周期性强但局部的随机扰动仍然存在误差滚雪球是物理规律决定的不是模型缺陷。解决评估时把未来 24 小时的预测分段报告——分别统计未来 1~6 小时、7~12 小时、13~24 小时的 MAE让业务方知道模型在什么提前量下可靠这比一个笼统的 MAE 更有指导意义。如果想进一步压制误差累积可以采用滚动预测策略每次只预测未来 3 小时然后把预测值追加到输入序列末尾重新构造窗口再预测下一个 3 小时这样每个时刻的预测都基于最新的观测误差不会无限滚大。代价是推理时间变长但准确率通常比一次性预测 24 步高 15%~20%。6. 让预测结果经得起质疑从单变量走向多特征与滚动预测前面的流程已经把单变量 LSTM 跑通了。如果想让这个项目在答辩或实际业务里更有说服力下面三个进阶方向值得投入成本从低到高排。6.1 输入特征从 1 个扩到 4 个把湿度、气压、风速三列加进输入input_size 从 1 改成 4。每列特征单独做 MinMaxScaler注意 sklearn 的 scaler 会一次性处理多列分别 inverse_transform 时选对列下标就行。加入气象特征后模型能学到“气压骤降通常伴随温度下降”这类知识对拐点的预测能力提升明显。代码上只需修改 create_sequences 的输入数据和模型 input_size其余环节不用动。6.2 用滚动预测代替一次性多步输出一次性输出 24 步的方法叫多步预测模式滚动预测则每次只输出 1 步然后把预测值作为输入滑到下一个时刻。实现上把 output_steps 改成 1推理时写一个循环拼装输入窗口。这个改动带来的效果在长期预测上尤其明显但推理速度会慢 24 倍。我的习惯是先用一次性模式快速验证数据质量确认模型能学到有效规律后再切到滚动模式生成最终交付的预测结果。6.3 超参数参考表直接照抄的稳定配置气温预测的标准配置我整理成一张表数据集在 1~2 年小时粒度时直接套用基本不会出错。参数建议值调整方向input_steps1687天数据周期更长可加到 336output_steps24短期预测用 12长期用 72hidden_size64过拟合降低欠拟合升高num_layers1数据超 2 年可试 2 层加 Dropoutlearning_rate1e-3不收敛降到 5e-4batch_size64样本少用 32optimizerAdam weight_decay 1e-5不用额外调参max_grad_norm1.0梯度爆炸时降到 0.5early_stop_patience10训练波动大时提到 15我自己跑这类项目有一个雷打不动的习惯每次实验先把所有随机种子固定住包括 numpy、random 和 PyTorch 的 manual_seed。同样的数据、同样的代码固定种子之前跑三次结果能差出 1~2 度的误差根本没法判断调参是否真的有效固定之后每一次改动带来的效果差异才是真实的。气温预测的模型容错率不高控制变量比盲目调参重要得多。希望这篇笔记能把你在数据、训练和可视化上踩坑的时间省下来真正把精力花在调优气象特征和业务对接上。本文还有配套的精品资源点击获取
返回列表