Learning to Segment Every Thing解读

版权声明:本文为博主原创文章,如若转载,请注明出处! https://blog.csdn.net/u013010889/article/details/80762638

kaiming ross他们一块的又一篇文章,基于mask rcnn的做的一个扩展,他们真是一直在推进着Detection Segmentation领域的发展

Introduction

目前做instance segmentation的方法都需要像素级标注,这样的话就很难有一个类别数目庞大的库做支撑,因为像素级标注太费人力物力财力了,已有的coco也只有80类,pascal voc只有20类,而box的标注相对比较简单,visual gnome有超过7000类的box标注(本文挑选最常用的3000类),那么本文就在想能不能利用coco 80类的像素级标注和vg 3000类的box标注来训练一个模型可以进行3000类的instance segmentation呢?本文提出了一个weight transfer function根据box detection branch的参数来预测mask branch的参数

Framework

这里写图片描述

在NLP领域中,经常用到的word embedding,就是语义相近的词通过embed后在另一个空间里距离很近,语义不同的词通过embed后在另一个空间里距离很远。在CV领域,我们最后一个fc对物体分类时,比如vgg最后一个fc的参数是4096x #classes,每个类别对应的参数是4096,这个4096维参数可以看做是对该类别的embeding,它包含了该类别appearance information。
已有的类似工作LSDA是将分类任务学习到的知识迁移到检测任务,训练一个能达到和分类任务一样类别数的检测模型,而本文是从检测迁移到分割。
该框架就是在有box标注的类别A、B和有mask标注的类别A上训练得到一个可以同时在类别A、B上进行instance segmentation的模型。
注意 mask分支多加了个类别无关的MLP来预测结果,为了和原有的FCN进行互补,MLP注重于整体轮廓,FCN注重于细节吧

Weight Transfer

前面我们也讲了,模型的参数相当于 embeding vector包含了类别的appearance information。在box和mask 分支中只有最后一层包含category-specific的参数,是类别专属的,原有的mask rcnn里这两路是独立训练学习的,本文不直接学习mask 分支最后一层的参数,而是通过Weight Transfer学习一个从box分支参数到mask 分支参数的变换。前提的insight就是box和mask最后一层包含category-specific的参数,是类别专属的,算是类别语义信息的embeding,是很相关的,可以通过学习从box变换到mask这边来的。

