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 配置文件的核心结构。你想想看,从输入到输出,数据是怎么一层层流过去的:

YOLOv3 配置文件结构解析 输入层 [net] 416×416×3 特征提取主干 卷积 + 残差 + 下采样 尺度1 (大目标) 13×13 特征图 尺度2 (中目标) 26×26 特征图 尺度3 (小目标) 52×52 特征图 YOLO检测头1 anchors: 大框 YOLO检测头2 anchors: 中框 YOLO检测头3 anchors: 小框 输出层 [yolo] 💡 关键点: 每个YOLO检测头对应一个尺度的特征图,使用不同的anchors。配置文件中的mask参数指定了每个头使用哪几个anchors。

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 或使用更小的模型
我的习惯: 每次加载权重后,我会打印第一层卷积的权重统计值(均值、方差),看看是不是合理的数值范围。如果全是 0 或者 NaN,那肯定加载出问题了。

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 个类别
我曾经踩过的坑: 有一次我复制了别人的 .names 文件,但里面混入了空行和 BOM 头。结果类别数量对不上,检测时索引越界。建议加载后先打印前 5 个类别名确认一下。

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 三个文件的协同关系

最后总结一下这三个文件怎么配合工作的。你想想看:

  1. .cfg 定义网络骨架——有多少层、每层什么参数
  2. .weights 填充骨架的血肉——每层的具体数值
  3. .names 告诉模型检测到的目标叫什么名字

加载顺序一定是:先解析 .cfg 构建网络结构,再加载 .weights 填充参数,最后用 .names 做标签映射。少一步都不行。

我的工作流: 拿到一个新模型,我第一步先看 .cfg 的总层数和 YOLO 层位置;第二步加载 .weights 并打印第一层权重统计;第三步确认 .names 的类别数和顺序。三步走完,心里就有底了。