资讯详情

CNN-LSTM结合SE注意力机制实现MATLAB时序数据分类预测

📅 2026/10/20 22:46:05 | 华诺云谱 👁 阅读
CNN-LSTM结合SE注意力机制实现MATLAB时序数据分类预测
最近在做一整套基于MATLAB的时序信号分类预测原模型用的是CNN-LSTM训练到一定轮数之后验证集准确率就一直停在91%附近上不去。后来我把SE注意力模块插在卷积块和LSTM中间变成CNN-LSTM-SE结构同一个测试集上成绩直接跳到94.6%。这篇文章就围绕这套基于卷积神经网络-长短时记忆网络结合SE注意力机制的数据分类预测展开重点说清楚三件事为什么SE模块值得加、在MATLAB里怎么落地、以及替换原模型时最容易踩的坑。有两次跑训经验有代码片段也有实际对比数据适合正在折腾时序分类、故障诊断或信号识别的朋友参考。1. 先从一次替换说起为什么非要在CNN-LSTM中间插一个SE1.1 一个基线模型卡在91%的典型场景先说背景。我做的是多通道传感器信号的多分类任务输入是12个通道、每个样本200个时间步的连续序列类别数在5到8类之间。最初的基线网络是两层一维卷积加一层LSTM再加全连接分类头结构大概是Conv1D - BN - ReLU - MaxPool - Conv1D - BN - ReLU - MaxPool - LSTM - FC - Softmax。这个结构其实是很多MATLAB时序分类项目里最常用的组合跑起来挺稳定也没有明显过拟合。但问题在于到了91%上下就开始卡平台。我试过把LSTM隐藏单元从64加到128试过卷积核从16加到32也试过把Dropout调到0.5结果在验证集上要么原地踏步要么训练集提升明显但验证集不升反降。回头去看混淆矩阵发现错分样本几乎集中在几组长得特别像的工况类别上。也就是说模型并不是学不动了而是对某些特征通道的区分力不够。这时候我意识到一个关键问题卷积层输出的每个通道本质上是信号在不同频带或不同模式下的响应强度。但LSTM在读取卷积特征时是把所有通道一律平等对待的——那些对分类起决定作用的通道和那些基本属于噪声的通道在进入LSTM时权重是一样的。这就像让一个人同时听八个声源的混音却没有提前告诉他哪个声源重要。于是我把注意力转到了通道重标定上也就是SE模块。1.2 SE注意力到底在解决哪个痛点SE的全称是Squeeze-and-Excitation中文常叫压缩-激励或者通道注意力。它和Transformer那套自注意力完全不是一回事自注意力是在计算位置与位置之间的相关性而SE做的是显式建模特征通道之间的依赖关系让网络自己学会哪些通道该放大哪些通道该抑制。SE的思路非常直白三步走。第一步Squeeze把每个通道在时间维度上压缩成一个全局标量一般用全局平均池化完成相当于统计每个通道在整个序列上的平均响应水平第二步Excitation用两个全连接层对压缩后的向量做非线性变换输出一组0到1之间的权重第三步Scale把这组权重沿着通道方向乘回原始特征图。我用一个很生活化的类比来理解它原始特征图是摄像机拍的画面Squeeze是先把每一帧画面缩成一张灰度缩略图Excitation是让一个调度员根据缩略图判断每个区域该开多大光圈Scale就是把光圈设定落到实际拍摄上。整个过程没有引入复杂的注意力图成本很低但能让LSTM拿到一份重点已标好的特征序列。在实际替换时并没有推翻原网络结构只是在卷积输出和LSTM输入之间多插了这一个模块。这也是后面所有实验的起点——改动足够小效果却立竿见影。2. 数据流全走查从卷积特征到通道重标定再到LSTM记忆2.1 卷积层输出的特征图在进入LSTM之前发生了什么要把SE插对位置首先要彻底搞懂数据在CNN-LSTM骨架里的流转过程。以我用的12通道时序数据为例单个样本的原始形状是12×200也就是12个通道变量、200个时间步。进入网络时按照MATLAB的深度学习习惯我们一般把样本组织成通道×批量×时间的三维张量用dlarray标记成CBT格式。经过第一层一维卷积后如果是32个卷积核那么输出就从12通道变成32通道序列长度还是200用了same填充紧接着池化层把时间维减半到100。第二层卷积再把通道扩到64池化后时间维变成50。在这一步特征图的形状就是64×批量×5064个通道代表64种局部特征模式50个时间步代表压缩后的序列长度。问题恰恰出在这里。LSTM在每个时间步输入的向量长度就是64也就是说LSTM会把这64个通道当作等权重的输入特征去处理。但现实中这64个通道的区分度差异非常大有些通道几乎总是在同类样本里保持稳定、在异类样本里差异明显有些通道则完全是噪声响应。如果不做任何加权LSTM的注意力资源会被大量浪费在无信息通道上。2.2 Squeeze、Excitation、Scale三步的维度变化SE模块在数学上不复杂但MATLAB里面写代码时要格外注意每一步的维度。以下是我实际运行时记录下来的张量变化过程直接用CBT格式叙述C是通道、B是批量、T是时间步阶段操作输入形状输出形状原始特征F卷积块输出C×B×TC×B×TSqueeze在时间维上求均值C×B×TC×BExcitation-1全连接C→C/rC×B(C/r)×BReLU激活非线性变换(C/r)×B(C/r)×BExcitation-2全连接(C/r)→C(C/r)×BC×BSigmoid归一化到0~1C×BC×BScale把权重扩成C×B×T并乘回C×B×TC×B×T这里有个需要特别提醒的点Squeeze是在时间维上求均值不是在整个batch上求均值。每个样本要保留自己的通道权重否则不同样本之间的通道重要性会被相互抹平模型就学成了全体平均注意力效果会打折扣。Excitation部分有一个超参数r叫缩减比。第一个全连接层把通道数压缩到原来的1/r第二个全连接层再恢复回去。这个设计的目的是用瓶颈结构减少参数量同时强迫权重向量学习一个低维的通道相关性表示。r太大信息丢失太多r太小起不到正则化作用。我在实验里的经验是时序信号分类场景下r8往往比r16效果更好因为信号通道之间的相关性比起图像像素通道更直接不需要那么深的压缩。2.3 LSTM如何消化经过重标定的特征序列经过Scale之后特征序列仍然是C×B×T的形状只是每个通道的幅值被重新缩放过了。关键点在于LSTM在每一个时间步t看到的输入向量是特征图在t时刻沿所有通道取出的长度为C的向量也就是F_hat(:, :, t)。经过SE重标定后这个向量中重要通道的数值被放大次要通道的数值被压缩。LSTM的输入门和遗忘门在计算时自然会对被放大的维度产生更强的响应。这带来的直接好处是LSTM不再需要从零开始学习哪些通道更重要这件事卷积层加SE已经把这一步做完了。梯度回传时也不必再费力去调整卷积核去弥补通道间的区分度不足整个网络各司其职。在实际组装时LSTM的OutputMode我设成了last也就是说只取最后一个时间步的隐状态作为整个序列的摘要向量再接Dropout和全连接分类。如果你的任务不是整条序列分类而是每个时间步都要预测那就要改成sequence输出。分类场景下用last更稳因为LSTM经过几十步迭代后最后一个隐状态已经隐含了整个序列的信息再用全序列输出去做均值池化反而容易引入尾部噪声。3. MATLAB里从零搭建SE层封装与网络装配3.1 统一dlarray维度标记别让顺序问题毁掉整个模型MATLAB和Python的一个显著区别是Python的深度学习框架通常靠约定维度顺序而MATLAB在自定义网络和训练循环里要求你用dlarray的维度标签来显式声明。比如CBT就表示第一个维度是通道、第二个是批量、第三个是时间。如果你的数据是普通的N×T×C矩阵读进来之后必须先做一次permute转成C×N×T否则网络会把时间步当成通道、把通道当成时间步训练出来完全没法看。我建议从一开始就养成固定格式的习惯。数据预处理阶段统一输出C×N×T的数值数组然后调用dlarray(X, CBT)包一层。尤其是当你要自己写SE层、LSTM层混合组装时维度标记不统一报错信息往往非常抽象排查半天才发现是维度顺序反了。3.2 用classdef封装一个简洁的SE层MATLAB的Deep Learning Toolbox里没有现成的SE层需要自己写一个继承了nnet.layer.Layer的类。以下是我在项目里实际使用的核心框架去掉了不少与业务无关的检查代码保留主干逻辑classdef seLayer nnet.layer.Layer % SE通道注意力层输入输出格式均为 CBT properties (Learnable) W1 % 压缩权重: [C/r, C] b1 % 压缩偏置: [C/r, 1] W2 % 恢复权重: [C, C/r] b2 % 恢复偏置: [C, 1] end properties Reduction end methods function layer seLayer(numChannels, reduction, name) layer.Name name; layer.Description Squeeze-and-Excitation layer; layer.Reduction reduction; r reduction; % 这里用简单正态分布初始化实际可按Xavier初始化 layer.W1 dlarray(0.01 * randn(numChannels / r, numChannels)); layer.b1 dlarray(zeros(numChannels / r, 1)); layer.W2 dlarray(0.01 * randn(numChannels, numChannels / r)); layer.b2 dlarray(zeros(numChannels, 1)); end function Z predict(layer, X) % X: C x B x T gap mean(X, 3); % 对时间维求均值: C x B s fullyconnect(gap, layer.W1, layer.b1); % 压缩 s relu(s); s fullyconnect(s, layer.W2, layer.b2); % 恢复 s sigmoid(s); % C x B s reshape(s, size(s, 1), size(s, 2), 1); % 扩成C x B x 1 weight repmat(s, 1, 1, size(X, 3)); % C x B x T Z X .* weight; end end end这里有个MATLAB版本相关的注意事项如果你的版本对自定义层支持自动微分只需要实现predict和forward如果版本较老可能还需要手动实现backward。我项目里使用的是较新的版本这个类可以直接参与dlnetwork和trainnet的训练流程。如果遇到not supported in custom layer之类的报错先确认版本再考虑补全反向传播。3.3 复用原CNN-LSTM主干只在中间做最小改造搭建完整网络的思路是替换不是推翻重来。保留原来CNN-LSTM的卷积块、LSTM层和分类头只在第二个卷积块输出和LSTM输入之间插入SE层。用layerGraph组织起来大概长这样lgraph layerGraph([ sequenceInputLayer(12, Name, input) convolution1dLayer(8, 32, Padding, same, Name, conv1) batchNormalizationLayer(Name, bn1) reluLayer(Name, relu1) maxPooling1dLayer(2, Stride, 2, Name, pool1) convolution1dLayer(5, 64, Padding, same, Name, conv2) batchNormalizationLayer(Name, bn2) reluLayer(Name, relu2) maxPooling1dLayer(2, Stride, 2, Name, pool2) seLayer(64, 8, se) % 插入SE模块通道数取conv2输出64 lstmLayer(64, OutputMode, last, Name, lstm) dropoutLayer(0.3, Name, drop) fullyConnectedLayer(8, Name, fc) softmaxLayer(Name, softmax) classificationLayer(Name, output) ]);这段结构里最需要注意的就是SE层的通道数必须和它前面卷积块的输出通道数一致。我最初接错了一次把通道数填成了输入信号的12导致SE在做压缩时维度对不上报错信息又是那种让人摸不着头脑的维度不匹配。后来加了一行注释每次搭网络都先算一遍通道数再填。3.4 训练配置与可复现设置网络搭好后训练配置直接影响SE模块能不能发挥效果。我用的是一套偏保守但稳定的配置rng(42); % 固定随机种子保证每次实验结果可复现 options trainingOptions(adam, ... InitialLearnRate, 0.001, ... MaxEpochs, 50, ... MiniBatchSize, 64, ... ValidationData, {XValidation, YValidation}, ... ValidationFrequency, 20, ... Shuffle, every-epoch, ... Plots, training-progress, ... Verbose, true);固定随机种子这一步很多人会忽略。尤其是加了SE之后模型多了两个全连接层的初始化随机性不固定种子的话你很难判断精度提升到底是SE的效果还是运气好。我的做法是先随机划分训练验证测试集再把划分时的随机种子一并固定下来这样不同模型之间的对比才有意义。4. 训练阶段最容易翻车的几个位置调参与实测对比4.1 学习率、BatchSize与SE新增参数的配合加了SE之后最直观的变化是模型多了一组全连接层参数。虽然有缩减比控制参数量但这一部分参数的优化节奏和主干卷积层不完全一致。我的经验是初始学习率保持0.001但要注意观察训练损失在前几个epoch有没有异常波动。SE模块里的两个全连接层需要经历一个校准期。前几个epoch里sigmoid输出的权重可能都集中在0.5附近相当于SE暂时是透明的对网络几乎没有影响。这是正常的。如果训练到十几个epoch之后损失还是明显高于基线模型那就要检查是不是学习率偏大导致SE的权重在早期就被推到饱和区sigmoid输出接近0或1梯度消失。另一种有效的配合是学习率衰减。我后来把训练策略改成了余弦退火式的学习率调整前中期保持0.001后期降到0.0001左右。SE模块在这种学习率规划下比固定学习率更容易找到精细的通道权重。4.2 BatchNorm与SE的先后顺序、权重初始化细节关于BatchNorm和SE的先后顺序我踩过一次坑。一开始我把SE插在BatchNorm之前结果训练非常不稳定。后来回想一下原因其实很清晰BatchNorm的作用是让每层输入保持稳定的均值和方差如果SE在BN之前计算通道权重那么它看到的特征分布还在被BN不断调整权重学起来自然不稳定。正确的顺序应该是Conv - BN - ReLU - Pool - SE让SE拿到的是已经归一化过的、分布相对稳定的特征。SE层内部不需要再额外加BN两个全连接层本身已经足够轻量再堆BN反而容易引入多余的超参数和不稳定因素。另外SE层的权重初始化不能随便用零初始化。如果W1和W2初始化为全零那么中间层的输出会被置零经过sigmoid后权重变成0.5这倒不会让网络崩掉但在早期梯度回传时SE的两个全连接层几乎收不到有效梯度网络相当于白加了一个模块。我用的是0.01 * randn这样的小随机数初始化保证每个通道的初始权重略有差异。4.3 三个数据集上的CNN-LSTM vs CNN-LSTM-SE实测为了验证SE到底是不是加了个寂寞我在三个差异比较大的时序分类数据集上分别跑了CNN-LSTM基线和CNN-LSTM-SE两个模型。数据集信息如下实验数据样本量类别数通道数时间步某旋转机械振动信号4200812200某多通道压力时序180058150某环境温湿度序列300036120每个数据集上两个模型使用相同的训练验证测试划分相同超参数配置。结果对比如下数据集CNN-LSTM验证准确率CNN-LSTM-SE验证准确率训练时间变化旋转机械振动信号91.4%94.2%增加约18%多通道压力时序87.3%91.6%增加约12%环境温湿度序列95.1%95.9%增加约15%从这三组数据能看出两个规律。第一SE模块在特征复杂度高、类别区分难度大的数据集上提升更明显第一个数据集提升了近3个百分点第二在原本就比较好分的简单任务上SE带来的收益有限只有0.8个百分点。这符合直觉通道注意力解决的是通道间重要性不明确的问题如果原始特征已经很干净那它的发挥空间自然就小。提示如果加了SE之后验证集精度反而下降不要急着调学习率。第一件事是检查sigmoid输出权重的分布。如果大量权重集中在0.5附近说明SE还没有被有效训练起来如果集中在0和1两端说明模型过拟合了通道权重可以尝试增大缩减比r或者加大Dropout。我还对比了参数量。以第一个数据集为例基线CNN-LSTM总参数量大约28万加了SE之后大约33万多出来的正好是SE两个全连接层的参数量。这个增加幅度总体可控换来的是接近3个百分点的精度提升性价比是相当高的。5. 替换式改造的迁移经验与适用边界5.1 用最小改动替换已有基线的完整步骤标题里提到的替换自其实就是这种渐进式改造。如果你手上已经有一个训练好或者正在训练的CNN-LSTM模型想升级成带SE的版本我的建议是不要从头开始重新训练而是走下面这几步找出原网络中最后一个卷积块的输出节点记下它的输出通道数。在卷积块和LSTM之间插入SE层通道数填刚才记下的数缩减比先用8。加载原CNN-LSTM已训练好的权重参数把SE层作为新初始化的模块。把初始学习率调低到1e-4用迁移学习的方式微调整个网络。这样原网络已经学到的特征提取能力能保留下来SE层只需要在已有特征的基础上学习通道权重而不是从零开始重新学。我在实践里用这种微调方式只跑了15个epoch就超过了原模型训练50个epoch的性能效果很明显。这里的关键是把学习率压低否则新加的SE层参数梯度较大会把原网络参数冲得七零八落。5.2 出现这三个信号时SE大概率能帮上忙结合几次实验的经验如果你正在做的时序分类任务出现了下面三个信号中的任意两个就可以认真考虑加SE混淆矩阵里总有一两组类别反复互相误分而且这两个类别在原始信号时域波形上肉眼都很难分辨。卷积层的输出通道数比较多一般至少大于16。通道数太少时SE重标定的空间被压缩效果不明显。训练曲线在验证集上出现很长的平台期模型的参数调整已经很难突破。反过来如果样本量非常小比如只有三五百条加SE就要慎重。SE新增的参数虽然不多但在小样本场景下仍然可能加重过拟合。我自己在样本量小于500的任务上试过一次加了SE之后验证集反而不如基线这时候可以考虑把缩减比调大到32同时配合更强的Dropout或者干脆放弃SE改用更简单的时间维注意力。5.3 进一步扩展的方向与部署注意点SE只是通道注意力的一种实现在这个骨架上还能继续做文章。比如可以把SE替换成更轻量的通道注意力变体减少一个全连接层也可以在LSTM输出端再接一层时间步注意力让模型在决策时不仅关注通道重要性还能关注哪些时间步更关键。这两者叠加在复杂长序列上通常能进一步提升分类精度。另外提醒一句部署问题。MATLAB导出模型时自定义层不一定都能被代码生成器支持SE层虽然在训练时表现良好但如果你打算把模型部署到嵌入式设备要先确认目标部署方式是否支持自定义层或者提前把SE模块的运算手动重写成不依赖自定义层的算子组合。这一步经常被忽略往往是训练验证都通过了临到部署才发现模型导不出去。最后分享一点个人体会。我在替换模型时习惯一次只加一个改动先把SE插进去跑通确认提升后再考虑其他注意力变体。SE不是任何场景的万能药但它是CNN-LSTM时序分类里成本最低、收益最稳的注意力形式之一。对于还在被基线模型平台期困扰的技术人员值得先拿自己的数据跑一版对比实验用二十分钟的程序改动换一个明确的精度增量这笔账怎么算都划算。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