TimesNet-Transformer时间序列预测实战:MATLAB实现与调优
2026/9/20 22:38:59 网站建设 项目流程

简介:面向熟悉MATLAB与深度学习框架的研发人员、数据科学家和工程师,这份下载包以TimesNet-Transformer多尺度卷积时序网络为核心,给出多变量时间序列预测的完整MATLAB项目实例。文档从项目背景、目标与挑战切入,详细拆解了模型架构和核心代码,覆盖数据预处理、模型训练、结果评估以及GUI交互界面,并结合多尺度卷积与Transformer编码器说明如何提取局部特征、捕捉长距离依赖,适用于金融市场预测、能源负荷预测、智能制造设备故障预测等场景。资源包为单个docx文档,体积77KB,内容包含项目特点、模块化架构设计、可视化分析说明以及未来改进方向,如自适应多尺度卷积、图神经网络,便于读者按目录快速定位与学习。已有519人学习下载,适合希望在工程中快速落地MATLAB深度学习时序建模的中高级开发者。

1. 核心架构拆解:为什么把 TimesNet 和 Transformer 放在一起

先聊个实际感受:单用 Transformer 做时间序列预测,最大的问题是它把时间步当成了一个单纯的 token 序列,注意力机制虽然能捕捉长程依赖,但时间序列里那种“按周、按月、按季度重复出现的局部模式”很难被有效编码。你喂给它 96 步的历史窗口,它可能学到的是“第 10 步和第 80 步有关联”,却很难意识到“每年 7 月用电量都会冲高”这种周期性规律。

TimesNet 这个思路恰好补上了这块短板。它的核心洞察是:时间序列里的周期性,本质上是一种可以“折叠”的结构。比如一个以 24 小时为周期的序列,你把 24 个时间步折叠成一个二维张量的一行,那 7 天的数据就能折叠成一个 7×24 的矩阵。在这个矩阵里,行方向是天的演进,列方向是小时的变化,周期内的局部特征变成了二维空间里的纹理。再用卷积去扫这个纹理,就能同时提取“哪几个小时是高峰”“高峰在相邻几天怎么漂移”这两类信息,比直接在原始一维序列上做卷积要高效得多。

Transformer 编码器在这里扮演的角色,是处理折叠成二维之后仍然存在的长程依赖。卷积擅长提取局部模式,但如果你要预测未来 48 小时的气温,当前时刻和 36 小时前的一个冷空气过程之间的关联,就得靠注意力机制来跨距离捕捉。所以 TimesNet-Transformer 的搭配逻辑很清楚:TimesNet 负责把多尺度周期特征“挖”出来,Transformer 负责在特征之上建模全局依赖,两个分支的特征融合后送到预测头。

我在实际项目里最常见的做法是双分支并行结构,而不是串联。原因很简单:串联会让 Transformer 的输入变成 TimesNet 的中间特征,一旦 TimesNet 某个周期提取不准,错误会一路传导到输出;并行结构让两个分支各自输出特征向量,在融合层拼接后再映射到预测结果,容错性更好,调参时也更容易定位问题出在哪个分支。这篇博文后面的代码实现,就是按并行双分支来写的。

2. 数据预处理与实验设计

2.1 数据集选择与多变量输入构造

做多变量时间序列预测,数据集的选择直接决定你后面所有工作的价值。我比较推荐从 UCI 机器学习库或者 Kaggle 上找公开数据集,比如用电负荷数据(包含气温、湿度、历史负荷、节假日标记等多个变量),或者空气质量数据(包含 PM2.5、CO、NO2、温度、气压等)。这类数据的特点是变量之间有真实的耦合关系,预测目标变量时,其他变量的贡献是实打实的。

拿到原始数据后,第一步要做的是时间对齐。很多公开数据集的时间戳并不是严格等间隔的,可能中间缺了几天,或者某几个小时的记录是空值。我踩过的坑是直接用fillmissing函数默认参数填充,结果把一段连续的缺失值填成了同一条直线,模型在测试集上的误差被严重低估。正确做法是先画出缺失值分布图,判断是随机缺失还是连续缺失,随机缺失用线性插值基本够用,连续缺失超过 3 个时间步时,最好是找同期历史数据做均值替代,或者直接从训练集中剔除这一段。

