最近在弄一个工业时序数据的分类预测项目,样本量不大,但序列之间的顺序依赖非常关键。试了一堆传统机器学习模型,精度始终卡在瓶颈上,后来把注意力机制搬过来,提升是有的,但新的麻烦也来了——Transformer的超参数多到让人头疼。编码器层数、注意力头数、嵌入维度、dropout、学习率,随便一个组合变动都能让验证集精度上下波动好几个点。手工调参太看缘分,网格搜索又扛不住训练代价,于是就把目光投向了粒子群优化算法(PSO)。这几个月断断续续在Matlab里把PSO和Transformer拼在一起,做成了一个分类预测模型,效果比手调好不少。今天把这套方案的完整思路、关键代码以及在实操中踩过的坑整理出来,给那些想在Matlab里用Transformer做分类预测、又不想被调参折磨的朋友做个参考。
1. 为什么非要用PSO去优化Transformer:分类预测场景下的两个痛点
1.1 Transformer在分类预测里的优势与参数敏感
Transformer最早是给自然语言处理设计的,但用在序列分类预测上同样成立。它的核心是自注意力机制,可以一次性建模序列中任意两个位置之间的依赖关系,这恰好补上了RNN/LSTM逐步传递信息导致的长程记忆丢失问题,也比CNN只看局部感受野要更全局。对很多分类预测任务来说,比如机械设备振动信号故障分类、电力负荷类别判断、股票涨跌方向预测,模型的输入本质上就是一段序列,Transformer能直接捕捉到关键时间点之间的相互影响,这是它优于传统模型的地方。
但Transformer有个让人头大的特性:参数极度敏感。嵌入维度取32还是64,注意力头数取4还是8,前馈网络维度是128还是256,dropout是0.1还是0.3,初始学习率是1e-3还是1e-4,这些参数互相耦合,而且每一组组合都在非线性地影响最终结果。我自己手调的时候经常遇到这样的情况:把学习率调小,验证集F1涨了1个点,于是兴冲冲去改层数,结果精度又掉回原样。这种“按下葫芦浮起瓢”的体验,经历过的人都懂。如果数据量再小一点,模型对dropout的取值就更敏感,稍微调大一点就欠拟合,调小一点就过拟合,手动搜索空间非常痛苦。
1.2 PSO适合做什么:全局搜索与超参数优化
粒子群优化算法是一种经典的群体智能搜索方法,灵感来自鸟群觅食。每个粒子代表搜索空间中的一个候选解,通常是一组超参数组合,粒子有自己的位置和速度,在每一轮迭代中,粒子会朝着两个方向飞行:一个是它自己历史找到的最优位置(pbest),另一个是整个群体历史找到的最优位置(gbest)。这个过程不需要梯度信息,也不要求目标函数有解析表达式,所以特别适合Transformer这类“参数和精度之间没有闭式函数”的黑箱优化场景。
相比网格搜索,PSO不需要枚举所有组合,计算量小得多,尤其在高维连续空间中优势明显。相比贝叶斯优化,PSO在Matlab里的实现非常简单,不带工具箱也能自己写,而且不容易因为核函数选择不当而失效。我在实际使用中感觉,PSO在搜索维度不超过10的情况下,收敛速度和稳定性都不错,而Transformer主要可调的超参数正好在这个量级。当然,PSO也有自己的毛病,比如容易早熟收敛,但这个问题可以通过调整惯性权重和粒子数来缓解。总体而言,PSO是“时间有限、想自动化调参”时的一个很划算的选择。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 模型搭建前的数据准备与评估口径:这一步决定了PSO能不能收敛
2.1 输入序列的构造方式
不管用什么网络做分类预测,送到Transformer里的输入都必须是固定形状的张量。假设原始数据有N个样本,每个样本是一条长度为L的时间序列,每个时间步有F个特征,那么输入张量尺寸一般是N×L×F,其中N是样本数,L是序列长度,F是特征维度。这里最容易翻车的是把L和F搞混。Transformer内部做的是特征维度的线性变换,注意力计算的是序列位置之间的权重,所以你的数据到底哪个维度代表时间步、哪个维度代表特征,必须从一开始就理清楚。
在Matlab里,深度学习工具箱通常接受numObservations×numTimeSteps×numFeatures的排布(在trainNetwork里也支持cell数组存放变长序列)。如果所有样本等长,直接用数值数组就可以;如果长度不齐,需要用cell数组或者对短序列做zero-padding。分类预测场景下,为了省事,我建议用固定长度的滑动窗口来截取样本。例如一段连续传感器信号,每隔step个点滑动取一个长度L的窗口,窗口对应的标签就是该窗口末端的类别。这样既增加了样本量,又保留了序列顺序信息。
matlab复制% 假设原始信号是signal,对应的类别标签序列是labels
winLen = 48;
step = 10;
numSamples = 0;
for t = 1:step:(length(signal)-winLen+1)
numSamples = numSamples + 1;
end
X = zeros(numSamples, winLen, 1);
Y = categorical(zeros(numSamples, 1));
idx = 1;
for t = 1:step:(length(signal)-winLen+1)
X(idx,:,1) = signal(t:t+winLen-1);
Y(idx) = labels(t+winLen-1);
idx = idx + 1;
end
注意,如果原始数据是多特征,那么第三维就是特征个数,窗口截取的时候要把所有特征都保留下来。
2.2 分类指标的选择与训练/验证集划分
PSO的适应度函数需要量化评估一组超参数的好坏,所以评估指标必须在搜索开始前就定下来。对于二分类,可以用准确率、F1分数、AUC等;多分类则常用总体准确率和宏平均F1。我自己的习惯是使用验证集上的宏平均F1作为适应度,因为实际拿到的分类数据往往类别不平衡,只看准确率很容易被多数类带偏。比如二分类中正类只占10%,模型全部预测负类也能拿到90%的准确率,但F1会很低,这才更真实反映模型性能。
验证集的划分方式在时序任务里是个大坑。千万不要用传统的随机打乱划分,因为相邻时间点的样本高度相关,随机划分会把“未来”信息混进训练集,造成数据泄漏。正确做法是按时间顺序切分:例如前70%的样本做训练集,后30%做验证集。PSO搜索时就在这个验证集上算F1。如果你还有独立的测试集
