简介:基于MATLAB实现的CNN-LSTM-Attention融合模型多输入多输出回归项目,面向有一定MATLAB编程基础并从事时间序列预测、工业过程建模或智能传感数据分析的研究人员与工程师,重点解决多源数据联合预测与复杂时序特征提取问题。资源以1个docx文档封装,压缩包仅144KB,但内容覆盖数据生成、滑动窗口切片、统计标准化、网络搭建、训练配置、反标准化评估及GUI可视化设计全流程。文档含项目背景、模型架构、挑战与解决方案、完整代码示例等模块,对CNN卷积特征提取、LSTM长期依赖记忆、注意力权重分配的实现细节逐段拆解,便于读者直接修改超参数并移植到新能源功率预测、设备健康评估等场景。项目从数据准备到预测评估形成闭环,并提供GUI交互界面,用户可通过界面完成数据导入、模型训练、预测分析与结果导出,便于非专业用户操作和教学演示。目前已有48人浏览学习,适合需要快速搭建可解释、可复用回归预测模型的工程技术人员参考。
1. 面对多变量回归预测,CNN-LSTM-Attention 为什么值得在 MATLAB 里做
多输入多输出回归预测在工程里并不罕见:你用前 12 小时的风速、温度、气压去推未来 3 小时的发电功率,或者用设备的多通道振动信号同时估计剩余寿命的上下界。这类任务的关键在于数据里既有局部特征(比如突变的异常脉冲),又有长时间依赖(比如缓慢漂移的趋势),还往往存在多变量之间的耦合关系。CNN-LSTM-Attention 的组合恰好覆盖了这三个需求:CNN 抓局部模式,LSTM 捕捉序列依赖,Attention 机制对时间步或特征通道做加权筛选。相比单一 LSTM 或纯 CNN,这种结构在多输入输出场景下通常有更低的误差,也更容易通过注意力权重解释模型关注到了哪些时刻。
选择 MATLAB 而不是 Python 来实现这个架构,核心理由有三。第一,MATLAB 的 Deep Learning Toolbox 从 R2019b 之后对 layerGraph 和 dlnetwork 的支持已经相当完整,CNN 和 LSTM 可以直接用内置层拼装,不需要手动写反向传播。第二,MATLAB App Designer 在构建小型预测工具时非常高效,训练好的网络可以打包进 GUI 里的回调函数,对不熟悉 Python 部署链路的工程师更友好。第三,MATLAB 自带的数据预处理函数(如 normalize、fillmissing、split)在时序数据清洗上比手写 numpy 代码更直观。这篇文章按数据准备、模型搭建、训练验证、GUI 封装和参数调优的顺序展开,我会给出可运行的 MATLAB 代码,并说明每一步的设计意图与常见的踩坑点。
2. 数据预处理与多输入多输出问题的建模方式
2.1 输入输出结构设计:先用 cell 数组定好数据格式
在 MATLAB 中实现 CNN-LSTM-Attention,入门者最容易绊倒的地方不是模型结构,而是数据格式。Deep Learning Toolbox 对序列回归任务要求输入格式为 numFeatures × numTimeSteps 的矩阵或 cell 数组(每个 cell 是一个样本),而多输出回归则要求目标输出为 numResponses × numTimeSteps 的矩阵,同样封装在 cell 数组里。以风速和功率预测为例,假设你有 6 个输入特征(风速、风向、温度、湿度、气压、前一时刻功率),需要预测下一时刻的 2 个输出(有功功率、无功功率),那么对于第 i 个训练样本,输入矩阵的尺寸是 6 × windowSize,输出矩阵的尺寸是 2 × 1(如果只预测一个未来时刻)。
% 假设 rawData 是 T×N 的原始数据矩阵,T 为时间步数,N 为特征数 % 其中前 6 列为输入特征,第 7、8 列为目标输出 numFeatures = 6; numResponses = 2; windowSize = 24; % 用过去 24 个时刻预测未来 1 个时刻 [XTrain, YTrain] = prepareData(rawData, numFeatures, numResponses, windowSize); function [XTrain, YTrain] = prepareData(data, numFeat, numResp, winSize) numSamples = size(data, 1) - winSize; XTrain = cell(numSamples, 1); YTrain = cell(numSamples, 1); for i = 1:numSamples XTrain{i} = data(i:i+winSize-1, 1:numFeat)'; % 转置为 numFeat × winSize YTrain{i} = data(i+winSize, numFeat+1:numFeat+numResp)'; % numResp × 1 end end上述代码中,XTrain{i} 做了转置操作,原因在于 MATLAB 的 sequenceInputLayer 期望输入维度是 numFeatures × numTimeSteps,而不是常见的 samples × time × features 结构。而 YTrain{i} 保持 numResp × 1,对应 regressionLayer 的输出维度。如果你的预测目标不是未来一个时刻,而是未来多个时刻(比如未来 6 个小时的功率曲线),那么 YTrain{i} 应当是 numResp × forecastHorizon 的矩阵,这要求模型最后的全连接层输出维度为 numResponses × forecastHorizon。
提示:不要忽略 cell 数组与普通数值矩阵的差异。如果直接将整个数据矩阵送入 trainNetwork,MATLAB 会默认每个样本是独立序列,而你的数据往往是连续截断的,会导致严重的时序信息泄露。
2.2 数据归一化与训练集划分:避免归一化参数来自测试集
在时序预测任务中,归一化通常采用 Z-score 或 min-max 两种方式。我一般优先选 min-max 到 [0,1] 区间,因为 LSTM 的 tanh 激活函数和注意力机制的 softmax 对输入尺度比较敏感,且多输出回归中如果不同输出量纲差异大,例如功率以千瓦为单位、温度以摄氏度为单位,未归一化的 loss 会被大数值量纲主导。MATLAB 的 normalize 函数提供了便捷接口,但必须注意 fit 和 transform 的分离,防止测试集信息通过归一化参数泄露到训练过程。
% 对每个特征单独做 min-max 归一化 [Xnorm, mu, sigma] = zscore(XTrain); % 使用 z-score 方式 % 注意:zscore 对矩阵按列计算,但 XTrain 是 cell 数组,需要循环处理 for i = 1:numel(XTrain) XTrain{i} = (XTrain{i} - mu') ./ sigma'; end % 对测试数据使用同样的 mu 和 sigma,而不是重新计算 for i = 1:numel(XTest) XTest{i} = (XTest{i} - mu') ./ sigma'; end这里有一个关键细节:zscore在计算均值和标准差时,应当基于训练集的所有样本拼接后的矩阵。如果每个样本单独归一化,会破坏时间步之间的相对关系。实际做法是将所有样本的二维矩阵沿时间维度拼接,计算全局的 mu 和 sigma,再对每个样本应用。这在上述代码中通过mu和sigma计算时的数据来源体现——你必须在循环之前对[XTrain{:}]做归一化统计。
训练集和测试集的划分也有讲究。随机打乱样本会破坏时序依赖,所以应当按时间顺序划分,比如前 80% 的数据做训练,后 20% 做测试。如果涉及超参数调优,中间还需要留出一段验证集,或者使用 MATLAB 内置的crossvalind做分层划分(尽管时间序列的分层不太严格)。我见过不少初学者在这个环节直接调用randperm打乱数据,结果模型在测试集上表现好但上线后崩溃,就是因为时间序列的局部平稳假设被破坏了。
2.3 滑动窗口构造样本:重叠采样与步长的取舍
滑动窗口构造样本时,窗口大小 windowSize 直接决定了模型看到的序列长度。窗口太短,模型抓不到长时间依赖;窗口太长,训练样本数减少且 LSTM 容易出现梯度衰减。常见经验值是覆盖 2~3 个完整周期,例如预测日功率曲线,如果采样间隔是 1 小时,窗口取 24~48 较为合理。步长 stride 决定样本重叠程度,stride=1 时样本量最大但相邻样本高度相关,容易过拟合;stride=windowSize 时样本独立但数据量骤降。我一般折中取 stride 为窗口的 1/4 到 1/2。
function [XTrain, YTrain] = prepareDataStride(data, numFeat, numResp, winSize, stride) numSamples = floor((size(data, 1) - winSize) / stride); XTrain = cell(numSamples, 1); YTrain = cell(numSamples, 1); idx = 1; for i = 1:stride:size(data, 1) - winSize if idx > numSamples, break; end XTrain{idx} = data(i:i+winSize-1, 1:numFeat)'; YTrain{idx} = data(i+winSize, numFeat+1:numFeat+numResp)'; idx = idx + 1; end XTrain = XTrain(1:idx-1); YTrain = YTrain(1:idx-1); end这个函数在循环中用idx控制实际生成的样本数,避免因stride不整除导致边界错误。MATLAB 的cell预分配在这里能明显提升性能,如果数据量大,建议用cell(numSamples, 1)预分配后逐个赋值,再裁剪掉末尾未填充的部分。
3. 在 MATLAB 中搭建 CNN-LSTM-Attention 组合网络
3.1 网络结构设计:从 layerGraph 到 dlnetwork 的两种路径
MATLAB 支持两种方式定义这个模型。第一种是用layerGraph+trainNetwork,适合纯内置层且不需要自定义训练循环的场景;第二种是用dlnetwork+modelGradients,适合需要自定义损失函数或者实现复杂注意力层的场景。对于 CNN-LSTM-Attention,由于 MATLAB 内置层里没有现成的注意力层,要么自己写customLayer,要么用attention相关的函数在 dlnetwork 里实现。这里给出一个用layerGraph配合自定义注意力层(以functionLayer的方式嵌入)的快速方案。
% 创建 layerGraph numFeatures = 6; numResponses = 2; windowSize = 24; layers = [ sequenceInputLayer(numFeatures, 'Name', 'input') % CNN 部分:两个卷积层 + ReLU + 最大池化 convolution1dLayer(3, 32, 'Padding', 'same', 'Name', 'conv1') reluLayer('Name', 'relu1') convolution1dLayer(3, 64, 'Padding', 'same', 'Name', 'conv2') reluLayer('Name', 'relu2') maxPooling1dLayer(2, 'Stride', 2, 'Name', 'pool1') % LSTM 部分 lstmLayer(64, 'OutputMode', 'sequence', 'Name', 'lstm1') % 注意力层:这里用一个自定义 functionLayer 代替 % 具体实现见 3.2 节的自定义层代码 attentionLayer('Name', 'attention') % 输出部分 fullyConnectedLayer(numResponses, 'Name', 'fc_out') regressionLayer('Name', 'reg') ]; lgraph = layerGraph(layers); % 对于 sequence-to-one 回归,LSTM 需要设置 OutputMode='last' % 但如果要在 LSTM 后接注意力,则需要 OutputMode='sequence'这段代码已经指定了lstmLayer的OutputMode为'sequence',这是因为注意力机制需要 LSTM 每个时间步的隐状态 h_t 来计算加权汇总,如果设为'last',你只能得到最后一个时间步的输出,注意力计算无从谈起。maxPooling1dLayer会降低时间维度,CNN 部分输出的时间步数变为原来的 1/2,LSTM 在这一长度上工作。如果窗口长度为奇数,需要先用Padding选项调整,或者用globalAveragePooling1dLayer避免长度匹配问题。
注意:在卷积层中我用了
convolution1dLayer,MATLAB 的 Deep Learning Toolbox 从 R2020a 开始提供这个层。如果你的版本较老,可以将convolution1dLayer(3, 32, 'Padding', 'same')替换为convolution2dLayer([3 1], 32, 'Padding', 'same'),但输入数据就需要 reshape 成 4 维张量,操作更繁琐。
3.2 注意力机制的自定义实现:三种常见形式与选择
MATLAB 中实现注意力层最灵活的方式是继承nnet.layer.Layer写一个自定义层,然后在layerGraph中调用。另一种方式是直接用dlnetwork在自定义训练循环里写score = softmax(fullyConnectedLayer(W, h_t)),但这对新手来说调试成本偏高。这里给出基于functionLayer的最小实现,适用于 sequence-to-one 且注意力权重是标量的场景。
function layer = attentionLayer(varargin) layer = functionLayer(@attentionForward, 'Name', 'attention'); end function Y = attentionForward(X) % X 的维度是 hiddenSize × numTimeSteps × batchSize [hiddenSize, numSteps, batchSize] = size(X); % 计算每个时间步的注意力得分:这里使用简单的全连接打分 % 实际中可以引入可学习的 W 和 b,但 functionLayer 不支持可学习参数, % 所以我们这里用固定初始化,或者改用 dlnetwork 实现可学习版本 W = randn(1, hiddenSize) / sqrt(hiddenSize); scores = W * reshape(X, hiddenSize, []); % 1 × (numSteps*batchSize) scores = reshape(scores, numSteps, batchSize); % softmax 归一化 weights = softmax(scores, 1); % numSteps × batchSize % 加权求和 Xr = reshape(X, hiddenSize, numSteps, batchSize); Y = zeros(hiddenSize, 1, batchSize); for b = 1:batchSize Y(:, :, b) = Xr(:, :, b) * weights(:, b); end end这个实现有两个明显局限。第一,functionLayer不能存储可学习参数,注意力矩阵 W 是固定随机初始化的,这在实际项目中效果不稳定。第二,for循环处理 batch 效率低。更推荐的方式是用dlnetwork定义完整的注意力模块,配合forward和modelGradients函数做自定义训练。如果希望保持trainNetwork的简洁流程,可以用%#function声明自定义层并且通过nnet.layer.Layer子类实现可学习参数,但代码量会上升到 100 行以上。介于篇幅,这里给一个在dlnetwork下使用的可学习注意力实现思路。
% 在 dlnetwork 里,注意力模块定义如下 % 假设 lstmOutput 是 dlarray,维度为 hiddenSize × numTimeSteps × batchSize function weights = computeAttentionWeights(lstmOutput) [hiddenSize, numSteps, ~] = size(lstmOutput); % 可学习的打分参数:Wq 和 Wk 可以类比 Transformer 中的 query 和 key persistent Wq Wk if isempty(Wq) Wq = dlarray(randn(hiddenSize, hiddenSize) / sqrt(hiddenSize)); Wk = dlarray(randn(hiddenSize, hiddenSize) / sqrt(hiddenSize)); end % 对每个时间步计算得分 query = Wq * lstmOutput(:, end, :); % 用最后一个时间步作为 query keys = Wk * lstmOutput; % 所有时间步作为 key scores = pagemtimes(query', keys); % batchSize × numSteps weights = softmax(scores, 2); % 按时间步归一化 end这里用pagemtimes替代循环计算批量矩阵乘法,在 GPU 上能显著加速。query取最后一个时间步的隐状态,这符合序列到单点回归的直觉——你需要从整个序列中挑选与最终时刻最相关的信息。实际项目中,我建议使用多头注意力的简化版本,即把 hiddenSize 拆成几个头分别计算,再拼接,这能提升模型的表达能力,对应热词里的“多头注意力机制原理”。
3.3 训练选项参数设置:解决 LSTM 梯度消失与过拟合
训练 CNN-LSTM-Attention 网络时,trainingOptions的参数选择直接影响收敛速度和最终精度。我第一次跑这个模型时最深刻的教训是学习率设置太大(0.01),导致 LSTM 的梯度爆炸,loss 直接变 NaN。对于 LSTM 家族,初始学习率通常设在 0.001~0.005 之间,并使用'adam'优化器。
options = trainingOptions('adam', ... 'MaxEpochs', 200, ... 'MiniBatchSize', 64, ... 'InitialLearnRate', 0.002, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropPeriod', 50, ... 'LearnRateDropFactor', 0.2, ... 'GradientThreshold', 1, ... 'Shuffle', 'never', ... 'Verbose', 1, ... 'Plots', 'training-progress');GradientThreshold设置为 1 是防止梯度爆炸的关键,LSTM 在长序列上梯度范数很容易超过 10,如果不截断,权重更新一步就会毁掉整个模型。Shuffle设为 'never' 是因为时序数据不能打乱样本顺序,虽然每个样本是独立窗口,但保持顺序有助于模型捕捉数据的连续分布。MiniBatchSize的选择取决于数据量和显存,如果序列长度是 24 且 hiddenSize 是 64,64 的 batch size 在普通 8GB 显卡上都能跑,但如果序列长度是 512,可能就要降到 16。
为了减少过拟合,我在实际项目中会在 LSTM 之后加一个dropoutLayer(0.2),在注意力层之后再接一个dropoutLayer(0.1)。注意 dropout 只应在训练时起作用,MATLAB 的 trainingOptions 会自动处理这一点,不需要手动开关。如果训练 loss 和验证 loss 在 100 轮后差距拉大,优先调节 dropout 比例而不是增加 L2 正则化系数,因为时序预测中 L2 对 LSTM 的隐藏状态约束过强,容易让模型退化为线性预测器。
4. GUI 界面设计与代码组织方式
4.1 App Designer 搭建预测工具:从加载数据到一键预测
MATLAB 的 App Designer 是比传统 GUIDE 更现代的 GUI 构建工具,支持回调函数的代码管理和组件拖拽布局。针对这个多输入多输出回归预测任务,GUI 至少需要以下几个功能模块:加载数据按钮(支持 .mat 或 .xlsx)、训练模型按钮、导入待预测数据按钮、结果显示区以及注意力权重可视化。使用 App Designer 的组件命名规范,可以在回调中直接访问训练好的模型。
% App Designer 中的训练回调函数(部分代码) function TrainButtonPushed(app, event) % 读取当前界面上的数据 data = app.DataEditField.Value; % 假设是一个数据路径字符串 rawData = load(data).rawData; % 数据预处理(使用 2.3 节的 prepareDataStride 函数) [XTrain, YTrain] = prepareDataStride(rawData, 6, 2, 24, 6); % 归一化并分割 % ... 这里省略归一化代码,参考 2.2 节 % 构建网络 lgraph = buildCNN_LSTM_Attention(6, 2, 24); % 设置训练选项 options = trainingOptions('adam', ... 'MaxEpochs', 100, ... 'MiniBatchSize', 32, ... 'InitialLearnRate', 0.002, ... 'GradientThreshold', 1, ... 'Plots', 'none', ... 'Verbose', 0); % 训练 app.Net = trainNetwork(XTrain, YTrain, lgraph, options); app.StatusLabel.Text = '训练完成'; end在上述回调中,buildCNN_LSTM_Attention是你定义的函数,返回一个 layerGraph。因为 MATLAB 的trainNetwork不接受输出为 cell 数组的 3D 张量,所以YTrain必须是 cell 数组,其中每个元素是 numResponses × numTimeSteps 的矩阵。这个结构在trainNetwork内部会被自动解析。如果你用dlnetwork做自定义训练,则 YTrain 通常是 dlarray 组成的 cell 数组,两者在数据格式上略有差异,需要在切换实现时同步调整。
4.2 模型保存与加载:避免每次打开 GUI 重新训练
训练一个 CNN-LSTM-Attention 网络动辄几分钟到几十分钟,如果 GUI 每次启动都要重新训练,体验很糟糕。标准的做法是用save和load函数将训练好的网络保存为 .mat 文件,并在 App Designer 的 startupFcn 里检查是否存在模型文件,如果有则直接加载。
% 保存模型 function SaveModelButtonPushed(app, event) [filename, pathname] = uiputfile('*.mat', '保存模型'); if isequal(filename, 0) return; end net = app.Net; save(fullfile(pathname, filename), 'net'); app.StatusLabel.Text = '模型已保存'; end % 加载模型(在 startupFcn 中调用) function startupFcn(app) if exist('trained_model.mat', 'file') S = load('trained_model.mat'); if isfield(S, 'net') app.Net = S.net; app.StatusLabel.Text = '已加载已有模型'; end end end保存时需要注意,trainNetwork返回的SeriesNetwork或DAGNetwork对象不能跨 MATLAB 版本随意加载,R2021a 训练的模型在 R2019b 中可能无法识别。因此,在 GUI 中保存模型时,建议同时保存lgraph的结构(层信息)和训练选项,以便在版本不匹配时能够重建网络结构并重新训练。这里还有一个潜在问题:如果模型是在 GPU 上训练的,加载到无 GPU 的机器上,预测速度会明显变慢,但不影响功能。GUI 的预测回调里直接用predict函数即可,不需要关注这些底层细节。
4.3 结果显示与评估指标:在界面上展示 RMSE 与拟合曲线
回归预测的 GUI 中,最常用的展示方式是绘制真实值与预测值的对比曲线、散点图以及误差分布直方图。App Designer 的 UIAxes 组件可以直接绘制这些图,相比旧的plot+figure方式更适合嵌入 UI。计算 RMSE 和 R² 需要用到 MATLAB 的评估函数,基础版本可以自己写。
% 预测回调中的评估部分 function PredictButtonPushed(app, event) net = app.Net; XTest = app.TestData; % cell 数组 YTest = app.TestTarget; % cell 数组 YPred = predict(net, XTest, 'MiniBatchSize', 32); % 将 cell 数组转换为矩阵用于计算误差 YPredMat = cell2mat(YPred')'; % numSamples × numResponses YTestMat = cell2mat(YTest')'; % 计算 RMSE 和 R² rmse = sqrt(mean((YPredMat - YTestMat).^2, 1)); ssRes = sum((YTestMat - YPredMat).^2, 1); ssTot = sum((YTestMat - mean(YTestMat, 1)).^2, 1); r2 = 1 - ssRes ./ ssTot; % 绘制第一个输出的对比曲线 plot(app.UIAxes, 1:size(YTestMat, 1), YTestMat(:, 1), 'b-', ... 1:size(YTestMat, 1), YPredMat(:, 1), 'r--'); legend(app.UIAxes, '真实值', '预测值'); app.RMSEEditField.Value = rmse(1); app.R2EditField.Value = r2(1); end这里要注意一个维度陷阱:predict返回的 cell 数组每个元素是 numResponses × 1 或 numResponses × horizon 的矩阵,cell2mat(YPred')'先沿横向拼接再转置,确保得到的二维矩阵是 numSamples × numResponses。如果直接用cell2mat(YPred),得到的矩阵维度是 numResponses × numSamples,画图时横纵坐标就反了。这个细节在 GUI 调试时经常耗费大量时间,提前注释清楚可以避免后续维护的困惑。
5. 注意力权重可视化:给模型一个可解释的理由
注意力机制在 MATLAB 实现中的最大优势是可以提取中间层的输出,从而可视化模型在不同时间步上的关注程度。利用activations函数可以获取 LSTM 层的输出,再与注意力权重结合,画出热力图。这种可视化不仅是论文里的加分项,更是工程调参的重要依据——如果注意力权重集中在几个固定的时间步上,说明窗口长度可能设置过大,模型在偷懒。
% 提取注意力权重并可视化 % 假设 net 是训练好的 DAGNetwork,'attention' 是自定义层名称 % 对于 functionLayer,可以使用 activations 获取输出 attOutput = activations(net, XTest, 'attention'); % attOutput 维度为 hiddenSize × 1 × numSamples(因为注意力层输出加权和) % 如果要可视化每个时间步的权重,需要在自定义层中额外输出权重 % 这里给出一种替代方案:利用 LSTM 输出与 final output 的梯度近似权重 % 使用 dlgradient 计算输入对输出的敏感度使用activations提取中间层输出时有一个限制:如果自定义层是用functionLayer写的,它不具备activations支持的层属性,调用会报错。解决方法是把注意力层写成一个真正的nnet.layer.Layer子类,并且在predict方法中同时输出加权后的结果和权重矩阵,或者使用dlnetwork的forward函数搭配dlfeval求梯度。
% 集成梯度近似注意力权重的简化实现 function attWeights = computeIntegratedGradients(net, X, targetIdx) % 使用 dlfeval 计算梯度 grad = dlgradient(dlarray(X), targetIdx); % 对梯度做时间维度的求和与归一化 attWeights = squeeze(sum(abs(grad), 1)); attWeights = attWeights / sum(attWeights); end上述集成梯度方法的逻辑是:如果某个时间步对最终输出的影响大,那么该时间步输入的变化会引起输出更大的梯度,因此梯度的绝对值可以在一定程度上反映注意力。这种方法不需要修改网络结构,适合快速验证。如果项目对可解释性要求高,建议直接在模型里加一个并行输出分支,即注意力权重本身作为一个输出层,通过trainNetwork的多输出机制实现联合训练,此时layerGraph需要用到addLayers和connectLayers来构建分叉结构。
在实际工程中,我还会用注意力权重做数据筛选。例如,如果可视化发现模型在 80% 的情况下只关注窗口的最后 5 个时间步,那么可以考虑把窗口从 24 缩短到 10,这不仅能加快训练速度,有时还能提升泛化精度——因为模型不再需要拟合大量与决策无关的历史信息,过拟合的风险也随之下降。这一步的验证方式是在缩短窗口后重新训练,对比验证集上的 RMSE 是否下降。这里提供一组我常用的排查顺序:先看 attention 热力图是否出现了明显的“长尾分布”(即少数时间步集中了大部分权重),如果是,再尝试不同的窗口长度和 LSTM hiddenSize,直到热力图分布相对平滑。
另一个进阶技巧是将注意力权重与输入特征的物理意义对照。在多输入多输出的场景下,你可以在 GUI 里加一个下拉框,选择查看哪个输入特征的时间步注意力,例如风功率预测任务中关注风速通道的注意力是否与真实的物理滞后时间吻合。如果模型学到的注意力峰值集中在风速突变后的第 3 个时间步,而这个滞后恰好符合流场的传输时间,那这个模型的可信度就很高,反之则要检查数据是否存在对齐错误。将注意力可视化作为一个常规的模型诊断工具,而不是论文里的一张配图,能显著减少后期调试模型的盲目性。
本文还有配套的精品资源,点击获取