多变量输入的构造方式,决定了模型能看到的“视野”。我的经验是用滑动窗口法:设定回看窗口长度为input_len,预测窗口长度为output_len,以步长 1 滑动切分样本。假设数据集有 5 个变量,回看窗口 96 步,预测未来 24 步,那么每个样本的输入形状是(96, 5),标签形状是(24, 1)(如果只预测其中一个变量)。这里有个细节:如果你的预测目标也包含多个变量,比如同时预测温度和湿度,那标签形状就是(24, 2),损失函数相应地要改成多输出 MSE。

2.2 归一化策略与训练集/测试集切分

归一化对这类模型的影响,比很多人想象的要大。TimesNet 里的卷积操作对输入尺度比较敏感,如果某个变量的数值范围是 0 到 1,而另一个变量是 1000 到 5000,卷积核计算出的特征图会被大数值变量主导,小数值变量的周期模式基本被淹没。我通常用 Z-score 归一化,也就是每一列减去均值再除以标准差。注意三个细节:一是归一化参数只能用训练集的均值和标准差来计算,不能用整个数据集的,否则会有信息泄露;二是测试集的归一化也要用训练集的参数,不能在测试集上重新计算;三是预测结果反归一化时,要用目标变量那一列的均值和标准差。

训练集和测试集的切分,我的建议是按时间顺序切分,不要随机打乱。时间序列数据有天然的时间依赖性,随机打乱会让模型偷看到未来的信息,测试集上的指标再好看也没有实际意义。常见比例是前 70% 到 80% 作为训练集,剩下的作为测试集。如果你还想做模型选择,可以在训练集尾部再切出一段验证集。

3. 模型搭建:MATLAB 代码逐段解析

3.1 TimesNet 多尺度卷积特征提取块的实现

TimesNet 的核心是周期发现和二维折叠。在 MATLAB 深度学习工具箱中,你没法直接用内置层完成折叠操作,需要用dlarray和自定义层来解决。我先给出一个实现二维折叠的示例逻辑:

% 输入 x: 形状为 (1, input_len, num_vars) 的 dlarray % 按周期 p 将序列重塑为 (p, input_len/p, num_vars) 的二维张量 function x_2d = fold_sequence(x, p, input_len) num_vars = size(x, 3); num_periods = floor(input_len / p); x_2d = reshape(x(:, 1:num_periods*p, :), [p, num_periods, num_vars]); x_2d = permute(x_2d, [2, 1, 3]); % 调整为 (num_periods, p, num_vars) end

实际的 TimesNet 区块包含周期估计、折叠、二维卷积、解折叠四个环节。在 MATLAB 中,我习惯将整个 TimesNet 块封装为一个自定义层,这样能在dlnetwork中直接串联:

classdef timesnetBlock < nnet.layer.Layer properties NumFilters KernelSize NumPeriods InputLen end properties (Learnable) Conv1 Conv2 end methods function layer = timesnetBlock(numFilters, kernelSize, numPeriods, inputLen) layer.NumFilters = numFilters; layer.KernelSize = kernelSize; layer.NumPeriods = numPeriods; layer.InputLen = inputLen; layer.Conv1 = convolution2dLayer(kernelSize, numFilters, 'Padding', 'same'); layer.Conv2 = convolution2dLayer(kernelSize, numFilters, 'Padding', 'same'); end function Z = predict(layer, X) % X 形状: (1, inputLen, numVars) inputLen = layer.InputLen; numVars = size(X, 3); p = layer.NumPeriods; numPeriods = floor(inputLen / p); % 折叠为二维 X_folded = reshape(X(:, 1:numPeriods*p, :), [numPeriods, p, numVars]); X_folded = permute(X_folded, [2, 1, 3]); % (p, numPeriods, numVars) % 二维卷积提取周期内特征 Z1 = layer.Conv1(X_folded); Z1 = relu(Z1); Z2 = layer.Conv2(Z1); Z2 = relu(Z2); % 解折叠回一维 Z2 = permute(Z2, [2, 1, 3]); Z = reshape(Z2, [1, numPeriods*p, numVars]); end end end

注意这里NumPeriods的选择。实际数据往往包含多个周期,比如用电数据既有 24 小时周期,又有 168 小时(7 天)周期。我的做法是设置多组周期,分别为[24, 168],每组周期生成一个独立的卷积分支,最后把多个分支的输出通过全连接层融合。这样模型能自适应地提取不同时间尺度上的模式,避免单一周期丢失重要信息。

