Ultralytics YOLO27: 加入候补名单。
开始使用

如何在自定义数据集上微调 YOLO

微调会从已学习的权重开始,而不是随机初始化,让预训练模型能够识别新类别。与从头开始训练数百个 epoch 不同,微调会复用预训练模型从 COCO 学到的特征,只需很短时间就能在自定义数据上收敛。

本指南介绍如何在自定义数据集上微调 YOLO26,涵盖基础用法以及冻结层和两阶段训练等高级技巧。

微调与从头开始训练#

预训练模型已从数百万张图像中学习了通用视觉特征,包括边缘检测、纹理识别和形状理解。通过微调进行迁移学习可以重用这些知识,只需教模型识别新类别,因此收敛更快,所需数据也更少。从头开始训练则会舍弃所有这些知识,迫使模型从像素级模式开始学习一切,因此需要的资源显著更多。请参阅模型 YAML 配置指南,了解仅包含架构的 .yaml 文件与检查点有何不同。

微调从头开始训练
起始权重在 COCO 上预训练(80 个类别)随机初始化
命令YOLO("yolo26n.pt")YOLO("yolo26n.yaml")
收敛更快 - 主干网络已完成训练更慢 - 所有层都从零开始学习
数据需求较低 - 预训练特征可弥补数据量不足较高 - 模型必须仅依靠数据集学习所有特征
适用场景包含自然图像的自定义类别与 COCO 截然不同的领域(医疗、卫星、雷达)
微调无需额外代码

使用 YOLO("yolo26n.pt") 加载 .pt 文件时,预训练权重会存储在模型中。之后调用 .train(data="custom.yaml") 会自动将所有兼容权重迁移到新的模型架构,重新初始化不匹配的层(例如类别数量不同时的最终分类层),然后开始训练。无需手动加载权重、操作层或编写自定义迁移学习代码。

预训练权重迁移的工作原理#

使用预训练模型在类别数量不同的数据集上进行微调时(例如从 COCO 的 80 个类别迁移到 5 个自定义类别),Ultralytics 会根据张量形状迁移权重:

  1. 主干网络和颈部网络会完整迁移 - 这些层用于提取通用视觉特征,其形状与类别数量无关。
  2. 检测头会部分重新初始化 - 只有每个分类分支(cv3、one2one_cv3)的最终卷积层形状与类别数量相关(80 对 5);如果两个模型使用相同的规模,分支的隐藏层会迁移。初始化不匹配的行之前,会先重新映射类别名称相同的兼容行。检测头中的边界框回归层(cv2、one2one_cv2)形状固定,不受类别数量影响,因此会正常迁移。
  3. 更改类别数量时,绝大多数权重都会迁移。例如,将基于 COCO(80 个类别)的 YOLO26n 微调到 5 类数据集时,会迁移 708 个权重张量中的 696 个,以及所有按名称匹配的兼容分类行。

如果数据集与预训练模型的类别数量相同(例如,在另一个包含 80 个类别的数据集上微调使用 COCO 预训练的权重),则 100% 的权重都会迁移,包括检测头。

通过名称别名迁移类别#

Ultralytics 会跨数据集按类别名称匹配分类头中的行,匹配时忽略大小写和首尾空格。如果相同类别使用了不同名称,请在加载前于内存中重命名源检查点中的类别。这样可以保留共享概念的预训练分类权重,否则这些权重会被视为未匹配项并随机初始化。

以下 Objects365 v2 到 COCO 的示例会重命名已加载检查点中的源类别,随后 train() 会将其作为预训练权重传入:

from ultralytics import YOLO

# 源 Objects365 v2 名称(小写)-> 目标 COCO 名称
ALIASES = {
    "wild bird": "bird",
    "handbag/satchel": "handbag",
    "luggage": "suitcase",
    "bowl/basin": "bowl",
    "orange/tangerine": "orange",
    "monitor/tv": "tv",
    "stuffed toy": "teddy bear",
    "hair dryer": "hair drier",
}

model = YOLO("path/to/yolo26s-objects365.pt")
# Objects365 类别名称采用首字母大写格式,因此按小写名称匹配
model.model.names = {i: ALIASES.get(name.lower(), name) for i, name in model.model.names.items()}
model.train(data="coco.yaml", epochs=100, imgsz=640)

默认情况下,cls_remap 设置会启用这种基于名称的迁移。训练过程中,如果复制了兼容行,会输出 Remapped N/M cls head rows from pretrained weights by class name;将 cls_remap=False 设为相应值即可禁用此功能。

微调基础示例#

示例
from ultralytics import YOLO

model = YOLO("yolo26n.pt")  # 加载预训练模型
model.train(data="custom.yaml", epochs=50, imgsz=640)

选择模型尺寸#

较大的模型容量更强,但需要更新的参数也更多;当训练数据有限时,这会增加过拟合风险。一个实用的方法是先从较小的模型(YOLO26n 或 YOLO26s)开始,只有在验证指标停滞时才扩大模型规模。最佳模型尺寸取决于任务复杂度、类别数量、数据集多样性以及可用于部署的硬件。

优化器和学习率的选择#

默认的 optimizer=auto 设置会根据训练迭代总数选择优化器和学习率:

  • 不超过 10,000 次迭代(小型数据集或较少轮次):AdamW,采用较低且自动计算的学习率
  • 超过 10,000 次迭代(大型数据集):MuSGD(Muon+SGD 混合优化器),lr=0.01

对于大多数微调任务,默认设置无需手动调整即可取得良好效果。以下情况可以考虑显式设置优化器:

  • 训练不稳定(损失突然升高或发散):尝试使用 optimizer=AdamW, lr0=0.001,以获得更稳定的收敛效果
  • 在小型数据集上微调大型模型:显式选择较低学习率的优化器,例如 optimizer=AdamW, lr0=0.001,有助于保留预训练特征
自动优化器会覆盖手动设置的 lr0

当 optimizer=auto 时,lr0 值会被忽略。若要手动控制学习率,请显式设置优化器:optimizer=SGD, lr0=0.005。

冻结层#

冻结可以阻止特定层在训练期间更新。当数据集相对于模型容量较小时,冻结层能加快训练并减少过拟合。

freeze 参数接受整数或列表。整数 freeze=10 会冻结前 10 层(索引 0-9),涵盖 YOLO26 主干网络的大部分。主干网络跨越第 0-10 层,因此 freeze=10 会使最后一个 C2PSA 模块(第 10 层)保持可训练;使用 freeze=11 可冻结整个主干网络。列表可以包含 freeze=[0, 3, 5] 这类层索引,用于部分冻结主干网络;也可以包含 freeze=["23.cv2", "23.one2one_cv2"] 这类模块名称字符串,以便精细控制某一层中的特定分支(此处为检测头的两个边界框回归分支)。

示例
from ultralytics import YOLO

model = YOLO("yolo26n.pt")
model.train(data="custom.yaml", epochs=50, freeze=10)

合适的冻结深度取决于目标域与预训练数据的相似程度,以及可用训练数据的数量:

场景建议原因
数据集较大,领域相似freeze=None(默认)数据充足,可以调整所有层而不会过拟合
数据集较小,领域相似freeze=10保留骨干网络特征,减少可训练参数
数据集非常小freeze=23仅训练检测头,尽可能降低过拟合风险
与 COCO 领域相差较大freeze=None骨干网络特征可能无法良好迁移,需要重新训练

冻结深度也可以作为一个超参数来处理——尝试几个值(0、5、10)并比较验证集 mAP,是为特定数据集找到最佳设置的实用方法。

微调的关键超参数#

与从头训练相比,微调通常需要调整的超参数更少。最重要的参数包括:

  • epochs:微调比从头训练收敛得更快。先使用适中的值,并在验证指标趋于平稳时使用 patience 提前停止。
  • patience:默认值 100 是为长时间训练设计的。将其降至 10-20,可以避免在模型已经收敛后继续浪费训练时间。
  • warmup_epochs:预热会在最初几个 epoch 内逐渐将学习率调整到计划值,从而降低早期批次扰乱预训练特征的可能性。微调时应保持预热非零,但不必使用默认的完整 3 个 epoch:用于 官方 YOLO26 COCO 微调 的进化搜索——从 Objects365 权重继续训练多个 epoch——最终为每种模型尺寸选择了约一个 epoch 的预热时间。

