
简介这份资源面向计算机、人工智能相关专业的本科生与研究生提供一套可直接运行的CNN-LSTM混合网络小时级天气预测源码适用于毕业设计、期末大作业与课程设计等场景。项目将卷积神经网络与长短期记忆网络结合用于从历史气象数据中挖掘时空特征并输出精细化逐小时预测结果同时附带RNN、LSTM、Bi-LSTM、GRU等对比模型脚本便于横向比较不同序列建模方案的预测效果。压缩包共26个文件约1.82MB其中8个Python源码文件承载模型定义、训练与可视化逻辑11张jpg与4张png图像记录损失曲线和真实值对比预测图另有CSV数据文件、docx说明手册及Markdown文档代码注释较为完整新手也能理解整体流程。目前已有163人学习下载下载后简单部署即可复现实验既能作为大作业直接提交也可作为学习时序预测与深度学习模型融合的实践参考。1. 从一份高分大作业说起CNN-LSTM 怎么做小时级天气预测如果你手头正拿着一份“基于 CNN-LSTM 混合网络的精细化小时级天气预测源码”大概率是三种人之一要交课程大作业的学生、想快速跑通一个时序预测 baseline 的算法新人、或者想拿气象数据练手却卡在数据预处理上的工程师。这份源码的核心价值不在于模型多深而在于它把“小时级天气预测”这个任务拆成了一条能跑通的链路历史气象观测数据进未来若干小时的温度、湿度、风速等目标出。CNN 负责从多变量时间序列里抽局部变化模式LSTM 负责记长期依赖两者串起来比单用 LSTM 或单用 CNN 在小时级粒度上更稳。这篇文章不假设你手里有那份源码的正文而是按这个标题最常见的实现路径把数据、模型、训练、调参、踩坑一条线讲清楚让你能自己复现也能看懂别人给的代码到底在干什么。2. 小时级天气预测的数据长什么样从原始观测到模型输入2.1 气象时间序列的字段与粒度小时级天气预测和日级预测最大的区别是噪声大、周期短、对缺失值敏感。常见的数据来源是气象站观测记录字段一般包括干球温度、露点温度、相对湿度、气压、风速、风向、降水量、能见度等。每条记录带一个时间戳粒度是 1 小时。你要预测的目标通常是未来 1 到 24 小时的某个或某几个变量比如温度或湿度。这里第一个容易翻车的地方是很多人直接把原始 CSV 丢进模型结果 loss 不降。原因不是模型不行而是时间戳没排序、缺失值没处理、不同量纲的字段混在一起。小时级数据里温度可能在 -10 到 40 之间气压在 1000 上下风速在 0 到 20 之间如果不做归一化梯度会被大量纲字段主导。常见做法是先把数据整理成一张宽表每行是一个小时每列是一个字段时间戳作为索引并升序排列。然后对每个字段单独做缺失值填充和归一化。缺失值少的时候用线性插值连续缺失超过 3 小时的段落建议直接标记为无效样本不要硬填。2.2 用滑动窗口把时序转成监督学习样本CNN-LSTM 吃的是三维张量样本数、时间步长、特征数。你需要用滑动窗口把连续时间序列切成一个个样本。假设你用过去 24 小时的数据预测未来 6 小时的温度那么每个样本的输入是 24 行、每行 N 个特征输出是 6 个温度值。import numpy as np import pandas as pd def make_windows(data, target_col, input_len24, output_len6): data: 归一化后的 DataFrame索引为时间戳 target_col: 要预测的列名 input_len: 输入时间步长 output_len: 输出时间步长 X, y [], [] values data.values target_idx data.columns.get_loc(target_col) for i in range(len(data) - input_len - output_len 1): X.append(values[i:i input_len, :]) # 过去 input_len 小时的所有特征 y.append(values[i input_len:i input_len output_len, target_idx]) # 未来 output_len 小时的目标 return np.array(X), np.array(y) # 假设 df 已经按时间升序、填充缺失、归一化 # X shape: (样本数, 24, 特征数) # y shape: (样本数, 6) X, y make_windows(df, target_coltemperature, input_len24, output_len6)这段代码的逻辑是从第 i 行开始取 24 行作为输入紧接着取 6 行的目标列作为输出。参数input_len决定模型能看到多长的历史output_len决定预测多远。小时级天气里input_len 一般取 24 或 48output_len 取 1、3、6、12、24。注意不要用未来信息泄漏到输入里窗口切分时输入和输出不能重叠。2.3 训练集、验证集、测试集怎么切才不泄漏时间序列不能随机打乱切分否则相邻小时的数据会同时出现在训练和测试里造成虚高精度。正确做法是按时间顺序切前 70% 训练中间 15% 验证最后 15% 测试。如果你的数据跨年最好保证测试集覆盖不同季节否则模型在冬季数据上训练、在夏季数据上测试误差会大得让你怀疑人生。另一个细节是归一化参数只能用训练集算。很多人图省事在全量数据上算均值和方差然后切分这等于把测试集的分布信息泄漏给了训练。正确流程是先切分再用训练集的均值和方差去变换验证集和测试集。3. CNN-LSTM 混合网络的结构设计与 Keras 实现3.1 为什么是 CNN 在前、LSTM 在后小时级天气数据里相邻小时的温度、湿度变化有很强的局部连续性CNN 的一维卷积核能在时间轴上滑动提取这种短时模式比如“过去 3 小时温度持续上升”。LSTM 则负责把这些局部模式串起来记住更长时间范围内的趋势比如“过去 12 小时气压持续下降可能要变天”。如果把 LSTM 放在前面、CNN 放在后面LSTM 输出的隐藏状态序列再经过卷积也能跑但常见做法还是 CNN 先做局部特征压缩再把压缩后的序列喂给 LSTM。这样 LSTM 的输入长度变短、特征更干净训练更快过拟合风险也低一些。3.2 一个能跑通的 Keras 模型定义下面是一个典型的 CNN-LSTM 结构一维卷积 最大池化 LSTM 全连接输出。输入形状是(input_len, n_features)输出是(output_len,)。from tensorflow.keras import layers, models def build_cnn_lstm(input_len, n_features, output_len): model models.Sequential([ # 一维卷积提取局部时间模式 layers.Conv1D(filters64, kernel_size3, activationrelu, paddingsame, input_shape(input_len, n_features)), layers.MaxPooling1D(pool_size2), layers.Conv1D(filters32, kernel_size3, activationrelu, paddingsame), layers.MaxPooling1D(pool_size2), # LSTM学习长期依赖 layers.LSTM(64, return_sequencesFalse), layers.Dropout(0.3), # 全连接输出 layers.Dense(32, activationrelu), layers.Dense(output_len) ]) model.compile(optimizeradam, lossmse, metrics[mae]) return model model build_cnn_lstm(input_len24, n_featuresX.shape[2], output_len6) model.summary()参数说明filters64是第一层卷积核数量小时级数据特征不多64 够用太多会过拟合。kernel_size3表示每次看 3 个小时的局部窗口这是小时级天气里比较稳的选择。pool_size2把时间步长减半减少 LSTM 的输入长度。LSTM(64)是隐藏单元数和卷积核数量匹配即可。Dropout(0.3)放在 LSTM 后面防止记忆训练集噪声。最后一层Dense(output_len)直接输出未来 6 小时的值不加激活函数因为回归任务输出范围不受限。3.3 训练时的批次、学习率和早停from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau callbacks [ 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_size64, callbackscallbacks, verbose1 )batch_size64是小时级数据常用的起点样本少就降到 32。EarlyStopping的patience10表示验证损失连续 10 轮不降就停并恢复最优权重。ReduceLROnPlateau在验证损失停滞时把学习率减半避免在局部最小值附近震荡。这两个回调基本是时序预测的标配能省很多手动调参时间。4. 训练完不算完评估、误差分析和调参方向4.1 回归指标不能只看 MSEMSE 对异常值敏感小时级天气里偶尔一次极端温度会让 MSE 很难看。建议同时看 MAE、RMSE 和 MAPE。MAE 反映平均绝对误差RMSE 放大大误差的权重MAPE 看相对误差。如果 MAPE 很大但 MAE 很小说明模型在低值区间误差比例高比如夜间温度接近 0 时稍微偏一点百分比就很大。from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np y_pred model.predict(X_test) mae mean_absolute_error(y_test, y_pred) rmse np.sqrt(mean_squared_error(y_test, y_pred)) print(fMAE: {mae:.3f}, RMSE: {rmse:.3f})注意如果你的目标做了归一化评估前要反归一化回原始量纲否则 MAE 看起来很小实际误差可能很大。4.2 按预测步长拆开看误差未来 1 小时和未来 24 小时的预测难度完全不同。把output_len个步长的误差分别算出来你会看到误差随时间步长增加而上升。如果第 1 步误差就很大说明模型连短期模式都没学到问题在输入特征或网络结构。如果第 1 步还行、后面几步迅速变差说明 LSTM 的记忆能力不够或训练数据太少。for step in range(y_test.shape[1]): step_mae mean_absolute_error(y_test[:, step], y_pred[:, step]) print(fStep {step1} MAE: {step_mae:.3f})这个拆解能帮你判断是该加 LSTM 单元数、加卷积层还是该换更长的输入窗口。4.3 调参的优先级顺序血泪经验是先调数据窗口再调网络深度最后调学习率。input_len从 24 加到 48 往往比加一层 LSTM 更有效因为小时级天气的日周期是 24 小时给够一个完整周期模型才能学到日变化规律。output_len如果设成 24模型要一次输出全天难度大可以改成 seq2seq 结构或逐步预测。卷积核数量从 64 降到 32 有时反而更好因为小时级数据噪声大容量太大容易记住噪声。5. 避坑与排查小时级天气预测里最容易翻车的 5 个地方5.1 现象训练 loss 正常下降验证 loss 从一开始就很高原因训练集和验证集分布不一致。最常见的是按时间切分时验证集落在某个特殊天气段比如连续降雨或寒潮而训练集没有类似模式。另一个原因是归一化参数用了全量数据验证集信息泄漏导致验证 loss 看起来异常低或异常高。解决检查切分点前后的数据分布画一下训练集和验证集的目标变量直方图。如果差异大改用滚动窗口验证或者确保训练集覆盖至少一个完整年度周期。归一化严格只用训练集统计量。5.2 现象模型预测值几乎是一条直线没有波动原因LSTM 输出被全连接层压平了或者学习率太低、训练轮数不够。更隐蔽的原因是输入特征里目标列的历史值被错误地归一化到接近零模型学不到变化。解决先检查输入里是否包含目标列的历史值如果有确认它的归一化没有把方差压没。然后看 LSTM 的return_sequences设置如果你只需要最后一步输出return_sequencesFalse是对的。如果还是直线把学习率调到 1e-3 或 1e-4增加训练轮数观察 loss 是否还在降。5.3 现象预测未来 1 小时很准未来 6 小时完全不能用原因模型过度依赖最近一步的输入LSTM 没有真正学到长期依赖。常见于input_len太短比如只给了 6 小时历史模型只能靠最后几小时外推。解决把input_len加到 24 或 48确保覆盖一个完整日周期。如果数据允许加到 72 小时覆盖三天。同时检查 LSTM 单元数是否太少64 是起点可以试 128。另外输出层直接预测 6 步比逐步递归预测更稳不要用预测值喂回输入。5.4 现象换了台机器或换了 TensorFlow 版本结果对不上原因随机种子没固定或者 CuDNN 的 LSTM 实现在不同 GPU 上数值精度有差异。CPU 和 GPU 的结果也可能不同。解决在代码开头固定 Python、NumPy、TensorFlow 的随机种子。如果要求完全可复现用 CPU 训练或者设置tf.config.experimental.enable_op_determinism()。但要注意这会降低训练速度。大作业里固定种子就够了不必追求 bit 级复现。import random, numpy as np, tensorflow as tf def set_seed(seed42): random.seed(seed) np.random.seed(seed) tf.random.set_seed(seed) set_seed(42)5.5 现象MAE 看起来不错但画图一看相位完全错位原因模型学到了平均趋势但预测的峰值和谷值时间点偏移了。小时级温度预测里模型可能把下午最高温预测到了傍晚。这通常是损失函数只优化 MSE而 MSE 对相位偏移不敏感。解决在损失函数里加入一阶差分项让模型关注变化趋势。或者改用 Huber loss对异常值更鲁棒。另一个办法是把目标变量做差分预测变化量而不是绝对值最后再累加回去。这个技巧在小时级温度预测里很实用。6. 让预测再稳一点两个进阶技巧和我的固定习惯第一个技巧是给模型加一个“日周期特征”。小时级天气有很强的 24 小时周期但原始时间戳对模型没有直接意义。你可以把小时数转成 sin 和 cos 两个特征加进输入模型就能感知到“现在是凌晨还是下午”。这个改动很小但在温度预测上通常能把 MAE 降 5% 到 10%。df[hour_sin] np.sin(2 * np.pi * df.index.hour / 24) df[hour_cos] np.cos(2 * np.pi * df.index.hour / 24)第二个技巧是预测多个目标变量。不要只预测温度把湿度、气压也一起作为输出多任务学习能让 LSTM 学到更通用的天气表征。输出层改成Dense(output_len * n_targets)然后 reshape。这样即使你只关心温度湿度作为辅助任务也能提升温度预测的稳定性。验证方法上我习惯留出一段连续一周的数据做“盲测”不参与任何训练和调参最后只跑一次。如果这一周的预测曲线和真实曲线在趋势和峰值时间上都对得上这个模型才敢拿去用。如果只是随机切分的测试集好看实际部署大概率翻车。最后说一个我的固定习惯每次改完模型结构或窗口参数先把训练 loss 和验证 loss 画在一张图上看两条线的间距。间距大就是过拟合间距小但都高就是欠拟合。这个图比任何指标都直观。小时级天气预测没有银弹CNN-LSTM 只是一个起点数据质量、窗口长度、归一化方式往往比网络结构更决定成败。希望帮到你。本文还有配套的精品资源点击获取