3. 深度学习基础回顾

说实话,很多同学一上来就急着调YOLO的代码,结果连卷积怎么算的、锚框干嘛用的都没搞明白。我见过太多人卡在“为什么我的模型检测不到小目标”这种问题上,其实根子就在这几个基础概念没吃透。

这一章,咱们就把YOLO背后的四个核心概念掰开揉碎讲清楚。别怕,我会用我踩过的坑帮你铺路。

3.1 卷积神经网络(CNN)——YOLO的骨架

YOLO本质上就是一个CNN。你想想看,一张图片进来,怎么变成一堆框和类别?靠的就是卷积层一层层地“看”图像。

3.1.1 卷积到底在干什么?

说白了,卷积就是一个滑动窗口。一个小矩阵(卷积核)在图像上滑过,每个位置做一次点乘求和。我刚开始学的时候,总觉得这玩意儿很玄乎,后来自己手写了一次前向传播,才明白它就是在提取特征——边缘、纹理、形状,一层比一层抽象。

核心公式: 输出特征图尺寸 = (输入尺寸 - 卷积核尺寸 + 2 × 填充) / 步长 + 1

举个例子,输入是224×224的图像,用3×3的卷积核,步长1,填充1,输出还是224×224。嗯,这里要注意,填充是为了不让图像越卷越小。

3.1.2 池化层——降维的艺术

池化层就是“浓缩精华”。最大池化取窗口里的最大值,平均池化取平均值。我在做一个小目标检测项目时,发现池化层用太多会把小目标直接“池”没了。后来我改用步长卷积来降采样,效果好了不少。

操作 作用 常见参数
卷积 提取特征 3×3, 1×1, 5×5
最大池化 保留最强响应 2×2, 步长2
平均池化 平滑特征 2×2, 步长2

3.2 感受野——网络到底看到了多大范围?

感受野这个概念,我当年面试的时候被问过好几次。它指的是特征图上某个点对应到原始图像上的区域大小。你想想看,如果感受野太小,网络只能看到局部,检测大物体就会出问题。

3.2.1 感受野的计算

计算公式其实不复杂:

# 感受野计算示例
def compute_rf(layers):
    rf = 1
    for k, s in layers:
        rf = rf + (k - 1) * s
    return rf

# 例子:3层3x3卷积,步长均为1
print(compute_rf([(3,1), (3,1), (3,1)]))  # 输出 7

3层3×3卷积堆叠,感受野是7×7。为什么不用一个7×7的卷积?因为三层3×3参数量更少,非线性更强。这是我个人习惯——能用小卷积堆叠就不用大卷积。

经验之谈: YOLOv5的Backbone用了大量3×3卷积和CSP结构,就是为了在控制计算量的同时扩大感受野。我在替换Backbone时,发现感受野不够大,小目标召回率直接掉了5个点。

3.3 锚框(Anchor Box)——YOLO的“预设答案”

锚框是YOLO里最容易被忽视但又最关键的设计之一。说白了,它就是一组预设的框,网络只需要预测这些框的偏移量,而不是直接预测坐标。

3.3.1 为什么需要锚框?

直接预测坐标太难了。网络要预测(x, y, w, h),这四个值范围太大,不稳定。有了锚框,网络只需要预测“相对于锚框的偏移”,范围小得多,训练也稳定得多。

我记得第一次自己训练YOLOv3时,用了默认的锚框,结果检测自行车总是偏大。后来我重新用K-Means聚类了训练集的标注框,换了锚框,mAP直接涨了3个点。

3.3.2 锚框的生成与匹配

YOLO通常在每个特征图网格上放3个不同尺寸的锚框。训练时,每个标注框会匹配IoU最大的锚框。匹配上了,这个锚框就负责预测这个物体。

特征图尺寸 锚框尺寸(以YOLOv5为例) 负责检测
80×80 (10,13), (16,30), (33,23) 小目标
40×40 (30,61), (62,45), (59,119) 中目标
20×20 (116,90), (156,198), (373,326) 大目标
避坑指南: 我曾经直接拿COCO的锚框去训练一个全是无人机的数据集,结果模型完全学不会。锚框一定要根据你的数据集重新聚类!别偷懒。

3.4 非极大值抑制(NMS)——去掉多余的框

模型预测完,同一个物体上可能叠了十几个框。NMS就是把这些框合并成一个。流程很简单:选置信度最高的框,去掉所有和它IoU大于阈值的框,重复直到没有框剩下。

3.4.1 NMS的代码实现

def nms(boxes, scores, iou_threshold=0.5):
    # boxes: [[x1,y1,x2,y2], ...]
    # scores: [0.9, 0.8, ...]
    indices = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)
    keep = []
    
    while indices:
        i = indices[0]
        keep.append(i)
        # 计算当前框与其余框的IoU
        ious = compute_iou(boxes[i], boxes[indices[1:]])
        # 保留IoU小于阈值的框
        indices = [indices[j+1] for j, iou in enumerate(ious) if iou < iou_threshold]
    
    return keep

这段代码我手写过不下十次。注意一点:IoU阈值设得太低(比如0.3),会漏掉挨得近的物体;设得太高(比如0.7),又会留下很多重复框。我一般从0.5开始调。

3.4.2 NMS的变种

YOLOv5用了加权NMS,不是简单去掉,而是把重叠框的坐标按置信度加权平均。Soft-NMS则是降低重叠框的置信度而不是直接去掉。这些改进在密集场景下特别有用。

关键参数: IoU阈值(0.5~0.7)、置信度阈值(0.25~0.5)、最大检测数(100~300)。这三个参数直接影响最终检测效果。

3.5 知识体系总览

下面这张图把四个概念串起来了。CNN提取特征,感受野决定网络能看到多大范围,锚框提供预设的检测模板,NMS负责后处理去重。YOLO就是靠这套组合拳实现端到端检测的。

YOLO核心概念知识体系 YOLO目标检测 卷积神经网络 (CNN) 感受野 (Receptive Field) 锚框 (Anchor Box) 非极大值抑制 (NMS) CNN提取特征 → 感受野决定特征范围 → 锚框提供检测模板 → NMS去重输出最终结果 四个概念环环相扣,缺一不可 CNN & 感受野 • 卷积层堆叠扩大感受野 • 小卷积核减少参数量 • 特征图分辨率逐层降低 锚框 & NMS • K-Means聚类生成锚框 • 多尺度特征图匹配不同大小 • NMS阈值影响检测密度

3.6 本章小结

这四个概念是YOLO的基石。CNN负责提取特征,感受野决定了网络能看到多大的上下文,锚框提供了检测的“先验知识”,NMS负责后处理去重。我建议你把这章的内容吃透再往下走,不然后面调参的时候会一头雾水。

实践建议: 打开一个YOLO模型,打印出每一层特征图的尺寸和感受野,再可视化一下锚框在图像上的分布。亲手做一遍,比看十遍书都管用。