完整的训练参数列表请参阅训练配置参考。对于参数未提供的功能——例如逐层学习率、梯度裁剪或自定义验证指标——请继承训练器类。

两阶段微调#

两阶段微调将训练分为两个阶段。第一阶段冻结骨干网络,仅训练颈部和检测头,让检测层适应新类别,同时不破坏预训练特征。第二阶段解冻所有层,并以较低学习率训练整个模型,以调整骨干网络,使其适应目标域。

当目标域与 COCO 差异很大(例如医学图像、航空影像、显微成像)时,这种方法尤其有用,因为骨干网络可能需要调整,但一次性训练所有层会导致不稳定。如需使用基于回调的方式自动解冻,请参阅冻结和解冻骨干网络。

两阶段微调
from ultralytics import YOLO

# 阶段 1:冻结骨干网络,训练检测头和颈部
model = YOLO("yolo26n.pt")
model.train(data="custom.yaml", epochs=20, freeze=10, name="stage1", exist_ok=True)

# 阶段 2:解冻所有层,以较低学习率进行微调
model = YOLO("runs/detect/stage1/weights/best.pt")
model.train(data="custom.yaml", epochs=30, optimizer="AdamW", lr0=0.001, name="stage2", exist_ok=True)

常见问题#

模型没有生成预测结果#

  • 训练数据不足:使用极少样本进行训练是最常见的原因——数据太少,模型无法学习或泛化。在排查其他原因前,请确保每个类别都有足够且多样的样本。

  • 检查数据集路径:无效的图像路径会引发数据集错误。单个缺失或空标签文件会被视为背景图像,并在扫描时报告;如果训练集划分中没有标签,则会引发错误。请在训练前验证数据集:

    yolo detect val model=yolo26n.pt data=custom.yaml
  • 降低置信度阈值:如果模型已生成预测结果,但结果被过滤掉了,可以在推理时尝试 conf=0.1。

  • 核对类别数量:确保 data.yaml 中的 nc 与标签文件中的实际类别数一致。

验证集 mAP 过早趋于平稳#

  • 增加数据:微调能从更多训练数据中显著获益,尤其是包含不同角度、光照和背景的多样化样本。
  • 检查类别平衡:样本不足的类别会有较低的 AP。请添加更多样本,或在验证集上调整 cls_pw。
  • 减少数据增强:对于非常小的数据集,过强的数据增强弊大于利。可以尝试 mosaic=0.5 或 mosaic=0.0。
  • 提高分辨率:对于包含小目标的数据集,可以尝试 imgsz=1280 来保留细节。

微调后,模型在原有类别上的性能下降#

这称为灾难性遗忘:模型只使用新数据进行微调时,会丢失之前学到的知识。如果不同时加入原始数据集中的图像,遗忘现象大多无法避免。要缓解这一问题:

  • 合并数据集:微调时,将原有类别的样本与新类别的样本一起纳入训练。这是防止遗忘的唯一可靠方法。
  • 冻结骨干网络和颈部:冻结骨干网络和颈部,仅训练检测头;对于学习率极低的短时间微调,这种方法会有所帮助。
  • 减少训练轮数:模型仅使用新数据训练的时间越长,遗忘就越严重。

常见问题#

  • 没有固定的最低数量——结果取决于任务复杂度、类别数量,以及领域与 COCO 的相似程度。相比图像的绝对数量,包含不同光照、角度和背景的多样化图像更重要。先用现有数据开始训练,如果验证指标不理想,再逐步增加数据。

  • 加载预训练的 .pt 文件,并调用 .train(),传入自定义 data.yaml 的路径。Ultralytics 会自动处理权重迁移、分类层重新初始化和优化器选择。完整代码示例请参阅基础微调部分。

  • 最常见的原因包括图像路径无效、标签文件缺失或为空、YAML 中的 nc 与实际标签文件不匹配,或置信度阈值过高。完整的故障排查清单请参阅常见问题。

  • 这取决于数据集大小和领域相似度。对于领域与 COCO 相似的小型数据集,冻结骨干网络(freeze=10)可以防止过拟合。对于与 COCO 差异很大的领域,不冻结任何层(freeze=None)可以让骨干网络适应新领域。详细建议请参阅冻结层。

  • 在训练数据中同时加入原有类别和新类别的样本。如果无法做到这一点,冻结更多层(freeze=10 或更高值)并降低学习率,有助于保留预训练知识。更多详情请参阅原有类别上的性能下降。

评论4

提出问题、分享结果,或帮助其他人。

  1. Yuan Yao(已编辑)

    这是我在训练时遇到的一个问题,

      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
    81/1000      6.68G          0          0          0        195        640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.1s
                   all       1449       8364          0          0          0          0
    
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
    82/1000      6.68G          0          0          0        184        640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.0it/s 11.3s
                   all       1449       8364          0          0          0          0
    
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
    83/1000      6.68G          0          0          0        221        640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:19
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.1s
                   all       1449       8364          0          0          0          0
    
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
    84/1000      6.68G          0          0          0        212        640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.2s
                   all       1449       8364          0          0          0          0
    
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
    85/1000      6.68G          0          0          0        221        640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.2s
                   all       1449       8364          0          0          0          0
    
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
    86/1000      6.68G          0          0          0        216        640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.0s
                   all       1449       8364          0          0          0          0
    
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
    87/1000      6.68G          0          0          0        198        640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:19
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.0it/s 11.4s
                   all       1449       8364          0          0          0          0
    
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
    88/1000      6.68G          0          0          0        229        640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.2s
                   all       1449       8364          0          0          0          0
    
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
    89/1000      6.68G          0          0          0        182        640: 100% ━━━━━━━━━━━━ 121/121 1.1s/it 2:18
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.0s
                   all       1449       8364          0          0          0          0
    
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
    90/1000      6.68G          0          0          0        218        640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.0s
                   all       1449       8364          0          0          0          0
    
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
    91/1000      6.68G          0          0          0        181        640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:19
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.1it/s 11.1s
                   all       1449       8364          0          0          0          0

    mAP一直是0。当时我用的是yaml文件用于训练模型,

    model_name = r"H:\1_yaoyuan\code\01_yolo26\yolo_Project\ultralytics-main\ultralytics\cfg\models\11\yolo11.yaml"

    之后改成了,

    model_name = 'yolo11n.pt' # 首次运行会自动下载

    然后训练才变的正常,

      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
     2/1000      5.47G      2.825      1.856     0.8776        131        640: 100% ━━━━━━━━━━━━ 121/121 1.2s/it 2:20
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 2.0it/s 11.5s
                   all       1449       8364      0.554      0.314       0.32      0.117
    
      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
     3/1000      5.47G      2.904      1.726     0.8738        206        640: 100% ━━━━━━━━━━━━ 121/121 1.1s/it 2:19
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% ━━━━━━━━━━━━ 23/23 1.9it/s 11.9s
                   all       1449       8364      0.496       0.29      0.269      0.085

    这是为什么?微调和从头开始训练差别很大吗? 我问了deepseek,他说:“.yaml 路径迫使模型在毫无知识的情况下承受初始的梯度冲击,导致了数值崩溃;而 .pt 路径让模型从一个稳定的知识基础开始,顺利进入了正常的学习轨道。”

    可如果是这样,如果这个世界还没有yolo的pt文件,那么是不是mAP永远都是0了?

    1. Paula DerrengerUltralytics

      不用道歉,这是很合理的问题!不是的:从 .yaml 构建模型是随机初始化,从 yolo11n.pt 开始则利用预训练特征,通常收敛更快、需要的数据更少;YOLO11 官方示例也支持从零训练,并不需要已有 .pt 才能学会检测。你这里不仅 mAP 为 0,三个 loss 也连续为 0,这不能简单解释为“从零训练比较慢”,更应优先排查训练数值异常;仅凭这些日志,还不能确认 DeepSeek 所说的“梯度冲击”。建议保留 YAML,在原来的 model.train(...) 中加入 amp=False,重新启动训练以排查混合精度问题(AMP 检查说明)。如果仍然全为 0,请贴出完整训练代码、启动到首个异常 epoch 的文本日志,以及 GPU 型号、Ultralytics 和 PyTorch 版本。