
简介这份资源面向具备Python与深度学习基础、希望上手时间序列预测的开发者与学习者提供一套用循环神经网络预测天气的完整源码帮助理解RNN、LSTM或GRU在气象数据建模中的实际落地方式。压缩包内共1个文件为单个py脚本体积约2KB代码结构紧凑便于直接阅读与二次修改。资源围绕温度、湿度、风速、气压等历史气象序列展开涉及数据清洗、缺失值处理、归一化、序列构造以及损失函数、优化器选择与均方根误差等评估环节可帮助读者把序列依赖、梯度消失等抽象概念对应到可运行代码。目前已有373人学习适合作为课程设计、入门练手或时间序列预测项目的参考起点读者可据此搭建训练与验证流程并在此基础上尝试超参数调优与模型改进。1. 从一份天气预测源码说起RNN 到底能预测什么很多人第一次接触循环神经网络都是被一份「循环神经网络预测天气代码」的压缩包吸引进来的。下载解压看到几个 .py 文件和一个 csv 数据集心里既兴奋又发虚这玩意儿真能预测明天下不下雨吗我当初也是这么想的直到自己把这份 Python 源码从头到尾跑了一遍才发现 RNN 预测天气这件事重点根本不在「预测得准不准」而在于你能不能把时间序列的输入输出结构搭对。循环神经网络Recurrent Neural Network简称 RNN处理的是带时间顺序的数据。天气数据天然就是时间序列温度、湿度、气压、风速每个小时一条记录前后之间有强相关性。普通全连接网络把每条样本当独立个体而 RNN 会把上一个时刻的隐藏状态传到下一个时刻相当于给网络装了一段「记忆」。这份源码要解决的核心问题就是用过去若干小时的天气观测去预测未来某一时刻的温度或降雨概率。适合读这篇的人有三类一是刚学完 Python 基础、想找一个完整项目练手的入门者二是做过机器学习但没碰过序列建模的开发者三是手里有气象、能耗、销量等时序数据想评估 RNN 方案值不值得投入的工程师。下面我按「数据怎么准备 → 模型怎么搭 → 训练怎么调 → 坑在哪」的顺序把这份源码背后的完整路径拆开讲清楚。2. 天气时序数据的准备与 RNN 输入结构2.1 为什么天气数据必须先做滑动窗口原始天气 csv 通常是「一行一个时刻」的宽表列包括时间戳、温度、湿度、气压、风向、风速、降水量等。RNN 不能直接吃这种表它要的是三维张量(样本数, 时间步长, 特征数)。把宽表变成三维张量的过程叫滑动窗口切分。假设你用过去 24 小时的 6 个特征预测第 25 小时的温度。那么每个样本的输入是 24×6 的矩阵输出是一个标量。窗口每次向后滑动 1 小时就多出一个样本。这个「24」就是时间步长timestep是 RNN 最重要的超参数之一太短模型看不到足够的历史趋势太长梯度容易在反向传播中衰减训练变慢甚至不收敛。我一般会先画一张自相关图ACF来判断合理的 timestep。天气温度的自相关性通常衰减较慢24 到 72 小时都合理降雨这种突变型目标timestep 反而要短一些12 小时左右就够。2.2 用 pandas 做滑动窗口的完整代码import pandas as pd import numpy as np # 读取天气数据parse_dates 把时间列转成 datetime 类型 df pd.read_csv(weather.csv, parse_dates[datetime]) df df.sort_values(datetime).reset_index(dropTrue) # 只保留数值特征列缺失值先用前向填充 feat_cols [temperature, humidity, pressure, wind_speed, visibility, dew_point] df[feat_cols] df[feat_cols].ffill().bfill() # 归一化RNN 对量纲敏感必须做 from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler() data scaler.fit_transform(df[feat_cols].values) # 滑动窗口过去 24 小时预测下一小时温度温度是第 0 列 TIMESTEPS 24 X, y [], [] for i in range(len(data) - TIMESTEPS): X.append(data[i:i TIMESTEPS]) # 形状 (24, 6) y.append(data[i TIMESTEPS, 0]) # 下一时刻温度 X np.array(X) # (N, 24, 6) y np.array(y) # (N,) # 按时间顺序切分不能随机打乱否则会数据泄漏 split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] print(X_train.shape, X_test.shape)这段代码有三个关键点。第一ffill().bfill()处理缺失值天气传感器掉线很常见直接dropna会丢掉大量连续时段。第二MinMaxScaler必须只在训练集上 fit再 transform 测试集否则测试集的极值会泄漏到训练过程。第三切分必须按时间顺序绝不能train_test_split(shuffleTrue)这是时序任务最常见的翻车点。参数说明TIMESTEPS24表示输入序列长度可按自相关图调整feat_cols里的特征列要和 csv 表头完全一致归一化范围默认 [0,1]如果目标值分布偏态严重可以换成 StandardScaler。2.3 数据集划分与标签对齐的注意点很多人切完窗口后发现训练 loss 一直不降排查半天才发现标签错位了。y.append(data[i TIMESTEPS, 0])里的索引必须是i TIMESTEPS而不是i TIMESTEPS - 1。差一位模型学到的就是「用过去 24 小时预测第 24 小时」而第 24 小时已经在输入里了等于让模型抄答案验证集表现会虚高。另外如果你的目标是预测「未来 3 小时后的温度」标签索引要改成i TIMESTEPS 2同时输入窗口和输出之间要留出 gap避免信息泄漏。这个细节在源码里经常被忽略但直接决定模型上线后能不能用。3. 用 Keras 搭一个能跑通的 RNN 预测模型3.1 SimpleRNN、LSTM、GRU 怎么选标题里说的是「循环神经网络」但真正落地时你面对的是三个选项SimpleRNN、LSTM、GRU。SimpleRNN 结构最简单但梯度消失问题严重超过 20 个时间步基本学不动。LSTM 用输入门、遗忘门、输出门控制信息流能记住长距离依赖是天气预测的默认选择。GRU 是 LSTM 的简化版参数少三分之一训练更快在中小数据集上表现往往和 LSTM 持平。我的经验是数据量小于 1 万条样本先用 GRU 试数据量大、序列长用 LSTMSimpleRNN 只用来验证流程是否跑通不要指望它出好结果。这份源码如果用的是 SimpleRNN你完全可以替换成 LSTM 再跑一遍对比。3.2 模型定义与编译参数from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam model Sequential([ # 第一层 LSTMreturn_sequencesFalse 表示只输出最后时刻的隐藏状态 LSTM(64, input_shape(TIMESTEPS, len(feat_cols)), return_sequencesTrue), Dropout(0.2), LSTM(32, return_sequencesFalse), Dropout(0.2), Dense(16, activationrelu), Dense(1) # 回归任务输出一个温度值 ]) model.compile( optimizerAdam(learning_rate1e-3), lossmse, metrics[mae] ) model.summary()逐层说明第一层 LSTM 的return_sequencesTrue是为了把完整序列传给第二层 LSTM如果后面接的是 Dense就必须设成 False。Dropout(0.2)放在 LSTM 之后缓解过拟合天气数据噪声大这个层不能省。最后一层Dense(1)不加激活函数因为温度回归的输出范围是连续的加了 sigmoid 或 tanh 反而限制表达。编译参数里Adam(learning_rate1e-3)是安全起点。如果 loss 震荡厉害降到 5e-4如果 loss 下降太慢升到 2e-3 试试。lossmse适合回归如果你更关心绝对误差可以换成mae或huber。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_split0.2, # 从训练集尾部再切 20% 做验证 epochs100, batch_size32, callbackscallbacks, verbose1 )EarlyStopping的patience10表示验证 loss 连续 10 轮不下降就停restore_best_weightsTrue保证拿到的是最优轮次的权重相当于一颗后悔药。ReduceLROnPlateau在 loss 停滞时自动降学习率比手动调省事。validation_split0.2是从训练集尾部切不是随机切保持时间顺序。batch_size32是通用起点。显存够可以调到 64 或 128训练更稳样本少就降到 16。epochs100配合早停实际可能 30 轮就结束了。3.4 预测结果反归一化与评估# 预测 y_pred model.predict(X_test) # 反归一化温度在第 0 列构造一个同宽度的占位矩阵 def inverse_temp(scaled): dummy np.zeros((len(scaled), len(feat_cols))) dummy[:, 0] scaled.flatten() return scaler.inverse_transform(dummy)[:, 0] y_test_real inverse_temp(y_test) y_pred_real inverse_temp(y_pred) from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(y_test_real, y_pred_real) rmse np.sqrt(mean_squared_error(y_test_real, y_pred_real)) print(fMAE: {mae:.2f} RMSE: {rmse:.2f})反归一化是新手最容易漏的一步。模型输出的范围是 [0,1]直接拿去和真实温度比MAE 会小得离谱看着像完美模型实际毫无意义。这里构造占位矩阵是因为 scaler 是按 6 列 fit 的必须凑够列数才能 inverse_transform。评估指标上MAE 告诉你平均误差多少度RMSE 对大误差更敏感。如果 RMSE 远大于 MAE说明模型在某些时段误差特别大通常是天气突变的那几个小时需要单独看残差分布。4. 训练不收敛、预测滞后RNN 天气预测的避坑清单4.1 现象loss 降到某个值就卡住不动原因通常是学习率太大导致在最优解附近震荡或者输入特征没有归一化。先检查scaler是否真的作用到了训练数据上再试着把学习率降到 1e-4。如果还是不动看看 timestep 是不是设得太长梯度在反向传播中衰减没了把 24 改成 12 试试。4.2 现象预测曲线整体滞后真实曲线一截这是 RNN 回归任务的经典问题模型倾向于输出「上一个时刻的值」而不是真正预测未来。根因是输入窗口和预测目标之间相关性太强模型学到了恒等映射。解决办法有两个一是把预测目标改成「变化量」而不是绝对值让模型学差分二是在输入里加入时间特征比如小时、星期几的 one-hot 编码给模型额外信息。4.3 现象验证集 loss 远高于训练集 loss典型过拟合。先加 Dropout从 0.2 加到 0.4再减小模型容量把 LSTM 单元数从 64 降到 32如果数据量确实太少考虑用数据增强比如对温度加微小高斯噪声生成额外样本。注意不要用随机打乱的方式做增强时序数据的增强必须保持时间顺序。4.4 现象换了台机器跑结果完全不一样随机种子没固定。Python 的 random、numpy、tensorflow 各有各的种子必须全部设上import random, numpy as np, tensorflow as tf random.seed(42) np.random.seed(42) tf.random.set_seed(42)另外GPU 上的某些算子有非确定性如果要求完全可复现需要设置tf.config.experimental.enable_op_determinism()但会牺牲一些速度。4.5 现象预测值全是同一条水平线模型退化成输出均值了。检查标签是不是全被归一化到接近 0或者 loss 是不是一直没降。常见原因是学习率太小加上训练轮次不够模型还没开始学就停了。把 epochs 调大观察 loss 是否在下降。如果 loss 从一开始就不动检查输入数据里是不是有 NaNNaN 会让梯度直接变成 NaN整个训练静默失败。5. 把预测精度再压一压三个我常用的进阶技巧第一个技巧是双向 LSTM。天气序列虽然因果方向是单向的但在离线预测场景下用未来信息辅助编码历史是允许的。Bidirectional(LSTM(64))会把正向和反向的隐藏状态拼接通常能把 MAE 降低 5% 到 10%。代价是参数量翻倍推理变慢实时预测场景要权衡。第二个技巧是残差连接。在 LSTM 层外面套一个跳跃连接把输入直接加到输出上能缓解深层网络的梯度问题。实现上就是x Add()([lstm_out, input_proj])其中input_proj是一个 Dense 层把输入维度对齐到 LSTM 输出维度。这个改动在序列较长时效果明显。第三个技巧是预测区间而不是单点。天气预测真正有价值的是「温度在 20 到 23 度之间」而不是「22.3 度」。把最后一层改成输出两个值均值和方差用高斯负对数似然做损失模型就能给出置信区间。这个改动需要自定义 loss 函数但上线后实用性提升很大。验证方法上我习惯留出最近一个月的连续数据做「滚动预测」每次只预测下一小时然后把预测值喂回输入窗口模拟真实部署场景。如果滚动预测的误差随时间快速累积说明模型对自身误差没有鲁棒性需要加入 scheduled sampling 训练策略。最后说个我自己的习惯每次跑完模型我都会把预测值和真实值画在同一张图上不看指标先看形状。指标好看但曲线形状不对的模型上线一定出问题。这个习惯帮我省过好几次返工。希望帮到你。本文还有配套的精品资源点击获取