4. YOLO模型文件解析:Darknet配置文件结构、权重加载与类别管理
YOLO 跑起来其实就靠三个文件:.cfg、.weights 和 .names。很多人一上来就调参,结果模型根本不收敛——说白了,连配置文件里每一层在干什么都没搞清楚。这一章我们就彻底拆开这三个文件,看看里面到底藏了什么。
4.1 Darknet配置文件(.cfg)结构解读
.cfg 文件是 YOLO 的"骨架说明书"。它用纯文本定义了网络从输入到输出的每一层结构。我个人习惯拿到一个陌生模型,第一件事就是打开它的 .cfg 文件,从头到尾扫一遍。
4.1.1 文件基本结构
一个典型的 .cfg 文件由多个区块组成,每个区块用方括号 [xxx] 开头。比如:
[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=6000
policy=steps
steps=4800,5400
scales=.1,.1
[convolutional]
batch_normalize=1
filters=32
size=3
stride=1
pad=1
activation=leaky
[shortcut]
from=-3
activation=linear
[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
嗯,这里要注意:[net] 是全局配置段,后面的 [convolutional]、[shortcut]、[yolo] 才是网络层定义。每一层的参数顺序不能乱,Darknet 解析器是按行读取的。
4.1.2 关键参数详解
| 参数 | 所属区块 | 含义 | 我的经验 |
|---|---|---|---|
batch |
[net] | 每轮迭代的样本数 | 显存不够就调大 subdivisions |
subdivisions |
[net] | 将 batch 拆成多少份送入 GPU | 我一般设 8 或 16,稳定 |
width/height |
[net] | 输入图像尺寸,必须是 32 的倍数 | 416×416 是速度和精度的平衡点 |
max_batches |
[net] | 最大迭代次数 | 类别数 × 2000,至少 6000 |
filters |
[convolutional] | 卷积核数量 | YOLO 层前一层 filters = (classes+5)×3 |
anchors |
[yolo] | 预设锚框的宽高 | 用 k-means 在训练集上重新聚类 |
classes |
[yolo] | 检测类别数 | 必须和 .names 文件一致 |
classes 但忘记改 YOLO 层前一层的 filters,结果训练时 loss 直接炸到 NaN。公式很简单:filters = (classes + 5) × 3。比如 80 类,就是 (80+5)×3 = 255。
4.1.3 网络结构可视化
下面这张图展示了 YOLOv3 配置文件的核心结构。你想想看,从输入到输出,数据是怎么一层层流过去的:
4.2 预训练权重文件(.weights)加载
.weights 文件是二进制的,存的就是网络每一层的浮点参数。说白了,就是卷积核的数值和偏置项。你没法用文本编辑器打开看——全是乱码。
4.2.1 文件格式解析
权重文件的头部有 4 个 int32 值:
- 前 3 个是版本号(比如 0, 2, 0)
- 第 4 个是训练时的图片数量(seen)
之后就是逐层的权重数据,按 float32 存储。加载时,Darknet 会按照 .cfg 文件中层的顺序,依次读取对应大小的权重块。
加载权重的核心逻辑:
def load_weights(model, weights_path):
with open(weights_path, 'rb') as f:
# 跳过头部 4 个 int32
header = np.fromfile(f, dtype=np.int32, count=4)
# 读取剩余权重
weights = np.fromfile(f, dtype=np.float32)
# 按层分配权重
ptr = 0
for layer in model.layers:
if layer.trainable:
# 卷积层:权重 + 偏置
conv_weights = weights[ptr:ptr+layer.weights_size]
ptr += layer.weights_size
layer.set_weights(conv_weights)
return model
4.2.2 加载时的常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 权重文件大小不匹配 | .cfg 和 .weights 版本不一致 | 确保下载配套的 cfg 和 weights |
| 加载后 loss 不下降 | 权重被随机初始化覆盖了 | 检查加载顺序,先加载再训练 |
| 显存溢出 | 权重文件太大或 batch 太大 | 减小 batch 或使用更小的模型 |
4.3 类别名称文件(.names)管理
.names 文件最简单,就是一行一个类别名。但别小看它,我见过有人因为 .names 文件里多了个空格,导致类别索引错位,检测结果全乱套。
4.3.1 文件格式
person
bicycle
car
motorbike
aeroplane
bus
train
truck
boat
...
注意:行号从 0 开始。第一行是类别 0,第二行是类别 1,以此类推。这个顺序必须和训练时的标注文件一致。
4.3.2 在 OpenCV 中加载 .names
def load_class_names(names_path):
with open(names_path, 'r') as f:
class_names = [line.strip() for line in f.readlines()]
return class_names
# 使用
classes = load_class_names('coco.names')
print(f"共加载 {len(classes)} 个类别")
# 输出:共加载 80 个类别
4.4 模型结构可视化
光看配置文件还不够,我习惯把网络结构画出来。这样能直观看到每一层的输入输出尺寸、参数量,方便定位瓶颈。
4.4.1 使用 Netron 可视化
Netron 是一个轻量级的模型可视化工具,支持 .cfg 文件。你直接把文件拖进去就行:
- 每个节点显示层类型和参数
- 连线显示数据流向
- 点击节点查看详细配置
4.4.2 自己写可视化脚本
有时候我需要批量分析多个模型,就会用 Python 写个简单的结构打印函数:
def print_model_structure(cfg_path):
with open(cfg_path, 'r') as f:
lines = f.readlines()
layer_count = 0
for line in lines:
line = line.strip()
if line.startswith('[') and line.endswith(']'):
layer_type = line[1:-1]
if layer_type != 'net':
layer_count += 1
print(f"层 {layer_count:3d}: {layer_type}")
print(f"\n总层数(不含输入层): {layer_count}")
# 使用
print_model_structure('yolov3.cfg')
# 输出示例:
# 层 1: convolutional
# 层 2: convolutional
# ...
# 总层数(不含输入层): 106
可视化能帮你发现什么?
- 网络是否太深(梯度消失风险)
- 特征图尺寸变化是否合理
- YOLO 检测头的位置是否正确
- 参数量是否在可接受范围内
4.5 三个文件的协同关系
最后总结一下这三个文件怎么配合工作的。你想想看:
- .cfg 定义网络骨架——有多少层、每层什么参数
- .weights 填充骨架的血肉——每层的具体数值
- .names 告诉模型检测到的目标叫什么名字
加载顺序一定是:先解析 .cfg 构建网络结构,再加载 .weights 填充参数,最后用 .names 做标签映射。少一步都不行。