4. YOLO 配置文件解析:.cfg 文件结构、网络层参数与锚点框机制
配置文件,说白了就是 YOLO 的「骨架说明书」。我刚开始接触 YOLO 时,总觉得调代码比调 cfg 重要。后来有一次模型怎么都收敛不了,折腾了两天,最后发现是 cfg 里一个卷积层的步长写错了。嗯,从那以后我再也不敢小看这个文件了。
今天我们就把它彻底拆开看看。
4.1 .cfg 文件整体结构
一个标准的 YOLO .cfg 文件,其实就是一个纯文本的层描述列表。每一段以 [xxx] 开头,后面跟着该层的参数键值对。YOLO 的 Darknet 框架会按顺序逐层解析,构建出完整的计算图。
我个人习惯把 cfg 文件分成三大块:
- 网络头:定义输入尺寸、训练参数等全局信息
- 特征提取器:一堆卷积、残差、下采样层,负责把图片变成特征图
- 检测头:YOLO 层 + 上采样 + 路由层,负责输出检测结果
来看一个典型的 cfg 开头:
[net]
batch=64
subdivisions=16
width=416
height=416
channels=3
momentum=0.9
decay=0.0005
learning_rate=0.001
burn_in=1000
max_batches=500200
policy=steps
steps=400000,450000
scales=.1,.1
这里有个坑,我曾经踩过——subdivisions 这个参数。它把 batch 拆成小份,每份喂给 GPU。如果你的显存不够,调大 subdivisions 就行。但注意,它会影响 BN 层的统计效果,调太大模型会变「毛」。
4.2 核心网络层参数详解
卷积层(Convolutional)
卷积层是 YOLO 的主力。每个卷积层都长这样:
[convolutional]
batch_normalize=1
filters=32
size=3
stride=1
pad=1
activation=leaky
几个关键参数:
| 参数 | 含义 | 我的经验 |
|---|---|---|
filters |
输出通道数 | 一般 8 的倍数,方便内存对齐 |
size |
卷积核大小 | 3x3 最常用,1x1 用来降维 |
stride |
步长 | stride=2 时特征图尺寸减半 |
pad |
是否填充 | pad=1 且 size=3 时,输出尺寸不变 |
batch_normalize |
是否接 BN 层 | YOLOv3 之后几乎必开 |
上采样层(Upsample)
上采样层的作用很简单——把特征图放大。YOLO 用它来做多尺度融合。
[upsample]
stride=2
就这么两行。stride=2 表示把特征图宽高各放大 2 倍。实现方式默认是最近邻插值,速度快,但会有锯齿。我在项目中试过换成双线性插值,精度提升微乎其微,速度却慢了 10%,所以还是用默认吧。
YOLO 层(检测层)
这是整个 cfg 的核心。它定义了模型「怎么检测」:
[yolo]
mask = 0,1,2
anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
classes=80
num=9
jitter=.3
ignore_thresh=.7
truth_thresh=1
random=1
参数解读:
classes:检测类别数,COCO 是 80,VOC 是 20num:每个网格预测的锚点框总数,一般是 9mask:当前 YOLO 层使用的锚点框索引,后面细讲anchors:所有锚点框的宽高,共 9 组ignore_thresh:忽略阈值,低于此值的负样本不参与 loss 计算jitter:数据增强时,随机裁剪的抖动幅度
ignore_thresh 这个值很敏感。设太大(如 0.7),很多模糊样本会被忽略,模型学不到边界情况;设太小(如 0.3),负样本太多,模型容易「摆烂」。我一般从 0.5 开始调。
4.3 锚点框(Anchor Boxes)计算
锚点框是什么?说白了就是一组预设的「框模板」。YOLO 不直接预测框的宽高,而是预测相对于这些模板的偏移量。
锚点框怎么来的?用 K-Means 聚类。我在项目中写过这个脚本,核心逻辑是这样的:
# 伪代码:K-Means 聚类锚点框
def kmeans_anchors(boxes, k=9):
# boxes: 所有标注框的宽高 (N, 2)
# 距离度量用 IoU,不用欧氏距离
# 因为大框的欧氏距离天然大,聚类会偏向大框
anchors = random_choose(boxes, k)
for _ in range(max_iter):
# 计算每个 box 到每个 anchor 的 IoU 距离
distances = 1 - iou(boxes, anchors)
# 分配每个 box 到最近的 anchor
labels = argmin(distances, axis=1)
# 更新 anchor 为同类 box 的中位数
for i in range(k):
anchors[i] = median(boxes[labels == i])
return anchors
聚类出来的锚点框,YOLOv3 用的是 9 组:
| 索引 | 宽 | 高 | 适用目标 |
|---|---|---|---|
| 0 | 10 | 13 | 极小目标(如小动物) |
| 1 | 16 | 30 | 小目标 |
| 2 | 33 | 23 | 小目标(方形) |
| 3 | 30 | 61 | 中等目标 |
| 4 | 62 | 45 | 中等目标(宽) |
| 5 | 59 | 119 | 中等目标(高) |
| 6 | 116 | 90 | 大目标 |
| 7 | 156 | 198 | 大目标(高) |
| 8 | 373 | 326 | 极大目标 |
你想想看,为什么是 9 组?因为 YOLOv3 有 3 个检测尺度,每个尺度分配 3 组锚点框。小尺度特征图(13x13)负责大目标,用大锚点框;大尺度特征图(52x52)负责小目标,用小锚点框。
4.4 Mask 机制
Mask 机制,说白了就是「分家」。每个 YOLO 层只负责 3 组锚点框,mask 告诉它用哪 3 组。
看这个配置:
# 第一个 YOLO 层(大目标,13x13 特征图)
[yolo]
mask = 6,7,8
anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326
# 第二个 YOLO 层(中等目标,26x26 特征图)
[yolo]
mask = 3,4,5
# 第三个 YOLO 层(小目标,52x52 特征图)
[yolo]
mask = 0,1,2
为什么这么分?我画个图你就明白了。
Mask 机制的好处很明显——每个 YOLO 层只关注自己「擅长」的目标尺寸。大特征图上的锚点框大,小特征图上的锚点框小,各司其职。
4.5 实战:如何修改 cfg 适配自己的数据集
假设你要用自己的数据集训练 YOLO,需要改 cfg 里的几个地方:
- 修改 classes:把
classes=80改成你的类别数,比如classes=5 - 修改 filters:YOLO 层前面的卷积层,filters 必须等于
(classes + 5) × 3。5 是 x, y, w, h, confidence 这 5 个值。比如 5 类目标,filters = (5+5)×3 = 30 - 重新聚类锚点框:用你的数据集跑一遍 K-Means,把新的锚点框写进 cfg
- 调整 max_batches:一般设为
类别数 × 2000,但最少不低于 6000
我曾经帮一个做工业质检的朋友调 cfg,他检测的是电路板上的微小焊点。默认的锚点框太大,小焊点根本检测不到。重新聚类后,锚点框集中在 (5,5) 到 (20,20) 之间,mAP 直接从 0.3 飙到 0.85。所以说,锚点框一定要跟你的数据匹配。
好了,cfg 文件的核心内容就这些。你只要掌握了网络层参数、锚点框计算和 Mask 机制,就能看懂任何 YOLO 版本的配置文件。下次遇到模型效果不好,别急着调代码,先打开 cfg 看看——问题可能就藏在这里。