尧图精选

LSTM多变量时间序列预测实战:从数据预处理到温度预测模型构建

🕒 发布时间:2026/9/16 23:34:53 📁 来源:尧图网络
这是一个我踩了一整周坑才跑通的项目。做的时候我一度怀疑是自己的打开方式不对后来把问题一个个拆开才发现坑确实不少但每个坑都能填。这篇东西我不想写成教科书就按我实际折腾的顺序来从环境准备到模型训练再到结果分析把每一步的原理、代码和教训都摆出来希望你少走点弯路。先说这个项目到底在做什么。简单讲就是给一堆历史气象数据温度、湿度、气压、风速这些用LSTM去预测未来某个时刻的温度。它属于典型的多变量时间序列预测输入不止温度这一列而是把多个相关的环境变量一起喂给模型让模型自己学会它们之间的耦合关系。比起只用温度单变量去预测多变量在天气这种强耦合场景里通常能拿到更稳定的结果。适合谁来参考正在学Python、刚接触深度学习、打算用LSTM做时序预测但不知道从哪下手的同学尤其是那种“装环境装半天、一跑就报错”的新手。1. 整体思路拆解为什么温度预测要选LSTM1.1 温度预测本质上是时间序列问题我们面对的是一串按时间排列的观测值昨天、今天、明天上一小时、这一小时、下一小时。温度数据天然带有时间顺序相邻时刻的值之间存在强相关性——中午的气温大概率比凌晨高寒潮来了温度会连着几天走低。这种“前后关联”的特征让它天然适合用循环神经网络RNN系列模型来处理。普通的全连接网络处理这类问题有个麻烦它把每个输入当成独立的点根本不知道“上一个时刻”和“下一个时刻”有什么联系。这就好比让一个只看过零散照片的人去猜电影剧情信息严重不足。RNN之所以适合时序数据是因为它有一个“记忆”机制能把过去时刻的信息通过隐藏状态往后传。LSTMLong Short-Term Memory是RNN的改进版专门解决了传统RNN在长序列上容易“忘事”的梯度消失问题。LSTM内部有三个门遗忘门决定丢掉多少旧信息输入门决定写入多少新信息输出门决定当前时刻输出什么。这套机制让信息可以在几十甚至上百个时间步里被有选择地保留。对温度预测来说今天的温度可能受过去24小时甚至过去一周的天气过程影响LSTM这种长程记忆能力刚好对路。1.2 为什么不直接用ARIMA或传统机器学习很多人第一反应是用ARIMA或者XGBoost来做。确实能跑但各有各的局限ARIMA要求数据平稳天气数据季节性强、受突发因素影响大往往要做差分、做季节性分解前处理一堆事而且它本质还是线性模型对多变量的非线性交互关系拟合能力有限。XGBoost这类树模型可以做多变量但它没有序列记忆。你把过去10天的数据展开成几百个特征喂给树模型它能学但特征工程工作量极大而且很难学到“时间顺序”本身。LSTM则直接把“时间”融进网络结构里输入是三维张量样本数、时间步长、特征数模型在图结构上就天然感知顺序。这不代表LSTM在所有场景都碾压树模型但在温度这种有强时序依赖、多变量耦合的预测任务里LSTM确实是个很自然的选择。1.3 完整流程先过一遍我实际跑的流程分六步准备数据获取包含温度、湿度、气压、风速等字段的历史气象数据。数据清洗处理缺失值、异常值把时间列解析成索引。特征构造与归一化选择参与预测的特征列统一缩放到0到1区间。构建序列样本用滑动窗口把原始数据切成“过去N小时预测未来M小时”的样本对。搭建LSTM模型并训练用Keras搭两层LSTM加全连接输出层跑训练。评估与可视化用RMSE、MAE这些指标评估把预测值和真实值画在一起对比。整条链路里真正决定模型上限的不是网络有多深而是数据怎么处理、窗口怎么切、训练怎么防过拟合。后面我会逐个讲。2. 环境准备与数据获取装环境的坑比模型本身还多2.1 Python环境安装选对版本先赢一半我用的是Python 3.9。为什么选3.9而不是最新的3.12因为TensorFlow和Keras对太新的Python版本支持往往滞后尤其是Windows下经常出现“装上了但导入报错”的诡异问题。如果你不想折腾直接用3.8~3.10都行。装Python之后第一件事是换国内pip源不然下载依赖包能让你怀疑人生。在用户目录下建一个pip.iniWindows或pip.confLinux/Mac写上[global] index-url https://pypi.tuna.tsinghua.edu.cn/simple trusted-host pypi.tuna.tsinghua.edu.cn我用的是清华源实测下载速度能到几MB每秒比默认源快了一个量级。装依赖库的时候直接用pip install一次性装齐pip install numpy pandas matplotlib scikit-learn tensorflow这里要提醒一句如果你的电脑有NVIDIA显卡想用GPU训练那还需要额外装CUDA和cuDNN步骤比较繁琐。我这个项目数据量不算大CPU训练也就几分钟一个epoch完全够用没必要一上来就折腾GPU。2.2 数据集怎么找开源气象数据最省事数据是项目的粮食。我不建议自己搭传感器采集前期太浪费时间。我的做法是直接下载公开数据集既省事又能保证数据质量。可选的数据源有三个全球公开气象站数据各大气象机构都有历史观测数据开放下载字段最全包含气温、露点、气压、风速、云量等。国内公开数据集也有不少高校和机构公开的站点气象数据按城市/站点整理好的CSV比较友好。自己造一个小规模数据来调试代码先用模拟数据把流程跑通再换真实数据。我最后用的是某气象站逐小时观测数据包含temperature、humidity、pressure、wind_speed、wind_dir几个字段约两万条记录跨度两年多。我特意选了多变量都能拿到的数据因为项目叫“多变量温度预测”单列温度就没意义了。2.3 数据读取与初步探查先看再动手拿到CSV后我第一件事不是急着建模而是先看数据长什么样。import pandas as pd df pd.read_csv(weather_data.csv, parse_dates[date], index_coldate) print(df.head()) print(df.info()) print(df.describe())这一步能发现几个关键问题有没有空值、各列的数值范围差多大温度可能是0到40气压可能是980到1040、时间索引是否连续。describe()输出的统计量尤其有用可以用它来判断后续归一化的边界。我当时发现湿度列大概有0.3%的缺失值而且时间索引中间跳过了一些小时这两件事都得先处理不然喂给模型的就是错位数据。3. 数据处理序列化与归一化是决定成败的关键说实话我一开始以为建模是最难的跑完才发现数据处理才是真正的分水岭。很多论文里一笔带过的细节实操时全是坑。3.1 缺失值与异常值处理不能直接扔掉缺失值有两种处理方式删除或者填充。删除会破坏时间连续性如果缺的量不大、位置分散倒也能接受但如果像我这次一样缺了几十个小时我倾向于用前后向填充法fillna(methodffill)来补。温度、湿度这类气象变量短时间内变化不会太剧烈前向填充不会引入太大误差。异常值我用的是箱线图思路把超出四分位距IQR3倍以上的点视为异常。比如7月下午温度突然跳到60度这明显是传感器故障我会把它替换成前后两小时的平均值。3.2 归一化LSTM的隐形刚需LSTM的激活函数是tanh和sigmoid它们的输出范围分别在(-1,1)和(0,1)。如果输入特征数值差距太大比如气压1000、温度30、湿度0.8模型训练时梯度会被大数值特征主导小数值特征根本学不到东西。就好比一个班里有同学考100分也有同学考1分平均分一下就被拉过去了。我用的是sklearn.preprocessing.MinMaxScaler把所有特征缩放到0到1之间。这里有个极其重要的细节拟合scaler必须只用训练集的数据不能把测试集的数据一起放进去fit。简单说scaler.fit(train_data)之后再用scaler.transform(test_data)注意顺序。如果先对整个数据集fit测试集信息就泄露到训练过程里了虽然测试指标会“好看”但那是假的部署到真实场景时模型表现会立刻打回原形。3.3 滑动窗口怎么构造样本对有了归一化数据下一步是构造输入输出对。LSTM的输入是三维的(样本数量, 时间步长, 特征数量)。时间步长就是“用过去多少小时的数据来预测”特征数量就是参与预测的因素个数。我设置的窗口大小是24小时也就是用过去24个小时的数据预测未来1个小时的温度。你可以根据业务需求改成“预测未来6小时”“未来24小时”那就要改动输出层的设计。窗口大小的选择有讲究太小模型看不到足够的历史信息太大计算量上去了信息增益可能反而不明显。对小时级气象数据24或48是比较常见的起点。def create_sequences(data, seq_length): X, y [], [] for i in range(len(data) - seq_length): X.append(data[i:i seq_length]) y.append(data[i seq_length, 0]) # 预测目标是温度列第0列 return np.array(X), np.array(y)注意这里y取的是i seq_length这一时刻的温度值也就是“未来一小时后的温度”。如果你想让模型预测“未来6小时”就把索引改成i seq_length 6同时注意数据末尾要留出足够的长度。3.4 训练集/验证集/测试集划分时序数据不能随机打乱这一点我见过无数人栽跟头。普通机器学习里我们会用train_test_split随机划分数据把样本打乱保证分布一致。但时间序列数据绝对不能随机打乱否则模型就在偷看未来信息训练集里混着后面的数据测试集里混着前面的数据指标再好看也没有实际意义。正确做法是按时间顺序切分比如前70%训练、中间15%验证、最后15%测试train_size int(len(X) * 0.7) val_size int(len(X) * 0.15) X_train, X_val, X_test X[:train_size], X[train_size:train_sizeval_size], X[train_sizeval_size:] y_train, y_val, y_test y[:train_size], y[train_size:train_sizeval_size], y[train_sizeval_size:]这个“测试集最后切”的做法模拟的是真实场景我们用今天之前的所有数据训练去预测未来的天气。4. LSTM模型搭建与训练结构、参数、训练细节4.1 网络结构选型几层LSTM、加不加Dropout我的模型结构是两层LSTM加一个全连接层。第一层LSTM有64个单元return_sequencesTrue表示把每个时刻的隐藏状态都传给下一层第二层LSTM有32个单元return_sequencesFalse表示只保留最后一个时刻的输出这个输出就是整个序列的“总结”再接一层全连接输出预测温度值。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout model Sequential() model.add(LSTM(64, return_sequencesTrue, input_shape(seq_length, n_features))) model.add(Dropout(0.2)) model.add(LSTM(32, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizeradam, lossmse)为什么用两层而不是一层一层LSTM能捕捉到一定的时间依赖但在变量较多、关系较复杂时堆叠两层可以让模型学到更高层的抽象特征。比如第一层可能学到“温度、湿度这些变量的短期变化模式”第二层把这些模式组合成“天气过程的演变趋势”。当然不是越深越好气象时序还没复杂到需要三四层的地步层数过多反而容易过拟合。Dropout的作用是随机让一部分神经元在训练时“失活”减少神经元之间的协同依赖防止模型死记硬背训练数据。我这里是0.2如果发现过拟合严重可以调到0.3或0.4。4.2 损失函数与优化器MSE Adam是默认答案损失函数我用了均方误差MSE即预测值与真实值差的平方的平均值。它对大误差的惩罚更重比较适合温度这种连续性数值的回归任务。如果项目里更关心“预测值偏了多少度”的可解释性也可以用MAE平均绝对误差但训练时MSE通常收敛更稳。优化器选了Adam这是目前最省心的默认选择。它自适应调整学习率对不同参数一视同仁地给一个合理的更新步长。学习率设为默认的0.001即可要不是在训练过程中观察到loss剧烈震荡我一般不手动调。4.3 训练过程epoch数、batch size与Early Stoppingfrom tensorflow.keras.callbacks import EarlyStopping early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs100, batch_size32, callbacks[early_stop], verbose1 )batch size设为32意思是一批喂32个样本进模型算一次梯度更新。这个值不是越大越好也不是越小越好大了容易内存爆炸小了梯度噪声大、收敛慢。32到64在大多数场景是个甜点区。epochs我设了100但真正跑满的情况很少。Early Stopping会在验证集loss连续10个epoch不下降时自动停止并且恢复到验证集loss最小的那组权重。这能有效防止过拟合也省时间。训练过程中还需要监听一个信号如果训练loss在下降而验证loss反而上升那就是过拟合信号说明模型开始“记答案”而不是“学规律”。出现这种情况优先考虑减少层数或神经元数、增大Dropout、减少epoch数。4.4 训练结果诊断loss曲线怎么看训练结束后我会把训练集和验证集的loss曲线画出来plt.plot(history.history[loss], labeltrain loss) plt.plot(history.history[val_loss], labelval loss) plt.legend() plt.show()一个健康的曲线是两条线都下降最终趋于平缓而且两者之间的差距不大。如果train loss一路走低、val loss在某个点开始反弹就是经典的过拟合。如果两条线都还很高、下降缓慢可能是学习率太低或模型容量不够。这些判断虽然朴素但在实际项目中非常管用。5. 评估指标与可视化预测得好不好不能靠肉眼5.1 RMSE、MAE、R²三个指标的侧重点模型训完先别急着画图看效果先把指标算出来。我常用三个指标RMSE均方根误差对误差平方后取平均再开根单位和温度一致摄氏度对大误差敏感。MAE平均绝对误差误差绝对值的平均直观好懂不受大误差的过度影响。R²决定系数模型解释了数据的多少方差越接近1越好。from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score y_pred model.predict(X_test) y_pred_inv scaler.inverse_transform(np.concatenate([y_pred, np.zeros((len(y_pred), n_features-1))], axis1))[:, 0] y_test_inv scaler.inverse_transform(np.concatenate([y_test.reshape(-1, 1), np.zeros((len(y_test), n_features-1))], axis1))[:, 0] rmse np.sqrt(mean_squared_error(y_test_inv, y_pred_inv)) mae mean_absolute_error(y_test_inv, y_pred_inv) r2 r2_score(y_test_inv, y_pred_inv)这里有个细节要注意模型输出的是归一化后的预测值必须用之前fit好的scaler做逆变换把结果还原成真实温度。上面代码里我用的inverse_transform是把预测值和其余特征补零拼在一起再还原因为scaler是5个特征一起拟合的逆变换也需要5个维度的输入。如果你懒也可以单独对温度列fit一个一维scaler做逆变换更省事。我那次跑出来的结果是RMSE大约1.8度MAE约1.3度R²约0.94。对于小时级温度预测来说这个误差水平已算不错。不过指标好不好要看场景如果只是预测大概趋势2度误差可以接受如果要精确到空调系统控制的层面1.8度就还得继续优化。5.2 预测曲线可视化注意“横坐标太密集”的老大难画预测值vs真实值的曲线时我踩过一个特别常见的问题测试集如果有几千条数据横坐标标签全挤成一团什么也看不清。搜“python画图横坐标太密集”能看到一堆人问解法其实很简单用plt.xticks隔一段时间显示一个刻度或者直接只画测试集的一部分。import matplotlib.pyplot as plt plt.figure(figsize(14, 5)) plt.plot(y_test_inv[:200], labelTrue, linewidth1.5) plt.plot(y_pred_inv[:200], labelPred, linewidth1.5, linestyle--) plt.xticks(rotation45) plt.legend() plt.show()我习惯只画前200个点这样能看清细节想看全局就把[:200]去掉。另外rotation45可以旋转刻度标签比默认横排好看很多。如果想看误差分布可以画误差直方图直观看到预测值多数集中在哪些偏差范围。5.3 效果分析什么时候预测得好什么时候系统误差大从图上观察有几个规律很有意思平稳天气时段连续几天温度波动小预测误差很小曲线几乎贴合。突变天气比如冷空气来袭、短时间内温度骤降误差明显变大模型反应滞后。每天的最高温、最低温出现时刻附近预测值容易偏低或偏高1到2度。这背后是LSTM的特性它本质上是学一个“大概率延续历史趋势”的函数遇到未出现过的剧烈模式外推能力天然有限。这不是bug是所有统计模型共有的天花板。所以预测任务里有个心法评估模型时要分场景讨论不要总指望一个模型在所有天气状况下都完美。6. 踩坑实录从入门到入坑的十个典型问题这章算是全篇最“值钱”的部分。我把整个项目里遇到过的、和网友交流时高频出现的问题整理成了一张速查表再挑几个重点展开讲。症状原因解决办法训练loss不下降归一化没做或数据范围差异过大检查是否做了MinMaxScaler检查学习率val_loss先降后升过拟合增大Dropout、减少网络层数、加EarlyStopping预测曲线整体滞后一天窗口太短或特征不足增大seq_length尝试加入更多天气变量测试集指标奇高数据泄露检查scaler是否只fit训练集检查是否随机打乱了时序输入维度报错输入shape不对打印X.shape确认是(samples, timesteps, features)反向传播梯度爆炸学习率太高或网络太深调低学习率或加梯度裁剪训练极慢数据量大或batch过大减小batch_size用GPU训练LSTM输出全是同一个值模型欠拟合增加网络层数/神经元数增加训练轮数画图横坐标重复拥挤刻度标签太多用plt.xticks隔N个点显示或rotation45模型在真实新数据上失效训练数据分布与真实场景分布不一致重新采集训练数据或做增量学习6.1 归一化顺序出错数据泄露的经典案例有个朋友拿着几乎一样的代码来找我说他的测试集RMSE只有0.3度简直完美。我让他把数据切分的代码发过来发现他是先对整个数据集做了fit_transform然后再切分。这样测试集的信息在训练时已经被模型间接“见过”了所以测试指标才那么好看。这不是模型强是数据泄了题。我建议他用严格按时间顺序切分、只用训练集fit一遍的方法重跑他的RMSE立刻回到1.9度左右。这个数据泄露的问题算是时间序列预测里最隐蔽也最影响判断的坑。6.2 “预测曲线滞后”是怎么回事另一个常见现象是预测曲线看着和真实曲线形状几乎一样但整体向右平移了一截仿佛模型只是把昨天的温度搬到了今天。这是预测模型的“返祖现象”当模型学不到有效规律时它最稳妥的做法就是输出上一个时刻的观测值因为温度相邻时刻确实高度相关这样loss也不会太差。解决滞后问题没有银弹但有几个方向值得尝试增大输入窗口长度让模型看到更长的历史过程加入更多有预测力的特征比如气压变化率、露点温度如果你预测的是未来多步考虑用seq2seq结构Encoder-Decoder而不是一步到位。6.3 训练时间长到怀疑人生怎么办如果不加EarlyStopping我那个模型100个epoch跑完大概要20多分钟看起来不算太久但每次调参都要重跑一遍累积起来就非常熬人。优化的思路有三个在代码开头设置随机种子保证每次跑的结果可复现调参才有意义先把数据切成小批量跑5个epoch快速验证代码逻辑确认没问题再全量训练用EarlyStopping验证集loss不降就停省掉大量无效训练时间。6.4 随机种子想复现结果就别省这一步说到随机种子这也是一个很容易被忽视的点。深度学习模型初始化权重、数据加载顺序都带随机性两次跑的loss曲线可能完全不同。为了让实验可对比、可复现我习惯在代码开头写import numpy as np import tensorflow as tf import random np.random.seed(42) tf.random.set_seed(42) random.seed(42)这样至少保证在同一环境、同一数据下每次训练的结果是确定的。想跟别人交流调参心得时这一步非常关键不然你说“我调了学习率效果变好了”对方没法复现验证就很难判断到底是学习率起了作用还是随机性造成的假象。7. 后续还能怎么扩展我做完这个项目之后顺手试了几个方向的扩展简单说一下思路把“预测未来1小时”改成“预测未来24小时的温度曲线”这需要把输出层改成24个神经元并且训练标签也要改成未来24个时刻的温度序列。模型结构会变成序列到序列的预测复杂度提升但对实际场景更有用。尝试把LSTM和注意力机制结合。LSTM擅长捕捉顺序但长序列里有些关键时间点比如寒潮开始的那一刻会淹没在大量普通时刻里。注意力机制可以让模型自动聚焦到这些关键点上。换Transformer。很多热词里都在提Transformer它确实能建模长距离依赖但在气象这种中小规模时序任务上不一定比LSTM好多少而且数据量和训练成本要求更高。如果你时间充裕可以两种都试对比一下再下结论。这些扩展方向都不是必须做的但确实能帮你把LSTM吃得更透。根据我个人经验做这类项目最忌“一口气吃成大胖子”先把一条链路完整跑通再考虑花式扩展反而效率更高。你要是卡在某个环节回头看看上面那十类常见问题大多数情况都能对号入座。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →