【SOD论文阅读笔记】Visual Saliency Transformer

一、摘要

在这里插入图片描述

Motivation:

现有的SOTA显著性检测方法在很大程度上依赖于基于CNN的网络。可替代地,我们从卷积free的sequence-to-sequence的角度重新考虑此任务,并通过建模长期依赖关系来预测显著性,而这不能通过卷积来实现。

这篇论文的出发点就是利用transformer来创新,并且这篇文章是纯transformer(convolution-free),所以摘要中从transformer和CNN的最大的不同出发来写motivation——即transformer对比CNN来说,是sequence-to-sequence结构的,且更有利于对长期依赖关系建模。

Method:

提出基于纯变压器的模型,即视觉显著性变压器 (VST),用于RGB和RGBD的显著性检测。

  • 以图像补丁为输入,并利用transformer在图像补丁之间传播全局上下文
  • 与视觉变压器 (ViT) 中使用的常规结构不同,我们利用多级token融合,并在变压器框架下提出了一种新的token上采样方法,以获得高分辨率的检测结果。
  • 我们还开发了基于token的多任务解码器,通过引入与任务相关的token和新颖的补丁-任务-注意力机制,同时执行显着性和边界检测。

先解释一下图像补丁。由于transormer是从NLP任务传到CV领域的,在NLP的机器翻译任务中,输入的是一个个单词,所以,把transformer移植到图像任务时,为了与其输入结构保持一致,会把图像切割成不重叠的补丁序列(可以想像一下把一张图片切割成九宫格/N宫格,每一个宫格就是一个补丁)。

再解释一下token。刚刚的图像补丁就可以被称之为一个token,它属于patch token。patch token输入到transformer中后,经过处理得到的feature也可以成为token。此外,transformer中还有一种class token,它本质上就是一个可训练的向量,通常在分类任务中直接通过这个Class token来判断类别。

这篇论文里有一个任务相关的token(task-related tokens),其实相当于tokens的一个头部,代表这个tokens是用于做什么任务的。这是因为,这篇论文提出的是多任务模型,输出的是 显著映射 和 边缘映射,本意是借助边缘的监督提升其显著映射的准确性。

Experimental results

实验结果表明,我们的模型在RGB和RGBD SOD基准数据集上都优于现有方法。

二、Introduction

在这里插入图片描述

当前最先进的方法以CNN结构为主

它们通常采用编码器-解码器架构,其中编码器将输入图像编码为多级特征,解码器将提取的特征集成以预测最终的显着性图。

  • RGB-SOD,旨在检测吸引人们眼睛的物体,并可以帮助许多视觉任务。
    • 各种注意力模型,多尺度特征集成方法和多任务学习框架
  • RBGD-SOD,则多了来自深度数据的额外空间结构信息。
    • 各种模态融合方法,如特征融合,知识蒸馏,动态卷积,注意力模型 ,图神经网络 。

CNN结构的弊端

所有方法在学习全局远程依赖方面受到限制

长期以来,全局上下文 和全局对比度 对于显著性检测至关重要。然而,由于cnn在局部滑动窗口中提取特征的内在限制,以前的方法很难利用关键的全局线索。

尽管一些方法利用全连接层,全局池化和非本地模块来合并全局上下文,但它们仅在某些层中这样做,并且基于CNN的体系结构保持不变。

引出Transformer

最近,提出了Transformer用于机器翻译的单词序列之间的全局远程依赖关系。

Transformer的核心思想是自注意机制,它利用query-key的相关性来关联序列中的不同位置。Transformer在编码器和解码器中多次堆叠自注意层,因此可以对每一层中的长距离依赖进行建模。因此,将变压器引入SOD是很自然的,一路利用模型中的全局线索。

本文中

我们从新的序列到序列的角度重新考虑SOD,并基于纯变压器开发了一种新颖的RGB和rgb-d SOD统一模型,称为视觉显着性变压器。

最近提出的ViT模型 [12,74],将每个图像划分为补丁,并在补丁序列上采用变压器模型。然后,变压器在图像补丁之间传播长距离依赖,而无需使用卷积。

然而,将ViT应用于SOD并不简单,存在两大问题:

  • 1.关于密集预测: 如何基于纯变压器执行密集预测任务仍然是一个悬而未决的问题。
    - 我们通过引入与任务相关的token来设计基于token的变压器解码器从而学习决策嵌入。然后,我们提出了一种新颖的补丁-任务-注意力机制来生成密集预测结果,这为在密集预测任务中使用transformer提供了新的范例。
    - 在以前的SOD模型的激励下,利用边界检测来提高SOD性能,我们构建了一个多任务解码器,通过引入显著性token和边界token,同时进行显著性和边界检测。该策略通过简单地学习与任务相关的token来简化多任务预测工作流程,从而大大降低了计算成本,同时获得了更好的结果。
  • 2.关于高分辨率:ViT通常将图像标记为非常粗糙的大小。如何使ViT适应SOD的高分辨率预测需求还不清楚。
    - 受tokens-to-tokens (T2T) 转换 [74] 的启发,该转换减少了tokens的长度,我们提出了一种新的反向T2T转换,通过将每个tokens扩展为多个子tokens来向上采样tokens。然后,我们逐步对补丁tokens进行采样,并将其与低级token融合,以获得最终的全分辨率显着性图。此外,我们还使用交叉模态transformer来深入探索rgb-d SOD的多模态信息之间的相互作用。

在RGB和RGBD数据上,以有可比性的数量的参数和计算成本,优于现有的最先进的SOD方法

contributions

  • 以序列to序列建模的新视角,设计了一种基于纯变压器架构的RGB和rgb-d SOD的新型统一模型。
  • 设计了一种多任务变压器解码器,通过引入任务相关的token和补丁-任务-注意力来联合进行显著性和边界检测
  • 一种新的基于transformer的token上采样方法
  • state-of-the-art结果

三、Visual Saliency Transformer

在这里插入图片描述

在这里插入图片描述

我们为RGB和RGBD SOD提出的VST模型的整体架构。它首先使用编码器从输入的图像补丁序列中生成多级tokens。然后,采用转换器将补丁tokens转换为解码器空间,并对rgb-d数据进行跨模态信息融合。最后,解码器通过我们提出的与任务相关的token以及补丁-任务-注意机制同时预测显着图和边界图。还提出了一种RT2T转换,以逐步上采样补丁tokens。虚线表示rgb-d SOD的专用成分。

  • 主要组件包括3部分:基于T2T-ViT的变压器encoder (T2t_vit_t_14),用于将补丁tokens从编码器空间转换到解码器空间的变压器转换器 (Transformer),以及多任务变压器decoder (token_Transformer, Decoder)。
class ImageDepthNet(nn.Module):
    def __init__(self, args):
        super(ImageDepthNet, self).__init__()
        # VST Encoder
        self.rgb_backbone = T2t_vit_t_14(pretrained=True, args=args)
        # VST Convertor
        self.transformer = Transformer(embed_dim=384, depth=4, num_heads=6, mlp_ratio=3.)
        # VST Decoder
        self.token_trans = token_Transformer(embed_dim=384, depth=4, num_heads=6, mlp_ratio=3.)
        self.decoder = Decoder(embed_dim=384, token_dim=64, depth=2, img_size=args.img_size)

    def forward(self, image_Input):
        B, _, _, _ = image_Input.shape
        # image_Input [B, 3, 224, 224]
        # VST Encoder
        rgb_fea_1_16, rgb_fea_1_8, rgb_fea_1_4 = self.rgb_backbone(image_Input)
        # rgb_fea_1_16 [B, 14*14, 384]
        # rgb_fea_1_8 [B, 28*28, 384]
        # rgb_fea_1_4 [B, 56*56, 384]
        # VST Convertor
        rgb_fea_1_16 = self.transformer(rgb_fea_1_16)
        # rgb_fea_1_16 [B, 14*14, 384]
        # VST Decoder
        saliency_fea_1_16, fea_1_16, saliency_tokens, contour_fea_1_16, contour_tokens = self.token_trans(rgb_fea_1_16)
        # saliency_fea_1_16 [B, 14*14, 384]
        # fea_1_16 [B, 1 + 14*14 + 1, 384]
        # saliency_tokens [B, 1, 384]
        # contour_fea_1_16 [B, 14*14, 384]
        # contour_tokens [B, 1, 384]
        outputs = self.decoder(saliency_fea_1_16, fea_1_16, saliency_tokens, contour_fea_1_16, contour_tokens, rgb_fea_1_8, rgb_fea_1_4)
		# [mask_1_16, mask_1_8, mask_1_4, mask_1_1],[contour_1_16, contour_1_8, contour_1_4, contour_1_1]
		# mask_1_16/contour_1_16 [B, 1, 14, 14]
		# mask_1_1/contour_1_1 [B, 1, 224, 224]
        return outputs

