CLIP x 语义分割

记录一下笔者最近调研的几篇把CLIP等多模态预训练模型用在语义分割任务上的文章。

CLIP简单回顾

很多的博客、论文都从不同角度回顾了CLIP1。CLIP使用互联网上爬取并清洗的的400M图片文本对,进行对比学习训练,相比于传统图像分类数据集上的预训练模型,CLIP可以将各种文本进行编码,而不需要选择固定的词表,不需要微调,即可以有比较好的图像分类性能。可以说,CLIP具有强大zero shot/few shot的分类能力,其中,few shot的微调方式是linear probe。(当然,在预训练的时候应该是见过丰富的类别的)。

笔者这里想记录一下CLIP的视觉编码器的细节。

CLIP目前有两种架构的视觉编码器,ModifiedResNet或者ViT。

对于Modified ResNet,其中比较重要的改动是,ResNet最后一层的均值池化,换成了一种attention pool,对于第4层特征 \(\rm x_4\in \mathbb{R}^{H_4 W_4 \times C}\),其空间位置上的均值为 \(\overline{\rm x}_4\in \mathbb{R}^{1\times C}\),之后使用一个多头自注意力: \[ [\overline{\rm z}, \rm z] = \operatorname{MHSA}([\overline{\rm x}_4, \rm x _4]) \] 获得图像的全局表示 \(\overline{\rm z}\)

(总结来自DenseCLIP2)

CLIP的视觉编码器下采样率很大(ResNet-32, ViT-16),这是不利于直接适配到图像分割任务的一点。

只要对Transformer encoder/attn pooling中的位置编码进行某种线性插值,就可以做到接收任意尺寸的输入。

全监督

DenseCLIP:

DenseCLIP2

弱监督

CLIP-ES

这个工作代码开源且可用,就是因为改动了clip和pytorch-grad-cam库而不是继承等方式重写,代码初看起来比较混乱。

CLIP-ES3是CVPR'2023的一篇文章。做多阶段的弱监督语义分割。根据图像类别标签获得CAM,从self attn获得affinity map来refine CAM,最后再用refined CAM训练分割模型。

本文提出了多种技术。

类别间更互斥的CAM?

作者提出将Softmax引入CAM会使得激活区域更加discriminative。笔者发现,这样的技术实现似乎早就已经在pytorch-grad-cam中有了(model_targets.py中的ClassifierOutputSoftmaxTarget),本文通过求导,解释了这样做激活区域更“收缩”的原因,并(结合后续消融实验)说明这样是更适合多标签的分割任务以及CLIP的。

Self Attention refine CAM

用self attention获得affinity矩阵,之后用random walk微调CAM不是本文的独创方法,本文的独创之处在于,根据初步的CAM伪标签,得到一个bbox来mask affinity矩阵,使得通过random walk之后,前景区域能够被控制在合适的范围内细化。这个bbox是class-aware的,因此这个方法就称为Class-aware Attention-based Affinity (CAA)。

低置信度区域不学

对于置信度图 \(X\in \mathbb{R}^{h\times w \times c}\)\(X_{h,w,c}\) 表示\(X(h, w)\)位置属于前景 \(c\) 的概率。如果 \(\max_cX(h,w) > 0.5\),那么就认为该位置属于前景类别 \(c\),否则就属于背景/忽略类别。这样解释完,我们就可以理解作者对于置信度的定义了。

碎碎念

这篇工作与CLIP有一定关系,但是CAM->refine->train的流程还是WSSS常用的一套,从CLIP中最大的受益还是来自其zero-shot的分类能力。

为不同数据集挑选前景及其同义词、背景同义词、以及template的细节见原论文及代码。

Zero-Shot

MaskCLIP

方法简介

MaskCLIP4是ECCV'2022的一篇文章,提出的MaskCLIP只需要简单调整CLIP的权重结构,无需训练和任何微调,就可以实现开放域的语义分割。接着,又通过将MaskCLIP的预测结果作为伪标签,将其知识蒸馏给其他任意结构的分割模型(文中用的Deeplabv2),并在合适的时候进行自训练,得到MaskCLIP+。

MaskCLIP/MaskCLIP+的流程图

如上图所示,从技术角度上,本文的方法看起来很简单。给定任何的一个字典集合,使用适当的prompt工程,都可以得到一组固定的CLIP的text embeddings,可以用作线性分类器。

如何用实现局部分类

下面用自问自答的方式解释MaskCLIP的部分实现细节。

  1. 为什么可以用输入attention pooling的value embedding直接与text embedding计算相似度? 文中的式(3)说明了,因为MHSA的输出映射 \(\mathcal{F}\) 是线性操作,因此,可以用“分配律”放到求和符号内。而现在相对局部的图像快而非全局的图像进行分类,因此去除softmax的加权求和也是合理的。

  2. 对于ViT的视觉编码器,怎么改? 相比于attention pooling,Transformer block与之的区别主要在于:全局token使用专门的cls token而不是所有patch token的平均;有残差连接;有norm和一些线性计算。因此,只需要把最后一层的value embedding拿出来,通过这些残差连接、norm和线性计算即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # Transformer Encoder块的代码修改
    def forward(self, x, return_qkv=False):
    q, k, v = None, None, None
    if return_qkv:
    y = self.norm1(x)
    y = F.linear(y, self.attn.attn.in_proj_weight, self.attn.attn.in_proj_bias)
    N, L, C = y.shape
    y = y.view(N, L, 3, C//3).permute(2, 0, 1, 3).reshape(3*N, L, C//3)
    y = F.linear(y, self.attn.attn.out_proj.weight, self.attn.attn.out_proj.bias)
    q, k, v = y.tensor_split(3, dim=0)
    v += x
    v = self.ffn(self.norm2(v), identity=v)

    x = self.attn(self.norm1(x), identity=x)
    x = self.ffn(self.norm2(x), identity=x)
    return x, q, k, v

  3. 对于attention pooling,为什么不用经过MSHA输出的patch token,而是用计算MSHA之前的value embedding? 因为在attn pooling的输出中,只有 \(\overline{\rm z}\) 受到了后续损失函数的监督训练,\(\rm z\) 不受监督,不可用。

MaskCLIP还引入了key smoothing和prompt denoising的两个小tricks来改善“斑驳”的预测效果,由于只是tricks,细节见原文。

在新颖的设置中取胜

对于MaskCLIP+,技术上就是先伪标签蒸馏、再自训练。本文主要强调了MaskCLIP+对于CLIP能力的继承性。

无标签分割

使用3种分割常用的数据集,但是不使用任何人工标注的gt来监督,转而只使用MaskCLIP生成的伪标签监督MaskCLIP+,在这些验证集集上表现还不是很差,并且继承了CLIP的鲁棒性,面对多种输入图像的corruption性能还能有所保持。

开放字典分割

调整文本embedding,MaskCLIP是可以直接推理出各种我们想要的开放域标签,当然这肯定得益于CLIP预训练见过这些文本。不过对于MaskCLIP+,还是需要MaskCLIP引导、训练一波的。

Zero-Shot

zero-shot的setting中,是要求模型运用学习到的知识,能够推理出在训练时没有标注的类别。根据在训练时,模型有没有见过无标注类别的样本,见过则称为transductive,没见过则称为inductive。(关于ZSL的详细介绍)。由于用了CLIP引导生成伪标签,本文时transductive zero-shot。在保持与先前方法配置一样的前提下,远超之前的ZSL方法,甚至与全监督的baseline差不多。

本文的一些启发

  1. 微调CLIP的图像编码器、文本编码器不可靠,因为CLIP的图像编码器并没有为分割任务专门设计,并且传统微调过程会损失语义信息,使得模型失去zero-shot能力;
  2. CLIP本身把图像文本编码成特征并对齐的训练方式,尽管看起来是非常“全局”的行为,但是正如文中的例子那样,"the man at bat readies to swing at the patch while the umpire looks on" 这句话包含的多种语义信息可以对应图像的不同局部位置。这种把原始图像/文本空间向量映射到语义空间再分类的做法也正是ZSL的一种解决思路。

Referring

Referring Image Segmentation 的任务是,用用户感兴趣的对象的描述作为输出,输出是满足条件的对象的分割mask。与全监督语义分割相比,每个GT mask对应的类别标签换成了一个描述的句子(比如“在橘黄色车后面的白车”),这些句子不像类别,是开放集合。

ReSTR:使用Transformer融合多模态特征

ReSTRa是CVPR'2022上的一篇文章,主要完成Referring Image Segmentation任务,提出了一种基于Transformer的多模态特征融合的结构,并从粗(patch-level)到细(pixel-level)地解码预测mask。这篇文章没有使用CLIP,也还没有公开代码,但是一些解决问题的方法值得学习。

ReSTR模型结构

Vision Encoder使用在ImageNet-21K上与训练的ViT-B-16,Language Encoder使用预训练的GloVe模型。多模态Encoder使用与Vision Encoder一样的结构。而Decoder有4层(简单的线性投影+非线性激活),因为patch_size=16,每层decoder上采样2倍。

为什么要引入 class seed embedding? \(\mathbf{e}_s\in \mathbb{R}^{1\times D}\) 是随机初始化的可学习参数,经过Linguistic-Seed Model,作为一种文本全局信息的聚合,(在上图中,它应该抽象的表示出目标人类对象的文本语义),之后与融合了文本信息的图像特征 \(\mathbf{z}_v'\) 内积、归一化,并通过sigmoid,得到全局文本语义在各个patch上的注意力,而这可以作为一种patch-level的预测mask。这个mask本身还能与 \(\mathbf{z}_v'\)进行广播逐点乘法,也就是只保留与全局语义有关的patch上的语义信息,接着再与视觉特征concat,送入简单的decoder中,得到精细的分割结果。

为什么用了Visual-Linguistic和Linguistic-Seed两种编码器以及这样的连接方式来获得全局语义信息,以及融合文本信息的视觉特征?

作者尝试过的3中多模态编码器结构,(a)VME, (b)IME, (c)CME

上图是作者尝试过的三种多模态编码方式,VME是最直接的做法,但是作者发现,可能是因为图像patch个数 \(N_v = 900\)(意味着输入是480x480的分辨率),而句子长度上限为 \(N_l = 20\),两中模态的token数量差距过大,使得VME的多模态编码器总是会偏向视觉信息,下表(a)展示了在VME的4个Transformer块中,\(\mathbf e_s\) 的注意力几乎都关注在视觉特征上。因此希望 \(\mathbf{e}_s\) 不要与视觉特征计算注意力。而IME的效果又差了很多,因此设计了CME,\(\mathbf{e}_s\) 与融合了视觉信息的文本特征计算注意力。

\(a_v\)\(a_l\) 分别是将 \(N_v\)\(N_l\) 个注意力相加的结果,所以就算视觉特征的平均注意力与文本特征的平均注意力一样,总比例也是是90:2,第 2-4 个块基本都符合这个比例

VMA中cls-seed对于视觉和文本注意力的占比,以及不同多模态编码器结构的性能对比


CRIS:使用CLIP引导的Referring Image Segmentation

CRISb使用CLIP的Vision编码器(ResNet50/101)和Text编码器,使用如下3处改动将文本特征与像素级的图像特征进行融合解码:

  1. 设计了Cross-Modal Neck,将全局文本特征 \(F_s\) 与多层图像特征进行融合,并融合了coordinate特征,得到了16倍下采样的特征图 \(F_v \in\mathbb{R}^{N\times C}, N = \frac{H}{16}\times\frac{W}{16}\)
  2. 设计了Vision Language Decoder,将图像特征 \(F_v\) 和 文本特征 \(F_t \in\mathbf{L\times C}\) 分别加上固定的sine位置编码,接着将 \(F_v\) 送入带残差连接的MHSA得到 \(F_v'\),再让 \(F_v'\) 作为 query,\(F_t\) 作为 key 和 value,计算MHCA,得到 \(F_c' \in \mathbb{R}^{N\times C}\),之to后再送入带有残差连接的MLP。这样的MHSA-MHCA-FFN作为Decoder的一个Transformer块,共n块。最后得到的多模态特征 \(F_c\) 即可用于最后的分割。
  3. 使用Text-to-Pixel的对比损失作为额外的损失函数。将 \(F_c'\) (\(F_c\) 上采样4倍,即原图的4倍下采样大小) 和 \(F_s\) 线性投影到同样的空间后,计算内积的sigmoid作为相似度,对于在标签内的位置,将相似度度拟合到1,否则将相似度拟合到0。(使用CE Loss)

在消融实验部分,作者以不使用Vision Language Decoder、不使用Text-to-Pixel损失模型作为baseline,在3个基准数据集上证明了这两个模块的有效性,并且Decoder的层数 \(n = 3\)

根据论文中结果,CRIS比前文讲述ReSTR性能更好。

代码已经开源在CRIS.pytorch。但是作者目前还没有给出pretrained model,不过issue中有一个别人训练好的模型。

Q&A

  1. 为什么不直接用CLIP的vision分支直接在语义分割数据集上linear probe?

  2. CLIP的训练方式与Dense任务的“鸿沟”?

参考文献