3.2 Transformer 编码器的 MATLAB 实现

Transformer 编码器在 MATLAB 中没有直接的内置层,但可以从自注意力机制开始一步步搭建。我实现一个简化版的多头自注意力层:

classdef multiHeadSelfAttention < nnet.layer.Layer properties NumHeads HeadDim ModelDim end properties (Learnable) Wq, Wk, Wv, Wo end methods function layer = multiHeadSelfAttention(modelDim, numHeads) layer.ModelDim = modelDim; layer.NumHeads = numHeads; layer.HeadDim = modelDim / numHeads; % Xavier 初始化 layer.Wq = dlarray(randn(modelDim, modelDim) * sqrt(2/modelDim)); layer.Wk = dlarray(randn(modelDim, modelDim) * sqrt(2/modelDim)); layer.Wv = dlarray(randn(modelDim, modelDim) * sqrt(2/modelDim)); layer.Wo = dlarray(randn(modelDim, modelDim) * sqrt(2/modelDim)); end function Y = predict(layer, X) % X 形状: (seqLen, batchSize, modelDim) [seqLen, ~, modelDim] = size(X); numHeads = layer.NumHeads; headDim = layer.HeadDim; Q = pagemtimes(X, layer.Wq); % (seqLen, batchSize, modelDim) K = pagemtimes(X, layer.Wk); V = pagemtimes(X, layer.Wv); % 重塑为多头形式 Q = reshape(Q, seqLen, [], numHeads, headDim); K = reshape(K, seqLen, [], numHeads, headDim); V = reshape(V, seqLen, [], numHeads, headDim); % 缩放点积注意力 scores = pagemtimes(permute(Q, [1, 3, 2, 4]), permute(K, [3, 1, 2, 4])); scores = scores / sqrt(headDim); weights = softmax(scores, 'DataFormat', 'SSCB'); % 加权求和 context = pagemtimes(permute(weights, [1, 3, 2, 4]), V); context = reshape(context, seqLen, [], modelDim); % 输出投影 Y = pagemtimes(context, layer.Wo); end end end

pagemtimes是 MATLAB R2022a 之后引入的函数,专门用于批量矩阵乘法,比循环拼接高效得多。Transformer 编码器的完整结构还包括位置编码、残差连接和 LayerNorm,这些在dlnetwork中可以组合:

dlnetTransformer = dlnetwork(... [sequenceInputLayer(modelDim, 'Name', 'trans_input'), ... positionEmbeddingLayer(modelDim, inputLen, 'Name', 'pos_embed'), ... multiHeadSelfAttentionLayer(modelDim, 4, 'Name', 'mhsa_1'), ... layerNormalizationLayer('Name', 'ln1'), ... fullyConnectedLayer(modelDim * 4, 'Name', 'ffn1'), ... reluLayer('Name', 'relu_ffn'), ... fullyConnectedLayer(modelDim, 'Name', 'ffn2'), ... layerNormalizationLayer('Name', 'ln2')], ... 'Initialize', true);

这里有个容易忽略的点:positionEmbeddingLayer在官方工具箱中有实现,但版本不同函数名略有差异。如果你用的版本没有这个层,可以自定义一个可学习的位置编码层,原理是创建一个形状为(inputLen, modelDim)的参数矩阵,加到输入序列上。位置编码是 Transformer 能感知顺序的关键,不加的话,注意力机制会把“今天 8 点”和“昨天 8 点”当成同一个位置,预测结果会乱套。

3.3 双分支融合与整体网络组装

TimesNet 分支和 Transformer 分支的输出形状需要对齐后才能融合。TimesNet 的输出经过多层卷积后是一个特征向量,Transformer 的输出是序列特征,我的做法是先将 TimesNet 输出通过fullyConnectedLayer映射到与 Transformer 隐藏维度一致的维度,然后用concatenationLayer将两个特征向量拼接,最后接若干全连接层输出预测结果:

% 假设 inputLen = 96, numVars = 5, outputLen = 24 % 分支一:TimesNet timesnet_out = timesnetBlock(32, [3, 3], 24, 96); % 提取周期特征 % 分支二:Transformer transformer_out = dlnetTransformer; % 融合 fusion = concatenationLayer(3, 2, 'Name', 'fusion'); fc1 = fullyConnectedLayer(64, 'Name', 'fc_fusion'); drop = dropoutLayer(0.2, 'Name', 'drop_fusion'); fc_out = fullyConnectedLayer(outputLen, 'Name', 'fc_out'); % 通过 layerGraph 组装 lgraph = layerGraph(); lgraph = addLayers(lgraph, timesnet_out); lgraph = addLayers(lgraph, transformer_out); lgraph = addLayers(lgraph, [fusion, fc1, drop, fc_out]); % 手动连接各分支到融合层 lgraph = connectLayers(lgraph, 'timesnet_output', 'fusion/in1'); lgraph = connectLayers(lgraph, 'transformer_output', 'fusion/in2'); % 转换为 dlnetwork dlnet = dlnetwork(lgraph);

组装时要特别注意concatenationLayer的维度参数。在 MATLAB 中,(3, 2)表示在第 3 维上拼接两个输入。如果两个分支的输出都是形状为(1, batchSize, featureDim)的 dlarray,那么第 3 维拼接就是特征维拼接,得到(1, batchSize, featureDim*2),后续全连接层才能正确接收。

4. 训练细节、超参数调优与评估

4.1 训练循环与自定义损失函数

用 MATLAB 训练这类网络的常规方式是自定义训练循环。官方trainNetwork函数对自定义层和复杂网络结构支持有限,所以我用dlnetwork+minibatchqueue的组合:

% 构建 minibatchqueue mbq = minibatchqueue(trainingData, ... 'MiniBatchSize', 32, ... 'MiniBatchFormat', {'SCB', 'CB'}, ... 'OutputCast', 'single'); % 自定义训练循环 numEpochs = 100; learnRate = 0.001; averageGrad = []; averageSqGrad = []; figure; for epoch = 1:numEpochs shuffle(mbq); while hasdata(mbq) [X, Y] = next(mbq); [loss, gradients] = dlfeval(@modelLoss, dlnet, X, Y); [dlnet, averageGrad, averageSqGrad] = adamupdate(dlnet, gradients, ... averageGrad, averageSqGrad, epoch, learnRate); end end function [loss, gradients] = modelLoss(dlnet, X, Y) Y_pred = forward(dlnet, X); loss = mse(Y_pred, Y); gradients = dlgradient(loss, dlnet.Learnables); end

损失函数用 MSE 是最直接的,但如果你的预测目标有不同的物理意义(比如温度误差 1 度比湿度误差 1% 要严重得多),可以考虑加权 MSE。我在一个工业项目里用过loss = mean((Y_pred - Y).^2 .* weights),权重向量根据每个变量的量纲差异手动设定,效果比标准化后直接 MSE 要好。

4.2 关键超参数:学习率、批次大小、周期数选择

整理一份我实测下来的参数参考表:

参数推荐范围我的经验值调参心得
回看窗口长度48 ~ 16896太短捕捉不到日周期,太长增加计算量
预测窗口长度12 ~ 4824超过 48 后误差迅速增大,模型趋于平庸
TimesNet 周期数数据主周期 ± 50%[24, 168]用 FFT 先观察频谱峰值,不要盲目设
卷积核数量16 ~ 6432超过 64 后收益很小,计算量翻倍
Transformer 隐藏维度32 ~ 12864和输入变量数、数据量有关
注意力头数2 ~ 84必须能整除隐藏维度
学习率1e-4 ~ 1e-25e-4用余弦退火调度,前 20 轮高后降低
Dropout0.1 ~ 0.30.2数据量大时可去掉 dropout 加速收敛

周期数的选择有个实操技巧:先对每个变量做快速傅里叶变换(FFT),画出频谱图,观察功率最大的几个频率对应的周期值。比如频谱在 0.0417 Hz(对应 24 小时)和 0.006 Hz(对应 168 小时)处有明显峰值,那周期就设为 [24, 168]。如果数据有年周期而你的数据跨度不够一年,那就别硬设 365,模型学不到的。

4.3 结果评估:指标计算与预测效果可视化

评估时我习惯同时看三个维度的指标:整体误差(RMSE、MAE)、方向准确率、极端值捕捉能力。MATLAB 里计算这些指标很直接:

rmse = sqrt(mean((Y_true - Y_pred).^2, 'all')); mae = mean(abs(Y_true - Y_pred), 'all'); % 方向准确率:预测趋势方向与实际趋势方向一致的比例 dir_true = sign(diff(Y_true)); dir_pred = sign(diff(Y_pred)); dir_acc = mean(dir_true == dir_pred, 'all');

可视化预测效果时,我会画三种图:第一种是预测值和真实值的时间序列对比曲线,直观看出整体贴合程度;第二种是误差分布直方图,观察误差是否呈正态分布,如果明显偏态说明模型有系统性偏差;第三种是分变量误差热力图,定位到具体是哪个变量预测得最差。有一次我发现某个变量的误差远大于其他变量,查了半天发现是这个变量在数据预处理时没有做滞后校正,时间轴偏了一天。

5. 常见问题与排查技巧实录

5.1 训练不收敛或 Loss 震荡

时间序列预测任务里,Loss 震荡最常见的原因是学习率太大。Transformer 编码器对学习率非常敏感,我用过 0.01 的学习率,Loss 直接发散到 NaN;降到 0.0005 后稳定收敛。第二个原因是批次大小太小,导致梯度估计噪声太大。第三个原因比较隐蔽:归一化参数用错了,如果测试集的归一化参数没沿用训练集的,验证 Loss 会周期性跳变,因为每个 batch 的数值分布都不一样。

排查思路是:先固定一个很小的学习率(1e-4)跑 20 轮,同时打印训练集和测试集的 Loss。如果训练集 Loss 下降但测试集 Loss 不降,那就是过拟合,增加 Dropout 或减小模型容量;如果两边都不降,检查数据预处理是否有 bug,尤其是折叠reshape那一步,很多人会把维度搞混。

5.2 预测结果出现延迟或滞后

如果你发现预测曲线比真实曲线整体向右平移了一段,也就是模型输出差不多等于“把过去的值往后搬”,这是时间序列预测模型非常典型的退化现象。原因通常是模型过度依赖了输入序列最后一两个时间步的信息,而没学到真正的动态规律。我在 TimesNet-Transformer 里遇到这种情况时,第一个检查项是 TimesNet 的卷积核大小。卷积核越大,越倾向于提取平滑特征,预测结果越滞后。第二个检查项是损失函数,如果目标序列存在明显趋势,纯 MSE 会鼓励模型输出均值附近的预测,这时候可以考虑在损失中加入一阶差分项。

5.3 训练速度慢、显存不足

MATLAB 的dlarray在做自定义层时,如果算子写得不够向量化,循环会非常慢。一个常见性能瓶颈是在多头注意力实现中用for循环遍历每个头,导致梯度计算图非常庞大。解决方法是尽量用pagemtimes替代循环。显存不足则可以考虑减小批次大小,或者缩短历史窗口长度,或者用dlarraysingle精度而不是默认的double

6. 扩展思路:从预测到决策的实战升级

模型跑通之后,我建议你做一个更有价值的扩展:把预测结果接入到一个简单的决策模块中。比如预测的是设备温度,当未来 24 小时某时刻的温度预测值超过阈值时,系统自动触发预警。这种从“预测”到“决策”的闭环,才是时间序列项目在实际业务中的最终价值。

另一个值得尝试的方向是多步滚动预测和直接多步预测的对比。滚动预测每次预测一步,把预测值作为下一步的输入,缺点是误差会累积;直接多步预测一次输出多个时间步,训练简单但长程精度往往不如滚动预测。TimesNet-Transformer 在直接多步预测上的表现通常优于滚动预测,因为自注意力可以同时建模未来多个时间步之间的关系,你可以对比一下两者在同一个数据集上的差异,这会让你的实验内容更丰富。

在我实际做过的一个设备故障预警项目中,TimesNet-Transformer 相比单纯的 LSTM 模型,在 48 步预测的 RMSE 上降低了约 18%,方向准确率提升了 7 个百分点。但也要客观地说,模型复杂度带来的训练时间增加是明显的,如果数据量只有几千个时间步,LSTM 或者 XGBoost 可能反而是更务实的选择。这也是我想强调的一点:模型选型永远服务于数据规模、预测时域和业务成本约束,而不是单纯追求架构的先进性。

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

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

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

立即咨询