ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PSO-Transformer多特征分类超参数自动搜索的Matlab实现

PSO-Transformer多特征分类超参数自动搜索的Matlab实现

简介:这是一份面向具有一定编程基础的研究人员与数据科学家的Matlab项目实例,核心是展示如何利用粒子群优化算法自动搜索Transformer模型的最优超参数,从而解决多特征分类中的高维数据处理难、训练耗时长和超参数空间庞大等问题。文档从项目背景出发,依次说明自注意力机制、多头注意力机制与位置编码的核心原理,并给出PSO粒子更新公式、目标函数定义以及可运行的Matlab示例代码,覆盖数据加载、预处理、参数设置与优化过程,方便读者对照实现并迁移到自然语言处理、医疗数据分析、图像处理、金融预测等场景。资源为单个docx文档,压缩包约38KB,结构按背景、挑战、解决方案、创新点与应用领域分层组织,信息密度高。已有91人学习下载,适合希望提升分类模型性能并平衡训练效率的开发者快速参考。

1. 让 Transformer 自己找超参:PSO-Transformer 到底在解决什么问题

「Transformer 不是给文本用的吗,拿来做多特征分类预测是不是有点炫技?」这是我在介绍 PSO-Transformer 时最常听到的质疑。恰好相反,当输入数据里有几十个特征且互相耦合,Transformer 的多头注意力能把特征之间的依赖关系显式建模出来,而粒子群优化算法(PSO)负责把这些网络超参数从「手调到天亮」变成「自动搜索」。本文就是一个可以直接套用的 Matlab 实例:用 PSO 自动寻找 Transformer 的最优结构参数与训练参数,完成多特征分类预测任务,包括模型描述、完整示例代码和训练中容易翻车的 5 个细节。适合正在做故障诊断、信用评分、传感器数据分类的读者,也适合被超参数折腾到没脾气的调参党。

2. PSO 与 Transformer 的耦合点:优化变量、目标函数与建模口径

2.1 粒子群优化算法(PSO)为什么适合这类黑盒组合问题

粒子群算法是一种模仿鸟群觅食的群体智能算法。每个粒子代表一组候选超参数组合,粒子在参数空间中飞行,速度更新由个体最优 pbest 和全局最优 gbest 共同决定。它不需要梯度信息,优化目标是「给定一组超参,训练网络后在验证集上得到的分类误差」。这个函数没有解析式,训练一次成本又高,属于典型的黑盒优化。

业内做 Transformer 超参数搜索,首选思路往往是贝叶斯优化,但贝叶斯优化在离散超参数和高维空间里容易话痨——采集函数算得慢,而且很难并行评估。PSO 的好处很直接:代码量小,天然支持并行评估多个粒子,离散变量取整后依然能收敛。更重要的是,Matlab 里没有现成的「深度学习超参数搜索」工具箱,但粒子群优化算法自带或者手写都不难,这也是这个项目实例能落地的前提。

这里必须澄清一个常见误解:标题说的「PSO 优化 Transformer」,不是让 PSO 去更新网络的权重。Transformer 的权重仍然由反向传播和 Adam 优化器更新,PSO 优化的是网络超参数。这个边界一旦搞错,整个工程结构都会乱——正确设计是外层跑 PSO 循环,内层跑训练循环,两层各司其职。

2.2 多特征分类预测的目标函数怎么定义

多特征分类预测的输入输出定义如下:原始特征矩阵为 X,大小 N×F,N 是样本数,F 是特征数;标签向量为 Y,取值 1 到 C,C 是类别数。为了把 Transformer 用起来,需要把原始表格式数据做滑窗处理,得到形如 [特征维, 窗口长度, 样本数] 的序列数据,然后由 Transformer 对窗口内的特征关系建模,最后接分类层输出类别分布。

目标函数是最小化验证集错误率,可以写成:

f(θ) = 1 − validationAccuracy

其中 θ 代表一组超参数。下面这张表是本项目 PSO 要搜索的典型决策变量,它们分为结构超参和训练超参两类:

变量含义搜索范围建议类型
InitialLearnRateAdam 初始学习率1e-4 ~ 1e-2连续
NumHeads多头注意力头数1 ~ 8离散
HiddenUnits前馈层/注意力投影维度16 ~ 128离散
MiniBatchSize批大小8 ~ 64离散
MaxEpochs最大训练轮数10 ~ 80离散
WindowLength滑窗长度8 ~ 32离散

目标函数里需要注意一点:验证集必须固定。如果每次粒子评估都重新随机划分数据,那同一组超参数两次评估结果可能差好几个百分点,粒子之间的优劣比较就失去了意义。常见做法是在 PSO 开始前一次性切好训练集、验证集和测试集,验证集只服务于超参数搜索,测试集最后才能碰。

2.3 网格搜索、贝叶斯优化与 PSO 的选型边界

很多读者会问:既然深度学习工具箱自带 Bayesian optimization 相关功能,为什么还要用 PSO?这里给一张选型表:

方法对离散变量支持并行评估实现成本适用场景
网格搜索自然支持容易低变量少于 3 个、搜索范围小
贝叶斯优化需要特殊处理弱中连续参数为主、评估次数极少
PSO取整或索引映射容易低中等维度、离散连续混合

我一般会这样判断:如果只调学习率和批大小两个参数,网格搜索够用;如果参数维度到 5 个以上,且里面有 NumHeads、HiddenUnits 这种离散量,PSO 的实现简单度和鲁棒性会比贝叶斯优化更好。贝叶斯优化在连续空间里建模代理函数时很漂亮,但遇到离散变量后,代理函数本身就会失真,反而不如 PSO 这种「无模型」方法来得皮实。

3. 用 Matlab 搭起可训练的多特征 Transformer 网络:数据、结构与最小代码

3.1 数据准备:从多特征表到滑窗样本

先把原始特征矩阵组织成 Transformer 能吃的格式。Matlab 深度学习工具箱的 sequenceInputLayer 默认接受 [特征维, 序列维, 观测维] 的三维数组。我做多特征分类时,最常用的做法是用滑窗把每条样本扩展成一个窗口,窗口内的每一列代表一个特征在同一时间片段上的取值。

% 假设 X 是 N×F 特征矩阵,Y 是 N×1 标签列 L = 16; % 滑窗长度,也作为 PSO 的候选变量之一 numSamples = size(X,1) - L + 1; XSeq = zeros(size(X,2), L, numSamples); % [F, L, N] YSeq = zeros(numSamples, 1); for i = 1:numSamples XSeq(:, :, i) = X(i:i+L-1, :)'; % 转置成 [F, L] YSeq(i, 1) = Y(i+L-1); end

这段代码里最关键的是转置操作。原始数据是按时间或记录顺序排列的 N×F 矩阵,X(i:i+L-1, :) 取出来是 L×F,要转置成 F×L 才能放进 XSeq。初学者最容易在这里翻车:如果直接把 L×F 塞进去,transformerLayer 会把窗口长度当成特征数,模型结构完全走样,训练出来往往是垃圾模型。

生成好 XSeq 后,按比例切分训练集、验证集和测试集:

rng(42); % 固定划分,让整个实验可复现 numTotal = size(XSeq, 3); idx = randperm(numTotal); numTrain = round(0.7 * numTotal); numVal = round(0.15 * numTotal); XTrain = XSeq(:, :, idx(1:numTrain)); YTrain = YSeq(idx(1:numTrain), 1); XVal = XSeq(:, :, idx(numTrain+1:numTrain+numVal)); YVal = YSeq(idx(numTrain+1:numTrain+numVal), 1); XTest = XSeq(:, :, idx(numTrain+numVal+1:end)); YTest = YSeq(idx(numTrain+numVal+1:end), 1);

注意这里刻意不用 randperm 之后再 shuffle,目的是保证每次跑脚本都得到同样的验证集。超参数搜索阶段用验证集选参数,最后用测试集汇报结果,这样测试集才真正算「没见过」的数据。

3.2 网络建模:内置 transformerLayer 与手写自注意力的取舍

如果你的 Matlab 版本自带 transformerLayer,网络定义非常短。这也是我推荐优先走的路,毕竟内置层经过官方优化,反向传播和显存管理都更省心:

numFeatures = size(XTrain, 1); numClasses = max(YTrain); layers = [ sequenceInputLayer(numFeatures, 'Name', 'input') transformerLayer(4, 64, 'Name', 'attn') % 4 个头,隐藏单元 64 globalAveragePooling1dLayer('Name', 'pool') % 压缩序列维 fullyConnectedLayer(numClasses, 'Name', 'fc') softmaxLayer('Name', 'softmax') classificationLayer('Name', 'output') ];

