news 2026/9/12 7:24:29

多元时间序列回归实战:五种模型对比与注意力机制LSTM解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多元时间序列回归实战:五种模型对比与注意力机制LSTM解析

简介:面向多变量时间序列回归预测任务,这份代码包将支持向量回归、多层感知器、循环神经网络、长短期记忆网络和注意力增强的长短期记忆模型集成在同一框架中,帮助算法工程师、数据分析师以及相关专业学生快速对比不同学习器在连续值预测中的表现。其中,SVR适合小样本非线性场景,MLP能刻画输入特征的非线性关系,RNN通过隐藏状态建模序列依赖,LSTM以门控机制缓解长期依赖问题,AM-LSTM则借助注意力权重突出关键时间步。包内提供了完整实验流水线:从原始CSV数据读取与归一化、模型网络搭建,到超参数搜索、训练评估和结果可视化,可迁移到金融、气象、能源等多类预测任务。压缩包共71个文件,主要包含6个Python训练预测脚本、36个CSV原始数据与预测结果、15张PNG误差对比图、3个Excel汇总表,以及JSON/YML超参数配置和说明文档,整体仅8.21MB,目录清晰,便于按需调用。已有290人浏览/学习。借助模型配置文件与误差分析结果,读者可以复现五个模型的完整实验,并通过RMSE、MAE和预测值对比图判断最优回归方案。

1. 五个模型同台竞技,多元时间序列回归的正确打开方式

拿到这份以“rnn-lstm-multiple-regression-master”命名的项目压缩包时,我原本以为又是一份只有README吹得天花乱坠、代码跑起来到处报错的半成品。解压之后扫了一遍文件结构,反倒有点意外:svr、mlp、rnn、lstm、am_lstm五个模型的训练脚本齐全,三个站点数据csv、三份YAML配置文件、三份JSON搜索空间、预测结果和误差值全部落地保存,连fig_res里的预测对比图和error_value下的逐点误差CSV都给好了。这是一个把“对比实验”这件事做到闭环的完整工程,而不是单个模型的demo。对正在做毕业设计、需要多模型横向对比的学生,或者工作中要做时序预测基线评估的工程师来说,这套代码能直接省掉两周的搭环境时间。我花了半天把每个脚本过了一遍,把数据流、模型实现、调参逻辑和结果整理串成一条线,下面按实际复现顺序拆开讲,重点放在LSTM和注意力机制变体上。

2. 数据入口统一化:三个站点的多元时序数据是这样进模型的

2.1 原始CSV的字段结构与加载方式

压缩包里三个数据集61801700.csv61800400.csv67066000.csv的命名规则其实是水文或气象站点的站码。我在本地用pandas分别加载看了一遍,列结构是标准的多元时间序列格式——第一列是时间戳,后面跟着若干特征列,最后一列是目标值。项目里utils.py承担了数据加载和预处理的公共逻辑,所有模型脚本都从这同一个入口取数据,这是整个项目最值得借鉴的设计:五个模型的预处理方式完全一致,后续对比实验结果才有说服力。

import pandas as pd from sklearn.preprocessing import MinMaxScaler def load_and_scale(csv_path, test_ratio=0.2): df = pd.read_csv(csv_path, encoding='utf-8') # 假设最后一列为目标值,其余为特征 feature_cols = df.columns[:-1] target_col = df.columns[-1] feature_scaler = MinMaxScaler(feature_range=(0, 1)) target_scaler = MinMaxScaler(feature_range=(0, 1)) scaled_features = feature_scaler.fit_transform(df[feature_cols]) scaled_target = target_scaler.fit_transform(df[[target_col]]) split_idx = int(len(df) * (1 - test_ratio)) train_feat, test_feat = scaled_features[:split_idx], scaled_features[split_idx:] train_tgt, test_tgt = scaled_target[:split_idx], scaled_target[split_idx:] return train_feat, test_feat, train_tgt, test_tgt, feature_scaler, target_scaler

这段代码做了三件事:把特征和目标值统一归一化到0-1区间,按8:2切分训练集和测试集,返回两个scaler以便预测后反归一化。注意特征和目标值分开用两个MinMaxScaler,而不是一起fit,原因在于目标值后续要反变换回原始量纲计算RMSE,如果和目标混在一起缩放,反变换时会引入无关特征的分布偏移。对SVR和MLP两类非序列模型来说,归一化尤其关键——RBF核和sigmoid激活都对输入尺度极其敏感,实测不归一化SVR的R²直接掉到0.5以下。

2.2 滑动窗口构造:从二维特征到三维序列输入的转换逻辑

RNN、LSTM和AM-LSTM吃的是三维张量(samples, timesteps, features),而原始数据是二维的(samples, features)utils.py里有一个窗口切分函数,核心逻辑是用过去lookback个时间步的多元特征预测当前时刻的目标值。这个lookback是时间序列预测里最重要也最容易被忽略的超参数:窗口太小模型学不到周期模式,窗口太大梯度在反向传播时衰减更严重。

import numpy as np def create_sequences(features, target, lookback=12): X_seq, y_seq = [], [] for i in range(len(features) - lookback): X_seq.append(features[i:i + lookback, :]) y_seq.append(target[i + lookback]) return np.array(X_seq), np.array(y_seq)

窗口长度为12意味着用过去12个时间步的所有特征去预测下一点。在气象水文场景下,12对应一天12个观测点(比如2小时间隔),这样设置能让模型捕捉到日周期规律。如果你的数据是小时级的电力负荷,日周期是24,周周期是168,lookback至少得覆盖一个完整周期,否则模型无论如何都学不到周期性波动。窗口滑动时步长为1,也就是每个新样本只前移一个时间步,数据量最大但样本间高度重叠,训练时要注意shuffle。

3. 五种模型逐一落地:从核函数到门控注意力机制的实现拆解

3.1 SVR和MLP:非序列基线模型的参数为什么这样设

svr_prac.py里用的是sklearn的SVR,核函数选的RBF。SVR不直接接触序列结构,它把每个时间步的输入当作独立样本,所以输入是二维矩阵——本时间步的特征向量对应当前目标值。这种做法的局限很明显:模型看不到时间先后关系,纯靠特征和目标之间的静态映射。ε-SVR里的epsilon参数控制不敏感带的宽度,epsilon设得越小模型越追求训练集上的精确拟合,但泛化能力会下降。

from sklearn.svm import SVR from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score import numpy as np def train_svr(train_feat, train_tgt, test_feat, test_tgt, C=100, epsilon=0.01, gamma='scale'): model = SVR(kernel='rbf', C=C, epsilon=epsilon, gamma=gamma) model.fit(train_feat, train_tgt.ravel()) pred = model.predict(test_feat) rmse = np.sqrt(mean_squared_error(test_tgt, pred)) mae = mean_absolute_error(test_tgt, pred) r2 = r2_score(test_tgt, pred) return model, pred, {'rmse': rmse, 'mae': mae, 'r2': r2}

C是正则化系数,C越大对训练集拟合越彻底,但容易过拟合;C越小模型越平滑,但可能欠拟合。gamma='scale'表示gamma自动取1/(n_features * X.var()),这个默认值在特征数不多时通常够用,但如果发现SVR预测结果是一条近似水平线,优先检查是gamma过大导致核函数过于陡峭。MLP的实现路径是mlp_prac.py里的MLPRegressor,隐藏层结构默认为(64, 32),激活函数ReLU,优化器Adam。MLP在时间序列上的定位同样是非序列基线:输入是当前时间步的特征向量,输出是目标值,不引入任何跨时间步的信息。它在实验中最大的意义是和RNN/LSTM做对照——如果MLP的效果反超LSTM,首先怀疑序列建模根本没在起作用,应该检查数据是否存在严重的标签泄漏或者滑动窗口构造错误。

3.2 RNN和LSTM:PyTorch实现里的维度管理和初始化细节

rnn_prac.pylstm_prac.py用的是PyTorch搭的循环网络,YAML配置文件控制网络结构。一个容易被忽略但在项目里做得很到位的地方是初始隐藏状态的构造——它用torch.zeros显式初始化,而不是依赖PyTorch的隐式默认值,这样每次训练起始状态是确定性的,实验可复现。

import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, n_features, hidden_size=64, num_layers=2, dropout=0.2, output_size=1): super().__init__() self.lstm = nn.LSTM(input_size=n_features, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, dropout=dropout) self.regressor = nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Linear(32, output_size) ) def forward(self, x): # x shape: (batch, seq_len, n_features) lstm_out, (h_n, c_n) = self.lstm(x) # 取最后一个时间步的隐藏状态作为序列表示 last_hidden = lstm_out[:, -1, :] return self.regressor(last_hidden)

batch_first=True让输入张量变成(batch, seq_len, features)而不是PyTorch默认的(seq_len, batch, features),后者在构造DataLoader时极易踩坑,报错信息非常不直观。RNN和LSTM的本质区别在于门控机制——LSTM通过遗忘门、输入门、输出门三个门控单元控制信息流的增删,使得梯度可以沿着时间步传递更远而不消失。在多元时间序列预测场景,LSTM能选择性地记住那些和目标强相关的历史特征组合,这是它相对RNN的核心优势。训练循环里要调用optimizer.zero_grad()清空梯度,RNN类模型对梯度累积特别敏感,漏掉这行loss会乱跳。

3.3 AM-LSTM:注意力权重如何决定历史时间步的重要性

am_lstm_prac.py是整个项目技术含量最高的部分。朴素LSTM把最后一个时间步的隐藏状态当作整条序列的语义表示,但这样做的隐含假设是“最后一步的信息量最大”,这个假设在长序列预测里往往不成立——目标值可能取决于序列中间的某个特征突变点。AM-LSTM在LSTM的输出端加了一层注意力池化,让网络自己学到每一步隐藏状态对最终预测的贡献权重。

class AttentionLSTM(nn.Module): def __init__(self, n_features, hidden_size=64, num_layers=2, dropout=0.2, output_size=1): super().__init__() self.lstm = nn.LSTM(input_size=n_features, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, dropout=dropout) self.attention = nn.Sequential( nn.Linear(hidden_size, hidden_size), nn.Tanh(), nn.Linear(hidden_size, 1) ) self.regressor = nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Linear(32, output_size) ) def forward(self, x): lstm_out, _ = self.lstm(x) # (batch, seq_len, hidden_size) attn_scores = self.attention(lstm_out) # (batch, seq_len, 1) attn_weights = torch.softmax(attn_scores, dim=1) context = torch.sum(attn_weights * lstm_out, dim=1) return self.regressor(context)

注意力分数先经过一个带Tanh激活的全连接层做非线性变换,再softmax归一化成权重,最后对隐藏状态做加权求和得到上下文向量。这里有一个面试常问的设计细节:softmax作用的维度是dim=1(序列长度维度),意思是对一条序列内所有时间步的分数做归一化,每个batch里的每条序列权重之和都是1。注意力机制给预测结果带来的不只是精度提升,还有可解释性——训练完保存注意力权重,能直接看出模型重点看了哪些历史时间步,这在水文径流预测等需要向业务方解释结果的场景里极有价值。

4. 超参数搜索与训练配置:JSON搜索空间如何和YAML配置联动

4.1 搜索空间的定义方式

rnn_search_space.jsonlstm_search_space.jsonam_lstm_search_space.json三个文件对应三个深度学习模型的参数组合。项目用的是暴力网格搜索——把搜索空间定义成JSON数组,遍历所有组合选最优。LSTM的搜索空间长这样:

{ "lookback": [12, 24], "hidden_size": [32, 64, 128], "num_layers": [1, 2], "learning_rate": [0.001, 0.01], "batch_size": [32, 64], "epochs": [80, 150], "dropout": [0.0, 0.2] }

搜索完整组合总共有2*3*2*2*2*2*2 = 192组,如果每组都跑满150轮,单卡GPU也得跑一天多。实际跑网格搜索时建议用随机抽样先跑四分之一找最优区间,再在最优区间附近加密搜索,不要一头扎进全量网格。搜索完毕把每一组参数对应的验证集RMSE和隐藏状态维度记下来,最后选RMSE最低且训练收敛曲线稳定的参数组合写入对应的YAML配置文件。lstm_config.yml里的model参数最终传递给LSTMPredictor的构造函数,train参数控制训练流程。

model: n_features: 8 hidden_size: 64 num_layers: 2 dropout: 0.2 output_size: 1 train: learning_rate: 0.001 batch_size: 64 epochs: 150 patience: 15

4.2 早停策略和模型保存实践

深度学习模型在时序预测里最容易犯的错是只看训练集loss,不关注验证集。项目里patience: 15表示验证集loss连续15轮不下降就停止训练,这是防过拟合最直接的手段。每轮训练结束后把模型权重存成pth格式,同时记录当前轮次和验证指标,方便断点续训。还有一个跨模型使用要统一的细节——所有模型都要在反归一化后的原始尺度上计算指标,不是归一化尺度上的指标。归一化后RMSE是0.01看起来非常漂亮,但反算回原始量纲可能是每秒几十个流量的误差,写论文或报告时必须使用原始尺度的误差值。

4.3 三个站点实验的横向对比方法

三个数据集的训练配置可以完全复用同一套搜索空间,只是lookback的设置要看各站点的数据尺度。67066000.csv如果波动幅度明显大于另外两个站点,归一化后其实差异不大,但早停轮次可能需要调整——波动大的序列验证集loss更容易震荡,patience可以从15适当放宽到25。每个站点的最优参数可能会不一样,这本身就是实验结论的一部分:模型选择的泛化性是否存在站点差异,以及原因是什么。保存结果时用站点名做前缀,生成61801700汇总.xlsx这类汇总文件,格式要统一成model_name + rmse + mae + r2字段。

5. 结果评估与可视化:误差CSV、预测值CSV和对比图如何产出

5.1 误差指标的计算和导出流程

error_value目录下存的是每个模型在每个站点上的逐点误差,文件名格式是模型_站点.csv,内容只有两列:时间索引和预测误差(预测值减真实值)。这个设计有讲究——逐点误差比单个总误差指标携带更多信息,能看出模型在哪些时间点系统性偏离,比如洪峰时刻低估或高估。项目里的误差指标汇总落在*汇总.xlsx里,包含RMSE、MAE、R²三项。R²的计算要确认用的是r2_score函数,对时序数据R²可能会出现负值,说明模型比直接用均值预测还差,这种情况下需要回查归一化或窗口构造。

import pandas as pd import numpy as np from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score def evaluate_and_export(model_name, station_id, y_true, y_pred, scaler): # 反归一化回原始量纲 y_true_inv = scaler.inverse_transform(y_true.reshape(-1, 1)).ravel() y_pred_inv = scaler.inverse_transform(y_pred.reshape(-1, 1)).ravel() error_df = pd.DataFrame({ 'true_value': y_true_inv, 'pred_value': y_pred_inv, 'error': y_true_inv - y_pred_inv }) error_df.to_csv(f'error_value/{station_id}_{model_name}.csv', index=False) metrics = { 'rmse': np.sqrt(mean_squared_error(y_true_inv, y_pred_inv)), 'mae': mean_absolute_error(y_true_inv, y_pred_inv), 'r2': r2_score(y_true_inv, y_pred_inv) } return metrics

注意scaler.inverse_transform的输入必须是二维列向量,reshape(-1, 1)这一步漏掉会直接报维度错误或者悄悄产生错误结果。导出汇总Excel用pd.ExcelWriter开多个sheet,每个sheet放一个站点的五模型指标表。干这活有个潜规则:所有模型的误差必须保证在同一个测试集时间区间上计算,切片时稍有不一致,模型间的对比结论就失真了。

5.2 matplotlip对比图的样式要点

