论文阅读《FSCE: Few-Shot Object Detection via Contrastive Proposal Encoding》

提出了一种对比表征嵌入的方法来来实现小样本目标检测,观察到使用不同的 IoU 来检测物体与对比学习方法中对比不同“正对”和“负对”来实现分类有异曲同工之妙以及好的特征嵌入是提升小样本学习性能的关键。动机是观察到模型的错误更有可能是误分类而不是定位,本文解决这一问题的方法是对“正对”和“负对”施加了对比嵌入损失(CPE loss),使“正对”的得分远大于“负对”的得分,在当时的 PASCAL VOC 和 COCO 数据集上均达到了 SOTA。

“正对”、"负对"示例

在有监督对比学习的图像分类任务中,使用数据扩增来丰富“正对”。而在检测任务中对同一物体不同 IoU 值的 proposal,也可以看作是一种对“正对”的补充。

常见的二阶段检测模型 Faster Rcnn 的 RPN 模块可以很好的找出前景区域,最后的回归层也可以很好地定位出新颖类别的物体。在大数据集上两类相似物体的余弦相似度可以达到0.39,物体和背景的余弦相似度则为-0.21。而在小样本学习的设定下,相似物体的相似度可达到0.59,更容易出错。因此得出结论模型的错误更有可能是误分类而不是定位,文中也做了一个统计,如果能纠正分类错误的话新颖物体的平均检测精度可以涨20个点。

度量学习算法更关注对区分不同物体更有效的高层表征,而不是聚焦在像素级别的细节上。

传统的迁移学习方法违反直觉的地方是:FPN 和 RPN 学习到的提取 base instance 特征的能力可以直接迁移到提取 novel instance 的特征上。但是如果迁移后不冻结 FPN 和 RPN 的话,又会影响精度。但文中提出如果采用适当的训练策略,可以提高传统迁移学习的精度,文中称为 Strong Baseline。

Strong Baseline

大家一直都认同的一个观点是越多的模型组件被 fine tune,在 novel instance 上的精度就会越低。但是文中发现在 base 数据集和 novel 数据集上从 positive anchor 中挑出的 proposal 的数量差距很大,后者只有前者的四分之一,前景的 proposal 数量的差距同样也很大。

主要问题在于 RPN 使得 positive anchor 的得分太低,在经过 NMS 后剩下的 proposal 太少以及 proposal 的数量太少,导致背景的 proposal 主导了梯度下降。为了解决这两个问题提出了两个 Trick:

  • 将 RPN 中 NMS 之后留下来的 proposal 的最大数量增加一倍;
  • 将 Head 中用来计算损失的 RoI 的数量减少一半。

取得了不错的效果,对比的 baseline 是《Frustratingly simple few-shot object detection》,这里可以看作是两个 trick。

FSCE

在模型的 Head 增加了一个对比分支,这个分支度量了 proposal 的相似性。这个分支附带了一个损失函数,contrastive proposal encoding (CPE),用来将同一类别的实例凑的更“近”,而不同类别尽可能“远”的分离。这个对比分支使用一个 MLP 来实现,将 RoI 编码成一个128维的向量,即一个对比表征嵌入。使用这个嵌入来计算相似度得分以及将这个部分的损失函数 CPE 附加到总的损失函数里,这个 Contrastive Head 引导 RoI 学习易于对比的表征嵌入。这个对比分支可以作为二阶段网络的一个即插即用模块。

FSCE Overview

采用基于余弦相似度的 Box Classifier,计算 RoI 与各个类别的相似性度量,下式表示第 i 个 RoI 与第 j 类物体的相似性度量。

α 是超参数,用来放大梯度,文中采用20。

在余弦相似投影的超空间内,对比表征嵌入可以使得簇内距离更小,簇间距离更大。

训练过程分为两个阶段:首先在通用的大数据集上训练 Faster Rcnn。之后将其迁移到小数据集上,这个小数据集包括 novel instance 以及从大数据集里随机选取的 base instance。第二个阶段的训练冻结 backbone 的参数,更新 Neck 和新加上对比分支的 Head 的参数及损失函数。

\lambda 取0.5。

CPE Loss

对于一个小批量 N 的 RoI features,有 \left \{ z_{i},u_{i},y_{i} \right \}_{i=1}^{N},其中 zi 是第 i 个 proposal 的对比表征嵌入,ui 是 IoU 的得分,yi 是 ground truth 的标签。CPE Loss 定义为

Nyi 是标签为 yi 的 proposal 的数量,zi*zj 代表了余弦相似度。\tau 是正则项,这里对对比表征嵌入和正则项有一个消融实验。

and τ is the hyper-parameter temperature as in InfoNCE [48].

《Representation learning with contrastive predictive coding》

f(ui) 是为了防止 IoU 得分过低使得 proposal 中包含干扰的背景信息而定义的,包含阈值项和一个权重函数。

g() 为不同的 IoU 分配不同的权重参数,而 \phi 取0.7,这里有一个消融实验。

 t-SNE 证明了该损失函数的有效性。

Experiment

PASCAL VOC

可以看到迁移后的模型在 base 数据上精度依然有很大提升。

COCO

Conclusion

FSCE 对实例进行建模而不是对类别进行建模,通过 CPE Loss 来建模同一类别实例的相似性,指导 Contrastive Head 来学习易于对比的嵌入表征。

附加

  • 两个 Trick 的具体实现

将 NMS 之后留下来的 proposal 的最大数量增加一倍 (以 深度解析Faster RCNN (3)---从loss到全局解析 - 知乎 中的代码为例):

def anchor_target_layer(rpn_cls_score, gt_boxes, im_info, _feat_stride, all_anchors, num_anchors):

...

# 求所有的 anchor(共 h*w*9 个,这里的 h、w 是特征图的高和宽)与 gt_boxes 的重叠 IOU
    overlaps = bbox_overlaps(
        np.ascontiguousarray(anchors, dtype=np.float),
        np.ascontiguousarray(gt_boxes, dtype=np.float))
 
# 求各 anchor 与 gt 重叠面积最大的 gt 序号
    argmax_overlaps = overlaps.argmax(axis=1)
# 求最大重叠面积的大小  
    max_overlaps = overlaps[np.arange(len(inds_inside)), argmax_overlaps]

# 求各个 gt 最大重叠面积的 anchor 序号
    gt_argmax_overlaps = overlaps.argmax(axis=0)
# 求最大重叠面积的大小 
    gt_max_overlaps = overlaps[gt_argmax_overlaps,
                               np.arange(overlaps.shape[1])]
#获取与每个gt最大重叠面积的anchor序号的行
    gt_argmax_overlaps = np.where(overlaps == gt_max_overlaps)[0]


# 如果不需要抑制 positive 的 anchor,就先给背景 anchor 赋值,这样在赋前景值的时候可以覆盖。
    if not cfg.FLAGS.rpn_clobber_positives:  
        labels[max_overlaps < cfg.FLAGS.rpn_negative_overlap] = 0  
# 标记前景区域
    labels[gt_argmax_overlaps] = 1
    labels[max_overlaps >= cfg.FLAGS.rpn_positive_overlap] = 1
# 如果需要抑制 positive 的 anchor,就将背景 anchor 后赋值,# 在这里将最大 IoU 仍然小于阈值(0.3)的某些 anchor 置0
    if cfg.FLAGS.rpn_clobber_positives: 
        labels[max_overlaps < cfg.FLAGS.rpn_negative_overlap] = 0

'''
第一个 Trick 实现在这里,超参数中将 rpn_batchsize 设置为512就行
'''
    num_fg = int(cfg.FLAGS.rpn_fg_fraction * cfg.FLAGS.rpn_batchsize)  #0.5*256=128

    fg_inds = np.where(labels == 1)[0]
    if len(fg_inds) > num_fg: # 大于则进行随机采样
        disable_inds = npr.choice(
            fg_inds, size=(len(fg_inds) - num_fg), replace=False)  #从fg_inds中选择128个fg,其余的都设为-1
        labels[disable_inds] = -1
'''
背景的 proposal 也加倍
'''
    num_bg = cfg.FLAGS.rpn_batchsize - np.sum(labels == 1)  

    bg_inds = np.where(labels == 0)[0]
    if len(bg_inds) > num_bg: # 随机采样
        disable_inds = npr.choice(
            bg_inds, size=(len(bg_inds) - num_bg), replace=False)
        labels[disable_inds] = -1 #从bg_inds中选择128个bg,其余的都设为-1

...

    return rpn_labels, rpn_bbox_targets, rpn_bbox_inside_weights, rpn_bbox_outside_weights

以 FSCE 中的代码为例,将超参数中的 RPN.POST_NMS_TOPK_TRAIN 设置为4000(baseline 的 Faster Rcnn 中的 RPN 每层输出的是2000个 proposal):

def __init__(self, cfg, input_shape: Dict[str, ShapeSpec]):
    super().__init__()

    ...
    self.post_nms_topk = {
'''
        True: cfg.MODEL.RPN.POST_NMS_TOPK_TRAIN,
'''
        False: cfg.MODEL.RPN.POST_NMS_TOPK_TEST,
    }
    ...


proposals = find_top_rpn_proposals(
    outputs.predict_proposals(),  # transform anchors to proposals by applyi
### 实现断点续训功能的关键要素 在 Frustratingly Simple Few-Shot Object Detection (FSCE) 模型中实现断点继续训练的功能,需要通过保存和加载模型的状态字典以及优化器的状态字典来完成。以下是实现这一功能的具体方法: #### 1. **保存模型与优化器状态** 在训练过程中,可以通过 `torch.save` 方法保存模型的权重和优化器的状态。以下是一个示例代码,展示如何在训练过程中定期保存检查点: ```python import torch # 假设 model 是你的 FSCE 模型实例,optimizer 是优化器实例 def save_checkpoint(model, optimizer, epoch, loss, path): """ 保存模型、优化器状态及训练信息。 """ torch.save({ 'epoch': epoch, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict(), 'loss': loss, }, path) ``` 上述代码将模型的状态字典(`model.state_dict()`)、优化器的状态字典(`optimizer.state_dict()`)、当前的训练轮数(`epoch`)以及损失值(`loss`)保存到指定路径中[^1]。 #### 2. **加载模型与优化器状态** 在恢复训练时,需要从保存的检查点中加载模型和优化器的状态。以下是一个加载检查点的示例代码: ```python def load_checkpoint(model, optimizer, path): """ 加载模型、优化器状态及训练信息。 """ checkpoint = torch.load(path) model.load_state_dict(checkpoint['model_state_dict']) optimizer.load_state_dict(checkpoint['optimizer_state_dict']) epoch = checkpoint['epoch'] loss = checkpoint['loss'] return epoch, loss ``` 通过上述代码,可以从保存的检查点中恢复模型的权重、优化器的状态、训练轮数以及损失值,从而继续训练[^1]。 #### 3. **整合断点续训逻辑** 为了实现自动化的断点续训功能,可以在训练脚本中添加条件判断,检查是否存在之前的检查点文件。如果存在,则加载该文件并从中恢复训练;否则,从头开始训练。以下是一个完整的训练流程示例: ```python import os # 定义检查点路径 checkpoint_path = "fsce_checkpoint.pth" # 初始化模型和优化器 model = ... # 初始化 FSCE 模型 optimizer = ... # 初始化优化器 # 判断是否已有检查点 if os.path.exists(checkpoint_path): print("Loading checkpoint...") start_epoch, prev_loss = load_checkpoint(model, optimizer, checkpoint_path) else: print("Starting training from scratch...") start_epoch = 0 prev_loss = None # 训练循环 for epoch in range(start_epoch, num_epochs): for data in dataloader: # 训练步骤 ... # 保存检查点 save_checkpoint(model, optimizer, epoch, current_loss, checkpoint_path) ``` 上述代码展示了如何在训练过程中定期保存检查点,并在启动训练脚本时检查是否存在之前的检查点文件以决定是否恢复训练。 --- ### 注意事项 - 在保存和加载检查点时,确保使用相同的设备(CPU 或 GPU)。如果需要在不同设备间切换,可以使用 `map_location` 参数。例如:`torch.load(path, map_location=torch.device('cpu'))`。 - 如果训练过程中涉及学习率调度器(`lr_scheduler`),也需要将其状态保存和加载,类似于优化器的操作。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值