transformer-for-vision-task

transformer 是一种新提出的神经网络组件, 主要利用注意力机制来提取内在特征。本文主要介绍了视觉方向上 transformer 的应用。

本文先从 transformer 的基本组件 self-attention 入手, 介绍 self-attention 机制并逐渐展开至 transformer 模型。 然后在 计算机视觉领域挑选了四个重要的模型:ViT(classification)、DETR(detection)、D-DETR(detection) 和 TTSR(super resolution) 进行分享。最后阐述一些高效的 transformer 机制。

1. transformer 的提出 : attention is all your need

transformer 在 NLP 领域应用有三个重要的节点:

  • 2017年06月:首次完全基于注意力机制的 transformer, 并将其用于机器翻译和英语分析任务,发表于论文《 Attention is all your need 》。这是 transformer 的起点, 也是本文介绍的重点。
  • 2018年10月:引入了一种新的语言表示模型,称为 BERT, 通过联合调节左右上下文,从未标记的文本中预训练一个 transformer。
  • 2020年05月: GPT-3诞生:在 45TB 的纯文本数据上预训练了一个具有 1750 亿超参数的巨型 transformer 模型 GPT-3。这个模型在不进行微调的情况下,在不同的自然语言任务上实现了强大的性能。

1.1 vanilla transformer

​ 原始的 transformer 用于进行机器翻译任务, 其模型如下图所示, 它由一系列的 encoder 和 decoder 组成。 每个 encoder 由 self-attention 层和 feed forward nn 组成, 每个 decoder 则是由 self-attention、encoder-decoder attention 和 feed forward nn 组成。 在输入之前先将句子中的每个单词 embedding 为固定维度的向量, 然后经过 encoder-decoder 进行编解码,最后将输出的向量重新编码为一个 个单词组成句子, 从而完成翻译任务。图中的 encoder 中的 Self-Attention 和 decoder 中的 Encoder-Decode Attention 和几乎相同, 只是输入有所差别。

1.2 self attention layer

参考实现: http://nlp.seas.harvard.edu/2018/04/03/attention.html

​ self attention 机制可以描述为将一个查询 query 和一组键值对 key-val 映射到一个输出 output,其中 query、key、val 和 output 都是向量。输出 output 是以值的加权和来计算的,其中分配给每个值的权重 weight 是由 query 与对应 key 的相似度计算出来的。其具体的操作如下所示:

​ 在 self-attention 层, 首先将输入 vector 转化为三个不同的 vector, 这三个向量被称为 query vector(to match others) , key vector(to be matched) 和 value vector(information to be extracted)。将这三个向量打包为 Q、V、K 三个矩阵。在此之后,执行如下步骤:

Setp 1:计算不同输入之间的分数, 这里记为: $S = Q \cdot K^T$

Step 2:梯度标准化:$S_n = S / \sqrt{d_K}$

Step 3: 借助 softmax 将分数转化概率 $P = softmax(S_n)$

Step 4: 将 P 作为权重,乘以 V 矩阵 $Z = V \cdot P$

上述的处理过程可以被统一到如下的公式中:

​ 该公式的解释很简单。 第1步计算两个不同向量之间的分数,该分数的含义是当前位置的单词和其他单词的关联程度。第 2 步对分数进行规范化处理,主要是使其有更稳定的梯度(点积结果较大,输入 softmax 的值较大,从而导致 梯度较小),以达到更好的训练效果。第 3 步将分数转变成概率。第四步是将每个值向量乘以概率,概率较大的向量会被下面几层更加注意(attention)。 一个简单的 self-attention 实现如下:

def attention(query, key, value, mask=None, dropout=0.0):
    d_k = query.size(-1)
    scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)
    if mask is not None:
        scores = scores.masked_fill(mask == 0, -1e9)
    p_attn = F.softmax(scores, dim = -1)
    # (Dropout described below)
    p_attn = F.dropout(p_attn, p=dropout)
    return torch.matmul(p_attn, value), p_attn

​ 解码器中的 Encoder-Decode attention 和编码器中的 Self-Attention 几乎相同。不同的是矩阵 K 和 矩阵 V 来自于 编码器, 而 Q 来自上一层输出。

1.2 Multi-Head Attention

​ 为了提升 self-attention 层的性能,加入了一种叫做 multi-head attention 的机制,进一步完善了 self-attention 层。考虑我们在浏览句子的时候,对于一个给定的参考词,往往需要同时关注其他几个词, 而单头的注意力机制限定了对某一个特定位置的关注能力。多头注意力机制通过赋予注意力层不同的表示子空间来实现的。具体来说,对不同的头使用不同的查询、键和值矩阵,由于随机初始化,它们可以在训练后将输入向量投射到不同的表示子空间。

​ 在具体实现上, 通过线性函数将单个的 key, query 和 value 分裂为多个, 然后对应的 key, value 和 query 分别进行处理即可。最后将分裂的多个节点 concat, 并通过线性函数进行结合节课。

class MultiHeadedAttention(nn.Module):
    def __init__(self, h, d_model, dropout=0.1):
        "Take in model size and number of heads."
        super(MultiHeadedAttention, self).__init__()
        assert d_model % h == 0
        # We assume d_v always equals d_k
        self.d_k = d_model // h
        self.h = h
        self.linears = clones(nn.Linear(d_model, d_model), 4)
        self.attn = None
        self.dropout = nn.Dropout(p=dropout)
        
    def forward(self, query, key, value, mask=None):
        "Implements Figure 2"
        if mask is not None:
            # Same mask applied to all h heads.
            mask = mask.unsqueeze(1)
        nbatches = query.size(0)
        
        # 1) Do all the linear projections in batch from d_model => h x d_k 
        query, key, value = \
            [l(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)
             for l, x in zip(self.linears, (query, key, value))]
        
        # 2) Apply attention on all the projected vectors in batch. 
        x, self.attn = attention(query, key, value, mask=mask, 
                                 dropout=self.dropout)
        
        # 3) "Concat" using a view and apply a final linear. 
        x = x.transpose(1, 2).contiguous() \
             .view(nbatches, -1, self.h * self.d_k)
        return self.linears[-1](x)

1.3 position encoding

​ 需要注意的是,上述过程与每个词的位置无关,因此自注意力层缺乏捕捉句子中词的位置信息的能力。为了解决这个问题,在原始输入嵌入的基础上,增加一个维度为 $d_{model}$ 的位置编码,以得到单词的最终输入向量。具体来说,位置编码的公式如下:

这里的 $pos$ 表示单词在句子中的位置, $i$ 则表示当前编码的维度。

1.4 Other Parts in Transformer

论文 《attentionis all your need》 的 encoder-decoder 模型如下所示, 接下来介绍一下其他的组成部分:

  • Residual in the encoder and decoder: 如上图所示, 在编码器和解码器的每个子层中都增加了一个残差连接,以加强信息的流动,获得更好的性能。之后再进行 layer normalization。上述运算可以描述为:

    需要注意的是,这里用 $X$ 作为自关注层的输入,因为 query、key 和 value 矩阵 Q、K 和 V 都是由同一个输入矩阵 $X$ 导出的。

  • Feed-forward neural network:feed-forward NN 是由两个线性变换层和它们内部的 ReLU 激活函数组成的,该运算可以描述为:

    其中 $W1$和 $W2$ 是两个线性变换层的两个参数矩阵,$\sigma$ 代表 ReLU激活函数。

  • Final layer in decoder:decoder 的最后一层旨在将输出的向量重新编码为一个字。它是由一个线性层和一个softmax 层实现的。线性层将向量投射成一个 $d{word}$ 维度的 logits 向量,其中 $d{word}$ 是词汇中的单词数。然后,用 softmax 层将 logits 向量转化为概率。

2. Computer Vision 中的 transformer

​ 计算机视觉任务中使用的大多数 transformer 都利用了 transformer 的 encoder 模块。简而言之,它可以被视为一种新的特征选择器,它与卷积神经网络(CNNs)和递归神经网络(RNNs)是不同的。

  • 与 CNN只关注局部特征相比,transformer 能够捕捉长距离特征,这意味着全局信息可以很容易地通过 transformer 得到。
  • 与 RNN 的隐藏状态必须依次计算相比,transformer 的效率更高,因为自注意层和全连接层的输出可以并行计算,容易加速。

​ 相比于 NLP 输入的一维序列来说, CV 领域的数据则是二维的图像矩阵。 这是两者的区别所在, 所以在最初的研究中, 主要的手段就是考虑怎么将图像转换为训练输入 transformer, 得到输出。接下来,我们从分类、检测、超分三个领域分别选取一到两个模型,对计算机视觉领域的 transformer 进行讲解。

(1) ViT for classification

​ 着眼于分类任务, 将图片切分为贴片序列, 将纯 transformer 直接应用于 贴片 序列

​ 这篇论文是比较早的将 transformer 机制应用到 classification 领域的文章。首先,对原始图片进行分块,展平成为序列,然后将其输入进原始的 Transformer 模型的编码器 Encoder 部分,最后连接一个全连接层对图片进行分类。

github: https://github.com/lucidrains/vit-pytorch

Paper: An Image is worth 16X16 words: transformers for image recognition at scale

img

  1. 数据处理部分:原始输入的图片数据时 HWC, 我们先对图片进行分块,再进行展平。 假设每个块的长宽为(P, P), 那么分块的数目为 N = H W / (P P)。 图中的 N = 9, 论文中的 P = 16。对每个图片块展平成一维向量, 每个向量大小为 P P C, 总的输入变换为 N (P^2 C)。

    x = rearrange(img, 'b c (h p1) (w p2) -> b (h w) (p1 p2 c)', p1 = p, p2 = p) # use einops的拓展包
  2. Patch Embedding:使用全连接层对每一个向量都做一个线性变换, 将向量的维度压缩为D。

    self.patch_to_embedding = nn.Linear(patch_dim, dim)
  3. cls token + Positional Encoding

    • 原始的 Transformer 引入了一个 Positional encoding 来加入序列的位置信息,同样在这里也引入了pos_embedding,是用一个可训练的变量替代。

      self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim))
              
      x += self.pos_embedding[:, :(n + 1)]
    • ViT 在 Transformer 输入序列前增加了一个额外可学习的 [class] 标记位,并且该位置的 Transformer Encoder 输出作为图像特征。

      self.cls_token = nn.Parameter(torch.randn(1, 1, dim))
      
      cls_tokens = repeat(self.cls_token, '() n d -> b n d', b = b)
      x = torch.cat((cls_tokens, x), dim=1) # 添加一个 
  4. transformer:

    class Transformer(nn.Module):
        # 堆叠多层的 Attention 模块 和 Feed Fordward
        def __init__(self, dim, depth, heads, mlp_dim, dropout):
            super().__init__()
            self.layers = nn.ModuleList([])
            for _ in range(depth):
                self.layers.append(nn.ModuleList([
                    Residual(PreNorm(dim, Attention(dim, heads = heads, dropout = dropout))), 
                    Residual(PreNorm(dim, FeedForward(dim, mlp_dim, dropout = dropout)))
                ]))
                
        def forward(self, x, mask = None):
            for attn, ff in self.layers:
                x = attn(x, mask = mask)
                x = ff(x)
            return x
  5. mlp_head: 使用 layernorm 和 全连接 实现

    self.mlp_head = nn.Sequential(
        nn.LayerNorm(dim),
        nn.Linear(dim, num_classes)
    )

(2) DETR for detection

​ DETR 的网络结构如下所示:

​ 首先用 CNN 将输入图像 embedding 成一个二维表征。 并将二维表征转换为一维表征并结合 positional encoding 一起送入 transformer 进行 encoder-decoder。然后将 decoder 得到的每个 output embedding 传递到一个共享的前馈网络(FFN)。最后使用 set loss function 作为监督信号来进行端到端训练,然后同时预测所有目标。三个创新点:(1)使用 transformer (2) 使用 set loss 并抛弃了 anchor 的概念和 nms (3) end2end。 虽说论文对标的是 faster rcnn。 但是采用新的方法去探索目标检测, 还是值得肯定的。

1. backbone

​ CNN backbone 只用于提取特征。CNN backbone 处理 $X_{img} \in R^{N 3H_0W_0}$ 维的图像,把它转换为 $ F \in R^{N C H W}$ 维的feature map(一般来说 N = 2048 或 N = 256, H = H_0 / 32,W = W_0 / 32)。

​ 通道数压缩: 用 1x1 卷积, 将 channels 数量从 C 压缩, 得到一个 $z_0 \in R^{N d H W}$。并将其 reshape 为 $N, HW, d$ 维度的 feature map。

​ 位置编码: 在得到 $z_0 \in R^{N d H * W}$ 之后, 需要进行位置编码。 需要注意一点的是, 原版的 transformer 只考虑了 $x$ 方向的位置编码, 但是 DETR 考虑 $xy$ 两个方向的编码。另一点不同的是, 原版的 transformer 只在 encoder 输入处进行了 位置编码, 但是 DETR 在 encoder 的每一个 Multi-head self-attention 之前都使用了 positional encoding, 且只对 query 和 key 使用了 positional encoding。

2. encoder-decoder

这里直接套用了 NLP 上的 transformer,有些许不同:

  • 位置向量编码要加入到 每个 encoder layer 中。

  • decoder 的输入添加了 object queries。Object queries是一个维度为 (100, b, 256) 维的张量,数值类型是nn.Embedding,说明这个张量是可以学习的,即:我们的Object queries是可学习的。 Object queries矩阵内部通过学习建模了 100 个物体之间的全局关系,例如房间里面的桌子旁边( A 类)一般是放椅子( B 类),而不会是放一头大象( C 类),那么在推理时候就可以利用该全局注意力更好的进行解码预测输出。

3. prediction heads

​ prediction 由 3 层 perceptron 和一层 linear projection 组成。FFN 预测出 box 的归一化 xywh 和 classes。DETR 预测的是固定数量的 N 个 box 的集合,并且 N 通常比实际目标数要大的多,所以使用一个额外的空类来表示预测得到的 box 不存在目标。 最终 DETR 输出的张量的维度为 (b, 100, class + 1) 和 (b, 100, 4)。这个 100 是个预先设定的, 远大于图中目标总数的数字。 通过这两个张量,就可以解码出对应的检测框和分类。

4. bipartite loss

​ 使用匈牙利算法来寻找 prediction box 和 image object 匹配的总 cost 最小的二分图匹配方案。首先定义每对 prediction box 和 image object 的 cost 损失:

简单解释一下, 如果当 image object 为空 时, 这对 cost 为 零, 当 image object 不为零时, 当(预测相同的)类别概率越大,bbox 的差距越小时, 配对的 cost 越小。 这里的 $L_{box}$ 定义为:

​ 我们就完全定义好了每对 prediction box 和 image object 配对时的cost。再利用匈牙利算法即可得到二分图最优匹配。 基于这个最优匹配,来计算 set prediction loss,即评价 transformer 生成这些 prediction boxes 的效果好坏。Set prediction loss 计算公式如下:

(3) IPT

​ 不同于高层视觉语义任务的目标是进行特征抽取,底层视觉任务的输入和输出均为图像。除超分辨率任务之外,大多数底层视觉任务的输入和输出维度相同。相比于高层视觉任务,输入和输出维度匹配这一特性使底层视觉任务更适合由 Transformer 处理。 具体而言,研究者在特征图处理阶段引入 Transformer 模块,而图像维度匹配则交给了头结构与尾结构,如下图所示:

(1) 首先将图片经过一个头结构变换为特征图:

(2) 对特征图进行切块与拉平操作。首先按照 P×P 的大小将特征图切割成 N 块,每一个特征块再被拉平为维度为 $P^2×C$ 的向量,这里的处理类似于 ViT 中的操作。

这样一来,每个特征向量可以等同于一个「单词」,即可送入 Transformer 进行处理,得到维度相同的输出特征:

(3) 这些输出特征再经过整形和拼接操作,还原为与输入相同维度的特征图。如此处理得到的特征图会被送入一个尾结构,被解码为目标图像。

​ 头结构和尾结构负责维度变换,transformer 模块可以专心地做特征处理。这使得多任务的扩展变得简单:对于不同的任务,只需要增加新的头结构与尾结构即可,多种任务之间的 transformer 模块是共享的。为了适应多任务,研究者在 Transformer 的解码模块中加入了一个可学习的任务编码。
​ transformer 的成功离不开大量数据预训练带来的性能提升。在这篇论文中,针对底层视觉任务,研究者使用 ImageNet 数据集生成多种退化图像,构成多种底层视觉任务训练集。利用这些人工合成的数据,配以对应任务的多头多尾结构,多个任务的训练数据同时进行训练。训练时的损失函数则使用 L1 损失作为监督损失函数, 使用特征块之间的相关性自监督损失函数

3. efficient transformer

高效的 transformer

一些思考和待解决的问题

4. 参考资料


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!