☰
YOLO11自定义网络使用AdamW训练报错,换成SGD却正常:优化器兼容性与参数分组排查
2026/10/7 8:17:11 网站建设 项目流程

最近我在做一个圆环产品的工业视觉检测,遇到了一个比较绕的问题:数据里只标注了一种缺陷,测试时却出现了其他类别的误检,而且有些框出现在产品旁边的背景区域。

当时我的标注类别编号是1,部署软件固定保留0~20共21个类别。我的需求很明确:只检测目标缺陷,并让软件按照业务类别1处理结果。

但实际测试中,既有类别1的命中,也出现了类别0的误检。同一张图片反复测试,结果还出现过不一致。

排查过程中,我补充过误判图片,也尝试关闭 AMP。关闭 AMP 后,我当时再次验证推理,之前的问题没有再出现,结果恢复正常。

不过,“调整后恢复正常”和“已经证明唯一根因”是两回事。这篇文章记录我遇到的现象,以及这次排查中需要分清的几个问题:训练类别编号、部署类别映射、预训练权重和混合精度。

一、先把实际问题说清楚

这个项目要检测的是圆环产品上的螺纹特征。按照当时的判定标准,无螺纹以及允许范围内的浅纹属于合格品,达到不良标准的螺纹才是需要检测的目标。

我遇到的情况主要有下面几个:

项目当时的情况
实际标注内容一种目标缺陷
标签文件中的类别编号1
软件中的类别列表0~20,共21类
模型初始化使用预训练权重
异常表现合格品或背景区域出现0类误检,同图测试结果出现过不一致
排查中的变化关闭 AMP 后,当次验证推理恢复正常

我最初怀疑过:是不是预训练模型原本有很多类别,训练后还保留了其他类别的识别能力,所以会冒出0类?

要判断这个猜测,首先得弄清楚:我说的“只训练一类”,到底是只标了一类,还是模型实际上只有一个输出类别。

二、只标注一种缺陷,不等于模型只有一个类别

这是这次问题里最容易混淆的地方。

在普通的 YOLO 检测训练中,类别编号从0开始。如果没有启用single_cls等类别重映射逻辑,可以这样理解:

数据配置与标签含义
配置1个类别,标签编号为0正常的单类别配置
配置1个类别,标签编号为1编号越界,需要修正配置或标签
配置21个类别,标签只使用1声明了21个类别,只给其中一类提供了正样本

第三种情况不能叫“模型只有一类”。模型不会因为标签文件只出现过1,就自动把其他20个输出类别全部删掉。反过来,声明21类也不能直接证明它就是误检的原因。

Ultralytics 的检测数据集格式说明明确要求类别编号从0开始。对于真正只有一种目标的检测任务,我会使用下面这种配置:

# 示例:按实际数据目录修改 path path: /data/ring_thread train: images/train val: images/val nc: 1 names: 0: thread_defect

对应的检测标签可以是:

0 0.500000 0.500000 0.200000 0.100000

第一列是类别编号,后面四个数是归一化后的框中心坐标、宽和高。这一行只是格式示例,实际坐标要来自标注。

这里还要强调一点:**YOLO检测里的类别0不是自动预留的“背景类”。**在上面的单类别配置中,0就是thread_defect。正常图片没有目标,应当没有目标框,而不是专门画一个框标成0。

所以,如果我把模型正确改成了单类别,之后出现0类检测框本身完全正常。需要判断的是它有没有框到真正的缺陷。

三、软件固定21类,训练标签是不是就必须用1?

我当时的实际限制是:软件已经保留了21个类别,业务上希望使用类别1。

这就需要把两套编号分开看:

  • 模型类别编号:模型内部用来区分检测目标;
  • 业务类别编号:软件用来显示、统计、输出判定结果。

如果软件接口允许适配,我更倾向于让训练端保持单类别,再在识别结果进入业务逻辑之前做映射:

环节编号及含义
数据标注0 = thread_defect
模型检测结果class_id = 0
软件业务结果business_class_id = 1

映射逻辑本身很简单:

# 仅示意编号转换,实际要接入软件的结果处理流程 model_to_business = {0: 1} model_class_id = 0 business_class_id = model_to_business[model_class_id]

但这里有一个现场限制,不能省略。

如果软件只是界面上保留21个业务类别,可以考虑在结果层做映射;如果底层推理代码把输出按21类写死,就必须同时调整输出解析。

后一种情况下,只改标签或者只改names,并不能让旧软件正确读取新模型。要先确认模型输出形状、类别数和后处理接口,再安排训练与部署一起修改。

单类别加业务映射是我建议的整理方向,不能把它当成这次已经完成全部现场验证的修复结果。

四、使用预训练权重,会不会把原来的类别带进来?

我当时确实怀疑过预训练权重,但不能只凭“用了官方权重”就下结论。

在标准的 Ultralytics 检测训练流程中,模型会依据目标数据集的类别数构建,再加载可用的预训练权重。官方的检测训练器实现可以看到这个流程。

因此,一个正确构建的单类别检测模型,不会因为初始化来自多类别权重,就在输出里自动多出其他类别。

比起猜测,我会先检查实际加载的文件、类别表和检测头:

import torch import ultralytics from ultralytics import YOLO # 在能够加载该模型自定义模块的原环境中运行 model = YOLO("best.pt") print("Ultralytics版本:", ultralytics.__version__) print("PyTorch版本:", torch.__version__) print("类别表:", model.names) print("类别表数量:", len(model.names)) # 适用于常见的 Ultralytics YOLO11 检测模型结构 head = model.model.model[-1] print("检测头nc:", getattr(head, "nc", "请按自定义结构核对"))

如果类别表仍然有21项,就需要结合检测头的nc、训练日志和输出形状继续核对。

如果模型确认只有一类,标准解析得到的类别ID应当是0;软件却显示其他编号,就要追踪是不是业务映射、错误解析,或者加载了其他模型文件。

**修改model.names只是修改类别名称信息,不能把一个21类模型直接变成单类别模型。**对于改过网络或训练器的项目,我会以本地源码和实际输出为准。

五、AMP是什么,为什么我会尝试关闭它?

AMP 是 Automatic Mixed Precision,也就是自动混合精度。

按照 PyTorch 的说明,它会让不同运算使用合适的数值类型。例如,在常见的 CUDA FP16 混合精度训练中,一部分运算使用 FP16,一部分仍使用 FP32,训练时还会配合梯度缩放。

它的用途是提高计算效率、减少部分显存开销,并不是“开了就会误检”的选项。

不过,混合精度会改变部分计算的数值行为。如果网络中存在对数值精度比较敏感的计算,就值得把它列为排查变量。这是一个需要验证的方向,不能直接当作我这次已经证明的底层原因。

在 Ultralytics 训练中,可以通过下面的参数关闭训练 AMP:

amp=False

参数含义可参考官方训练文档。不同版本的接口可能变化,项目里应以实际安装版本为准。

这次关闭 AMP 后,我当时验证推理正常,这是一个有用的结果。但如果要进一步确认原因,还需要把数据、初始权重、其他训练参数和推理设置固定下来,再比较开启与关闭 AMP 的结果。

尤其不能忽略这一点:重新训练会得到另一组权重。两次训练的结果不同,不等于已经找到了某个算子的精度错误。

另外,训练日志里的AMP checks passed也不是项目验收结论。官方的 AMP 检查实现包含通用模型和示例图片的对照,并没有用我的全部产品数据验证最终效果。

训练AMP和部署FP16,要分别检查

这两个设置容易混在一起,但它们作用在不同阶段:

阶段检查内容影响范围
训练model.train()中的amp训练计算过程以及得到的权重
验证、预测当前版本的推理精度设置,例如half等参数这一次运行使用的计算精度
TensorRT部署引擎构建时的精度配置已构建引擎的执行行为

推理精度的参数名称需要查对应版本的预测文档。

在训练脚本里改成amp=False,不会自动修改已经存在的best.pt,也不会改变已经构建好的 TensorRT 引擎。重新训练后的权重如果要用于现场,还需要重新导出、构建并完成部署验证。

六、如果再做一次严格排查,我会怎么对照?

我会先保存当前能正常工作的模型和配置,再建立一组可以重复的对照实验。

第一步,确认类别配置合法。

核对data.yaml、实际标签、训练日志、模型类别表和部署映射。只有一个类别时,不能一边配置nc=1,一边继续使用标签1。

如果修改了标签,还需要确认训练实际重新扫描了修改后的数据。必要时清理对应数据集的标签.cache缓存,让它重新生成。

第二步,只改变AMP这一项。

下面是关闭 AMP 的训练示例,模型路径、图片尺寸和训练参数需要按项目调整:

from ultralytics import YOLO if __name__ == "__main__": model = YOLO("yolo11n.pt") model.train( data="data.yaml", imgsz=1024, batch=2, epochs=100, device=0, amp=False, seed=0, deterministic=True, resume=False, name="ring_amp_off", )

这段代码演示参数位置,不能把里面的默认模型和参数当成我的现场最优配置。实际对照时,两组都要使用相同的项目结构、初始权重、数据划分和其他训练参数,只改变amp,并用不同实验名称保存。

如果一组用自定义网络,另一组改用官方网络,或者同时换了优化器、batch和数据,就又增加了变量。

两组都应该从同一份初始权重开始,不能一组从头训练,另一组接着前一组的best.pt继续训练。固定随机种子有助于比较,但也不能保证跨版本、跨硬件完全复现,具体限制可参考 PyTorch 的可复现性说明。

第三步,把两组权重放在同一推理设置下比较。

我会固定测试图片、输入尺寸、置信度阈值、NMS参数和推理精度,再对比误报与漏检。这样才能减少“训练设置和推理设置一起变了”的干扰。

如果需要排查推理精度,则保持同一份权重,再单独比较不同推理精度的结果。

第四步,单独检查同图重复推理。

同一张图片的结果明显反复变化,不能只用“训练时开过AMP”来解释。

我会先在固定环境中,用同一模型、同一张本地图片、单路推理重复测试,记录原始类别ID、置信度和框坐标。可以连续运行100次作为一次复现测试,但这不是最终稳定性验收标准。

如果单路稳定、接入视觉软件后才异常,我会重点追踪输入是否被改动、模型是否混用,以及并发任务有没有共用输入输出缓冲区等问题。

七、补充负样本时,不能把正常区域也标成缺陷

排查过程中,我补充过误判图片。这一步有价值,但要先分清图片里到底有没有需要检测的目标。

以这次圆环检测为例,我会分别整理:

  • 无螺纹的合格产品;
  • 在验收标准允许范围内的浅纹产品;
  • 曾经引发误报的背景、反光或纹理区域;
  • 真正达到不良标准的螺纹产品。

对于确实没有目标缺陷的图片,应该作为负样本处理。在 Ultralytics 的 YOLO 检测数据格式中,无目标图片可以没有对应标签文件;使用空标签文件时,也要确认标注和数据处理工具能够正确识别。

**不能为了“告诉模型这张图是好的”,就在正常区域画一个框并标成0。**如果0代表目标缺陷,这个操作等于告诉模型:正常区域也是缺陷。

如果一张图里既有真实缺陷,又有容易误检的背景,我会保留真实缺陷的标注,不会把整张图当成负样本。

还有一点容易让验证结果看起来很好:把误判图片加入训练后,又只拿同一批图来证明模型提升。

这些图片适合做回归检查,但还需要另外保留没有参与训练的正常品和不良品。划分时,我也会尽量让同一件产品的连续拍摄图片留在同一组,避免相似图片同时进入训练集和测试集。

八、最后我会看哪些结果?

对于工业检测,我更关心最终有没有把正常品误判成不良,以及真实缺陷有没有漏掉。

下面这张表,是我给这类问题整理的复查清单:

检查项我会怎样判断
类别编号标签、模型类别表、检测头与部署映射是否一致
正常品误报无目标缺陷的图片,是否仍被判为不良
不良品漏检达到判定标准的目标,是否仍然能被检出
旧问题回归原来出错的图片,重新测试是否正常
新样本表现没有参与训练的图片,是否同样稳定
同图重复推理类别、框和置信度是否出现无法解释的明显变化
部署一致性同一批图片在本地模型与现场软件中的结果是否一致

统计时也要统一口径。按图片统计的误报率,和产线上按整件产品统计的误剔率,不一定是同一个数。多相机项目尤其需要说明结果如何汇总。

我也不会只靠提高置信度阈值来判断问题是否解决。误报可能少了,但边界缺陷的漏检也可能增加,两边都要看。

这次我能确认的是:**关闭 AMP 后,当次验证推理恢复正常。**类别编号、软件映射和精度设置,是后续需要分别核对的环节;现有结果还不足以证明它们共同构成了唯一根因。

以后再遇到“只标了一类,却出现其他类别”的情况,我会先确认模型到底有几个输出类别,再去追踪类别映射和数值精度。对于目前已经验证正常的配置,我会先保留它;是否重新开启 AMP,则用固定测试集和重复对照的结果来决定。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询