简介这份资源围绕使用LSTM实现未来气温预测展开面向具备一定深度学习基础、希望动手实践时间序列预测的学习者与开发者。包内共3个文件以2个Python脚本和1个CSV数据文件为主压缩包约9KB脚本负责模型搭建与训练流程CSV则提供气温序列数据便于直接运行与调试。资源中涉及MSE、MAE等回归评估指标并讨论了monitor、min_delta、patience等早停参数在5折交叉验证场景下的设置思路例如在准确率70%至90%波动时如何权衡抖动与真正下降对调参排错有实际参考价值。目前已有1433人学习下载适合想快速跑通LSTM气温预测、理解评估与早停机制的读者参考借鉴。1. 用 LSTM 做未来气温预测一条时间序列到底能往前看多远连续一周的午后雷阵雨把某地气象站的气温曲线砸出一个深坑第二天又迅速反弹。这种昨天还 35℃今天掉到 26℃的剧烈波动正是 LSTM 时间序列预测最想啃下的硬骨头。用 LSTM 做未来气温预测本质是把过去若干天的温度、湿度、气压等观测值当成一条有序序列让网络学会从历史窗口推下一格的映射关系再滚动地把预测值喂回输入一步步外推未来。它解决的是传统 ARIMA 在非线性、多变量、长依赖场景下容易失手的问题适合手上有气象站历史数据、想快速搭一套可迭代预测管线的工程师和数据分析师。读完你能拿到一条从数据清洗、滑窗构造、模型搭建到滚动预测和误差排查的完整路径而不是停在LSTM 很厉害这种层面。2. 先搞清楚 LSTM 气温预测的输入输出长什么样2.1 时间序列预测和普通回归的区别在哪普通回归是给一组特征预测一个标签样本之间互相独立。气温预测不是这样今天 14 时的气温和昨天 14 时、前天 14 时高度相关样本之间存在时间上的先后依赖。如果你把每条记录打乱顺序丢进全连接网络模型就丢掉了谁在谁前面这个最关键的信息。LSTM 的核心是三个门——遗忘门、输入门、输出门加上一条贯穿时间步的细胞状态。遗忘门决定上一时刻的细胞状态保留多少输入门决定当前输入写入多少输出门决定当前细胞状态暴露多少给隐藏状态。这套机制让网络能在几十个时间步的跨度上选择性地记住或忘掉信息比普通 RNN 更不容易梯度消失。放到气温预测里输入通常是一个形状为(batch, time_steps, features)的三维张量。time_steps是你回看的窗口长度比如 24 小时或 7 天features是每个时间步的变量数可以只有气温一列也可以加上湿度、气压、风速、日照。输出是未来第 1 步、第 N 步或者一整段未来序列。提示单变量气温预测能跑通但多变量往往更稳。气压和湿度对温度变化的领先性经常能补上单靠气温自回归抓不到的拐点。2.2 滑动窗口把一条曲线切成监督学习样本原始数据是一长串按时间排列的记录LSTM 需要的是输入窗口 → 目标值的配对。做法是用滑动窗口切分窗口长度设为look_back目标设为窗口后第horizon步的值。import numpy as np import pandas as pd def make_sliding_window(series, look_back24, horizon1): series: 一维或二维数组形状 (n_timesteps, n_features) look_back: 回看窗口长度 horizon: 预测未来第几步 返回 X: (samples, look_back, n_features), y: (samples, n_features) X, y [], [] n len(series) for i in range(n - look_back - horizon 1): X.append(series[i : i look_back]) y.append(series[i look_back horizon - 1]) return np.array(X), np.array(y) # 假设 df 已按时间排序temp 为气温列 data df[[temp, humidity, pressure]].values.astype(float32) X, y make_sliding_window(data, look_back24, horizon1) print(X.shape, y.shape) # 例如 (8760, 24, 3) (8760, 3)这段代码的逻辑是从第 0 个时间步开始取连续look_back个点作为输入取窗口结束后第horizon个点作为标签然后整体后移一位继续切。look_back24表示用过去 24 小时预测下一小时horizon1表示只预测未来一步。如果你要预测未来 6 小时把horizon改成 6或者把y改成窗口后的连续多步。参数选择上look_back不是越大越好。气温有日周期24 小时和年周期窗口至少要覆盖一个完整日周期否则模型看不到白天升温、夜间降温的规律。但窗口拉到 72030 天以上样本数骤减训练容易过拟合。我一般先用 24 或 48 起步再根据验证集误差调整。2.3 归一化别让气压的数值量级压垮气温信号气温可能在 -10 到 40 之间气压在 980 到 1040 之间湿度在 0 到 100 之间。如果不做处理气压的绝对数值远大于气温网络在计算梯度时会被大量级特征主导气温的变化模式反而学不到。常见做法是对每个特征单独做 Min-Max 归一化或 Z-Score 标准化。时间序列有个坑不能用全量数据的均值和方差来归一化因为那会引入未来信息。正确做法是只用训练集统计量然后应用到验证集和测试集。from sklearn.preprocessing import MinMaxScaler # 按时间切分前 70% 训练中间 15% 验证后 15% 测试 n len(data) train_end int(n * 0.7) val_end int(n * 0.85) scaler MinMaxScaler(feature_range(0, 1)) scaler.fit(data[:train_end]) # 只用训练集拟合 data_scaled scaler.transform(data) # 全量转换 train_data data_scaled[:train_end] val_data data_scaled[train_end:val_end] test_data data_scaled[val_end:]fit只在训练集上调用transform可以作用于全量。预测结束后用scaler.inverse_transform还原到原始温度单位否则你看到的误差是归一化空间里的没有物理意义。3. 搭一个能跑通的气温预测 LSTM 模型3.1 用 PyTorch 定义 LSTM 回归网络模型结构不需要一上来就堆很深。气温预测的常见配置是 1 到 2 层 LSTM隐藏单元 32 到 128后面接一个全连接层输出预测值。import torch import torch.nn as nn class TempLSTM(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, output_size1, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0 ) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x: (batch, time_steps, input_size) out, (h_n, c_n) self.lstm(x) last_step out[:, -1, :] # 取最后一个时间步的隐藏状态 return self.fc(last_step) model TempLSTM(input_size3, hidden_size64, num_layers2, output_size1)batch_firstTrue让输入维度是(batch, time_steps, features)符合大多数人的直觉。out[:, -1, :]取的是最后一个时间步的输出因为 LSTM 已经把前面所有时间步的信息压缩到了这个向量里。dropout只在多层 LSTM 之间生效单层时设为 0。hidden_size是模型容量的关键参数。32 适合小数据集快速验证128 以上容易在小样本上过拟合。num_layers2通常比 1 层表达能力强但超过 3 层在气温预测这种任务上收益很小反而增加训练时间。3.2 训练循环与损失函数的选择气温预测是回归任务损失函数用 MSE 或 MAE。MSE 对大误差惩罚更重适合你更在意极端温度预测准确性的场景MAE 对异常值更鲁棒适合数据里有传感器故障导致的尖刺。from torch.utils.data import DataLoader, TensorDataset # 构造 Dataset X_train, y_train make_sliding_window(train_data, look_back24, horizon1) X_val, y_val make_sliding_window(val_data, look_back24, horizon1) train_ds TensorDataset(torch.tensor(X_train), torch.tensor(y_train[:, 0:1])) val_ds TensorDataset(torch.tensor(X_val), torch.tensor(y_val[:, 0:1])) train_loader DataLoader(train_ds, batch_size64, shuffleTrue) val_loader DataLoader(val_ds, batch_size64, shuffleFalse) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(50): model.train() train_loss 0 for xb, yb in train_loader: optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() optimizer.step() train_loss loss.item() model.eval() val_loss 0 with torch.no_grad(): for xb, yb in val_loader: pred model(xb) val_loss criterion(pred, yb).item() if (epoch 1) % 10 0: print(fEpoch {epoch1}, train_loss{train_loss/len(train_loader):.5f}, val_loss{val_loss/len(val_loader):.5f})训练集shuffleTrue打乱样本顺序但验证集和测试集必须shuffleFalse因为时间序列的评估要保持时间顺序。y_train[:, 0:1]只取气温这一列作为预测目标如果你要同时预测多个变量把输出维度改掉即可。学习率1e-3是 Adam 的常用起点。如果验证损失震荡不降降到1e-4如果下降太慢可以试3e-3。批次大小 64 在几千到几万样本量级上比较稳样本很少时用 16 或 32。3.3 滚动预测把预测值喂回输入单步预测只能告诉你下一小时多少度。要预测未来 24 小时需要滚动外推用模型预测第 1 步把预测值拼到输入窗口末尾丢掉最老的一个时间步再预测第 2 步如此循环。def rolling_forecast(model, last_window, steps24, scalerNone): last_window: (1, look_back, n_features) 归一化后的最后窗口 steps: 预测未来多少步 model.eval() preds [] window last_window.copy() with torch.no_grad(): for _ in range(steps): x torch.tensor(window, dtypetorch.float32) pred model(x).item() preds.append(pred) # 构造新的一行气温用预测值其他特征沿用上一时刻 new_row window[0, -1, :].copy() new_row[0] pred window np.concatenate([window[:, 1:, :], new_row.reshape(1, 1, -1)], axis1) if scaler is not None: preds scaler.inverse_transform(np.array(preds).reshape(-1, 1))[:, 0] return preds滚动预测的误差会累积第 1 步的小偏差进入输入后第 2 步可能放大到第 24 步可能已经偏离很远。这是自回归外推的固有特性不是模型 bug。缓解办法包括多步直接预测一次输出未来 24 个值、在训练时加入 scheduled sampling逐步用预测值替代真实值或者对预测结果做平滑。4. 气温预测 LSTM 的避坑与排查清单4.1 损失降不下去先查数据泄漏和归一化现象训练损失和验证损失都卡在同一个较高水平模型像在瞎猜。原因最常见的是归一化时用了全量数据统计量或者滑动窗口切分时训练集和验证集有重叠。另一个隐蔽原因是数据里有大量缺失值被填成 0而 0 在归一化后变成一个极端值干扰训练。解决确认scaler.fit只在训练集上调用切分窗口时按时间顺序切不要随机打乱缺失值用前后插值或均值填充不要直接填 0。检查df.isnull().sum()把缺失比例超过 30% 的特征直接丢掉。4.2 验证损失远高于训练损失模型在背答案现象训练损失持续下降验证损失先降后升典型过拟合。原因模型容量相对数据量太大或者look_back太长导致样本数太少。气温数据如果有明显的季节性训练集和验证集分布不一致也会加剧这个问题。解决减小hidden_size从 128 降到 32 或 64增加dropout0.2 到 0.4加 L2 正则weight_decay1e-4。如果数据量确实少用早停验证损失连续 5 个 epoch 不降就停。4.3 预测曲线整体平移像慢了半拍现象预测值跟真实值形状相似但整体滞后一两个小时。原因模型学到的是用上一时刻预测当前时刻本质是 persistence 模型的变体。当气温变化快时滞后就明显。解决在特征里加入时间编码小时、星期、月份的正弦余弦让模型知道当前处于一天的哪个阶段。加入气压变化率、温度一阶差分等衍生特征给模型提供趋势信息。损失函数改用 MAE减少模型对平滑预测的偏好。4.4 极端高温低温预测严重偏低现象日常温度预测还行但 38℃ 以上或 0℃ 以下的样本误差巨大。原因极端值在训练集中占比低MSE 损失被大量正常样本主导模型倾向于预测接近均值的值。解决对极端样本加权或者在损失函数里对高误差样本加大惩罚。也可以对目标值做分段建模高温和低温各训一个模型。数据层面如果极端样本太少考虑用 SMOTE 类方法过采样但时间序列的过采样要小心破坏时序结构。4.5 滚动预测几步后崩掉现象单步预测误差很小但滚动 10 步以后预测值发散。原因误差累积加上输入分布偏移。训练时模型见到的输入都是真实值滚动时输入逐渐变成预测值分布不一致。解决训练时加入 scheduled sampling让模型逐步适应带噪声的输入。或者改用 seq2seq 结构一次输出整段未来序列避免逐步外推。评估时不要只看单步误差要按预测步数分别统计 RMSE画出误差随步数增长的曲线。5. 把气温预测做扎实的两个进阶技巧5.1 用误差随步数曲线判断模型真实能力单步 RMSE 好看不代表模型有用。我习惯在测试集上做滚动预测然后按 horizon 分组算 RMSEdef evaluate_by_horizon(model, test_data, look_back24, max_steps24, scalerNone): results {step: [] for step in range(1, max_steps 1)} n len(test_data) for i in range(look_back, n - max_steps): window test_data[i - look_back:i].reshape(1, look_back, -1) preds rolling_forecast(model, window, stepsmax_steps, scalerscaler) for step in range(1, max_steps 1): true_val test_data[i step - 1, 0] if scaler: true_val scaler.inverse_transform([[true_val, 0, 0]])[0, 0] results[step].append((preds[step-1] - true_val) ** 2) for step in range(1, max_steps 1): rmse np.sqrt(np.mean(results[step])) print(fHorizon {step}h RMSE: {rmse:.2f}) return results这张表比单个 loss 值有信息量得多。如果 1 小时 RMSE 是 0.8℃6 小时涨到 2.5℃24 小时到 5℃ 以上说明模型只适合做短临预测。如果曲线在某个步数后突然翘起检查是不是那个时间尺度上有什么周期性因素没被特征覆盖。5.2 特征工程比换模型更值得投入我试过把 LSTM 换成 GRU、Transformer、TCN在同一份数据上差距往往不到 10%。但加入几个关键特征后误差能降 20% 以上。最有效的几个特征构造方式作用小时正弦余弦sin(2πh/24), cos(2πh/24)告诉模型日周期位置温度一阶差分temp.diff()提供变化趋势气压变化率pressure.diff()气压骤降常伴随降温滚动均值temp.rolling(6).mean()平滑短期波动露点温度由温度和湿度计算反映水汽条件这些特征不需要额外数据源从现有列就能算出来。构造完后重新做归一化和滑窗再训一版对比evaluate_by_horizon的输出。如果 6 小时 RMSE 从 2.5 降到 2.0说明特征起了作用。我自己的习惯是每加一批特征先只跑 10 个 epoch 看验证损失趋势有改善再跑满。别一上来就调模型结构先把数据喂对。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?