我们以Mask RCNN(ResNet101-FPN)为例,box分支包含分类和回归,分类部分最后一层,每个类别的cls参数vector维度为1024-d,回归部分是1024x4=4096d,mask分支,输出分辨率是28x28,最后一层每个类别的seg参数vector维度为256-d(从roialign出来是14*14*1024,然后通过4个conv变成14*14*256,再加一个deconv到28*28*256,最后一个1*1的conv得到score map,这个1*1卷积的参数就是刚说的seg参数,总共参数是256*1*1*#classes,每个类别的参数就是256*1*1)

我们现在就要加个Weight Transfer,将检测分支的参数cls和box变换到seg,θ是class-agnostic(类别无关的),学习过来的。
这里写图片描述

# 输入为检测分支的参数,cls或者box或者cls cat box的参数
if (cfg.MRCNN.BBOX2MASK.INCLUDE_CLS_SCORE and
        cfg.MRCNN.BBOX2MASK.INCLUDE_BBOX_PRED):
    # Subcase a) using cls+box
    concat_cls_score_bbox_pred(model)
    class_embed = 'cls_score_bbox_pred'
    class_embed_dim = 1024 + 4096
elif cfg.MRCNN.BBOX2MASK.INCLUDE_CLS_SCORE:
    # Subcase b) using cls
    class_embed = 'cls_score_w'
    class_embed_dim = 1024
elif cfg.MRCNN.BBOX2MASK.INCLUDE_BBOX_PRED:
    # Subcase c) using box; 'bbox_pred_w' need to be flattened
    model.net.Reshape(
        'bbox_pred_w', ['bbox_pred_w_flat', '_bbox_pred_w_oldshape'],
        shape=(model.num_classes, -1))
    class_embed = 'bbox_pred_w_flat'
    class_embed_dim = 4096
# 变换函数,即需要学习的参数θ,简单的就是一层fc,复杂的话就多加一层fc和激活层等
 if (not fc_mask_head_type) or fc_mask_head_type == '1_layer':
        raw_mlp_branch = model.FC(
            blob_in, 'mask_mlp_logits_raw', dim_in, dim_out,
            weight_init=('GaussianFill', {'std': 0.001}),
            bias_init=('ConstantFill', {'value': 0.}))
    elif fc_mask_head_type == '2_layer':
        mlp_l1 = model.FC(
            blob_in, 'fc_mask_head_mlp_l1', dim_in, dim_h,
            weight_init=('MSRAFill', {}),
            bias_init=('ConstantFill', {'value': 0.}))
        model.net.Relu(mlp_l1, mlp_l1)
        raw_mlp_branch = model.FC(
            mlp_l1, 'mask_mlp_logits_raw', dim_h, dim_out,
            weight_init=('GaussianFill', {'std': 0.001}),
            bias_init=('ConstantFill', {'value': 0.}))
    elif fc_mask_head_type == '3_layer':
        mlp_l1 = model.FC(
            blob_in, 'fc_mask_head_mlp_l1', dim_in, dim_h,
            weight_init=('MSRAFill', {}),
            bias_init=('ConstantFill', {'value': 0.}))
        model.net.Relu(mlp_l1, mlp_l1)
        mlp_l2 = model.FC(
            mlp_l1, 'fc_mask_head_mlp_l2', dim_h, dim_h,
            weight_init=('MSRAFill', {}),
            bias_init=('ConstantFill', {'value': 0.}))
        model.net.Relu(mlp_l2, mlp_l2)
        raw_mlp_branch = model.FC(
            mlp_l2, 'mask_mlp_logits_raw', dim_h, dim_out,
            weight_init=('GaussianFill', {'std': 0.001}),
            bias_init=('ConstantFill', {'value': 0.}))

Experiment

数据集设置: coco的80类包含pascal的20类,然后把这20类当成有mask标注的集合(A),把另外60类当作只有box标注的集合(B),用这样的数据训练得到模型。(然后由于剩下60类其实也是有mask标注的,刚好评测使用“A+B”数据训练的模型的instance segmentation效果)

Baseline: 原有的mask rcnn中mask分支是类别相关的,经过fcn生成#classes个channel的score map,然后根据det分支预测的类别来抽取对应channel后做sigmoid。此处由于缺少全类别的mask标注,所以可以直接把mask分支改成类别无关的,经过fcn生成1*28*28的结果,然后直接sigmoid得到mask。(这里由于是标注限制才这样做,刚好作为本文的baseline,如果有全类别的mask标注肯定是使用类别相关的预测比较好,因为两个不同的物体它俩的bounding box可能是一样的,这样同样的框送到mask分支,在一个物体上需要预测这一块是前景,而在另一个物体上又要预测另一块地方是前景,这样同样的框特征送过来两次对应的gt却不一样,让mask分支怎么学习,需要还是要类别相关的)

Oracle: 即upper bound,使用80类的mask标注进行训练

transfer w/randn: 不采用det分支的参数,而是采用高斯随机出来的参数
transfer w/Glove: 不采用det分支的参数,而是这个参数应该是从语言模型做word embeding里拿过来,此处是用的Glove(这个参数本身是对词做embeding用的,以得到词向量,也是具有类别相关的信息,包含了类别的语义信息,语义学相似的类别词通过这个参数得到embeding vector后,距离是很近的)

# Case 1) From a pre-trained embedding vector (e.g. GloVe)
class_embed = cfg.MRCNN.BBOX2MASK.PRETRAINED_EMBED_NAME
class_embed_dim = cfg.MRCNN.BBOX2MASK.PRETRAINED_EMBED_DIM
# This parameter is meant to be initialized from a pretrained model
# instead of learned from scratch. Hence, the default init is HUGE
# to cause NaN loss so that the error will not pass silently.
model.AddParameter(model.param_init_net.GaussianFill(
    [], class_embed, shape=[num_cls, class_embed_dim], std=1e12))
# Pretrained embedding should be fixed during training (it doesn't
# make sense to update them)
model.StopGradient(class_embed, class_embed + '_no_grad')
class_embed = class_embed + '_no_grad'

实验结果如下:
这里写图片描述

其他结果不再赘述


猜你喜欢

转载自blog.csdn.net/u013010889/article/details/80762638
今日推荐