CNN-A-LSTM小时天气预测实战:从源码跑通到Attention调参避坑
简介这份资源是面向深度学习初学者与气象数据分析爱好者的实战项目包围绕CNN与LSTM结合的混合模型实现小时级天气预测。项目以Python为主要开发语言借助TensorFlow、Keras等框架搭建网络利用CNN提取气象图像的空间特征再由LSTM建模时间序列的长期依赖最终输出未来天气状况的预测结果适合作为课程设计、毕业项目或算法练手的参考模板。压缩包共27个文件约1.11MB包含8个py源码文件涵盖cnn_lstm、cnn_A_lstm、Bi_lstm、gru、rnn、lstm等不同模型实现及util工具脚本、1个csv数据集、2个md说明文档以及若干jpg、png训练损失与预测对比图便于直观评估模型效果。目前已有249人学习下载。读者可从中获取完整的数据预处理、模型定义、训练验证与结果可视化流程并借助文档理解CNN-A-LSTM的融合思路与调参方向快速复现并迁移到自己的时序预测任务中。1. 拆开这个压缩包之前CNN-A-LSTM 做小时天气预测到底在解决什么小时级天气预测和常见的日级预报是两码事。日级预报关心「明天热不热」小时级关心的是「未来 1 到 6 小时降不降雨、气温怎么抖」。这个时间尺度上数值模式NWP的更新频率跟不上而纯时序模型又容易把空间相关性丢掉——一个站点的气温不只取决于它自己的历史还受周边站点、气压场、湿度场影响。CNN-A-LSTM 就是冲着这个矛盾来的用 CNN 从多变量气象序列里抽局部空间/通道特征用 LSTM 记长时间依赖中间加一层 Attention 让模型自己决定「当前该重点看哪几个时间步」。你拿到的这个基于CNN-A-LSTM的小时天气预测的Python源码文档说明.zip本质是一套可跑通的端到端方案数据预处理、模型定义、训练循环、评估指标、文档说明都在里面。适合两类人——想拿它当毕业设计或课程设计底座的学生以及想快速验证「注意力机制到底有没有用」的算法工程师。下面按「先跑通、再调参、最后避坑」的顺序拆。2. 环境与数据把源码跑起来前必须对齐的几件事2.1 依赖版本与 Python 环境配置拿到源码第一步不是急着python train.py而是先看requirements.txt或文档里写的依赖。CNN-A-LSTM 这类项目通常依赖 PyTorch 或 TensorFlow两者对 Python 版本和 CUDA 版本很敏感。我一般会先建独立虚拟环境避免和系统里的包打架。# 创建并激活虚拟环境Windows 用 venv\Scripts\activate python -m venv venv source venv/bin/activate # 安装核心依赖版本以文档说明为准这里给常见组合 pip install torch2.0.1 numpy pandas scikit-learn matplotlib pip install torchinfo # 用于打印模型结构排查维度问题逻辑说明虚拟环境隔离是防止numpy版本冲突导致np.float报错这类玄学问题的第一道防线。参数说明torch版本要和你的 CUDA 驱动匹配没有 GPU 就装 CPU 版scikit-learn主要用来做归一化和指标计算。如果文档里指定了tensorflow把torch换成对应版本即可但不要两个都装容易在 import 时抢后端。2.2 气象数据的字段结构与预处理小时天气预测的数据通常是 CSV 或 NC 格式字段包括时间戳、气温、气压、湿度、风速、降水量等。CNN-A-LSTM 要求输入是三维张量(样本数, 时间步长, 特征数)。很多人卡在「怎么把一张 CSV 变成模型能吃的形状」。import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler # 读取数据parse_dates 把时间列转成 datetime df pd.read_csv(weather_hourly.csv, parse_dates[timestamp]) df df.sort_values(timestamp).reset_index(dropTrue) # 选取特征列目标列单独拿出来 feature_cols [temperature, pressure, humidity, wind_speed, precipitation] target_col temperature # 归一化CNN-LSTM 对量纲敏感不做归一化 loss 会炸 scaler MinMaxScaler() data_scaled scaler.fit_transform(df[feature_cols]) # 滑动窗口构造样本look_back 是历史小时数horizon 是预测未来第几小时 def create_sequences(data, look_back24, horizon1): X, y [], [] for i in range(len(data) - look_back - horizon 1): X.append(data[i:ilook_back]) y.append(data[ilook_backhorizon-1, 0]) # 假设第0列是目标 return np.array(X), np.array(y) X, y create_sequences(data_scaled, look_back24, horizon1) print(X.shape, y.shape) # 期望 (样本数, 24, 5) 和 (样本数,)逻辑说明look_back24表示用过去 24 小时预测下一小时这是小时天气预测的常见起点。参数说明horizon改成 3 就是预测未来第 3 小时但注意y的索引要同步改。归一化必须用训练集 fit再 transform 验证集否则数据泄露会让评估指标虚高——这是血泪经验很多人论文里 RMSE 低得离谱就是这里翻车。2.3 训练集/验证集/测试集的时间顺序切分时间序列不能随机 shuffle 切分否则未来信息泄露到训练集。正确做法是按时间先后切前 70% 训练中间 15% 验证最后 15% 测试。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:] # 转成 torch tensor import torch X_train torch.FloatTensor(X_train) y_train torch.FloatTensor(y_train)逻辑说明按索引顺序切分保证训练集时间早于验证集验证集早于测试集。参数说明比例不是固定的数据量少时可以 80/10/10但绝不能 shuffle。如果源码里用了train_test_split(shuffleTrue)直接改掉这是最常见的翻车点之一。3. CNN-A-LSTM 模型结构从 Conv1d 到 Attention 的维度推演3.1 CNN 层提取局部变化特征CNN 在这里的作用不是图像识别而是用一维卷积在时间轴上滑动捕捉短时突变比如气温骤降、湿度突增。Conv1d的输入是(batch, channels, length)而我们的数据是(batch, length, features)需要先 permute。import torch.nn as nn class CNNExtractor(nn.Module): def __init__(self, in_channels, out_channels64, kernel_size3): super().__init__() self.conv nn.Sequential( nn.Conv1d(in_channels, out_channels, kernel_size, padding1), nn.ReLU(), nn.BatchNorm1d(out_channels), nn.MaxPool1d(2) # 时间步长减半 ) def forward(self, x): # x: (batch, length, features) - (batch, features, length) x x.permute(0, 2, 1) x self.conv(x) return x.permute(0, 2, 1) # 换回 (batch, length/2, out_channels)逻辑说明padding1配合kernel_size3保持时间步不变MaxPool1d(2)把 24 小时压成 12 步减少 LSTM 的计算量。参数说明out_channels是卷积核数量太小欠拟合太大过拟合64 是常见起点kernel_size取 3 或 5对应捕捉 3 小时或 5 小时的局部模式。3.2 LSTM 与 Attention 的衔接方式LSTM 接收 CNN 输出的序列输出每个时间步的隐藏状态。Attention 的作用是给这些隐藏状态加权求和让模型自动关注关键时间步而不是只用最后一个 hidden state。class Attention(nn.Module): def __init__(self, hidden_dim): super().__init__() self.attn nn.Linear(hidden_dim, 1) def forward(self, lstm_out): # lstm_out: (batch, seq_len, hidden_dim) weights torch.softmax(self.attn(lstm_out), dim1) # (batch, seq_len, 1) context torch.sum(weights * lstm_out, dim1) # (batch, hidden_dim) return context, weights class CNNALSTM(nn.Module): def __init__(self, in_features, cnn_out64, hidden_dim128, num_layers2): super().__init__() self.cnn CNNExtractor(in_features, cnn_out) self.lstm nn.LSTM(cnn_out, hidden_dim, num_layers, batch_firstTrue) self.attention Attention(hidden_dim) self.fc nn.Linear(hidden_dim, 1) def forward(self, x): x self.cnn(x) lstm_out, _ self.lstm(x) context, weights self.attention(lstm_out) out self.fc(context) return out.squeeze(-1), weights逻辑说明batch_firstTrue让 LSTM 接受(batch, seq, feature)和 CNN 输出对齐。Attention 用一层线性映射打分再 softmax权重可视化后能看出模型关注哪几个小时。参数说明hidden_dim128是 LSTM 隐藏层维度数据量大可以加到 256num_layers2表示两层堆叠再多容易梯度消失除非加残差连接。3.3 训练循环与损失函数选择小时天气预测是回归任务损失函数用 MSE 或 Huber。Huber 对异常值更鲁棒因为气象数据里偶尔有传感器跳变。import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model CNNALSTM(in_featuresX_train.shape[2]).to(device) criterion nn.HuberLoss(delta1.0) optimizer optim.Adam(model.parameters(), lr1e-3) for epoch in range(50): model.train() optimizer.zero_grad() pred, _ model(X_train.to(device)) loss criterion(pred, y_train.to(device)) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() if epoch % 10 0: print(fEpoch {epoch}, Loss: {loss.item():.4f})逻辑说明clip_grad_norm_防止 LSTM 梯度爆炸这是 RNN 类模型的标配操作。参数说明lr1e-3是 Adam 的常用学习率loss 不降就降到 1e-4delta1.0控制 Huber 的转折点归一化后的数据用 1.0 合适如果没归一化要按数据尺度调。4. 评估与调参RMSE、MAE 之外还要看什么4.1 反归一化后的指标才有物理意义直接在归一化数据上算 RMSE 没有意义必须用 scaler 反变换回原始量纲。model.eval() with torch.no_grad(): pred_test, attn_weights model(X_test.to(device)) pred_test pred_test.cpu().numpy() # 反归一化只反目标列构造一个占位矩阵 dummy np.zeros((len(pred_test), len(feature_cols))) dummy[:, 0] pred_test pred_inv scaler.inverse_transform(dummy)[:, 0] dummy[:, 0] y_test.numpy() true_inv scaler.inverse_transform(dummy)[:, 0] rmse np.sqrt(np.mean((pred_inv - true_inv) ** 2)) mae np.mean(np.abs(pred_inv - true_inv)) print(fRMSE: {rmse:.2f}, MAE: {mae:.2f})逻辑说明inverse_transform需要和 fit 时相同的列数所以用 dummy 矩阵占位。参数说明RMSE 对极端误差敏感MAE 更稳健两个一起看如果 RMSE 远大于 MAE说明模型在某些时段预测崩了去查那些时段的 Attention 权重。4.2 关键超参数的影响与调参顺序参数常见范围影响调参建议look_back12 / 24 / 48历史窗口长度从 24 开始加到 48 看验证集是否改善cnn_out32 / 64 / 128卷积特征数数据量小用 32防止过拟合hidden_dim64 / 128 / 256LSTM 容量和 cnn_out 同量级即可num_layers1 / 2 / 3LSTM 深度超过 2 层要加 dropoutlr1e-4 / 1e-3学习率loss 震荡就降收敛慢就升调参顺序建议先定 look_back再调 hidden_dim最后动 lr。不要一上来就网格搜索小时天气数据量通常不大随机搜索更划算。4.3 Attention 权重可视化验证模型是否学到东西Attention 权重是这套模型的「黑匣子」出口。如果权重均匀分布说明 Attention 没起作用如果集中在某几个小时说明模型找到了关键滞后。import matplotlib.pyplot as plt # 取一个测试样本的权重 sample_weights attn_weights[0].cpu().numpy().flatten() plt.bar(range(len(sample_weights)), sample_weights) plt.xlabel(Time step) plt.ylabel(Attention weight) plt.title(Attention distribution over past hours) plt.show()逻辑说明权重条越高表示该时间步对预测贡献越大。参数说明如果权重全挤在最后几步说明模型退化成普通 LSTM可以尝试加 Attention 正则或增大 CNN 感受野。5. 避坑与排查跑 CNN-A-LSTM 天气预测时最容易翻车的 5 个点5.1 现象loss 变成 NaN训练几个 batch 就崩原因学习率过大或数据没归一化导致梯度爆炸。LSTM 对输入尺度非常敏感原始气温 300K 和湿度 0.8 混在一起梯度方向会被大数值主导。解决先确认MinMaxScaler对所有特征做了归一化再把lr降到 1e-4最后加clip_grad_norm_。三步做完基本能稳住。5.2 现象验证集 loss 比训练集低很多原因数据泄露。常见于随机切分或归一化时用了全量数据 fit。时间序列里未来信息混进训练集模型在验证集上「见过答案」。解决检查切分代码有没有shuffleTrue检查 scaler 是不是在切分前 fit 的。正确顺序是先切分再对训练集 fit然后 transform 验证集和测试集。5.3 现象预测曲线整体平移形状对但数值偏原因目标列反归一化时用错了 scaler或者预测的是差分值但没还原。有些源码会对数据做一阶差分再训练预测完忘记累加回去。解决确认inverse_transform用的 scaler 和训练时是同一个如果做了差分预测后要np.cumsum再加回基准值。5.4 现象Attention 权重全一样模型没学到重点原因Attention 层初始化不好或者 LSTM 输出本身没有区分度。也可能是训练轮数不够Attention 还没分化。解决给 Attention 的线性层加小初始化增加训练轮数检查 LSTM 的hidden_dim是不是太小导致所有时间步输出趋同。5.5 现象GPU 显存够但训练速度极慢原因数据加载没用DataLoader每个 batch 都在 CPU 和 GPU 之间来回拷贝或者look_back设得太大序列长度爆炸。解决用TensorDataset和DataLoader封装设置batch_size64左右look_back超过 72 要谨慎LSTM 在长序列上本身就会慢。6. 把小时预测从「能跑」推到「能用」多步预测与滚动验证单步预测只是起点实际业务要的是未来 6 小时甚至 12 小时的连续预测。直接让模型输出多步有两种做法一是改fc层输出维度为horizon二步是递归预测——用上一步预测值当输入。前者训练稳定但误差会累积后者灵活但容易漂移。我一般先用直接多输出跑基线。# 直接多步输出fc 输出 horizon 个值 class CNNALSTM_MultiOut(nn.Module): def __init__(self, in_features, cnn_out64, hidden_dim128, horizon6): super().__init__() self.cnn CNNExtractor(in_features, cnn_out) self.lstm nn.LSTM(cnn_out, hidden_dim, 2, batch_firstTrue) self.attention Attention(hidden_dim) self.fc nn.Linear(hidden_dim, horizon) # 一次输出未来 horizon 小时 def forward(self, x): x self.cnn(x) lstm_out, _ self.lstm(x) context, weights self.attention(lstm_out) return self.fc(context), weights逻辑说明horizon6表示一次预测未来 6 小时标签y也要对应改成(样本数, 6)。参数说明多步输出的 loss 可以用加权 MSE近期小时权重高一些因为远期本身难预测。滚动验证是检验模型稳定性的关键。不要只切一次测试集用滑动窗口在整段数据上跑看 RMSE 随季节的变化。def rolling_validate(model, data, window24*30, step24*7): errors [] for start in range(0, len(data) - window - 24, step): segment data[start:startwindow] # 在 segment 上切训练/测试训练后预测下一小时 # 这里省略重复的训练代码核心是记录每次的 RMSE errors.append(rmse) return np.mean(errors), np.std(errors)逻辑说明window是每次验证用的历史长度step是滑动步长。参数说明step取 7 天可以覆盖不同天气类型如果 RMSE 标准差很大说明模型对某些天气模式不稳定需要扩充训练数据或加正则。最后说个我自己的习惯每次改完模型结构先跑 5 个 epoch 看 loss 曲线不降就直接查数据管道别硬调参。CNN-A-LSTM 这套结构本身不复杂翻车多半在数据切分和归一化上。把这两步做扎实Attention 权重可视化能给你不少信心。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →