TCN-Transformer+LSTM双输入时间序列预测的MATLAB实现
2026/9/20 4:40:44 网站建设 项目流程

简介:一份基于MATLAB的TCN-Transformer+LSTM双输入神经网络时间序列预测项目实例,核心面向具备一定深度学习基础、希望解决复杂时序预测难题的开发者、科研人员与研究生。资源为1个docx文档,压缩包仅57KB,虽体量不大,但内容完整覆盖项目设计、实现与部署全链路:从环境配置、数据预处理,到TCN、Transformer与LSTM多模型融合搭建、模型训练,再到结果评估与可视化,并配有GUI设计思路和逐段代码详解。针对金融、气象、能源等行业的长时间依赖与非线性特征,文档详细讲解了融合多头自注意力、膨胀卷积和循环门控的建模策略,同时给出防止过拟合、超参数调优以及引入GAN或强化学习实现模型扩展的进阶建议,帮助读者举一反三。目前已有81人学习下载,适合想深入理解混合深度学习模型并快速落地应用的工程与研究人员参考。

1. TCN-Transformer+LSTM双输入架构:时间序列预测的融合解法

很多时序预测项目做到后半程会发现,单模型的天花板很明显:LSTM能记忆长期状态,但对局部突变不敏感;TCN靠膨胀卷积可以拉大感受野,却缺少全局注意力;Transformer把注意力铺满序列,计算开销又高。于是就有了把三者串并联的融合思路。这个项目给出的是一套完整的MATLAB实现,TCN和Transformer处理主序列,LSTM分支处理辅助特征序列,双输入融合后经全连接层输出预测值。它解决的是一类实际问题,即同时捕捉长时间依赖、局部模式与外部特征之间的联合影响,适合手里有中等规模数据、想直接落地跑通的开发者和研究生。整个链条涉及数据窗口化、模型装配、训练评估和GUI交互,下面按实操顺序逐块拆开。

2. 数据入口与序列构造:窗口怎么切、维度怎么对齐

融合模型的收益首先取决于样本怎么构造。窗口长度、预测步长、归一化方式直接决定模型能看到什么,这一章把数据侧的关键步骤拆开讲清楚,尤其是双输入的数据格式如何组织。

2.1 数据预处理:归一化与异常值处理

不管用TCN还是LSTM,输入数据先做归一化几乎是必须的。Transformer的注意力机制依赖点积计算,特征尺度差异过大会让注意力权重被大数值特征主导;LSTM的遗忘门和输入门同样受输入尺度影响。常见做法是用mapminmax把数据缩放到[0,1]或[-1,1],同时保留ps结构体用于预测结束后反归一化:

% 读取原始序列,假设数据为列向量 dataRaw dataRaw = load('series.mat').data; % 或 readmatrix('data.xlsx') % 归一化到 [0,1],ps 保存归一化参数供反变换使用 [dataNorm, ps] = mapminmax(dataRaw', 0, 1); dataNorm = dataNorm';

mapminmax是按行计算的,所以输入数据先转成行向量,归一化完成后再转回列向量。ps里保存了xmin、xmax、ymin、ymax等参数,测试集预测完成后用同一组ps做反归一化,才能回到原始量纲。特别要注意的是ps只能由训练集拟合,如果先用全量数据算min/max再切分,验证集和测试集的信息就已经泄漏进了训练过程,后续所有评估指标都会偏乐观。异常值处理上,我一般先用isoutlier配合movmedian做滑动中位数检测,把超过三倍MAD的点替换成邻域中位数,避免个别坏点把归一化边界拉偏,这一步对金融和传感器数据尤其重要,因为TCN的卷积核会同时扫过异常点和正常点,异常值带来的局部梯度容易被当成有效时序模式。缺失值则用fillmissing的'spline'插值补齐,线性插值会破坏序列原有的波动形态。

2.2 滑动窗口构建:从一维序列到监督样本

时间序列预测的第一步是把序列变成(X, Y)监督样本。窗口长度windowSize表示用过去多少个点预测未来horizon步的值,窗口越长模型能看到的上下文越完整,但样本数会减少,训练负担也变大。下面的函数把一维数据切分成dlnetwork训练用的四维数组:

function [XMain, XAux, Y] = createSamples(dataNorm, windowSize, horizon, diffData) numSamples = length(dataNorm) - windowSize - horizon + 1; % dlnetwork 输入约定:[特征维度, 时间步, 通道数, 样本数] XMain = zeros(1, windowSize, 1, numSamples); Y = zeros(1, 1, 1, numSamples); XAux = zeros(1, windowSize, 1, numSamples); for i = 1:numSamples XMain(1, :, 1, i) = dataNorm(i:i+windowSize-1)'; % 差分序列作为 LSTM 分支的辅助输入 XAux(1, :, 1, i) = diffData(i:i+windowSize-1)'; Y(1, 1, 1, i) = dataNorm(i+windowSize+horizon-1); end end

这段代码把原始窗口和差分窗口组织成两路输入。差分序列提供一阶变化信息,与原始水平值互补,比如原始序列在一段时间内趋势平缓但波动加剧,差分值能反映出这种局部变化,而水平值本身看不出差异。四维数组的维度顺序是MATLAB深度学习工具箱的标准约定,第一维特征数、第二维时间步、第三维通道数、第四维样本数,后续卷积核沿第二维做时间方向滑动。窗口大小与样本数的关系可以这样估算:1000个数据点,windowSize取48、horizon取1时,样本数为1000-48-1+1=952个,配合batch size 32,每轮约29个迭代步,训练80轮的总迭代量在2300次左右,中等配置的GPU几分钟内可以跑完。

窗口长度预测步长生成样本数(N=1000)适用场景
241976小时级、日级短期预测
481952跨天模式明显的数据
723926需要远期预测的对齐窗口

窗口越长样本越少,两者需要根据数据量平衡。若样本量不足,可以用重叠方式取窗,但重叠会让相邻样本强相关,做验证集划分时要注意不能与训练集窗口重叠,否则验证集等于变相看到了训练数据。

2.3 训练集与测试集划分的时间顺序问题

划分数据时不能随机打乱。时间序列样本之间有强自相关性,随机划分会造成数据泄漏,验证集里混入训练集相邻时间段的样本,评估指标会虚高。正确做法是按时间顺序切分,前70%做训练、中间15%做验证、最后15%做测试,验证集用于early stopping和调参,测试集只在最终评估时碰一次:

% 按时间顺序切分,不要用 cvpartition 或 randperm numTotal = size(XMain, 4); numTrain = floor(numTotal * 0.7); numVal = floor(numTotal * 0.15); idxTrain = 1:numTrain; idxVal = numTrain+1:numTrain+numVal; idxTest = numTrain+numVal+1:numTotal;

idxTest落在时间轴末端,代表真实预测场景下最接近当前时刻的数据。如果数据存在概念漂移,训练集与测试集分布差异较大,可以考虑在测试集上做滑动回测,每次只预测一个点,然后把预测值加入历史窗口继续滚动,这样更接近线上预测的实际情况,也能减少一次性长段预测带来的滞后误差。

3. TCN与Transformer、LSTM如何搭成可训练网络

模型核心在这一章。双输入融合架构里,三条路径分别承担不同类型特征提取,最后用拼接完成融合。这部分的难点不在单个层怎么选,而在各层输出形状如何对齐。

3.1 三条路径的分工与信息流

TCN路径负责局部因果建模,用膨胀卷积按时间顺序扫描,每个输出位置只能看到当前及之前的输入,不引入未来信息;Transformer路径对TCN输出做全局自注意力,计算任意两个时间步之间的关联权重;LSTM路径接收第二路输入,比如差分序列或外部协变量,保留长期记忆状态。最后把Transformer输出与LSTM输出拼接,经全连接层得到预测值。

这个信息流的合理性在于:TCN把原始序列编码成更高层的局部特征,相当于先做了一次特征压缩,让后续注意力计算聚焦在更有语义的表示上;Transformer补全了TCN感受野之外的远距离依赖;LSTM则独立处理另一路特征,避免辅助信息被主序列的卷积池化过程稀释。三条路径各管一段,融合后对序列形态变化的鲁棒性高于任何单一模型。

3.2 TCN分支的实现细节

TCN的核心是因果膨胀卷积。普通卷积在计算时会看到未来数据,因果卷积通过左侧补零保证t时刻的输出只依赖t及以前的输入。膨胀系数d让卷积核在时间轴上隔d个点取值,感受野随层数指数扩大,计算公式为:

receptiveField = 1 + sum((kernelSize - 1) * dilation_i)

kernelSize为卷积核大小,dilation_i是第i层的膨胀系数。常见配置是kernelSize取3,膨胀系数按1、2、4、8递增,四层后感受野覆盖约30个时间步。MATLAB里用convolution1dLayer实现:

tcnsBlock = [ convolution1dLayer(3, 32, 'Padding', 'causal', 'DilationFactor', 1, 'Name', 'tcn_conv1') layerNormalizationLayer('Name', 'tcn_norm1') reluLayer('Name', 'tcn_relu1') convolution1dLayer(3, 32, 'Padding', 'causal', 'DilationFactor', 2, 'Name', 'tcn_conv2') layerNormalizationLayer('Name', 'tcn_norm2') reluLayer('Name', 'tcn_relu2') ];

convolution1dLayer第一个参数3是卷积核大小,第二个参数32是输出通道数,Padding设为'causal'保证因果性,DilationFactor控制膨胀系数。每层卷积后接layerNormalization和ReLU,层归一化在batch size较小时比batch normalization更稳定,因为BN在小批量上的均值和方差估计波动大,而LN只依赖当前样本自身统计量。需要注意'causal'选项在R2021a及以后版本可用,老版本需要手动在序列左侧补d*(k-1)个零达到相同效果。

参数取值示例作用
FilterSize3卷积核覆盖的时间步数,越大感受野越大
NumFilters32输出特征通道数,决定TCN输出维度
DilationFactor1,2,4,8逐层膨胀,控制感受野扩张速度
Paddingcausal左侧补零,不泄露未来信息

3.3 Transformer编码器:自注意力怎么接在TCN后面

Transformer段负责全局依赖建模。TCN输出的每个时间步是一个长度为32的特征向量,把这些向量按时间顺序组织成序列送入自注意力层,计算任意两个时间步之间的关联权重。MATLAB从R2023b开始提供transformerLayer,可以直接使用,参数名称在不同版本略有差异:

% R2023b及以上版本可用 transformerLayer,参数以当前版本文档为准 transformerBlock = [ transformerLayer(4, 32, 'PreLayerNorm', true, 'Name', 'transformer_enc') ];

第一个参数4是注意力头数,第二个参数32是特征维度,需要与TCN输出通道数保持一致。注意力头数为4意味着把特征分成4个子空间并行计算注意力,每个子空间关注不同的时序依赖模式。PreLayerNorm设为true表示在注意力计算前先做层归一化,这是Transformer训练中常用的做法,能缓解深层网络训练初期的梯度不稳定问题。如果不支持这个选项,去掉即可。较老版本没有transformerLayer时,可以用attentionLayer自建多头注意力:先用三个全连接层把特征投影为query、key、value,再调用attentionLayer计算加权和,最后拼接多头结果过一层全连接,效果等价但代码量会多出几十行。

Transformer输出保持序列长度不变,每个时间步仍对应一个特征向量。到这一步,主序列路径已经拿到了同时包含局部模式与全局依赖的表示,接下来要与LSTM分支的辅助特征融合。

3.4 LSTM分支与融合层

LSTM分支接收第二路输入,即2.2节构造的差分序列窗口,也可以是滚动均值、外部协变量等多维特征。LSTM的隐藏单元数需要和主路径输出维度搭配,通常取32到128之间,过大容易过拟合,过小则记不住长程依赖。这里设置64,并只输出最后一个时间步的隐藏状态:

lstmBranch = [ lstmLayer(64, 'OutputMode', 'last', 'Name', 'lstm_branch') dropoutLayer(0.2, 'Name', 'lstm_dropout') ];

OutputMode设为'last'后,LSTM输出形状是[64, 1, 1, N],与Transformer路径的[32, T, 1, N]在时间维上不一致,直接拼接会报维度错误。常见做法是先对Transformer输出做全局平均池化,把时间维压缩成1,得到[32, 1, 1, N]的向量,再与LSTM输出拼接成[96, 1, 1, N]。如果保留Transformer全部时间步也可以,但拼接后维度会变成[96, T, 1, N],需要再接flatten层或池化层降维,计算量和过拟合风险都会增加。融合部分用深度学习工具箱的深度拼接层:

fusionLayers = [ globalAveragePooling1dLayer('Name', 'transformer_gap') concatenationLayer(1, 2, 'Name', 'fusion_cat') fullyConnectedLayer(32, 'Name', 'fc1') reluLayer('Name', 'fc_relu') dropoutLayer(0.3, 'Name', 'fc_dropout') fullyConnectedLayer(1, 'Name', 'fc_out') regressionLayer('Name', 'output') ];

concatenationLayer(1, 2)表示在第一维即特征维拼接两个输入,一个来自transformer_gap,一个来自lstm_dropout。globalAveragePooling1dLayer沿时间维做平均,输出形状变成[32, 1, 1, N]与LSTM对齐。全连接层先压到32维再做二次映射,避免高维特征直接映射到1维输出时出现过拟合。

3.5 完整网络组装与双输入绑定

上面这些层必须通过layerGraph组合起来,再用dlnetwork封装成可训练网络。装配的核心是把两个输入层分别绑定到TCN分支和LSTM分支,再把各分支末端连到融合层:

lgraph = layerGraph(); % 主序列输入 -> TCN -> Transformer lgraph = addLayers(lgraph, sequenceInputLayer(1, 'Name', 'input_main')); lgraph = addLayers(lgraph, tcnsBlock); lgraph = addLayers(lgraph, transformerBlock); % 辅助序列输入 -> LSTM lgraph = addLayers(lgraph, sequenceInputLayer(1, 'Name', 'input_aux')); lgraph = addLayers(lgraph, lstmBranch); % 融合与输出 lgraph = addLayers(lgraph, fusionLayers); lgraph = connectLayers(lgraph, 'input_main', 'tcn_conv1'); lgraph = connectLayers(lgraph, 'tcn_relu2', 'transformer_enc'); lgraph = connectLayers(lgraph, 'transformer_enc', 'transformer_gap'); lgraph = connectLayers(lgraph, 'transformer_gap', 'fusion_cat/in1'); lgraph = connectLayers(lgraph, 'input_aux', 'lstm_branch'); lgraph = connectLayers(lgraph, 'lstm_dropout', 'fusion_cat/in2'); dlnet = dlnetwork(lgraph);

connectLayers里fusion_cat/in1和fusion_cat/in2对应concatenationLayer的两个输入端口。组装完成后先做一次前向传导,用随机数据确认各层形状匹配,再进入训练。这一步能提前暴露维度问题,比如Transformer输出与池化层不匹配、LSTM时间步与辅助输入不对齐等,避免训练到一半才报错。常见报错是维度不匹配或format字符串错误,定位方式是把每一层输出用size函数打印出来逐个比对。

4. 训练配置、评估指标与GUI可视化

模型架构确定之后,训练策略直接决定收敛质量。融合网络的不同分支梯度量级差异较大,训练配置和评估方式都需要相应调整。

4.1 训练超参数与优化器

用Adam优化器训练融合网络,初始学习率0.001,梯度衰减因子0.9,二阶矩衰减因子0.999,这两个参数沿用各框架默认值即可。LSTM分支的梯度范数通常比TCN路径小,训练过程中若出现损失尖峰,根因往往是梯度爆炸而非学习率过大,因此梯度裁剪是必要的:

learnRate = 0.001; maxEpochs = 80; miniBatchSize = 32; clipThreshold = 2; % 梯度裁剪阈值 for epoch = 1:maxEpochs shuffledIdx = randperm(size(XTrain, 4)); numIterPerEpoch = floor(numel(shuffledIdx) / miniBatchSize); for iter = 1:numIterPerEpoch idx = shuffledIdx((iter-1)*miniBatchSize+1 : iter*miniBatchSize); [loss, grads] = dlfeval(@modelLoss, dlnet, ... dlarray(XTrain(:,:,:,idx), 'SSCB'), ... dlarray(XAux(:,:,:,idx), 'SSCB'), ... dlarray(YTrain(:,:,:,idx), 'SSCB')); % 逐参数梯度裁剪 grads = dlupdate(@(g) min(max(g, -clipThreshold), clipThreshold), grads); [dlnet, avgGrad, avgSqGrad] = adamupdate(dlnet, grads, ... avgGrad, avgSqGrad, iter, learnRate, 0.9, 0.999); end end

模型损失函数里直接用均方误差即可,时间序列回归不需要自定义复杂损失:

function [loss, grads] = modelLoss(dlnet, dlX1, dlX2, dlY) dlYPred = forward(dlnet, dlX1, dlX2); loss = mse(dlYPred, dlY); grads = dlgradient(loss, dlnet.Learnables); end

梯度裁剪阈值2的含义是每个参数的梯度更新量被限制在[-2,2]区间内,防止LSTM长程反向传播时的梯度爆炸。损失曲线出现尖峰时把阈值降到1.0,训练过慢则适当提高到3.0。学习率衰减方面,每20个epoch乘0.5,比cosine schedule更直观,验证集损失连续10个epoch不下降时提前终止训练。batch size取32是兼顾稳定性和速度的起点,样本量只有几千时降到16。

这里dlarray的格式串'SSCB'对应[特征、时间、通道、样本]四个维度,不同MATLAB版本对卷积层与LSTM层的format要求略有差异,前向时报维度不匹配时优先检查输入矩阵各维顺序与format字符串是否一致。

4.2 评估指标怎么算、怎么看

测试集预测完成后,先用训练时保存的ps做反归一化,再计算指标。归一化参数只能由训练集拟合,验证集和测试集共用同一组ps,这是评估逻辑里最容易出错的地方:

YPredNum = extractdata(dlYPred); YPredNum = squeeze(YPredNum)'; % 转成 [1, N],mapminmax 按行处理 YPredRaw = mapminmax('reverse', YPredNum, ps)'; yTestNum = extractdata(dlYTest); yTestNum = squeeze(yTestNum)'; yTestRaw = mapminmax('reverse', yTestNum, ps)'; rmseVal = sqrt(mean((yTestRaw - YPredRaw).^2)); maeVal = mean(abs(yTestRaw - YPredRaw)); mapeVal = mean(abs((yTestRaw - YPredRaw) ./ yTestRaw)) * 100; ssRes = sum((yTestRaw - YPredRaw).^2); ssTot = sum((yTestRaw - mean(yTestRaw)).^2); r2Val = 1 - ssRes / ssTot;
指标公式说明
RMSEsqrt(mean((y-ŷ)²))对大误差敏感,量纲与原始数据一致
MAEmean(abs(y-ŷ))平均绝对偏差,受异常点影响小
MAPEmean(abs((y-ŷ)/y))*100%相对误差,真实值接近0时不可用
1 - SS_res/SS_tot越接近1表示模型解释度越高

MAPE在样本真实值接近0时会产出异常大的值,这类数据建议改用sMAPE。预测曲线与真实曲线叠加绘图只是第一步,更有效的验证是画残差图:残差在零线附近均匀分布说明模型没有系统性偏差;残差随预测值增大呈漏斗状发散,说明模型在高值区间不稳定,可考虑对目标值做log变换后再训练。

4.3 GUI可视化与交互操作

项目自带的GUI用uifigure加uigridlayout实现,核心交互包含文件选择、参数设置、训练启动、结果展示四块。训练耗时较长,按钮回调里要用timer或并行池避免界面卡死,否则点击训练按钮后整个窗口失去响应:

fig = uifigure('Name', 'TCN-Transformer+LSTM 时间序列预测', ... 'Position', [100 100 1100 700]); g = uigridlayout(fig, [3 2]); g.RowHeight = {40, '1x', 180}; g.ColumnWidth = {'1x', '1x'}; btnTrain = uibutton(g, 'Text', '开始训练', ... 'ButtonPushedFcn', @(btn, event) startTraining()); axRaw = uiaxes(g); axPred = uiaxes(g); axRaw.Layout.Row = 2; axRaw.Layout.Column = 1; axPred.Layout.Row = 2; axPred.Layout.Column = 2;

文件选择模块用uigetfile读取Excel或.mat格式数据,参数设置模块提供窗口长度、隐藏单元数、学习率、epochs四个输入框。参数提交后要做范围校验,比如窗口长度不能大于数据长度的一半,学习率不能大于0.1,否则无效配置会浪费整轮训练时间。训练结束后把预测曲线、残差图、评估指标表分别渲染到右侧绘图区。结果展示里建议加一张误差热图,按时间窗口分组统计预测误差,能直观定位模型表现差的时间段,这类时间段通常对应数据中的突变段或缺失段。

5. 防过拟合与超参数调优的实操清单

融合模型的参数量比单模型大,过拟合风险相应更高。判断过拟合的标准简单直接:训练损失持续下降而验证损失在第N轮开始回升,就是过拟合的起点。三条对策优先级最高,早停、dropout、数据扩充。早停阈值设为10个epoch,验证损失连续10轮不下降就终止;dropout推荐在TCN输出后加0.2,LSTM输出后加0.2,全连接前加0.3;数据扩充在时间序列里常用加噪声和窗口抖动两种方式,窗口抖动比加噪声更稳定,做法是随机微调窗口起点,让模型看到更多变体,但样本间的相关性会增加,验证集划分时要留出足够间隔。

几个关键超参数的调整经验整理如下:

超参数推荐范围调整方向
窗口长度预测周期的1~3倍偏小欠拟合长期模式,偏大引入噪声
TCN卷积核大小3或5数据平滑用5,突变多用3
膨胀系数序列1,2,4,8序列越长,最大膨胀系数越大
注意力头数4或8特征维度低时头数不要超过8
LSTM隐藏单元数32~128和输入特征量级匹配,过大易过拟合
学习率0.0005~0.002损失震荡就降一半

TCN的感受野要覆盖至少一个完整周期。日粒度数据预测月趋势,窗口至少30到60,膨胀卷积层数按感受野公式反推。调参时R²高到0.9以上要优先怀疑数据泄漏,最常见的泄漏点是归一化参数用了全量数据的min/max,正确做法是只用训练集拟合ps,验证集和测试集复用同一组ps。收敛速度方面,前30个epoch观察验证损失曲线:完全不动说明学习率太小或梯度消失,前几个epoch就冲到Inf说明学习率过大或裁剪阈值太高。这两件事排查完,剩下的就是组合结构参数并用早停控制训练轮次,每轮实验记录窗口长度、隐藏单元数、RMSE三个值,横向对比比凭感觉调参效率高得多。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询