CNN-A-LSTM小时天气预测源码解析:从数据管道到模型调优

发布时间:2026/10/3 10:15:58
CNN-A-LSTM小时天气预测源码解析:从数据管道到模型调优 简介这份资源是面向深度学习初学者与气象数据分析人员的CNN-A-LSTM小时天气预测实战项目提供完整Python源码与配套文档帮助读者理解如何将卷积神经网络与长短时记忆网络结合用于处理时间序列气象数据并预测未来天气状况。压缩包共27个文件约1.11MB包含8个py脚本涵盖cnn_lstm、cnn_A_lstm、lstm、gru、rnn、Bi_lstm等模型实现及util工具与数据展示代码、1个csv数据集、2个md说明文档以及11张jpg和4张png训练损失与预测对比图便于直观评估各模型效果。目前已有249人学习。读者可借此掌握数据预处理、模型定义、训练验证与结果可视化的完整流程并对比CNN、LSTM、GRU、双向LSTM等不同结构的预测表现适合作为课程设计、科研入门或项目开发的基础模板。1. 拆开这份 CNN-A-LSTM 小时天气预测源码它到底能解决什么小时级天气预测这件事真正难的不是把模型跑起来而是把「过去 24 小时的温度、湿度、气压」变成「未来 1 小时的温度」这种带时序依赖的回归问题。这份《基于 CNN-A-LSTM 的小时天气预测的 Python 源码文档说明.zip》就是冲着这个场景来的用 CNN 先提局部时序特征再交给 LSTM 抓长程依赖最后接全连接层输出预测值。它适合两类人——一类是刚学完 python 基础语法、想找一个完整时序项目练手的入门者另一类是需要快速搭一个单站点小时预测 baseline 的从业者。整套代码是纯 Python 技术栈不依赖冷门框架文档说明把数据格式、训练流程和参数含义都写清楚了拿到手改改数据列就能跑自己的站点数据。下面我按「数据怎么进、模型怎么搭、坑在哪」的顺序拆一遍。2. 数据管道与 CNN-A-LSTM 结构从原始 CSV 到可训练张量2.1 为什么是 CNN 接 LSTM而不是单独用 LSTM单用 LSTM 做小时天气预测最直接的问题是它把每个时间步当成同等重要的输入而气象数据里相邻几个小时的温度、气压变化其实高度局部相关——比如连续 3 小时的降温趋势这种短窗口模式用卷积核扫一遍比让 LSTM 自己学要高效得多。CNN 在这里的角色不是图像处理而是一维卷积Conv1D沿时间轴滑动提取「局部变化模式」LSTM 接在后面负责把这些局部模式串成更长的依赖关系。这个组合在中小规模时序数据上通常比纯 LSTM 收敛更快也更不容易过拟合。常见做法是把输入组织成滑动窗口用过去 N 小时的多维特征预测下一小时的目标值。这份源码里 N 一般取 24也就是拿一整天的小时数据预测下一个小时。特征维度取决于你的 CSV 有多少列气象变量目标列通常是温度或你要预测的那个量。2.2 数据准备CSV 列结构与归一化拿到源码第一步不是急着训练而是确认你的数据列名和代码里读取的列对得上。典型的气象 CSV 长这样一列时间戳后面若干列数值特征最后一列或指定列是预测目标。下面是我一般会先跑的检查脚本用来确认数据能被正确读进来、没有缺失值断层import pandas as pd import numpy as np # 读取原始气象数据parse_dates 把时间列转成 datetime 索引 df pd.read_csv(weather_hourly.csv, parse_dates[datetime]) df df.sort_values(datetime).reset_index(dropTrue) # 检查缺失情况小时数据最怕中间断档 print(总行数:, len(df)) print(缺失值统计:\n, df.isnull().sum()) # 对数值列做前向填充气象传感器偶发丢点很常见 num_cols [temperature, humidity, pressure, wind_speed] df[num_cols] df[num_cols].ffill().bfill() # 归一化CNN 和 LSTM 对输入尺度敏感不归一化收敛会很慢 from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler() df[num_cols] scaler.fit_transform(df[num_cols]) print(df.head())这段代码的逻辑是先按时间排序保证时序不乱再检查缺失——小时数据一旦中间缺几行滑动窗口就会把不连续的两段拼在一起模型学到的就是错的。前向填充是气象数据的常规处理因为传感器丢点通常只是短暂中断。归一化用 MinMaxScaler 把特征压到 0 到 1这一步不做的话气压约 1000 hPa和风速个位数量级差太多梯度会被大量级特征主导。参数上要注意parse_dates的列名必须和 CSV 里实际的时间列一致num_cols要按你数据里真实存在的列改别照抄。如果你的目标列也需要归一化记得单独保存 scaler预测完要反归一化才能得到真实温度值这一步漏了是最常见的翻车点。2.3 滑动窗口构造与 Dataset 封装数据归一化完下一步是把连续序列切成「输入窗口 目标值」的样本对。这份源码一般会有一个create_sequences之类的函数核心逻辑如下def create_sequences(data, target_idx, window_size24): data: 归一化后的 numpy 数组形状 (时间步, 特征数) target_idx: 目标列在特征中的下标 window_size: 用过去多少小时预测下一小时 xs, ys [], [] for i in range(len(data) - window_size): # 取连续 window_size 行作为输入 x data[i:i window_size] # 取窗口后一行的目标值作为标签 y data[i window_size, target_idx] xs.append(x) ys.append(y) return np.array(xs), np.array(ys) # 假设 temperature 在第 0 列 X, y create_sequences(df[num_cols].values, target_idx0, window_size24) print(样本形状:, X.shape, y.shape) # 期望 (样本数, 24, 特征数)逻辑说明循环从第 0 行走到倒数第 window_size 行每次取 24 行作为一个样本第 25 行的目标值作为标签。这样每个样本的输入是(24, 特征数)的二维数组正好对应 Conv1D 需要的(时间步, 通道)格式。参数window_size是最关键的超参——取太小模型看不到日变化周期取太大样本数骤减且引入过多噪声24 是小时数据里比较稳的起点。提示切训练集和测试集时一定要按时间顺序切不能随机打乱。时序数据随机切分会让未来信息泄漏到训练集测试指标虚高上线就崩。3. 模型搭建与训练Conv1D LSTM 的层序和参数怎么定3.1 网络结构逐层拆解这份源码的模型主体用 Keras 或 PyTorch 搭都常见结构思路一致一维卷积 → 池化 → LSTM → 全连接。下面用 Keras 写一个等价结构方便对照源码里的层from tensorflow.keras import layers, models def build_cnn_lstm(window_size, n_features): model models.Sequential([ # 一维卷积64 个卷积核核大小 3沿时间轴提取局部模式 layers.Conv1D(filters64, kernel_size3, activationrelu, paddingsame, input_shape(window_size, n_features)), # 最大池化把时间步减半降低 LSTM 的计算量 layers.MaxPooling1D(pool_size2), # LSTM 抓长程依赖64 个隐藏单元 layers.LSTM(64, return_sequencesFalse), # 全连接输出单个预测值 layers.Dense(32, activationrelu), layers.Dense(1) ]) model.compile(optimizeradam, lossmse, metrics[mae]) return model model build_cnn_lstm(window_size24, n_featureslen(num_cols)) model.summary()逐层看Conv1D的filters64决定提取多少种局部模式kernel_size3表示每次看连续 3 小时paddingsame保证输出时间步不变。MaxPooling1D(pool_size2)把 24 步压成 12 步既降维又保留最显著的局部特征。LSTM(64)的return_sequencesFalse表示只取最后一个时间步的输出因为我们只要预测一个值。最后两层 Dense 做非线性映射到标量输出。参数调整经验如果数据量小几千条以内把filters和 LSTM 单元数降到 32 能明显减轻过拟合如果欠拟合、训练 loss 下不去先加 LSTM 单元数而不是加卷积核。kernel_size一般 3 或 5再大对小时窗数据意义不大。3.2 训练循环与早停训练部分关键是回调配置这份源码的文档说明里通常会提到 EarlyStopping。我一般这样配from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau callbacks [ # 验证损失 10 轮不降就停并恢复最优权重 EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue), # 损失停滞时把学习率砍半帮助跳出平台 ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-6) ] history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs100, batch_size32, callbackscallbacks, verbose1 )EarlyStopping的patience10是容忍度验证损失连续 10 轮不改善就停restore_best_weightsTrue保证拿到的是验证集最优那轮的权重而不是最后一轮的。ReduceLROnPlateau在损失卡住时降学习率这两个回调配合能省掉大量手动调参时间。batch_size32是小时数据常用的起点样本量特别小可以降到 16。注意validation_data必须是从训练集尾部切出来的连续时间段不能从测试集里拿否则早停的判据就被污染了。3.3 预测与反归一化训练完拿到模型预测出来的值是归一化空间的必须反变换回真实温度# 预测 y_pred_scaled model.predict(X_test) # 反归一化构造一个和目标列同维度的占位数组 dummy np.zeros((len(y_pred_scaled), len(num_cols))) dummy[:, 0] y_pred_scaled.flatten() # 目标在第 0 列 y_pred scaler.inverse_transform(dummy)[:, 0] # 真实值同样反归一化后对比 dummy_true np.zeros((len(y_test), len(num_cols))) dummy_true[:, 0] y_test y_true scaler.inverse_transform(dummy_true)[:, 0] from sklearn.metrics import mean_absolute_error print(MAE:, mean_absolute_error(y_true, y_pred))这里容易踩的坑是inverse_transform要求输入维度和 fit 时一致所以要先造一个和num_cols同宽度的占位数组把预测值填进目标列再反变换。直接对一维数组调inverse_transform会报维度错误。MAE 是小时温度预测最直观的指标一般能到 1 到 2 摄氏度以内算可用。4. 避坑与排查这份源码跑不起来时先看这几条4.1 现象训练 loss 一直是 nan原因通常是输入里混进了 NaN 或无穷值归一化前没处理干净或者学习率过大导致梯度爆炸。解决在create_sequences之前强制df df.dropna()并检查np.isfinite(X).all()学习率从默认 adam 的 1e-3 降到 1e-4 试一轮。4.2 现象验证 loss 远高于训练 loss且越训差距越大这是典型过拟合。原因可能是模型容量相对数据量太大或者训练集和验证集分布不一致。解决先把filters和 LSTM 单元数各砍一半加Dropout(0.2)到 LSTM 后面再确认切分是按时间顺序而非随机随机切分会让验证集里混入和训练集相邻的样本指标失真。4.3 现象预测曲线整体平移形状对但数值差一截多半是反归一化环节出错比如 scaler 是对全部特征 fit 的但反变换时目标列的位置填错了。解决打印scaler.data_min_和data_max_确认目标列对应的 min/max 是不是真实温度范围再核对target_idx和填占位数组时的列下标是否一致。4.4 现象换自己的数据后报形状不匹配源码里window_size、n_features往往是写死的换数据后特征列数变了但模型input_shape没改。解决把n_features改成len(num_cols)动态传入window_size也做成参数别在模型定义里硬编码。另外确认 CSV 时间列格式能被parse_dates正确解析格式不对会整列变成 NaT后续全崩。4.5 现象训练很快但预测结果几乎是一条直线模型没学到东西常见于归一化后目标列方差极小或者 LSTM 单元数太少。解决检查目标列归一化后的标准差如果接近 0 说明这列本身没什么变化把 LSTM 单元数加到 64 以上并确认return_sequencesFalse时取的是最后一步而非被池化吃掉。5. 进阶技巧把单步预测改成多步并验证模型没在偷懒单步预测只能看下一小时实际用起来往往想要未来 6 小时或 12 小时的曲线。最省事的改法是把输出层改成多神经元标签也改成对应长度的向量def create_multistep_sequences(data, target_idx, window_size24, horizon6): xs, ys [], [] for i in range(len(data) - window_size - horizon 1): xs.append(data[i:i window_size]) # 取窗口后连续 horizon 个目标值作为多步标签 ys.append(data[i window_size:i window_size horizon, target_idx]) return np.array(xs), np.array(ys) X, y create_multistep_sequences(df[num_cols].values, target_idx0, window_size24, horizon6) # 输出层相应改成 Dense(horizon)逻辑上就是把原来取一个点改成取连续horizon个点模型最后一层Dense(1)改成Dense(horizon)损失仍是 MSE只不过现在是对整段预测求平均误差。horizon越大后面几步的误差通常越大这是时序预测的固有特性别指望 12 步和 1 步一样准。改完多步之后一定要做一件事拿一个朴素基线对比。最简单的基线是「用窗口最后一个值当作未来所有步的预测」如果模型 MAE 打不过这个基线说明它根本没学到有效模式只是输出了一个接近均值的平滑结果。我一般会这样验证# 朴素基线用窗口最后一步的值重复 horizon 次 last_value X_test[:, -1, 0] baseline_pred np.repeat(last_value[:, None], horizon, axis1) # 反归一化后对比 MAE # 模型 MAE 必须明显低于基线才算有效这个对比是我踩过坑之后养成的习惯——有一次模型 MAE 看着不错结果一对比基线发现只好了 0.05等于白训。从那以后我每次做完时序模型都强制走一遍基线对比确认模型真的在预测而不是在复读。希望这份源码和上面的拆解能帮你少走几个弯路拿到手先从数据检查脚本跑起再动模型参数。本文还有配套的精品资源点击获取