transformerLayer(4, 64) 的第一个参数是注意力头数,第二个参数是注意力内部的隐藏单元数。它内部会完成多头注意力、前馈网络、残差连接和层归一化,对外只暴露序列输入和序列输出。globalAveragePooling1dLayer 把 [特征维, 序列维, 观测维] 压缩成 [特征维, 1, 观测维],这样后续全连接层就拿到一个汇总了整段窗口信息的向量。

如果你的 Matlab 版本没有内置 transformerLayer,也不要急着放弃这个方向。手写一个单头自注意力层的前向计算并不复杂,核心逻辑就是 Q、K、V 三个线性投影加缩放点积注意力:

function dlZ = selfAttentionForward(dlX, Wq, Wk, Wv, dk) % dlX: [F, L, N],F 特征维,L 窗口长度,N 批大小 [~, L, ~] = size(dlX); % 把特征维转到最后,让每个时间步的特征向量排在行方向 Xt = permute(dlX, [2 1 3]); % [L, F, N] Q = pagemtimes(Xt, Wq); % [L, dk, N] K = pagemtimes(Xt, Wk); % [L, dk, N] V = pagemtimes(Xt, Wv); % [L, dk, N] scores = pagemtimes(Q, permute(K, [2 1 3])) / sqrt(dk); % [L, L, N] attn = softmax(scores, 'DataFormat', 'STB'); % 对序列维做 softmax dlZ = permute(pagemtimes(attn, V), [2 1 3]); % 回到 [F, L, N] end

这段代码把尺度缩放因子 sqrt(dk) 放在 scores 之后,避免点积数值过大导致 softmax 饱和。pagemtimes 是 Matlab 里做批矩阵乘的常用函数,它会把最后一个维度当成批维度,正好符合这里 N 个样本同时参与矩阵运算的需求。Wq、Wk、Wv 是 dk×dk 的可学习矩阵,需要作为网络参数传入训练过程;完整的多头实现只要把头维度拆开,循环调用上面的单头计算再拼接即可。

必须说明的是,内置 transformerLayer 和手写层在输出行为上有一些差异:内置层自带残差连接和前馈网络,手写单头注意力只是纯注意力。如果走手写路线,建议在注意力层后面自己补上 LayerNorm 和两层全连接,否则网络深度不够,表达能力会明显不足。

3.3 模型描述:位置编码、残差连接与分类头的组合细节

Transformer 模型在自然语言处理里离不开位置编码,因为文本是有顺序的。但在多特征分类预测场景里,输入窗口的每一行是不同特征,特征之间不存在天然的先后顺序,位置编码通常可以省掉。不过如果输入数据本质上是时间序列,比如传感器按照时间顺序采样得到的多通道数据,那窗口内的位置信息就至关重要,此时要在序列维上加一组可学习的位置编码,或者用 sin 余弦位置编码。

残差连接是 Transformer 能训练深的根基。内置 transformerLayer 已经处理了这部分,手写实现时不要漏掉:把注意力层的输入和输出相加,再做层归一化,后面接前馈网络时同样再来一次残差连接。训练过程中如果发现 loss 降不下去但梯度没有爆炸,先从残差连接和初始化下手,不要急着加学习率。

分类头部分,本项目用的是 globalAveragePooling1dLayer 接 softmax,而不是直接把最后一个时间步接全连接。原因很简单:多特征分类预测的窗口里,信息分散在整个窗口上,取平均值比单点采样更鲁棒。如果换成取最后一个时间步,模型会过度依赖窗口末端的特征,局部噪声的影响会被放大。

4. 把 PSO 接进训练循环:粒子编码、适应度评估与收敛控制

4.1 粒子编码方式与种群初始化

粒子位置本质上就是一个向量,每个维度对应一个超参数。连续参数直接映射,离散参数通过取整或者索引映射来处理。本项目采用 5 维粒子:

% 粒子位置 [lr, numHeads, hiddenUnits, miniBatchSize, maxEpochs] nvars = 5; lb = [1e-4, 1, 16, 8, 10]; % 下界 ub = [1e-2, 8, 128, 64, 80]; % 上界 % 初始化种群 nPop = 12; pos = lb + rand(nPop, nvars) .* (ub - lb); vel = zeros(nPop, nvars); % 初始速度为 0 pbestPos = pos; pbestFit = inf(nPop, 1); gbestPos = pos(1, :); gbestFit = inf;

