☰
LSTM时间序列预测实战:未来气温预测的完整指南
2026/10/12 15:48:36 网站建设 项目流程

简介:这份资源围绕使用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_back=24, horizon=1): """ 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_back=24, horizon=1) print(X.shape, y.shape) # 例如 (8760, 24, 3) (8760, 3)

这段代码的逻辑是:从第 0 个时间步开始,取连续look_back个点作为输入,取窗口结束后第horizon个点作为标签,然后整体后移一位继续切。look_back=24表示用过去 24 小时预测下一小时,horizon=1表示只预测未来一步。如果你要预测未来 6 小时,把horizon改成 6,或者把y改成窗口后的连续多步。

参数选择上,look_back不是越大越好。气温有日周期(24 小时)和年周期,窗口至少要覆盖一个完整日周期,否则模型看不到"白天升温、夜间降温"的规律。但窗口拉到 720(30 天)以上,样本数骤减,训练容易过拟合。我一般先用 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_size=64, num_layers=2, output_size=1, dropout=0.2): super().__init__() self.lstm = nn.LSTM( input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, dropout=dropout 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_size=3, hidden_size=64, num_layers=2, output_size=1)

batch_first=True让输入维度是(batch, time_steps, features),符合大多数人的直觉。out[:, -1, :]取的是最后一个时间步的输出,因为 LSTM 已经把前面所有时间步的信息压缩到了这个向量里。dropout只在多层 LSTM 之间生效,单层时设为 0。

hidden_size是模型容量的关键参数。32 适合小数据集快速验证,128 以上容易在小样本上过拟合。num_layers=2通常比 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_back=24, horizon=1) X_val, y_val = make_sliding_window(val_data, look_back=24, horizon=1) 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_size=64, shuffle=True) val_loader = DataLoader(val_ds, batch_size=64, shuffle=False) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-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(f"Epoch {epoch+1}, train_loss={train_loss/len(train_loader):.5f}, val_loss={val_loss/len(val_loader):.5f}")

训练集shuffle=True打乱样本顺序,但验证集和测试集必须shuffle=False,因为时间序列的评估要保持时间顺序。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, steps=24, scaler=None): """ 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, dtype=torch.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)], axis=1) 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),增加dropout(0.2 到 0.4),加 L2 正则(weight_decay=1e-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 分组算 RMSE:

def evaluate_by_horizon(model, test_data, look_back=24, max_steps=24, scaler=None): 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, steps=max_steps, scaler=scaler) 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(f"Horizon {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 看验证损失趋势,有改善再跑满。别一上来就调模型结构,先把数据喂对。希望帮到你。

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

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

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

立即咨询