Transformer Encoder(T2t_vit_t_14)

以下是Transformer Encoder的整体框架

class T2T_ViT(nn.Module):
    def __init__(self, img_size=224, tokens_type='performer', in_chans=3, num_classes=1000, embed_dim=768, depth=12, num_heads=12, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop_rate=0., attn_drop_rate=0.,
                 drop_path_rate=0., norm_layer=nn.LayerNorm):
        super().__init__()
     
        self.tokens_to_token = T2T_module(img_size=img_size, tokens_type=tokens_type, in_chans=in_chans, embed_dim=embed_dim)
        num_patches = self.tokens_to_token.num_patches

        self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
        self.pos_embed = nn.Parameter(data=get_sinusoid_encoding(n_position=num_patches + 1, d_hid=embed_dim), requires_grad=False)

        self.blocks = nn.ModuleList([
            Block(
                dim=embed_dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
                drop=drop_rate, attn_drop=attn_drop_rate, norm_layer=norm_layer)
            for i in range(depth)])
        self.norm = norm_layer(embed_dim)
    
    def forward(self, x):
        B = x.shape[0]
        x, x_1_8, x_1_4 = self.tokens_to_token(x)
		#[B,196,384],[B, 28×28, 384],[B, 56×56, 384]
        cls_tokens = self.cls_token.expand(B, -1, -1)
        #[1,1,384]->[B,1,384]
        x = torch.cat((cls_tokens, x), dim=1)
        #cat([B,1,384],[B,196,384])->[B,197,384]
        x = x + self.pos_embed
        #[B,197,384]+[1,197,384]->[B,197,384]

        # T2T-ViT backbone
        for blk in self.blocks:
            x = blk(x)
		#[B,197,384]
        x = self.norm(x)
  		#[B,197,384]
        return x[:, 1:, :], x_1_8, x_1_4

可以看出,Transformer Encoder由一个T2T模块和一些后处理步骤构成。
输入:(B,3,224,224)
输出:由于我们做的是像素级分类而不是对象级分类,所以输出了多级特征:fea_1_16 [B, 14×14, 384],fea_1_8 [B, 28×28, 384],fea_1_4 [B, 56×56, 384]。

T2T模块:待会儿详细介绍。
后处理步骤:

  1. 首先,x被concat了一个1维的全零分类tokens,由于其被初始化为0,所以没什么好介绍的。

x = torch.cat((cls_tokens, x), dim=1)

  1. 其次,x被add了一个shape与其shape相同的正弦位置tokens

self.pos_embed = nn.Parameter(data=get_sinusoid_encoding(n_position=num_patches + 1, d_hid=embed_dim), requires_grad=False)
x = x + self.pos_embed

这里对self.pos_embed的初始化是有讲究的,用到的是《Attention is all you need》中提出的正弦位置,参数就是要生成的shape的参数。

3.最后,重复经过depth个Blocks。这里depth设置为14。
每个Block:

class Block(nn.Module):

    def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0.,drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm):
        super().__init__()
        self.norm1 = norm_layer(dim)
        self.attn = Attention(
            dim, num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop)
        self.norm2 = norm_layer(dim)
        mlp_hidden_dim = int(dim * mlp_ratio)
        self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)

    def forward(self, x):
        x = x + self.attn(self.norm1(x))
        x = x + self.mlp(self.norm2(x))
        return 

该过程就是不断Attention、MLP的迭代过程,且输出与输入的shape保持一致[B, 197, 384]。
Attention就是普通多头attention(Linear[通道数扩大三倍]、分为qkv、softmax(q*k)*v,最后再Linear[不改变通道数])

class Attention(nn.Module):
    def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.):
        super().__init__()
        self.num_heads = num_heads
        head_dim = dim // num_heads
        self.scale = qk_scale or head_dim ** -0.5
        self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
        self.attn_drop = nn.Dropout(attn_drop)
        self.proj = nn.Linear(dim, dim)
    def forward(self, x):
        B, N, C = x.shape
        #[B,197,384]
        qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
        # self.qkv(x):[B,197*3,384]
        #.reshape(B, N, 3, self.num_heads, C // self.num_heads): [B,197,3,6,64]
        #.permute(2, 0, 3, 1, 4): [3,B,6,197,64]
        q, k, v = qkv[0], qkv[1], qkv[2]
		#[B,6,197,64]
        attn = (q @ k.transpose(-2, 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值