初始化时让粒子均匀撒在搜索空间里比全部从下界出发要聪明得多,否则种群前期探索会集中在低学习率区域,收敛速度很慢。离散变量 numHeads 的初始值虽然是浮点数,但在适应度函数里会做 round 处理,这个细节放到下一节说明。

惯性权重 w 和加速常数 c1、c2 直接决定粒子的探索和开发平衡。这里给一组常用的保守参数:w 从 0.9 线性衰减到 0.4,c1 = c2 = 1.5。前 40% 迭代偏重全局探索,后 60% 迭代逐渐收敛到局部精细搜索。训练成本高时,建议把迭代次数压在 15 到 25 次之间,粒子数压在 12 左右,否则 GPU 或 CPU 都会烧得很痛。

4.2 适应度函数:训练、验证与返回打分

适应度函数是整个 PSO-Transformer 的枢纽。每个粒子都要在这里被翻译成一组超参数、构建网络、完成训练、输出验证错误率。我用一个独立的函数文件 psoFitness.m 来承载这个过程。

function valErr = psoFitness(params, XTrain, YTrain, XVal, YVal) % 从粒子位置解析超参数 lr = params(1); numHeads = max(1, min(8, round(params(2)))); hiddenUnits = max(16, min(128, round(params(3)))); miniBatchSize = max(8, min(64, round(params(4)))); maxEpochs = max(10, min(80, round(params(5)))); % 固定随机种子,避免同一超参评估结果抖动 rng(42); % 构建 Transformer 网络 numFeatures = size(XTrain, 1); numClasses = double(max(YTrain)); layers = [ sequenceInputLayer(numFeatures, 'Name', 'input') transformerLayer(numHeads, hiddenUnits, 'Name', 'attn') globalAveragePooling1dLayer('Name', 'pool') fullyConnectedLayer(numClasses, 'Name', 'fc') softmaxLayer('Name', 'softmax') classificationLayer('Name', 'output') ]; options = trainingOptions('adam', ... 'InitialLearnRate', lr, ... 'MiniBatchSize', miniBatchSize, ... 'MaxEpochs', maxEpochs, ... 'ValidationData', {XVal, categorical(YVal)}, ... 'ValidationFrequency', 20, ... 'ValidationPatience', 20, ... 'Verbose', false, ... 'Plots', 'none'); net = trainNetwork(XTrain, categorical(YTrain), layers, options); YPred = classify(net, XVal); valErr = 1 - mean(YPred == categorical(YVal)); end

这段代码里有几个参数要重点解释。首先是 rng(42):注意 random seed 放在 trainNetwork 之前,使得同一组超参数在重复评估时网络初始化一致,验证集误差不会被随机性淹没。然后是 ValidationFrequency 设置为 20 个 iteration 一次验证,Patience 设为 20,这两个值耦合起来起到早停作用。Transformer 的收敛节奏比卷积网络慢,Patience 太小会误杀好参数,这个问题在第 5 章还会展开。

ValErr 返回的是验证集错误率而不是准确率,因为 PSO 默认找最小值。如果你习惯返回准确率,记得在外面取负号,否则粒子比较逻辑会反过来。

4.3 PSO 主循环:速度更新、边界处理与早停

如果你的 Matlab 安装了 Global Optimization Toolbox,可以直接用内置 particleswarm,省去手写循环的调试成本:

fun = @(p) psoFitness(p, XTrain, YTrain, XVal, YVal); options = optimoptions('particleswarm', ... 'SwarmSize', 12, ... 'MaxIterations', 20, ... 'MaxStallIterations', 5, ... 'Display', 'iter', ... 'UseParallel', false); [xBest, fBest] = particleswarm(fun, nvars, lb, ub, options);

不过很多项目需要把粒子群优化算法进一步定制,比如加早停、存历史档案、限制单次训练时长。这种情况下手写主循环反而更灵活。我常用的简化版主循环如下:

maxIter = 20; w = 0.9; c1 = 1.5; c2 = 1.5; for iter = 1:maxIter % 评估每个粒子 for i = 1:nPop fit = psoFitness(pos(i, :), XTrain, YTrain, XVal, YVal); if fit < pbestFit(i) pbestFit(i) = fit; pbestPos(i, :) = pos(i, :); end if fit < gbestFit gbestFit = fit; gbestPos = pos(i, :); end end % 更新惯性权重和速度 w = 0.9 - 0.5 * (iter / maxIter); for i = 1:nPop r1 = rand(1, nvars); r2 = rand(1, nvars); vel(i, :) = w * vel(i, :) + ... c1 * r1 .* (pbestPos(i, :) - pos(i, :)) + ... c2 * r2 .* (gbestPos - pos(i, :)); pos(i, :) = pos(i, :) + vel(i, :); % 边界钳制,防止粒子飞出搜索空间 pos(i, :) = max(lb, min(ub, pos(i, :))); end fprintf('iter=%d, gbestFit=%.4f\n', iter, gbestFit); % 提前终止:连续 5 次迭代 gbest 没有下降就停 if iter > 5 && gbestFit == gbestFitPrev stall = stall + 1; if stall >= 5, break; end else stall = 0; end gbestFitPrev = gbestFit; end

惯性权重 w 从 0.9 衰减到 0.4 几乎是粒子群优化的经验起点,实测效果可靠。c1 和 c2 分别代表个体认知和社会认知的强度,1.5 附近是比较均衡的选择,不用过度调。边界钳制这一行很关键:如果没有它,粒子可能在几十次迭代后飞到学习率 1e-6 的死区里,再难拉回来。提前终止逻辑必须在 gbest 连续 5 次不更新时跳出,否则外层搜索会在模型已经收敛后继续烧钱。

这里有一个实现细节:PSO 每次迭代要评估 nPop 个粒子,每个粒子都要完整训练一个 Transformer,总训练次数 = nPop × maxIter。12 个粒子跑 20 代就是 240 次训练,即使每次训练只用 30 个 epoch,总体计算量也非常可观。实际项目中,我一般会先在小规模数据子集上跑 PSO,选出 Top 5 粒子后再到完整数据上精炼。

5. PSO-Transformer 避坑记录:5 条血泪经验

5.1 同一组超参数两次评估误差差 5 个百分点:随机种子没固定

现象:PSO 跑到第 6 代,发现同一个粒子位置被重复评估,验证误差却从 0.23 变成了 0.28,粒子优劣排序完全被打乱,整个迭代过程像无头苍蝇。

原因:trainNetwork 内部的权重初始化、数据打乱顺序都依赖随机数流。外层 PSO 每次调用适应度函数时如果不重置随机种子,即使超参数完全一致,训练结果也会有明显波动。PSO 把这种噪声当成了真实的适应度信号,导致 pbest 和 gbest 更新都失真。

解决:在 psoFitness 函数开头固定 rng(42),让同一超参在任何一次评估中都可以复现。如果想更保守一点,可以把「同位置重复评估两次取平均」作为适应度,但训练成本会翻倍。先固定种子,通常就能解决。

5.2 transformerLayer 报维度错误:序列维没有放在第二位

现象:训练一开始报维度不匹配,或者不报错但 loss 一直下不去,验证准确率卡在一个极低水平。

原因:Matlab 的序列数据要求 [特征维, 序列维, 观测维] 的三维排列。滑窗代码里如果把 XSeq 声明成 [L, F, N],那么 Transformer 会把窗口长度 L 当成特征数,把真正的特征 F 当成时间步,注意力机制建模的对象完全错了。

解决:统一用 size(X, 2) 作为特征维,滑窗赋值后加转置,用 size(XTrain, 1) 读取 numFeatures。如果数据维度仍然不对,可以在训练前写一行 disp(size(XTrain)),人工核对第一维是不是特征数。

5.3 PSO 在离散超参数上振荡:取整太晚导致速度向量跳来跳去

现象:NumHeads 的取值在 1 和 8 之间反复横跳,gbest 数值下降缓慢,整个搜索后期像在抽奖。

原因:粒子位置是连续值,取整发生在适应度函数内部,但速度更新仍然使用未取整的位置来计算。粒子一旦靠近两个整数值的中点,速度方向会被 pbest 和 gbest 反复拉扯,离散维在边界上来回振荡。

解决:把离散变量单独处理。最简单的手段是在边界钳制之后、速度更新之前对离散位置的维度取整,让它参与下一次速度计算时已经是整数;更稳妥的是把离散超参映射成整数索引,比如 NumHeads 映射到 [1, 2, 4, 8] 四个值,粒子只搜索索引 1 到 4,解释参数时再查表还原。

5.4 早停频繁触发:好参数被误杀

现象:PSO 迭代早期,大量粒子的验证误差停留在 0.5 附近,很多粒子几轮训练后就被 ValidationPatience 判了死刑,最终选出的参数在完整训练下表现反而一般。

原因:Transformer 的前几轮收敛本来就更慢,它需要先让注意力矩阵学会对齐特征关系,特征数量越大这个预热阶段越长。ValidationPatience 设置成 5 或 10,会在模型还没进入快速收敛期就触发早停,把潜力大的参数淘汰出局。

解决:在 PSO 评估阶段把 ValidationPatience 调到 20 以上,或者干脆不开早停,只跑固定 MaxEpochs。PSO 的目标是给超参数排序,不是追求单次训练效率,宁可每次多跑一点,也不能让早停成为噪声源。

5.5 换一次数据划分,最优超参完全变样

现象:同一个数据集,只是换了一个 randperm 随机种子重新划分,PSO 选出来的最优学习率和最优头数完全不同,测试集结果也忽高忽低。

原因:验证集太小或者划分不具代表性时,验证误差本身方差很大,PSO 会努力去拟合这个「偶然的」验证集,而不是真正的泛化能力。外层划分变了,最优参数自然跟着变。

解决:第一,划分数据时用 stratified 分层抽样,保证每个类别在训练和验证集中的占比和原始数据集一致;第二,PSO 搜索结束后,固定住它选出的最优超参,在重新划分的 3 折或 5 折数据上重新评估,如果各折误差方差在可接受范围内,才说明这个参数不是划分幸运儿。这也是最后一章会展开验证步骤的原因。

6. 别只盯准确率:验证、对比与一组可靠技巧

6.1 三步验证法:收敛曲线、随机基线、K 折复现

PSO 跑完直接拿最优参数去测测试集,是让人心里没底的做法。我现在给项目交付结论之前,一定会做三件事。

先把 PSO 的收敛曲线画出来。注意横轴应该是适应度函数评估次数,而不是迭代次数,因为每代有 nPop 个粒子同时评估。如果曲线是一条平滑阶梯下降,说明搜索过程健康;如果 gbest 长时间不动弹但 pbest 还在分散更新,说明粒子已经汇聚到局部最优,可以考虑用更大的 c1 或提高初始惯性权重重新跑。

第二步是和随机搜索对比。用同样的评估预算跑一次随机采样,如果随机搜索也能找到接近 PSO 的误差值,说明这个超参数空间本身比较平缓,PSO 的优势没有体现出来。这种情况下我会向对方如实说明:PSO 带来的收益有限,真正起作用的是 Transformer 模型本身。

第三步是 K 折交叉验证。把 PSO 选出的最优超参固定住,在重新划分的 3 折数据上重新训练和评估。这一步能挤出外层验证集的运气成分。如果 3 折误差方差小于 1%,参数可以放心交付;如果方差很大,就回到第 5 章第 5 条去查数据划分是否存在类别分布问题。

6.2 三条降低训练成本的小技巧

第一个技巧是先用小数据跑 PSO。取 20% 数据做一次粗搜索,粒子数降到 8,迭代次数降到 10,目的是筛掉明显不好的超参区域;然后用粗搜索结果收缩每个变量的上下界,在完整数据上做第二轮精搜。这样总训练成本大约能省一半。

第二个技巧是用档案记录每个粒子的位置和适应度。这个档案就是后悔药:如果最优参数在最终验证阶段表现不佳,可以从档案里按适应度排序依次取出后续参数,而不是重新跑一遍 PSO。实现上只需要在 psoFitness 里用 persistent 变量把每次评估结果追加到一个表里。

第三个技巧是评估阶段固定 MaxEpochs 而不是依赖早停。我习惯把 MaxEpochs 设为 30 或者 40,训练完成后记录「达到验证集最优时的 epoch」这个信息。第二轮精搜时,用它来决定训练轮数的上限,既省时间又比盲目设大值安全。

现在我做这类项目,已经不会只用单次准确率来验收,而是固定随机种子、记录收敛过程、做 K 折复现,然后把最优参数和它的邻域超参一起放进报告。以前我也吃过闭门造车的亏,单次测试集跑出 0.93 就急着交付,换数据划分后掉到 0.87,从那以后才养成了这套验证习惯。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进