尧图精选

Python LSTM气温预测实战:从数据构造到可视化评估

🕒 发布时间:2026/10/1 22:32:51 📁 来源:尧图网络
简介这份资源面向计算机、人工智能及相关专业的在校学生与教师也适合希望入门时间序列预测的开发者提供一套基于LSTM的气温预测与可视化完整项目。项目通过bs4从中国天气网爬取北京、上海、广州、郑州四城2011至2021年共3652条天气数据涵盖数据获取、模型训练到结果可视化的全流程可作为毕设、课程设计或项目立项演示的参考模板。压缩包共16个文件包含8个py源码、4个pyc编译文件、2个xlsx数据表与2个md说明文档整体约723KB源码与数据分离目录结构清晰便于按模块阅读与二次修改。目前已有241人学习下载。读者可从中获得爬虫脚本、数据集构建、LSTM模型定义与训练、预测及可视化等完整代码并附有README说明与配置模块方便快速复现与排错也可在此基础上扩展其他城市或气象指标。1. 气温预测这件事LSTM 到底比传统方法强在哪做气象数据或者能源负荷分析的人大概率都碰过同一个需求拿历史气温序列预测未来几小时到几天的温度走势。用 ARIMA 跑一版短期还行一遇到连续高温或者骤降过程就开始飘换成 LSTM同样的数据误差能压下来一截。这不是玄学是 LSTM 的门控结构天生适合处理这种「前面几十个小时的温度会影响后面几小时」的长依赖关系。这个标题讲的就是一套完整的落地流程用 Python 搭一个 LSTM 气温预测模型跑通训练和推理再把预测结果用图表呈现出来。适合两类人——一类是刚学完 LSTM 理论、想找个真实序列练手的另一类是在做电力负荷、农业气象、暖通空调调度需要快速验证 LSTM 能不能用在自家数据上的。整套东西不依赖 GPU 也能跑数据格式是常见的时间序列 CSV源码和文档说明配套拿到就能改。2. 数据准备与 LSTM 输入构造从原始气温表到三维张量2.1 气温数据长什么样先做哪几步清洗气温预测用的原始数据常见来源是气象站逐小时或逐日观测记录字段一般包括时间戳、温度、湿度、气压、风速。做 LSTM 之前第一步不是急着建模而是把时间序列整理成模型能吃的格式。我一般按这个顺序走时间列统一转成datetime类型按时间升序排列去掉重复时间戳检查缺失值气温序列里连续缺 3 个点以上直接线性插值会失真我一般用前后各 24 小时均值补或者标记后跳过异常值处理超出物理合理范围比如 -60°C 到 60°C 之外的值置为 NaN 再补如果做多变量输入把湿度、气压也归一化后拼进来import pandas as pd import numpy as np # 读取气温数据假设列名为 datetime, temp, humidity, pressure df pd.read_csv(temperature.csv, parse_dates[datetime]) df df.sort_values(datetime).drop_duplicates(subsetdatetime) df df.set_index(datetime) # 缺失值先线性插值再对首尾残留 NaN 做前向/后向填充 df[temp] df[temp].interpolate(methodlinear, limit3) df[temp] df[temp].fillna(methodffill).fillna(methodbfill) # 异常值处理 df.loc[(df[temp] -60) | (df[temp] 60), temp] np.nan df[temp] df[temp].interpolate(methodlinear) print(df.describe())这段代码的逻辑是先把时间轴理顺再处理缺失和异常。interpolate的limit3表示最多连续插 3 个点超过就留给后面的填充兜底。参数上limit设太大容易把真实的气温骤降过程抹平设太小又补不全3 到 6 之间比较稳。2.2 滑动窗口怎么切才能让 LSTM 学到时序依赖LSTM 的输入是三维张量(样本数, 时间步长, 特征数)。气温预测里时间步长就是你用过去多少个小时预测未来多少个小时。常见做法是用过去 24 小时预测未来 1 小时或者过去 72 小时预测未来 24 小时。def create_sequences(data, lookback, horizon): data: 归一化后的二维数组 (n_samples, n_features) lookback: 输入时间步长 horizon: 预测未来第几个点 X, y [], [] for i in range(len(data) - lookback - horizon 1): X.append(data[i : i lookback]) y.append(data[i lookback horizon - 1, 0]) # 第0列是气温 return np.array(X), np.array(y) # 归一化 from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler() scaled scaler.fit_transform(df[[temp, humidity, pressure]].values) LOOKBACK 24 HORIZON 1 X, y create_sequences(scaled, LOOKBACK, HORIZON) print(X.shape, y.shape) # (样本数, 24, 3) (样本数,)lookback24意味着模型每次看过去 24 小时的数据。horizon1表示预测未来第 1 个时间点。如果你要预测未来 24 小时把horizon改成 24y取对应位置即可。注意归一化必须用训练集的 scaler 去 transform 测试集否则会引入未来信息这是时间序列里最常见的翻车点之一。2.3 训练集/验证集/测试集怎么分才不泄露未来时间序列不能随机打乱划分。我一般按 7:1.5:1.5 的比例顺序切分确保验证集和测试集都在训练集之后。n len(X) train_end int(n * 0.7) val_end int(n * 0.85) X_train, y_train X[:train_end], y[:train_end] X_val, y_val X[train_end:val_end], y[train_end:val_end] X_test, y_test X[val_end:], y[val_end:] print(ftrain: {X_train.shape}, val: {X_val.shape}, test: {X_test.shape})提示归一化 scaler 只能在训练集上 fit然后 transform 验证集和测试集。如果先对全量数据 fit测试集的分布信息就漏进训练过程了。3. 用 PyTorch 搭 LSTM 气温预测模型层数、隐藏单元和训练循环3.1 模型结构怎么定单层还是双层隐藏单元给多少气温序列的复杂度不算高单层 LSTM 加一个全连接输出层大多数场景够用。隐藏单元从 32 起步序列越长、特征越多可以加到 64 或 128。层数超过 2 层在气温预测里收益很小反而容易过拟合。import torch import torch.nn as nn class TempLSTM(nn.Module): def __init__(self, input_size, hidden_size64, num_layers1, 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, 1) def forward(self, x): # x: (batch, seq_len, input_size) out, (h_n, c_n) self.lstm(x) last_step out[:, -1, :] # 取最后一个时间步 return self.fc(last_step).squeeze(-1)batch_firstTrue让输入维度变成(batch, seq, feature)跟前面构造的数据对齐。dropout只在多层时生效单层设了也没用。fc把隐藏状态映射成一个标量就是预测温度。3.2 训练循环里学习率和早停怎么设from torch.utils.data import DataLoader, TensorDataset device torch.device(cuda if torch.cuda.is_available() else cpu) train_ds TensorDataset(torch.FloatTensor(X_train), torch.FloatTensor(y_train)) val_ds TensorDataset(torch.FloatTensor(X_val), torch.FloatTensor(y_val)) train_loader DataLoader(train_ds, batch_size64, shuffleFalse) val_loader DataLoader(val_ds, batch_size64, shuffleFalse) model TempLSTM(input_sizeX.shape[2]).to(device) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience5, factor0.5) best_val_loss float(inf) patience_counter 0 EARLY_STOP_PATIENCE 10 for epoch in range(100): model.train() train_loss 0 for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() train_loss loss.item() model.eval() val_loss 0 with torch.no_grad(): for xb, yb in val_loader: xb, yb xb.to(device), yb.to(device) val_loss criterion(model(xb), yb).item() val_loss / len(val_loader) scheduler.step(val_loss) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_lstm.pth) patience_counter 0 else: patience_counter 1 if patience_counter EARLY_STOP_PATIENCE: print(fEarly stop at epoch {epoch}) break if epoch % 10 0: print(fEpoch {epoch}: train_loss{train_loss/len(train_loader):.6f}, val_loss{val_loss:.6f})学习率1e-3是 Adam 的常用起点。ReduceLROnPlateau在验证损失不降时把学习率减半patience5表示等 5 个 epoch。梯度裁剪max_norm1.0是防 LSTM 梯度爆炸的常规操作。早停 patience 设 10再大就浪费训练时间了。3.3 预测结果怎么反归一化回真实温度model.load_state_dict(torch.load(best_lstm.pth)) model.eval() with torch.no_grad(): X_test_tensor torch.FloatTensor(X_test).to(device) pred_scaled model(X_test_tensor).cpu().numpy() # 反归一化只对气温列操作 temp_min scaler.data_min_[0] temp_max scaler.data_max_[0] pred_real pred_scaled * (temp_max - temp_min) temp_min y_test_real y_test * (temp_max - temp_min) temp_min反归一化必须用训练集 scaler 的data_min_和data_max_不能用测试集的。这一步搞错预测曲线整体偏移但形状看着还对排查起来很费时间。4. 可视化与评估把预测曲线和误差指标一起画出来4.1 预测对比图怎么画才有诊断价值光画一条预测线和一条真实线看不出模型在哪些时段容易错。我一般把误差也画在下面用双 y 轴或者子图。import matplotlib.pyplot as plt fig, axes plt.subplots(2, 1, figsize(14, 8), sharexTrue) axes[0].plot(y_test_real, labelActual, colorsteelblue, linewidth1) axes[0].plot(pred_real, labelPredicted, colortomato, linewidth1, alpha0.8) axes[0].set_ylabel(Temperature (°C)) axes[0].legend() axes[0].set_title(LSTM Temperature Prediction) error pred_real - y_test_real axes[1].plot(error, colorgray, linewidth0.8) axes[1].axhline(0, colorblack, linestyle--, linewidth0.5) axes[1].set_ylabel(Error (°C)) axes[1].set_xlabel(Time Step) plt.tight_layout() plt.savefig(prediction_result.png, dpi150) plt.show()上图看趋势拟合下图看误差分布。如果误差在某个时间段集中偏大说明那段有突变过程模型没学到可以考虑加特征或者调整 lookback。4.2 MAE、RMSE、MAPE 三个指标分别说明什么指标含义气温预测里的参考值MAE平均绝对误差1 小时预测通常 0.5~1.5°CRMSE均方根误差对大误差更敏感比 MAE 高 20%~40% 正常MAPE平均绝对百分比误差气温接近 0°C 时会失真慎用from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(y_test_real, pred_real) rmse np.sqrt(mean_squared_error(y_test_real, pred_real)) print(fMAE: {mae:.3f} °C, RMSE: {rmse:.3f} °C)MAE 和 RMSE 差距越大说明存在少数大误差点。如果 RMSE 是 MAE 的两倍以上回去看误差图大概率有极端天气过程没预测准。4.3 多步预测的可视化怎么做如果你把horizon设成 24预测的是未来 24 个点。可视化时按预测步长画多条曲线或者画成热力图。# 假设 pred_multi shape: (n_samples, 24) plt.figure(figsize(12, 6)) for step in [0, 5, 11, 23]: plt.plot(pred_multi[:200, step], labelfStep {step1}h, alpha0.7) plt.plot(y_multi[:200, 0], labelActual 1h, colorblack, linewidth1.5) plt.legend() plt.title(Multi-step Prediction (first 200 samples)) plt.xlabel(Sample Index) plt.ylabel(Temperature (°C)) plt.tight_layout() plt.savefig(multi_step.png, dpi150)步长越大曲线越平滑这是 LSTM 多步预测的典型特征——它倾向于输出均值附近的保守预测。如果要做更激进的预测得换 seq2seq 结构或者加注意力机制。5. 避坑与排查气温预测里最容易翻车的 5 个地方5.1 损失降到很低但预测曲线是一条直线现象训练 loss 持续下降验证 loss 也还行但画出来的预测曲线几乎水平完全没有日变化波动。原因归一化后气温值集中在 0.3~0.7 之间MSE 损失下模型输出均值就能拿到不差的 loss。加上如果 lookback 太短或者隐藏单元太少模型学不到周期模式。解决把 lookback 加到 48 或 72隐藏单元从 64 提到 128损失函数换成 MAE 或者 Huber对偏离均值的预测惩罚更直接。5.2 验证 loss 比训练 loss 低很多现象训练 loss 0.01验证 loss 0.005看着像好事但测试集表现很差。原因验证集可能恰好落在气温平稳时段训练集里包含了几个剧烈波动过程。另外 DataLoader 的shuffleFalse在时间序列里是对的但如果验证集切分位置刚好避开了突变就会造成这种假象。解决检查验证集和测试集的时间分布确保覆盖不同季节或不同天气类型。如果数据量够做滚动验证而不是固定切分。5.3 预测值整体偏高或偏低一个固定量现象预测曲线形状对但整体比真实值高 2~3°C。原因反归一化时用错了 scaler 的 min/max或者归一化时对多列特征一起 fit反归一化只取了气温列但索引对错了。解决单独对气温列做 scaler或者明确记录气温列在特征矩阵中的位置。反归一化前打印scaler.data_min_和scaler.data_max_确认数值合理。5.4 训练到一半 loss 突然变成 NaN现象前几个 epoch 正常突然 loss 变 NaN模型参数全废。原因学习率太大或者某批数据里有极端异常值没处理干净梯度爆炸。解决加梯度裁剪clip_grad_norm_(max_norm1.0)学习率降到5e-4或1e-4检查数据里有没有没被异常值处理覆盖到的极端值。5.5 用未来数据做了归一化现象离线评估指标很好上线后预测一塌糊涂。原因归一化时用了全量数据的 min/max测试集的分布信息泄露到了训练阶段。上线时新数据的范围可能超出训练时的 min/max导致输入分布偏移。解决归一化只在训练集上 fit验证集和测试集用同一个 scaler transform。上线时如果新数据超出训练范围做截断或者重新校准。6. 让 LSTM 气温预测再准一点的三个进阶技巧第一个技巧是加周期性特征。气温有很强的日周期和年周期把小时的正弦余弦编码、月份的正弦余弦编码作为额外特征拼进输入模型学周期会快很多。具体做法是sin(2π * hour / 24)、cos(2π * hour / 24)月份同理。这两个特征不用归一化本身就在 -1 到 1 之间。df[hour_sin] np.sin(2 * np.pi * df.index.hour / 24) df[hour_cos] np.cos(2 * np.pi * df.index.hour / 24) df[month_sin] np.sin(2 * np.pi * df.index.month / 12) df[month_cos] np.cos(2 * np.pi * df.index.month / 12)第二个技巧是残差连接。在 LSTM 输出后面加一个线性层直接映射输入的最后一步让模型学残差而不是绝对值。气温预测里当前时刻的温度和下一时刻高度相关残差结构能让模型把「基础值」和「变化量」分开学。class ResidualLSTM(nn.Module): def __init__(self, input_size, hidden_size64): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, batch_firstTrue) self.fc nn.Linear(hidden_size, 1) self.skip nn.Linear(input_size, 1) def forward(self, x): out, _ self.lstm(x) last out[:, -1, :] return self.fc(last).squeeze(-1) self.skip(x[:, -1, :]).squeeze(-1)第三个技巧是滚动预测代替单次多步预测。如果你要预测未来 24 小时不要一次性输出 24 个值而是预测 1 步、把预测值拼回输入、再预测下一步。这样误差不会累积得那么快但推理时间会线性增加。我一般对 6 小时以内的预测用单次多步超过 6 小时用滚动。验证方法上我习惯留出最近一个月的完整数据做最终测试不参与任何调参。如果这一个月里 MAE 稳定在 1.5°C 以内这个模型才值得往生产环境推。调参阶段看到的好指标很多时候是切分方式带来的幻觉血泪经验就是永远用时间上最靠后的数据做最终验证。这套东西我前后改过好几版最大的教训是别在归一化和切分上偷懒模型结构反而不是最关键的。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →