实现一个简化版YOLO目标检测器,理解检测pipeline的每个环节。
1.将图像分成S×S网格
2.每个网格预测B个边界框和C个类别
3.输出张量:S×S×(B×5+C)
4.非极大值抑制(NMS)去除重复检测
L = λ_coord·L_loc + λ_noobj·L_conf + L_cls
定位损失:边界框坐标的MSE
置信度损失:有/无目标的BCE
分类损失:类别交叉熵
import torch
import torch.nn as nn
import math
# 简化YOLO实现
print("=== 简化YOLO目标检测 ===")
# YOLO检测头
class YOLOHead(nn.Module):
def __init__(self, in_channels, num_anchors, num_classes):
super().__init__()
self.num_anchors = num_anchors
self.num_classes = num_classes
# 每个锚框预测: 4(bbox) + 1(obj) + num_classes
self.out_channels = num_anchors * (5 + num_classes)
self.conv = nn.Conv2d(in_channels, self.out_channels, 1)
def forward(self, x):
out = self.conv(x)
B, _, H, W = out.shape
out = out.view(B, self.num_anchors, 5 + self.num_classes, H, W)
return out
# 测试
head = YOLOHead(128, 3, 10)
feature_map = torch.randn(2, 128, 13, 13)
out = head(feature_map)
print(f"输入特征图: {list(feature_map.shape)}")
print(f"检测输出: {list(out.shape)}")
print(f"(batch={out.shape[0]}, anchors={out.shape[1]}, 5+classes={out.shape[2]}, H={out.shape[3]}, W={out.shape[4]})")
# YOLO损失函数
class YOLOLoss(nn.Module):
def __init__(self, num_classes, lambda_coord=5.0, lambda_noobj=0.5):
super().__init__()
self.num_classes = num_classes
self.lambda_coord = lambda_coord
self.lambda_noobj = lambda_noobj
def forward(self, predictions, targets):
# 简化实现
obj_mask = targets[..., 4] > 0 # 有目标的掩码
noobj_mask = ~obj_mask
# 定位损失(仅对有目标的框)
if obj_mask.sum() > 0:
loc_loss = F.mse_loss(predictions[obj_mask][:, :4], targets[obj_mask][:, :4])
else:
loc_loss = torch.tensor(0.0)
# 置信度损失
obj_loss = F.binary_cross_entropy_with_logits(predictions[obj_mask][:, 4], targets[obj_mask][:, 4]) if obj_mask.sum() > 0 else torch.tensor(0.0)
noobj_loss = F.binary_cross_entropy_with_logits(predictions[noobj_mask][:, 4], targets[noobj_mask][:, 4]) if noobj_mask.sum() > 0 else torch.tensor(0.0)
total = self.lambda_coord * loc_loss + obj_loss + self.lambda_noobj * noobj_loss
return total
import torch.nn.functional as F
# NMS演示
print("\n=== NMS去重 ===")
boxes = torch.tensor([
[0.5, 0.5, 0.3, 0.3], # 真实目标1
[0.52, 0.48, 0.28, 0.32], # 重复检测1
[0.49, 0.51, 0.31, 0.29], # 重复检测1
[0.2, 0.2, 0.25, 0.25], # 真实目标2
[0.22, 0.18, 0.23, 0.27], # 重复检测2
])
scores = torch.tensor([0.95, 0.85, 0.80, 0.90, 0.70])
labels = torch.tensor([0, 0, 0, 1, 1])
def compute_iou(box1, boxes):
x1 = torch.max(box1[0]-box1[2]/2, boxes[:,0]-boxes[:,2]/2)
y1 = torch.max(box1[1]-box1[3]/2, boxes[:,1]-boxes[:,3]/2)
x2 = torch.min(box1[0]+box1[2]/2, boxes[:,0]+boxes[:,2]/2)
y2 = torch.min(box1[1]+box1[3]/2, boxes[:,1]+boxes[:,3]/2)
inter = torch.clamp(x2-x1, 0) * torch.clamp(y2-y1, 0)
area1 = box1[2] * box1[3]
area2 = boxes[:,2] * boxes[:,3]
return inter / (area1 + area2 - inter + 1e-6)
# NMS
keep = []
order = scores.argsort(descending=True)
while len(order) > 0:
i = order[0].item()
keep.append(i)
if len(order) == 1:
break
ious = compute_iou(boxes[i], boxes[order[1:]])
mask = ious < 0.5
order = order[1:][mask]
print(f"NMS前: {len(boxes)}个检测框")
print(f"NMS后: {len(keep)}个检测框, 保留: {keep}")
for i in keep:
print(f" Box{i}: [{boxes[i].tolist()}], score={scores[i]:.2f}, class={labels[i]}")
本课的核心知识点构成了实战项目学习的重要一环。让我们从更高维度审视这些知识之间的关系:
⚠️ 初学者常犯的错误:
💡 最佳实践:
深度学习是一个整体——每一课的知识都不是孤立的:
💡 继续深入的学习路径:
在VOC数据集上训练YOLO,评估mAP。
对数据集做K-means聚类,学习最优锚框尺寸。
实现Soft-NMS,对比标准NMS的效果。
⚠️ 需要避免的典型错误:
💡 提升模型性能的系统方法:
理解本课内容需要将其置于更大的知识网络中。每个核心概念都不是孤立的,它们相互关联、相互支撑:
建立正确的直觉比记住公式更重要:
1. 维度直觉:高维空间中,数据分布与低维直觉截然不同(维度诅咒)
2. 信息瓶颈:神经网络逐层压缩信息,保留任务相关的、丢弃无关的
3. 流形假设:高维数据实际分布在低维流形上,学习即发现流形结构
4. 偏差-方差权衡:简单模型欠拟合(高偏差),复杂模型过拟合(高方差)
本课作为实战项目阶段的核心内容,与前后课程紧密关联:
| 关联课程 | 关联点 | 协同效应 |
|---|---|---|
| 前序课程 | 提供了本课的基础知识 | 循序渐进的理解 |
| 后续课程 | 本课内容是后续的基础 | 逐步构建能力 |
| 平行课程 | 同一阶段的互补知识 | 多角度深入理解 |
| 实战项目 | 综合运用所有知识 | 理论与实践结合 |
💡 准备面试时,确保能回答以下问题:
掌握本课内容后,可以通过以下方式继续深入:
YOLO目标检测实战——检测高手!