1. 从"调参靠玄学"到"算法自己找路":AHA-Transformer-LSTM到底在解决什么
做故障识别这行的朋友大概都有个共同感受:模型结构选好了、数据也清洗完了,最后卡在超参数上——学习率设0.001还是0.0005,LSTM隐藏层单元数取64还是128,Transformer头数用4还是8,这些组合排列下来能跑死人。更让人头疼的是,工业场景下的故障信号往往是非平稳、多工况、强噪声的,手工调出来的一组参数在实验室数据集上表现不错,换到现场设备上直接崩掉。
这个项目要干的事情,说白了就是用人工蜂鸟优化算法(Artificial Hummingbird Algorithm, AHA)去自动搜索Transformer-LSTM混合网络的超参数组合,让模型自己找到一组"在验证集上最能打"的配置,而不是靠人一遍遍试。关键词里的AHA、Transformer、LSTM、Matlab、故障识别,五个词其实勾勒出了一条完整的技术链路:AHA负责寻优,Transformer负责捕捉全局依赖,LSTM负责建模时序动态,Matlab负责工程落地,故障识别是最终要交付的业务目标。
适合谁来参考这份内容?如果你正在做旋转机械故障诊断、电力设备状态监测、工业过程异常检测这类课题,手头有Matlab和深度学习工具箱,想找一个"结构不太复杂但创新点说得过去"的方案,那这套AHA-Transformer-LSTM的组合值得仔细看看。它不需要你从零推导注意力机制的数学公式,但要求你对LSTM的输入输出维度、Transformer的编码器结构有基本的工程认知。
我先说一个反直觉的结论:在这个方案里,AHA的优化效果好不好,很大程度上不取决于AHA本身,而取决于你怎么定义它的搜索空间和适应度函数。很多人把AHA当成一个万能的黑盒优化器,随便扔进去十几个参数让它搜,结果迭代几百代也不收敛。真正有效的做法是把搜索空间压缩到3到5个关键参数,适应度函数直接用验证集的分类准确率或者F1分数,这样AHA才能在可接受的代数内找到有意义的解。这个观点贯穿全文,后面会反复展开。
2. 三个模块各司其职:Transformer、LSTM、AHA的技术分工拆解
2.1 Transformer在故障识别里到底捕捉什么
故障信号的一个典型特点是:局部突变往往由全局工况决定。比如轴承外圈故障的冲击特征,在时域上表现为周期性的脉冲,但这个周期会随着转速变化而漂移;如果只看局部窗口,很容易把转速波动误判成故障。Transformer的自注意力机制恰好擅长处理这种"长距离依赖"——它能让模型在判断某个时间点的状态时,直接"看到"整个序列中其他所有时间点的信息,并给不同位置分配不同的注意力权重。
在Matlab里实现Transformer编码器,核心是三个矩阵:Query、Key、Value。输入序列经过线性映射得到这三个矩阵,然后计算注意力分数:
Attention(Q, K, V) = softmax(Q * K' / sqrt(d_k)) * V其中d_k是Key的维度,除以sqrt(d_k)是为了防止内积过大导致softmax梯度消失。多头注意力就是把这个过程并行做h次,每次用不同的线性映射,最后拼接起来再映射一次。Matlab的Deep Learning Toolbox从R2021a开始提供了attention函数和selfAttentionLayer,可以直接调用,不需要自己手写矩阵运算。但要注意,selfAttentionLayer默认不支持因果掩码,做故障识别这种不需要预测未来的任务,用双向注意力反而更合适。
2.2 LSTM补的是Transformer的哪块短板
Transformer虽然全局视野好,但它对时序顺序的敏感度不如LSTM。自注意力机制本身是置换不变的——如果把输入序列的时间步打乱,注意力分数不变,只是位置编码会变。而故障信号中,冲击的上升沿和下降沿、振动的衰减过程,这些局部时序模式对判别故障类型非常关键。LSTM的门控机制(遗忘门、输入门、输出门)天然适合建模这种"当前状态依赖前一状态"的动态过程。
所以这个混合结构的逻辑是:Transformer先对原始信号做全局特征提取,输出一个融合了全序列信息的特征序列;LSTM再对这个特征序列做时序建模,捕捉局部动态变化;最后接全连接层做分类。在Matlab里,可以用sequenceInputLayer接收原始时序,接若干selfAttentionLayer和layerNormalizationLayer,再接lstmLayer,最后接fullyConnectedLayer和softmaxLayer。需要注意的是,lstmLayer的OutputMode要设为'last',因为分类任务只需要最后一个时间步的输出。
2.3 AHA凭什么比粒子群、遗传算法更适合这个场景
人工蜂鸟优化算法是2022年提出的一种新型元启发式算法,模拟蜂鸟的三种觅食行为:轴向飞行、对角线飞行和全向飞行。相比粒子群(PSO)和遗传算法(GA),AHA有几个特点值得关注:
- 搜索模式更丰富:轴向飞行负责局部精细搜索,对角线飞行负责中等范围探索,全向飞行负责全局跳跃,三种模式自适应切换,不容易陷入局部最优。
- 参数更少:AHA的核心控制参数只有访问表大小和飞行模式切换概率,不像GA需要设置交叉率、变异率、选择策略等一堆参数。
- 收敛速度:在低维优化问题上(比如5到10个参数),AHA通常比PSO快,因为它的轴向飞行本质上是一种坐标下降策略,对每个维度单独精细搜索。
但AHA也不是没有缺点。在高维问题上(超过20个参数),它的全向飞行容易导致搜索方向过于随机,收敛变慢。所以前面才强调,搜索空间要压缩到3到5个关键参数,这样AHA的优势才能发挥出来。
| 算法 | 核心参数数量 | 适合维度 | 收敛特点 | 在故障识别中的表现 |
|---|---|---|---|---|
| PSO | 3-4个 | 10-30维 | 前期快,后期易震荡 | 容易早熟,需要调惯性权重 |
| GA | 4-6个 | 20-50维 | 稳定但慢 | 适合离散参数,连续参数效率低 |
| AHA | 2-3个 | 3-15维 | 低维快,高维慢 | 适合本项目的超参数搜索 |
3. 把搜索空间定下来:AHA到底该优化哪几个参数
3.1 不是所有超参数都值得让AHA去搜
很多人第一次做超参数优化,恨不得把学习率、批大小、隐藏层单元数、注意力头数、dropout率、L2正则系数全部扔给优化算法。结果搜索空间维度爆炸,AHA迭代500代还在原地打转。我的经验是:只让AHA优化那些"对性能影响大且难以凭经验确定"的参数。
具体到这个项目,我建议AHA只搜以下4个参数:
- 初始学习率:范围设为1e-4到1e-2,取对数均匀分布。学习率对训练稳定性的影响是决定性的,设大了不收敛,设小了训练慢。
- LSTM隐藏层单元数:范围设为32到256,取整数。这个参数直接决定模型容量,太小欠拟合,太大过拟合。
- Transformer注意力头数:范围设为2到8,取整数。头数影响模型捕捉多种依赖关系的能力,但头数太多会导致参数量激增。
- Dropout率:范围设为0.1到0.5,取连续值。这个参数控制正则化强度,对泛化性能影响很大。
批大小我一般固定为32或64,因为批大小对最终性能的影响相对较小,而且它和硬件显存直接相关,不适合让优化算法去试。优化器固定用Adam,因为Adam对学习率的鲁棒性比SGD好,更适合和AHA配合。
3.2 适应度函数的设计比优化算法本身更重要
适应度函数决定了AHA往哪个方向搜。最直接的做法是用验证集分类准确率作为适应度,但这样做有个隐患:如果验证集类别不平衡,准确率会偏向多数类。比如正常样本占90%,故障样本占10%,模型全预测正常也能拿90%准确率,但这样的模型毫无意义。
更稳妥的做法是用验证集上的宏平均F1分数作为适应度。宏平均F1是先算每个类别的F1,再取平均,对类别不平衡不敏感。在Matlab里,可以用confusionmat函数得到混淆矩阵,然后手写几行代码计算宏平均F1:
function f1 = macroF1(YTrue, YPred) C = confusionmat(YTrue, YPred); numClasses = size(C, 1); f1 = 0; for i = 1:numClasses TP = C(i, i); FP = sum(C(:, i)) - TP; FN = sum(C(i, :)) - TP; precision = TP / (TP + FP + eps); recall = TP / (TP + FN + eps); f1 = f1 + 2 * precision * recall / (precision + recall + eps); end f1 = f1 / numClasses; end还有一个细节:适应度评估要用K折交叉验证的平均值,而不是单次验证集的结果。因为单次划分的验证集可能恰好比较难或比较容易,导致AHA搜出来的参数过拟合到某一次划分。我通常用3折交叉验证,每折训练20个epoch,取3折F1的平均值作为适应度。这样虽然计算量大了3倍,但搜出来的参数鲁棒性好很多。
3.3 AHA的种群规模和迭代代数怎么定
AHA的种群规模一般设为20到50,迭代代数设为50到100。这个项目里,我建议种群规模30,迭代代数60。为什么?因为每次适应度评估都要训练一次Transformer-LSTM模型,如果种群太大、代数太多,总训练次数就是30乘以60等于1800次,即使每次只训练20个epoch,在单张GPU上也要跑好几个小时。
一个实用的技巧是:先用小种群、少代数跑一轮,看看适应度曲线的趋势。如果前10代适应度就基本平稳了,说明搜索空间可能太小或者适应度函数区分度不够;如果60代还在明显上升,说明可以适当增加代数。我实测下来,这个项目的适应度曲线通常在30到40代左右进入平台期,60代足够收敛。
4. Matlab里的工程实现:从数据到模型的完整链路
4.1 数据准备:故障信号怎么组织成Transformer-LSTM能吃的格式
故障识别常用的数据集有凯斯西储大学轴承数据集、帕德博恩轴承数据集、以及各种自建的振动信号数据集。不管用哪个,数据组织的逻辑是一样的:把连续振动信号切分成固定长度的样本,每个样本对应一个故障标签。
假设原始信号采样频率是12kHz,故障特征频率通常在几百赫兹到几千赫兹,一个样本至少包含2到3个冲击周期才有判别力。我通常取样本长度为1024个点,如果采样频率12kHz,对应约85毫秒,足够覆盖多个冲击周期。切分时用重叠采样,重叠率50%,这样可以增加样本数量,同时避免相邻样本差异过大。
在Matlab里,数据组织的核心代码大概长这样:
% 假设signal是原始振动信号,label是对应标签 sampleLength = 1024; overlap = 512; step = sampleLength - overlap; numSamples = floor((length(signal) - sampleLength) / step) + 1; X = zeros(sampleLength, 1, 1, numSamples); Y = categorical(zeros(numSamples, 1)); for i = 1:numSamples startIdx = (i - 1) * step + 1; endIdx = startIdx + sampleLength - 1; X(:, 1, 1, i) = signal(startIdx:endIdx); Y(i) = label; end注意这里X的维度是[sampleLength, 1, 1, numSamples],对应Matlab深度学习工具箱要求的[序列长度, 通道数, 批大小]格式。如果是多通道信号(比如三轴加速度计),通道数就是3。
4.2 Transformer-LSTM网络的Matlab搭建细节
Matlab从R2021a开始支持selfAttentionLayer,但直接堆叠多层self-attention容易梯度爆炸,需要配合layerNormalizationLayer和残差连接。下面是一个可用的网络结构:
numHeads = 4; % 这个由AHA优化 numKeyChannels = 64; dropoutRate = 0.3; % 这个由AHA优化 lstmUnits = 128; % 这个由AHA优化 layers = [ sequenceInputLayer(1, 'Name', 'input') % Transformer编码器部分 selfAttentionLayer(numHeads, numKeyChannels, 'Name', 'attn1') layerNormalizationLayer('Name', 'ln1') dropoutLayer(dropoutRate, 'Name', 'drop1') additionLayer(2, 'Name', 'add1') % LSTM时序建模部分 lstmLayer(lstmUnits, 'OutputMode', 'last', 'Name', 'lstm') dropoutLayer(dropoutRate, 'Name', 'drop2') % 分类输出 fullyConnectedLayer(numClasses, 'Name', 'fc') softmaxLayer('Name', 'softmax') classificationLayer('Name', 'output') ]; % 添加残差连接 lgraph = layerGraph(layers); lgraph = connectLayers(lgraph, 'input', 'add1/in2');这里有个坑要注意:selfAttentionLayer的输出维度和输入维度可能不一致,如果要做残差连接,需要确保numKeyChannels等于输入通道数,或者加一个线性映射层把维度对齐。我通常把numKeyChannels设为和输入通道数一样,这样残差连接可以直接相加。
4.3 AHA优化循环的Matlab实现框架
AHA的主循环需要和网络训练交替进行。每一代中,每只蜂鸟代表一组超参数,用这组参数训练网络,在验证集上评估适应度,然后根据AHA的三种飞行模式更新位置。核心框架如下:
% AHA参数 popSize = 30; maxIter = 60; dim = 4; % 优化4个参数 lb = [1e-4, 32, 2, 0.1]; % 下界 ub = [1e-2, 256, 8, 0.5]; % 上界 % 初始化种群 pop = repmat(lb, popSize, 1) + rand(popSize, dim) .* repmat(ub - lb, popSize, 1); fitness = zeros(popSize, 1); % 评估初始种群 for i = 1:popSize fitness(i) = evaluateFitness(pop(i, :), XTrain, YTrain, XVal, YVal); end % AHA主循环 for iter = 1:maxIter for i = 1:popSize % 轴向飞行、对角线飞行、全向飞行 % 根据当前迭代次数和随机数选择飞行模式 % 更新位置并评估适应度 % 如果新位置更好,则更新 end % 记录当前最优 [bestFitness, bestIdx] = min(fitness); bestSolution = pop(bestIdx, :); fprintf('Iteration %d: Best Fitness = %.4f\n', iter, bestFitness); endevaluateFitness函数里做的事情就是:把AHA传来的4个参数解码成学习率、LSTM单元数、注意力头数、dropout率,然后用这些参数构建网络、训练、在验证集上算宏平均F1,返回1 - F1作为适应度(因为AHA默认是最小化适应度)。
这里有个工程上的细节:训练网络时要用trainingOptions设置'ValidationData'和'ValidationFrequency',并开启'Plots', 'none',因为AHA循环里会训练几百次网络,如果每次都弹训练进度图,Matlab会卡死。另外,'ExecutionEnvironment'设为'gpu'可以大幅加速,如果没有GPU就设'cpu',但训练时间会成倍增加。
5. 实测中那些文档不会告诉你的坑
5.1 适应度函数震荡导致AHA不收敛
我第一次跑这个方案时,发现AHA的适应度曲线上下震荡,最优解一直在变。排查后发现原因是:每次训练网络时,权重初始化是随机的,导致同一组超参数在不同次评估中得到的F1分数差异很大。这种随机性淹没了超参数本身的优劣信号,AHA根本分不清哪组参数是真的好。
解决办法有两个:一是固定随机种子,在每次评估前调用rng(42),这样同一组超参数每次评估的结果完全一致;二是增加评估次数取平均,每组超参数训练3次取平均F1。我推荐第一种,因为计算量小,而且能保证AHA的搜索过程可复现。固定种子后,适应度曲线立刻变得平滑,AHA在25代左右就收敛到了不错的结果。
5.2 Transformer层数不是越多越好
一开始我想着"Transformer不是越深越强吗",堆了4层self-attention。结果训练loss降到0.1就降不下去了,验证集准确率只有70%出头。后来把层数降到1层,验证集准确率反而升到了92%。原因在于:故障信号的特征相对简单,1层注意力足够捕捉全局依赖,层数多了反而过拟合。而且每层self-attention的参数量是4乘以d_model的平方,4层下来参数量翻了好几倍,在小样本数据集上必然过拟合。
所以我的建议是:Transformer编码器只用1到2层,LSTM只用1层。这个项目的创新点在于AHA优化,不在于网络有多深。把网络做浅、把优化做精,比堆深度更容易出结果。
5.3 AHA的边界处理不能简单截断
AHA更新位置后,可能会超出搜索空间的上下界。最简单的做法是直接截断到边界值,但这样做有个问题:如果最优解恰好在边界附近,截断会导致大量个体堆积在边界上,降低种群多样性。更好的做法是反射边界:如果超出上界,就反射回上界以内,反射距离等于超出距离。Matlab里可以这样实现:
function x = reflectBounds(x, lb, ub) for j = 1:length(x) if x(j) < lb(j) x(j) = lb(j) + (lb(j) - x(j)); elseif x(j) > ub(j) x(j) = ub(j) - (x(j) - ub(j)); end % 如果反射后仍然越界,再截断 x(j) = max(x(j), lb(j)); x(j) = min(x(j), ub(j)); end end这个细节看起来不起眼,但实测下来,用反射边界比直接截断的收敛速度快了大约15%。
5.4 学习率搜索范围要用对数尺度
学习率的搜索范围是1e-4到1e-2,如果直接在均匀分布上采样,大部分样本会落在1e-3到1e-2之间,而1e-4到1e-3之间的样本很少。但恰恰是1e-4到1e-3这个区间,往往是模型表现最好的区域。所以学习率的初始化要用对数均匀分布:
logLr = log10(lb(1)) + rand() * (log10(ub(1)) - log10(lb(1))); lr = 10^logLr;这样采样出来的学习率在1e-4到1e-2之间是对数均匀的,每个数量级都有足够的样本。
6. 这套方案还能怎么改:几个值得尝试的扩展方向
6.1 把AHA换成多目标优化
现在的适应度函数是宏平均F1,只优化了分类性能。但实际工程中,模型大小和推理速度也很重要。如果能把F1和参数量同时作为优化目标,用多目标AHA搜出一组Pareto最优解,就能根据部署环境的算力灵活选择。Matlab的gamultiobj函数支持多目标遗传算法,但AHA的多目标版本需要自己实现,核心思路是维护一个外部存档,保存非支配解。
6.2 用AHA同时优化网络结构和超参数
现在的方案是网络结构固定(1层Transformer加1层LSTM),只优化超参数。更进一步的做法是让AHA同时搜索网络层数、每层类型(Transformer还是LSTM)、跳跃连接方式。这样搜索空间会变大,但AHA的全向飞行模式恰好适合这种离散和连续混合的搜索空间。不过要注意,搜索空间变大后,种群规模和迭代代数都要相应增加,计算成本会显著上升。
6.3 把特征提取也纳入优化
现在的方案是直接把原始振动信号喂给网络,让网络自己学特征。但在故障识别领域,手工特征(时域统计量、频域谱峰、时频域小波能量)往往比原始信号更有效。可以先用AHA优化一组手工特征的权重,把加权后的特征和原始信号一起输入网络。这样既保留了深度学习的端到端优势,又融入了领域知识。
我在实际使用中发现,AHA优化超参数这件事,最耗时的部分不是AHA本身的迭代,而是每次适应度评估时的网络训练。如果能把单次训练时间从5分钟压缩到1分钟,整个优化过程就能从8小时缩短到1.5小时。压缩训练时间的几个实用技巧:用GPU加速、把批大小设大一点(64或128)、用早停策略(验证集loss连续5个epoch不降就停)、把数据预加载到GPU内存里避免重复传输。这些工程细节看起来琐碎,但决定了你是一天跑完实验还是一周跑完实验。