检测中 Denoising Training (去噪训练) 怎么理解?

1. 什么是 Denoising?

Denoising Training(去噪训练) 是 DETR 系列方法中引入的一种训练技巧,用于加速 Transformer 检测器的收敛。

1.1 核心思想

Denoising 的核心思想是:给模型一些"带噪声的 GT(Ground Truth)",让模型学习如何从这些噪声中恢复出正确的 GT

这类似于:

  • 自监督学习:模型需要从损坏的输入中恢复原始信息
  • 数据增强:通过添加噪声增加训练样本的多样性
  • 正则化:防止模型过拟合

1.2 为什么需要 Denoising?

问题: DETR 系列方法训练收敛慢,需要很多轮训练才能达到好的效果。

原因:

  1. 二分匹配的不稳定性:在训练初期,模型预测很差,Hungarian matching 的结果不稳定
  2. 缺乏明确的监督信号:模型需要从随机初始化的 queries 学习,没有明确的起始点
  3. 正负样本不平衡:大部分 queries 匹配不到 GT,只有少数是正样本

解决方案: Denoising

  • 提供带噪声的 GT作为输入,模型知道这些 noisy anchors 应该回归到哪个 GT
  • 这给模型提供了明确的监督信号,加速收敛
  • 同时训练模型处理有噪声的输入,提高鲁棒性

2. 带噪声的 GT 是什么?

2.1 概念

带噪声的 GT(Noisy Ground Truth) 是指:

  • 真实的 GT boxes 开始
  • 添加随机噪声(偏移、缩放等)
  • 得到带噪声的 anchor boxes
  • 这些 noisy anchors 的目标仍然是原始的 GT

2.2 具体操作

# 1. 从 GT boxes 开始
gt_boxes = [batch, num_gt, 10]  # 真实的 GT boxes

# 2. 添加随机噪声
noise = torch.rand_like(gt_boxes) * 2 - 1  # [-1, 1]
noise *= noise_scale  # 例如: [2.0, 2.0, 2.0, 0.5, 0.5, ...]
noisy_anchors = gt_boxes + noise

# 3. 这些 noisy anchors 的目标仍然是原始的 GT
target_boxes = gt_boxes  # 目标不变
target_labels = gt_labels  # 标签不变

2.3 噪声的尺度

不同维度使用不同的噪声尺度:

  • 位置 (x, y, z):较大的噪声(如 2.0),因为位置变化范围大
  • 尺寸 (w, l, h):较小的噪声(如 0.5),因为尺寸变化范围小
  • 角度 (yaw):中等噪声
  • 速度 (vx, vy, vz):较小的噪声
# Sparse4D 中的配置
dn_noise_scale = [2.0] * 3 + [0.5] * 7
#                 位置     尺寸+角度+速度

3. Denoising 的具体操作流程

3.1 生成带噪声的 Anchors

def get_dn_anchors(cls_target, box_target, gt_instance_id=None):
    """
    生成带噪声的 anchors 用于 denoising training
    
    输入:
        cls_target: [batch, num_gt] - GT 类别标签
        box_target: [batch, num_gt, 10] - GT boxes
        gt_instance_id: [batch, num_gt] - GT 实例 ID(用于时序)
    
    输出:
        dn_anchor: [batch, num_dn_groups * num_gt, 10] - 带噪声的 anchors
        dn_box_target: [batch, num_dn_groups * num_gt, 10] - 对应的 GT boxes
        dn_cls_target: [batch, num_dn_groups * num_gt] - 对应的 GT 标签
        attn_mask: [num_dn, num_dn] - attention mask
        valid_mask: [batch, num_dn_groups * num_gt] - 有效掩码
        dn_id_target: [batch, num_dn_groups * num_gt] - 实例 ID
    """
    
    # 1. 准备 GT(padding 到相同长度)
    max_dn_gt = max([len(x) for x in cls_target])
    cls_target = pad_to_length(cls_target, max_dn_gt)
    box_target = pad_to_length(box_target, max_dn_gt)
    
    # 2. 复制 num_dn_groups 次(每组使用不同的噪声)
    if num_dn_groups > 1:
        cls_target = cls_target.tile(num_dn_groups, 1)
        box_target = box_target.tile(num_dn_groups, 1, 1)
    
    # 3. 生成随机噪声
    noise = torch.rand_like(box_target) * 2 - 1  # [-1, 1]
    noise *= dn_noise_scale  # 应用噪声尺度
    dn_anchor = box_target + noise  # 带噪声的 anchor
    
    # 4. (可选)添加负样本
    if add_neg_dn:
        noise_neg = torch.rand_like(box_target) + 1  # [1, 2]
        noise_neg *= random_sign() * dn_noise_scale
        dn_anchor = concat([dn_anchor, box_target + noise_neg], dim=1)
    
    # 5. 使用 Hungarian matching 匹配 noisy anchors 和 GT
    # (虽然我们知道对应关系,但使用 matching 可以处理边界情况)
    cost = compute_box_cost(dn_anchor, box_target)
    anchor_idx, gt_idx = hungarian_matching(cost)
    
    # 6. 分配目标
    dn_box_target[anchor_idx] = box_target[gt_idx]
    dn_cls_target[anchor_idx] = cls_target[gt_idx]
    
    # 7. 生成 attention mask(同一组内的 anchors 可以互相 attention)
    attn_mask = create_group_attention_mask(num_dn_groups, num_gt)
    
    return dn_anchor, dn_box_target, dn_cls_target, attn_mask, valid_mask, dn_id_target

3.2 在模型中使用

# 在 forward 中
def forward(self, feature_maps, metas):
    # 1. 获取正常的 learnable instances
    instance_feature, anchor = self.instance_bank.get(...)
    
    # 2. 生成带噪声的 anchors(仅在训练时)
    if self.training:
        dn_metas = self.sampler.get_dn_anchors(
            metas["gt_labels_3d"],
            metas["gt_bboxes_3d"],
            gt_instance_id
        )
        
        # 3. 拼接 learnable instances 和 noisy instances
        anchor = concat([anchor, dn_anchor], dim=1)
        instance_feature = concat([instance_feature, dn_feature], dim=1)
        
        # 4. 设置 attention mask
        # - learnable instances 之间可以 attention
        # - noisy instances 在同一组内可以 attention
        # - 不同组之间不能 attention
    
    # 5. 通过 decoder
    for layer in decoder_layers:
        instance_feature = layer(instance_feature, anchor, ...)
    
    # 6. 分离预测结果
    prediction = model_output[:, :num_learnable]  # 正常预测
    dn_prediction = model_output[:, num_learnable:]  # denoising 预测
    
    return {
        "prediction": prediction,
        "dn_prediction": dn_prediction,
        "dn_reg_target": dn_box_target,
        "dn_cls_target": dn_cls_target,
        ...
    }

3.3 计算 Loss

def loss(self, model_outs, data):
    # 1. 正常预测的 loss
    normal_loss = compute_loss(
        prediction, 
        reg_target, 
        cls_target
    )
    
    # 2. Denoising 的 loss
    dn_loss = compute_loss(
        dn_prediction,
        dn_reg_target,  # 原始的 GT boxes
        dn_cls_target,  # 原始的 GT 标签
        weight=dn_loss_weight  # 通常权重较大,如 5.0
    )
    
    total_loss = normal_loss + dn_loss
    return total_loss

4. Sparse4D 中的 Denoising

4.1 配置参数

sparse4dv3_temporal_r50_1x8_bs6_256x704.py 中:

sampler=dict(
    type="SparseBox3DTarget",
    num_dn_groups=5,              # Denoising 组数
    num_temp_dn_groups=2,         # 时序 denoising 组数
    dn_noise_scale=[2.0] * 3 + [0.5] * 7,  # 噪声尺度
    max_dn_gt=32,                 # 最多使用 32 个 GT
    add_neg_dn=True,              # 是否添加负样本
)

4.2 关键参数说明

参数 说明 默认值
num_dn_groups Denoising 组数,每组使用不同的噪声 5
num_temp_dn_groups 时序 denoising 组数(用于时序融合) 2
dn_noise_scale 噪声尺度,不同维度使用不同尺度 [2.0]*3 + [0.5]*7
max_dn_gt 最多使用的 GT 数量(限制计算量) 32
add_neg_dn 是否添加负样本(远离 GT 的 noisy anchors) True

4.3 工作流程

1. 输入 GT
   └─> gt_boxes: [batch, num_gt, 10]
   └─> gt_labels: [batch, num_gt]

2. 生成带噪声的 Anchors
   └─> 复制 num_dn_groups 次
   └─> 每组添加不同的随机噪声
   └─> dn_anchor: [batch, num_dn_groups * num_gt, 10]

3. 匹配和分配目标
   └─> 使用 Hungarian matching
   └─> 分配对应的 GT boxes 和 labels

4. 拼接到模型输入
   └─> learnable instances: [batch, 900, ...]
   └─> noisy instances: [batch, num_dn_groups * num_gt, ...]
   └─> 总输入: [batch, 900 + num_dn_groups * num_gt, ...]

5. 通过 Decoder
   └─> 所有 instances 一起处理
   └─> 使用 attention mask 控制交互

6. 分离预测结果
   └─> normal_prediction: [batch, 900, ...]
   └─> dn_prediction: [batch, num_dn_groups * num_gt, ...]

7. 计算 Loss
   └─> normal_loss: 正常预测的 loss
   └─> dn_loss: denoising 的 loss (权重 5.0)

4.4 时序 Denoising

Sparse4D 还支持时序 denoising,用于时序融合:

# 在单帧 decoder 后
if len(prediction) == num_single_frame_decoder:
    # 缓存当前帧的 denoising instances
    self.sampler.cache_dn(
        dn_instance_feature,
        dn_anchor,
        dn_cls_target,
        valid_mask,
        dn_id_target
    )

# 在时序 decoder 中
# 使用缓存的时序 denoising instances
# 与当前帧的 denoising instances 进行匹配和融合

5. Attention Mask

Denoising 使用特殊的 attention mask 来控制不同 instances 之间的交互:

# Attention Mask 结构
attn_mask = [
    # Learnable instances (900 个)
    [0, 0, ..., 1, 1, ...],  # 可以互相 attention
    [0, 0, ..., 1, 1, ...],
    ...
    # Noisy instances (num_dn_groups * num_gt 个)
    [1, 1, ..., 0, 0, ...],  # 同一组内可以 attention
    [1, 1, ..., 0, 0, ...],
    ...
]

# 规则:
# - Learnable instances 之间:可以 attention (mask=0)
# - Noisy instances 同一组内:可以 attention (mask=0)
# - Noisy instances 不同组间:不能 attention (mask=1)
# - Learnable 和 Noisy 之间:不能 attention (mask=1)

6. 为什么 Denoising 有效?

6.1 提供明确的监督信号

  • 正常训练:模型需要从随机初始化的 queries 学习,初期匹配不稳定
  • Denoising:模型知道 noisy anchors 应该回归到哪个 GT,提供明确的监督

6.2 增加正样本数量

  • 正常训练:只有匹配到的 queries 是正样本,数量少
  • Denoising:所有 noisy anchors 都有对应的 GT,都是正样本

6.3 提高鲁棒性

  • 模型学习从有噪声的输入中恢复正确的输出
  • 提高模型对不完美输入的鲁棒性

6.4 加速收敛

  • 实验表明,使用 denoising 可以将训练轮数减少 50% 以上
  • 同时提高最终的性能

7. 代码示例

7.1 生成带噪声的 Anchors

# 在 SparseBox3DTarget.get_dn_anchors 中

# 1. 准备 GT
box_target = encode_reg_target(gt_boxes)  # [batch, num_gt, 10]

# 2. 复制 num_dn_groups 次
if num_dn_groups > 1:
    box_target = box_target.tile(num_dn_groups, 1, 1)
    # [batch, num_dn_groups * num_gt, 10]

# 3. 生成噪声
noise = torch.rand_like(box_target) * 2 - 1  # [-1, 1]
noise *= torch.tensor([2.0, 2.0, 2.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])
dn_anchor = box_target + noise

# 4. 匹配和分配
cost = compute_box_cost(dn_anchor, box_target)
anchor_idx, gt_idx = hungarian_matching(cost)
dn_box_target[anchor_idx] = box_target[gt_idx]

7.2 在 Forward 中使用

# 在 Sparse4DHead.forward 中

# 1. 获取正常 instances
instance_feature, anchor = self.instance_bank.get(...)

# 2. 生成 denoising instances
if self.training:
    dn_metas = self.sampler.get_dn_anchors(
        metas["gt_labels_3d"],
        metas["gt_bboxes_3d"]
    )
    dn_anchor, dn_box_target, dn_cls_target, attn_mask, valid_mask, dn_id_target = dn_metas
    
    # 3. 拼接
    anchor = torch.cat([anchor, dn_anchor], dim=1)
    instance_feature = torch.cat([
        instance_feature,
        torch.zeros_like(instance_feature[:, :dn_anchor.shape[1]])
    ], dim=1)
    
    # 4. 设置 attention mask
    # attn_mask 已经设置好

7.3 计算 Loss

# 在 Sparse4DHead.loss 中

# 1. 正常预测的 loss
normal_loss = compute_loss(prediction, reg_target, cls_target)

# 2. Denoising 的 loss
if "dn_prediction" in model_outs:
    dn_loss = compute_loss(
        model_outs["dn_prediction"],
        model_outs["dn_reg_target"],
        model_outs["dn_cls_target"],
        weight=self.dn_loss_weight  # 5.0
    )
    
total_loss = normal_loss + dn_loss

8. 总结

Denoising Training 的核心:

  1. 生成带噪声的 GT:从真实 GT 添加随机噪声
  2. 明确的监督信号:这些 noisy anchors 的目标是原始 GT
  3. 增加正样本:所有 noisy anchors 都是正样本
  4. 加速收敛:提供更好的训练信号

关键点:

  • 不是真正的"去噪",而是"从噪声中恢复"
  • 噪声尺度需要仔细设计(位置大,尺寸小)
  • 使用 attention mask 控制不同 instances 的交互
  • 可以显著加速训练并提高性能

类比理解:

  • 就像给模型一些"模糊的答案"(带噪声的 GT)
  • 让模型学习如何"修正"这些模糊答案,得到正确答案
  • 这样模型既能学习检测,又能处理不完美的输入
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