码到成功
FRCNN 和 YOLOv3 损失函数详解
## 先把观察角度摆好
如果只盯着公式本身,很容易越看越乱。更稳的办法是反过来:
先看标签,再看网络输出,最后问一句“这个损失到底在逼谁变好”。
用这把尺子去量,`Faster R-CNN` 更像“分阶段、逐步收紧”,`YOLOv3` 更像“几项任务打包一起优化”。两家路子不同,但核心工具其实没那么玄乎,主要还是交叉熵和回归误差。
## Faster R-CNN:两阶段,思路很直
先看第一阶段,也就是 `RPN` 这一段。
### 真实标签长什么样
常见实现里会准备两组标签:
- `y_rpn_cls.shape = (1, row, col, 18)`
- `y_rpn_regr.shape = (1, row, col, 72)`
这里可以把它理解成:
- `y_rpn_cls` 的前 9 个值表示这个位置上的 anchor 能不能参与当前训练
- `y_rpn_cls` 的后 9 个值表示这个 anchor 更像前景还是背景
- `y_rpn_regr` 的前 36 个值本质上是前景背景信息扩成 4 倍后的掩码
- `y_rpn_regr` 的后 36 个值才是真正的框回归目标
换句话说,第一阶段一边在挑 anchor,一边在判前后景,一边还顺手做了框回归。
### 网络输出是什么
对应地,网络会给出两份输出:
- `x_class.shape = (n, row, col, 9)`
- `x_regr.shape = (n, row, col, 36)`
前者管分类倾向,后者管回归偏移量。
### 第一阶段的两个损失到底在干嘛
第一个是 `rpn_loss_cls`。它看上去写得有点绕,但把掩码和归一化项先放一边,本质就是一个“带筛选条件的二分类交叉熵”。
直觉上可以这么理解:
- 只对有效 anchor 计算损失
- 让前景 / 背景判断更准
- 顺手把真正参与训练的 anchor 范围收得更聚焦
也就是说,它不是单纯在问“你分对没”,还在通过掩码机制告诉模型“别什么 anchor 都抓着不放”。
第二个是 `rpn_loss_regr`。这一项的骨架就是回归误差,不过工程里很少直接上裸的 `L2`,而是换成更稳一点的 `smooth L1`。
它的味道可以概括成一句话:
- 小误差区域像平方误差,鼓励细修
- 大误差区域像绝对值误差,避免梯度太炸
所以第一阶段合起来,优化的是三件事:
- 该启用哪些 anchor
- 这个 anchor 更像前景还是背景
- 这个 anchor 该往哪个框回归
### 第二阶段怎么理解
到了第二阶段,逻辑其实没变,只是对象从“候选框生成”切到了“候选框精修与分类”。
这时候通常会看到:
- 一个分类损失,常见是 `softmax + categorical crossentropy`
- 一个回归损失,依然常见是 `smooth L1`
所以二阶段检测器的感觉很像一套接一套地做筛选和修正:先粗筛,再精修。

## YOLOv3:把几件事一口气联合优化
看完 `Faster R-CNN`,再看 `YOLOv3`,你会明显感觉到风格变化了。
它不是先来一段、再来一段,而是把坐标、置信度、类别这些东西揉在一起,直接联合训练。
### 真实标签是什么样
`YOLOv3` 的 `y_true` 一般来自 3 个尺度的特征层,也就是常见的金字塔输出:
- `(13, 13)`
- `(26, 26)`
- `(52, 52)`
每层标签的 shape 都可以写成:
`(batch, row, col, 3, 5 + class_num)`
这一串维度读顺以后,后面的损失基本就不神秘了:
- `batch`:批次大小
- `row, col`:当前尺度下的网格尺寸
- `3`:每个位置分到的 3 组 anchor
- `5`:`x, y, w, h, confidence`
- `class_num`:类别数
### 网络输出是什么
实现里常见几个名字:
- `raw_pred`
- `pred_xy`
- `pred_wh`
- `grid`
可以粗略理解成:
- `raw_pred` 是网络原始输出
- `pred_xy` 是中心点位置相关结果
- `pred_wh` 是宽高相关结果
- `grid` 帮你把“相对网格偏移”还原到整张图的坐标语境里
### YOLOv3 的四块损失
`YOLOv3` 常见实现里,核心损失通常拆成四部分:
- `xy_loss`
- `wh_loss`
- `confidence_loss`
- `class_loss`
#### 1. `xy_loss`
这一项主要管目标中心点。
它通常会乘上两个很关键的权重:
- `object_mask`
- `box_loss_scale`
前者可以理解成“这个位置到底有没有目标”,后者常写成和框面积相关的权重,常见理解是 `2 - w * h` 这类形式。它的作用很实用:小目标别被轻轻带过,大目标也别一股脑吃掉所有权重。
所以这项在优化时,实际上不只是中心点坐标本身,还会和目标存在性、框尺度权重绑在一起考虑。
#### 2. `wh_loss`
这一项更像纯回归,但又不是完全裸回归。
它会让预测框的宽高往真实框靠,同时也受 `object_mask` 和框尺度权重影响。所以你会发现,`YOLOv3` 的损失设计非常喜欢“一个项不只做一件事”,而是让多个目标在同一套结构里互相牵制。
#### 3. `confidence_loss`
这一项很关键,因为它决定模型怎么看“这里到底有没有东西”。
除了正常的前景 / 背景二值交叉熵,还会有一个 `ignore_mask`。这个东西很像在说:
“有些位置虽然不是最终负责预测的那个框,但它也不完全该被粗暴打成背景,先别急着罚。”
这个设计和两阶段检测里常说的“中性样本”有点类似味道。它能减少一些不必要的误罚,让训练更稳。
#### 4. `class_loss`
这一项就相对直接了,就是在有目标的地方去做类别预测,常见写法是带 `object_mask` 的多标签 / 多分类交叉熵。
最后总损失就是把这几项加起来。
## 一段更顺手的 Python 理解版
如果你只是想把核心思路先摸熟,用下面这种“缩水版”写法会比直接啃工程代码舒服很多。
### 用 NumPy 看 `smooth L1`
```python
import numpy as np
def smooth_l1(diff: np.ndarray) -> np.ndarray:
abs_diff = np.abs(diff)
small = abs_diff <= 1.0
return np.where(small, 0.5 * diff ** 2, abs_diff - 0.5)
target = np.array([0.2, 0.4, -0.1, 1.8], dtype=np.float32)
pred = np.array([0.1, 0.1, -0.3, 0.5], dtype=np.float32)
loss = smooth_l1(target - pred).mean()
print("smooth_l1 =", float(loss))
```
这段基本就能对应上 `Faster R-CNN` 里那类回归损失的核心形状。
### 用 Keras 风格理解 RPN 分类损失
```python
import tensorflow as tf
from tensorflow.keras import backend as K
def demo_rpn_cls_loss(valid_mask, fg_bg_true, fg_bg_pred):
bce = K.binary_crossentropy(fg_bg_true, fg_bg_pred)
masked = valid_mask * bce
return K.sum(masked) / K.sum(1e-6 + valid_mask)
```
这里最重要的不是语法,而是那个意思:
- 先用 `valid_mask` 挑出需要参与训练的 anchor
- 再只对这些位置算二分类交叉熵
### 用 PyTorch 写一个 YOLOv3 简化版框架
```python
import torch
import torch.nn.functional as F
def yolo_loss_demo(raw_pred, raw_true_xy, raw_true_wh,
object_mask, true_class_probs, box_loss_scale):
xy_loss = object_mask * box_loss_scale * F.binary_cross_entropy_with_logits(
raw_pred[..., 0:2], raw_true_xy, reduction="none"
)
wh_loss = object_mask * box_loss_scale * 0.5 * (
raw_pred[..., 2:4] - raw_true_wh
) ** 2
conf_loss = F.binary_cross_entropy_with_logits(
raw_pred[..., 4:5], object_mask, reduction="none"
)
cls_loss = object_mask * F.binary_cross_entropy_with_logits(
raw_pred[..., 5:], true_class_probs, reduction="none"
)
total = xy_loss.mean() + wh_loss.mean() + conf_loss.mean() + cls_loss.mean()
return total
```
这不是完整训练代码,但很适合拿来建立直觉:`YOLOv3` 的损失确实就是几块任务一起推。

## 把两家的风格放一起看
聊到这里,差异其实已经很清楚了。
`Faster R-CNN` 更像一套分工明确的流程:
- 先做候选框
- 再做分类和精修
- 每一阶段的目标都很直接
`YOLOv3` 则更像把多个小任务捆在一起冲:
- 中心点要准
- 宽高要准
- 置信度要稳
- 类别也得一起跟上
如果把它们各自的气质说得更直白一点:
- `Faster R-CNN` 偏“精准优化”
- `YOLOv3` 偏“联合优化”
前者层层推进,后者整体联动。