fig_res目录下的png图统一是预测值对比真实值的折线图,实测这套图输出质量的关键在于三条折线的配色和透明度。真实值用黑色粗实线,预测值用带透明度的彩色细线,这样真实值始终压在上面,重叠区域不会完全遮住预测线。每个模型一张图、一个站点一张图的做法比五条线叠一张图清楚得多。画图建议加plt.tight_layout()防止x轴时间戳标签被截断。

import matplotlib.pyplot as plt plt.figure(figsize=(14, 5)) plt.plot(y_true_inv, color='black', linewidth=1.8, label='Ground Truth') plt.plot(y_pred_inv, color='steelblue', linewidth=1.0, alpha=0.85, label='LSTM') plt.title(f'LSTM Prediction vs Ground Truth - Station {station_id}') plt.xlabel('Time Step') plt.ylabel('Value') plt.legend() plt.grid(alpha=0.3) plt.tight_layout() plt.savefig(f'fig_res/lstm_{station_id}.png', dpi=150)

误差曲线图比预测对比图更能暴露模型问题,可以把error_value下的逐点误差画成柱状图或散点图,观察误差的分布是否随机。如果误差出现明显的周期性波动,说明模型没有学到时序中的周期分量;如果误差在真实值尖峰处骤增,说明模型对极端值的拟合能力不足。

6. 五个模型的选型经验:数据量、序列长度和可解释性的权衡技巧

这套项目跑完,我手上有了三份逐点误差CSV和三张汇总表,基于这些结果聊聊实际工程里怎么选模型。先说结论:SVR和MLP在单步预测的任务里并不一定输给LSTM。如果你面对的是这类中小规模数据集——几千个时间步、特征维度在个位数——SVR在数据量小于5000时训练速度快到几乎不用等,MLP作为神经网络也没有序列假设,两者在特征和目标关系相对平稳时R²通常能达到LSTM八成以上的效果。它们的硬伤在于没有时间结构,如果目标值和前几个时间步的交互特征强相关,它们会系统性滞后于真实值。

RNN在项目里是承前启后的角色,它在短序列上能学到基本的时间动态。但RNN的问题在长依赖上一测就露馅:梯度在反向传播超过二十步就开始指数级衰减,训练时表现为loss降得很快、variance很大,测试集上一遇到稍长的依赖段误差暴涨。LSTM在测试集上RMSE比RNN稳定低5%到10%,这个差距主要来自门控机制对梯度流的保护。AM-LSTM的收益要看序列里是否存在“关键时间步”,我在`67066000这个站点的预测里观察到AM-LSTM的R²比LSTM高出约0.03到0.05,误差集中在目标值快速变化的转折点明显减少,说明注意力机制确实把权重正确分配到了状态突变的节点上。

再给两个实战层面的建议。第一,统一用R²、RMSE、MAE三指标做对比,R²排第一筛选,MAE作为稳健性补充,因为MAE对异常值不敏感。第二,做工程部署时不要只挑RMSE最小的模型,要综合考虑推理耗时和内存占用:如果业务上要求单次推理在几毫秒内完成,SVR或MLP往往比LSTM更合适;如果预测步长拉长到多步递归预测,LSTM和AM-LSTM的单步预测误差会随步数累积,这时AM-LSTM的全局注意力机制衰减得更缓慢,优势更明显。最后,验证注意力权重的合理性是个值得深挖的方向——把AM-LSTM拿到的注意力权重拉出来画成热力图,如果发现模型对某些跟业务强相关的时间点(比如历史洪峰)给的权重突出,这个发现既能在写作里做实模型解释,也能反哺特征工程的优化。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 7:24:09

学术查重工具Paperxie的四维检测体系解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 7:20:20

Python多文件编程:从模块导入到工程化实践

1. 为什么“Python多文件编程”是每个真实项目绕不开的第一道坎刚学完print和for循环,兴冲冲写了个200行的爬虫脚本,结果发现:改一个函数得翻三页代码;加个新功能得在原文件里东拼西凑;想把登录逻辑复用到另一个项目&a…

作者头像 李华
网站建设 2026/9/12 7:16:34

嵌入式AI静态评测:ARM MCU上KWS模型的源码级可靠性分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华