搞懂 Vision Transformer 原理和代码,看这篇技术综述就够了(四)
极市导读
本文为详细解读Vision Transformer的第四篇,主要包括2种vision Transformer的内部机制,即:1. 如何更好地利用图像patch内部信息?2. 如何设计更灵活的位置编码?附有超详细的代码解读。 >>加入极市CV技术交流群,走在计算机视觉的最前沿
本文目录
9 充分挖掘patch内部信息:Transformer in Transformer:TNT
(来自北京华为诺亚方舟实验室)
9.1 TNT原理分析10 探究位置编码的必要性:Do We Really Need Explicit Position Encodings for Vision Transformers?
(来自美团)
10.1 CPVT原理分析
Transformer 是 Google 的团队在 2017 年提出的一种 NLP 经典模型,现在比较火热的 Bert 也是基于 Transformer。Transformer 模型使用了 Self-Attention 机制,不采用 RNN 的顺序结构,使得模型可以并行化训练,而且能够拥有全局信息。
本文分析的文章都是针对Transformer内部机制的探究,从而提出的对于ViT,DeiT模型的改进。第1篇是针对Transformer模型处理图片的方式:将输入图片划分成一个个块(patch),然后将这些patch看成一个块的序列 (Sequence)的不完美之处,提出了一种TNT架构,它不仅考虑patch之间的信息,还考虑每个patch的内部信息,使得Transformer模型分别对整体和局部信息进行建模,提升性能。
对本文符号进行统一:
Multi-head Self-attention:
式中,
,
,
,
,
代表序列长度,
代表hidden dimension。
MLP层:
式中,
为2个FC层的权重,
是bias值。
Layer Normalization:
LN层是 Transformer 模型快速训练和稳定收敛的重要保证。LN层的作用对象是
,
代表每个样本的均值和方差。
是可学习的仿射参数。
9 充分挖掘patch内部信息:Transformer in Transformer:TNT
论文名称:Transformer in Transformer
论文地址:
https://arxiv.org/pdf/2103.00112.pdfarxiv.org
9.1 TNT原理分析:
Transformer 网络推动了诸多自然语言处理任务的进步,而近期 transformer 开始在计算机视觉领域崭露头角。例如,DETR 将目标检测视为一个直接集预测问题,并使用 transformer 编码器 - 解码器架构来解决它;IPT 利用 transformer 在单个模型中处理多个底层视觉任务。与现有主流 CNN 模型(如 ResNet)相比,这些基于 transformer 的模型在视觉任务上也显示出了良好的性能。其中,将Transformer应用在图像识别领域的两个经典的工作是:ViT(https://zhuanlan.zhihu.com/p/342261872)和DeiT(https://zhuanlan.zhihu.com/p/349315675)。ViT的结构如下图1所示。它先将输入图片划分成一个个块(patch),然后将这些patch看成一个块的序列 (Sequence),这里假设序列长度为
。再把这个序列中的每个patch进行展平操作 (Flatten),这样一来,每个patch就转化成了一个向量,我们假设这个向量是
维的。那么经过以上所有操作,一张输入图片就成为了一个大小为
的矩阵。这个矩阵与一个向量
一起被输入Transformer的Encoder中,来处理图像 patch 序列,最终由
的输出做图像识别。
DeiT对ViT的结构做了一点微小的改动,输入图片依然经过相同的操作变成一个大小为
的矩阵,只是此时这个矩阵与一个向量
和另一个向量
一起被输入Transformer的Encoder中,来处理图像 patch 序列。在训练时,对
的输出使用有监督损失,对
的输出使用蒸馏损失。最终由
的输出和
的输出的均值做图像识别。
图1:ViT结构
到这里我们发现,DeiT相比于ViT来讲,在训练方式上做出了改进,这使得Transformer在建模patch之间的信息时考虑更多的因素,提升了Transformer模型的整体性能。但是,DeiT有一个没有解决的问题是:
DeiT依然把图片转化成patch并进行Flatten操作之后输入Transformer模型,而这种转化成patch的办法,真的是处理图片这种2D输入信息的最合适的方法吗?
我们知道,Transformer需要的是序列 (Sequence)的输入信号,而我们有的是image这种2D的输入信号,那直接把图片分块以后进行Flatten操作是一种很直觉的处理方式。但是,这种intuitive的方法能不能够完美地建模图像呢?
答案显然是否定的。因为我们缺少了一部分非常重要的信息,即:每个patch的内部信息。
问:为什么每个patch的内部信息这么重要?
答:Transformer这种模型之所以有效,是因为它能处理长度为
的输入序列中这
个输入之间的关系 (relationship),而对于每个输入的内部信息的relationship,它是无能为力的,因为ViT,DeiT,IPT,SETR,ViT-FRCNN这类模型把每个patch展平为了一个向量,破坏了每个patch的内部信息。
所以我们认为,每个输入的内部信息,即每个patch的内部信息,没有被Transformer所建模。是一个欠考虑的因素。
所以本文的动机是:使得Transformer模型既建模那些不同patch之间的关系,也要建模每个patch内部的关系。
所以作者这里设计了一种Transformer in Transformer (TNT)的结构,第1步还是将输入图片划分成
个块(patch):
式中
是每个块的大小。ViT,DeiT,IPT,SETR,ViT-FRCNN到这里就把它们输入Transformer了,本文为了更好地学习图片中global和local信息的关系,还要再进行一步:
接下来再把每个patch通过PyTorch的unfold操作划分成更小的patch,之后把这些小patch展平,就得到了
式中
,那么
代表的物理意义是第
个layer的第
个大patch包含的所有小patch,
是channel数。意思是:原图是
,分成
个大patch,每个大patch可以看做是由很多
的小patch组成的。
例如,大patch的大小是
,这里
,那么小patch的大小是
。每个大patch被分成了4个小patch,即
相当于是4个小patch。
接下来作者把
这一堆小patch视为一个pixel embedding:
pixel embedding由
个向量组成,每个向量
。
如下图2所示,输入是一个大patch,输出的黄色大长条是这个patch展平以后的patch embedding,输出的彩色小长条是这个patch划分成更小的patch之后再展平以后的pixel embedding。
图2:对patch进行unfold操作后得到更小的patch
图2的操作进行完之后就得到了大patch的 patch embedding以及小patch的pixel embedding。接下来把它们送入Transformer的Block里面建模特征,如下图3所示。Transformer是由很多TNT Blocks组成的,每个TNT Block包含2个Transformer Block,分别是:
Outer block 建模 patch embedding 之间的 global relationship。 Inner block 建模 pixel embedding 之间的 local structure information。
图3:TNT结构
这两种Block对应2个不同的数据流,其中 Outer block 的数据流在不同patch之间运行,而 Inner block 的数据流在每个patch内部运行。
Inner Transformer:
定义
,我们把这个值传入Inner Transformer
,则有:
注意正常的Transformer的输入应该是
的张量,这里
代表batch size,
代表序列长度,
代表hidden dimension。不考虑batch size这一维,就是一个
的矩阵,也可以看做是
个
维向量,那么对于Inner Transformer
来讲,这里的
。也就是说,Inner Transformer
的输入是
个
维的向量。注意这里的
就是这
个向量的其中一个。所以Inner Transformer的第
个layer的输出就可以写为:
Inner Transformer
建模的是更细小的像素级别的relationship,例如,在一张人脸中,属于眼睛的像素与眼睛的其他像素更相关,而与前额像素的relationship较少。
Outer Transformer:
Outer Transformer
就相当于是ViT中的Transformer,它建模的是更答大的patch级别的relationship,输入的patch embedding使用ViT类似的做法,添加
,它们初始化为0。
定义
为第
个layer的第
个向量,则Outer Transformer的表达式为:
那么现在既有Outer Transformer的第
个layer的输出向量:
也有Inner Transformer的第
个layer的输出向量:
下面的问题是:要如何把它们结合起来,以融合global和local的信息呢?
作者采用的方式是:
式中,
代表Flatten操作,
代表权重。
通过这种方式,把第
个layer的第
个patch embedding向量和第
个pixel embedding向量融合起来,即对应图3(b)的结构。
总的来说,TNT Block第
个layer的输入和输出可以表示为:
在TNT Block中,Inner Transformer建模 pixel embedding 之间的 local structure information之间的关系,而Outer block 建模 patch embedding 之间的 global relationship。通过将TNT Block堆叠
次,作者构建了Transformer in Transformer。最后,使用一个分类头对图像进行分类。
位置编码:
位置编码的作用是让像素间保持空间位置关系,对于图像就是保持二维信息,它对于图像识别任务来讲很重要。具体来说,就需要对patch embedding和pixel embedding分别设计一种位置编码。
patch positional encoding:
作者这里使用的是可学习的1D位置编码:
式中,
是给patch embedding使用的位置编码,它用来编码全局空间信息 (global spatial information)。
pixel positional encoding:
作者这里使用的是可学习的1D位置编码:
计算复杂度分析,标准的Transformer Block的结构:
一个标准的Transformer Block的结构包括multi-head self-attention (MSA) 和 multi-layer
perceptron (MLP)。
MSA的计算复杂度是:
图4:MSA的计算复杂度
MLP的计算复杂度是:
式中,
是expansion ratio。
所以一个标准的Transformer Block的计算复杂度是:
经常设置为4,且
和
一般是相等的,所以:
一个标准的Transformer Block的参数量为:
。
计算复杂度分析,TNT Block的结构:
的计算复杂度:
的计算复杂度:
Linear Transformation的计算复杂度是:
所以一个标准的TNT Block的计算复杂度是:
一个标准的TNT Block的参数量为:
看似TNT相比于标准的Transformer结构的计算量和参数量都增加了很多,但是实际上并没有增加多少。因为这里的:
。
比如在ViT-B/16 configuration中,设置:
,则:
,增长了大概 1.09倍,同样地,参数量增长了大概1.08倍。通过少量增加计算和内存成本,TNT Block可以有效地模拟局部结构信息,并在准确性和复杂性之间实现更好的平衡,如实验所示。
网络架构:
超参数设置为:
大patch的patch size:16×16,小patch的patch size:4×4,设计了2种不同大小的TNT模型,分别是:TNT-S和TNT-B,模型的参数量大小分别是23.8M和65.6M。计算量分别是5.2B和14.1B。
图5:TNT网络结构配置
作者还使用了SE模块进行channel-wise的attention,具体来讲,把每个patch的d个channel的embedding做平均操作,经过一个2层的MLP得到d个attention values。这d个attention会对应地乘在d个channel上面。SE模块的好处是仅仅增大了一点参数但却能通过dimension-wise attention实现特征的增强。
Experiments:
Dataset:
数据集如下图6所示,除了ImageNet以外,下面几行的小数据集用作迁移学习。数据增强方式包括了:random crop, random clip, Rand-Augment, Mixup 和 CutMix。
图6:数据集
训练的方式如下图7所示:
图7:Implementation Details
实验1:ImageNet结果:
作者比较了TNT与基于Transformer的ViT和DeiT模型,以及几个具有代表性的CNN模型比如ResNet,RegNet和EfficientNet,结果如图8,9所示。
图8:ImageNet实验结果
图9:ImageNet实验结果
结果表明,TNT超过了所有的基于Transformer的分类模型,TNT-S模型达到了81.3%的精确度,在与DeiT-S参数量相当的前提下提升了1.5%,再次证明了local structure information的重要性。再通过添加SE模块,TNT可以达到81.6%的性能,也超过了基于卷积的ResNet和RegNet。
对比实验1:位置编码的影响
位置编码在图像识别任务中的作用还是挺大的,因为它让像素间保持空间位置关系,对于图像就是保持二维信息。比如一个像素点的前后左右,直接flatten之后进行self-attention的结果和左右前后flatten之后进行self-attention的结果其实是一样的。所以如果没有位置编码,就没法建模这个前后左右的位置信息。patch position encoding的目的是存储全局空间信息,而pixel position encoding的目的是存储局部相对信息。如下图10所示,去掉位置编码之后会带来一定程度的accuracy drop。
图10:位置编码的影响
对比实验2:head数量的影响
head数量适中 (2, 4) 时能达到比较好的性能。
图11:head数量的影响
对比实验3:小patch size的影响
TNT模型先和ViT一样的做法,把图片划分为16×16的patch,再进一步把这些patch使用unfold操作变成
的小patch,从图12的结果可以发现,
的取值对最终的结果影响不大,考虑到模型的大小和计算量,最终选择了
。
图12:小patch size的影响
实验2:可视化
作者可视化了DeiT和TNT的learned feature maps,具体是从第1,6,12个Block的输出随机采样12个feature map。与DeiT相比较而言,TNT更好地保持了local information。作者还是用T-SNE可视化了Block 12输出的所有384个feature maps,可以看到, TNT的特征比DeiT更丰富,包含的信息也更丰富。这些好处归功于Inner Transformer的引入,以建模局部特征。
图13:patch level的特征图可视化
除了patch level的特征以外,作者还可视化了pixel level的信息,如下图14所示。一张224×224的图片分成大小为16×16的patch,可以得到14×14个。作者将Block 1,6,12的输出的所有feature map进行求平均的结果打印出来,发现 shallow layer很好地保留了局部信息,而随着网络的深入,表示逐渐变得更加抽象。
图14:pixel level的特征图可视化
实验3:迁移学习
作者为了证明TNT的泛化性能,将TNT-S,TNT-B这2个模型迁移到了小数据集 (CIFAR-10, CIFAR-100, Oxford-IIIT Pets, Oxford 102 Flowers)上面。所有模型都使用了384×384的数据fine-tune。如图15所示,我们发现TNT在大多数数据集的参数较少时优于DeiT ,这表明了建模像素级关系以获得更好的feature representation的优越性。fine-tune时为了在不同的分辨率中微调,作者还对位置编码进行了插值。
图15:使用ImageNet与预训练对下游任务的结果。384表示使用384×384分辨率的数据进行微调。
10 探究位置编码的必要性:Do We Really Need Explicit Position Encodings for Vision Transformers?
论文名称:Do We Really Need Explicit Position Encodings for Vision Transformers?
论文地址:
https://arxiv.org/pdf/2102.10882.pdfarxiv.org
10.1 CPVT原理分析:
self-attention这种结构的特点是可以建模一整个输入序列的信息,并能根据图片的内容来动态调整感受野,但是self-attention的缺点是:排列不变性 (permutation-invariant),即:无法建模输入序列的顺序信息,输入这个序列顺序的调整是不会影响输出结果的。为了解决这个问题,Transformer引入了位置编码机制。位置编码在图像识别任务中的作用还是挺大的,因为它让像素间保持空间位置关系,对于图像就是保持二维信息。比如一个像素点的前后左右,直接flatten之后进行self-attention的结果和左右前后flatten之后进行self-attention的结果其实是一样的。所以如果没有位置编码,就没法建模这个前后左右的位置信息。位置编码可以设置为可学习的,也可以设置为不可学习的正弦函数。
但是,位置编码的缺点是:它的长度往往是固定的。比如输入图片的大小是
,分成大小为
的patch,那么patch的数量就是
。比如现在训练集输入图片是224×224的,分成大小为16×16的patch,那么序列长度是196。所以训练时把位置编码的长度也设置为196。但是后续进行迁移学习时输入图片是384×384的,分成大小为16×16的patch,那么序列长度是576。此时你的长度是196的位置编码就不够了,这时人们通常的做法有这么几种:
1. 去掉位置编码
会严重地影响分类性能,因为输入序列的位置信息丢失了。以 DeiT-tiny 为例,不使用位置编码会使性能从72.2%降为68.2%。
2. 相对位置编码
相对位置编码的相关工作有以下几个:
Relative Position Encoding 考虑sequence 元素之间的距离,也是一种有效的手段。
https://arxiv.org/pdf/1803.02155.pdf
2D 相对位置编码:
https://arxiv.org/pdf/1904.09925.pdf
相对位置编码的表达式如下:
式中,
是:当把输入sequence视为有向的全连接的图时
和
的edge distance。
这种做法的缺点是实现起来比较复杂,不够efficient,因为需要改变Transformer内部的表达式。
3. 插值法
把位置编码进行双三次插值 (bicubic interpolation),把196的位置编码插值成长度为396的,以适应新的数据集。
即便是有这样的补救措施,但是:
许多视觉任务都需要不断改变输入图片的大小,也就需要不断改变输入序列的长度,这样做很不方便。 插值的方法会影响性能。
以上这3种办法的性能的比较如下图所示,结果发现:在使用位置编码的情况下,无论是使用可学习的位置编码 (learnable),还是固定的位置编码 (sin-cos),其训练的模型性能是差不多的。但是使用相对位置编码后性能会有下降,不使用位置编码性能会大幅下降。证明位置编码对于Vision Transformer的重要性。
图16:在ImageNet验证集的不同位置编码策略的性能比较
所以目前面临的问题就是:我们需要一种新的位置编码策略,既能解决传统位置编码不可变长度的局限性,又能起到位置编码的作用。
本文就是为了解决这个问题,即:灵活地把位置信息引入Transformer中。之前工作的位置编码一般是预定义好并与输入无关 (predefined and inputagnostic),本文的位置编码是即时的 (on-the-fly),就是需要多长的编码就立刻有多长的编码。
这种方法取名为CVPT,如下图17所示。CVPT能自动生成一种包含位置信息的编码PEG,提升Transformer模型的性能。通过这样做,Transformer可以处理任意大小的输入图像,而无需双三次插值。
图17:CVPT
其实对于Positional Encoding的研究之前已有一些:CNN操作看上去没有进行卷积操作,但是实际上却能够隐含地编码绝对位置信息。具体来说,zero padding 和 borders 其实扮演了一种anchor的角色,以获取空间信息。关于这个研究可以参考下面的2个链接:
Xinlong Wang:CNN是怎么学到图片内的绝对位置信息的?
https://zhuanlan.zhihu.com/p/99766566
https://arxiv.org/pdf/2101.12322.pdf
CoordConv 使用 concatenation 代替 Transformer 中的位置编码的加法操作。
https://arxiv.org/pdf/1807.03247.pdf
Relative Position Encoding 考虑sequence 元素之间的距离,也是一种有效的手段。
https://arxiv.org/pdf/1803.02155.pdf
2D 相对位置编码:
https://arxiv.org/pdf/1904.09925.pdf
LambdaNetwork 使用 Lambda layers 来建模长距离的内容和位置信息的interactions,这样就可以避免使用self-attention layer。LambdaNetwork的实验证明:位置信息的interaction对于性能的提升是必要的,而基于内容的interaction 只能带来性能的小幅度的改善。
Transformer的位置编码表达式为:
式中,
代表某个词在序列中的位置,
代表这个Transformer模型的embedding dimension。
代表当前的dimension,取值范围是:
。
回到CVPT上来,它的motivation说:一个好的位置编码应该具备以下3个特征:
继续保持很强的性能。 能够避免排列不变性 (permutation equivariance),即:输入序列的顺序变化时,结果也不同,能起到位置编码的作用。且随着输入size的改变要也可以灵活地变化。 高效,易于实现。不改变原版Transformer的公式。
CVPT的Positional Encoding Generator (PEG)的具体做法是:
先把输入
reshape回2D的张量
。
再把
通过2D的Transformation
并把输出再变为
。
具体是个卷积,卷积核
,zero-padding
,这样不改变spatial resolution。
class token
保持不变。
最后把 和concatenate起来,得到PEG输出。
图18:PEG结构
问:为什么要这么做?这种做法怎么就能把位置信息引入Transformer了?
答:给Transformer引入位置信息,说白了就是给一个sequence的
个向量assign a position。那这个position它既可以是绝对信息,也可以是相对信息。相对信息就是定义一个参考点然后给每个向量一个代表它与参考点相对位置的信息。这种做法相当于是使用卷积操作得到positional encoding,而卷积操作的zero-padding就是相当于是参考点,卷积操作相当于提取了每个向量与参考点的相对位置信息。所以这种办法用一句话概括就是:
PEG的卷积部分以zero-padding作为参考点,以卷积操作提取相对位置信息,借助卷积得到适用于Transformer的可变长度的位置编码。
我们通过下面的可视化结果来看下位置编码的影响:
考虑一张224×224的image,分成14×14个patch。我们使用一个196×196的矩阵来表示这196个patch之间的self-attention score,如下图19所示。左边是DeiT的结果,右边是DeiT去掉位置编码的结果。我们发现DeiT学到了a schema of locality。每个patch只与它的local neighbor interacts strongly,与那些距离它很远的patch interacts weakly。但是去掉位置编码以后,这种schema 消失了,每个patch与它的neighbor 变得没有联系了,位置信息也消失了。
图19:DeiT第2个encoder block的输出的Normalized Self-attention Scores,取消位置编码后,DeiT丧失了local information
下图20是使用CPVT以后第2个encoder block的输出的attention maps,reshape到了14×14的格子。最左上方的格子关注 (attention) 的点在左上,而最左下方的格子关注 (attention) 的点在左下,以此类推,所以CPVT依然能够学习到local information。
图20:CPVT依然能学到local information
Experiment:
数据集:ImageNet
模型variants:
图21:不同大小的CPVT模型
8卡V100跑CPVT-Tiny,Small,Base模型分别需要1.3days,1.6days和2.5days。
Training details:
300 epochs,batch size=2048,AdamW。超参数与DeiT保持一致。
学习率的变化公式:
实验1:ImageNet与SOTA的对比:
CPVT在模型参数和计算量一致的前提下比DeiT的性能有所提升。
图22:ImageNet实验结果
值得一提的是,这个表格的最右侧一列是在224×224的数据集上训练好的模型直接在384×384的数据集上测试,而不进行任何fine-tuning的结果。作者发现DeiT-tiny的性能从72.2%降为了71.2%,而CVPT的性能却从73.4%上升到了74.2%,证明了CVPT方法对于数据集大小变化的适应性。它不需要任何其他的措施来适应这一变化,仅靠着一层网络来适应数据集尺寸的变化。
实验2:目标检测任务性能:
作者又对Transformer做检测任务的DETR模型使用了本文的PEG模块,epochs数量从500降为50,优化器是AdamW,batch size为32,weight decay为0.0001。backbone和Trasnformer的初始learning rate分别为
和
。learning rate使用stepLR策略,在epoch为40是降为0.1倍。实验结果如图23所示。为了对比的公平DETR和Deformable-DETR都使用了sine positional encoding。如果去掉位置编码,DETR的mAP从33.7%降为32.8%;但如果使用了本文的PEG模块会提升至33.9%。Deformable-DETR也得到了相似的实验结果,证明PEG优于原本的positional encoding。
图23:COCO实验结果
实验3:PEG复杂度分析:
参数量:
这一部分主要来看看PEG这个模块为模型引入了多少额外的参数量和计算量。我们假设Transformer模型的embedding dim为
,如果只使用
个Depthwise convolution,增加的参数量是
,如果使用
个Depthwise Separable convolution,增加的参数量是
。当
时,CVPT-Ti模型的
带来大约
的参数。而DeiT-Tiny的位置编码参数量为
,所以CVPT-Ti节约了大约
个参数。即便你使用Depthwise Separable convolution,PEG参数量为
,相比原来的
只多了960个参数,可以忽略不计。
计算量:
层Depthwise convolution的计算量是
,当
时计算量大概是
,相比Tiny模型2.1G的计算量来说可以忽略。
对比实验1:PEG模块插入策略比较
作者对比了几种不同的插入策略,图中的LE代表learnable encoding,RPE代表relative positional encoding,sin-cos代表absolute positional encoding。
图24:PEG模块插入策略比较
结论:
sin-cos和可学习的位置编码差别不大。
在每个blk前插入PEG效果更好。
对比实验2:PEG模块插入位置比较
图25:PEG模块插入位置比较
结论:
PEG最佳的插入位置是第1个encoder block的输出到第4个encoder block的输出。
为什么插入位置为-1 (第1个encoder block的输入)时的效果比插入位置为0时 (第1个encoder block的输出)的效果差很多?
作者认为是插入位置为-1的感受野更小,插入位置为0时的感受野更大。所以在插入位置为-1时扩大感受野应该可以得到相似的性能。所以作者进行了下图26的实验:
图26:在插入位置为-1时扩大感受野
把3×3卷积扩大为27×27,并使用13的zero-padding,扩大感受野后,在插入位置为-1时得到了与插入位置为0时相似的性能。
作者也在插入位置为0时使用了不同大小的卷积核:
图27:不同大小的卷积核
当卷积核为1时,由于无法获得位置信息,所以性能掉点严重,而卷积核为5,7时对性能提升意义不大。
作者进一步比较了在多个位置插入PEG模块给Transformer模型带来的影响,如下图28所示。
图28:插入数量的影响
在Tiny模型的位置0-5都插入PEG模块时,性能超过了DeiT-tiny 1.2%。插入过多的PEG模块的影响不大。
作者进一步对比了在0位置PEG使用1个Depth-wise Convolution,1个Separable Convolution,4个Separable Convolution的性能对比,如下图29所示。
使用4个Separable Convolution可以在只增加0.1M参数量的前提下获得0.5%的涨点。
图29:Efficiency vs. Complexity
对比实验3:Padding的作用
为了探索zero-padding的作用,作者将它去掉之后再看CPVT模型的性能,降为了70.5%。原因是zero-padding起到定位绝对位置的作用。它能暗示出图像的哪些部分是中间,哪些部分是边缘。这个实验也侧面证明了绝对位置编码的作用。
图29:Padding的作用
对比实验4:到底是什么提升了性能?
作者想要探索到底是PEG的什么提升了性能,究竟是PEG卷积层的representative power还是它的位置表示能力?
如果是PEG卷积的位置表示能力起了作用,那我把卷积换成FC层,应该会掉点; 如果是PEG卷积的representative power起了作用,那我让卷积的参数固定住不更新,应该会掉点;
实验结果如图30所示,验证了第1点,所以是PEG卷积的位置表示能力在起作用。即使只使用卷积层而不学习它,那么通过zero-padding也可以把位置信息融入Transformer里面。
图30:PEG卷积的位置表示能力在起作用
13.2 CVPT代码解读:
Vision Transformer:
import torch
import torch.nn as nn
class VisionTransformer:
def __init__(layers=12, dim=192, nhead=3, img_size=224, patch_size=16):
self.pos_block = PEG(dim)
self.blocks = nn.ModuleList([TransformerEncoderLayer(dim, nhead, dim*4) for _ in range(layers)])
self.patch_embed = PatchEmbed(img_size, patch_size, dim*4)
def forward_features(self, x):
B, C, H, W = x.shape
x, patch_size = self.patch_embed(x)
_H, _W = H // patch_size, W // patch_size
# x: (b, N, d)
x = torch.cat((self.cls_tokens, x), dim=1)
# 循环通过所有的Encoder Blocks, 只是第1个encoder block的输出通过PEG模块.
for i, blk in enumerate(self.blocks):
x = blk(x)
# 第1个encoder block的输出通过PEG模块.
if i == 0:
x = self.pos_block(x, _H, _W)
return x[:, 0]
分类任务:PEG模块:
class PEG(nn.Module):
def __init__(self, dim=256, k=3):
self.proj = nn.Conv2d(dim, dim, k, 1, k//2, groups=dim) # Only for demo use, more complicated functions are effective too.
def forward(self, x, H, W):
B, N, C = x.shape
# 把class token提出来, 不变.
# 只处理feat_token.
cls_token, feat_token = x[:, 0], x[:, 1:]
cnn_feat = feat_token.transpose(1, 2).view(B, C, H, W)
x = self.proj(cnn_feat) + cnn_feat
x = x.flatten(2).transpose(1, 2)
x = torch.cat((cls_token.unsqueeze(1), x), dim=1)
return x
检测任务:PEG模块:
from torch import nn
class PEGDetection(nn.Module):
def __init__(self, in_chans):
super(PEGDetection, self).__init__()
self.proj = nn.Sequential(nn.Conv2d(in_chans, in_chans, 3, 1, 1, bias=False, groups=in_chans), nn.BatchNorm2d(in_chans), nn.ReLU())
def forward(self, x, mask, H, W):
"""
x N, B, C ; mask B N
"""
_, B, C = x.shape
_tmp = x.transpose(0, 1)[mask]
x = x.permute(1, 2, 0).view(B, C, H, W)
# 与分类任务不同,直接把x通过卷积之后与自己相加.
x = x + self.proj(cnn_feat)
x = x.flatten(2).transpose(1, 2)
# mask保持不变.
x[mask] = _tmp
return x.transpose(0, 1)
总结:
Patch内部信息的处理与位置编码是vision Transformer的2个很重要的问题。本文深入探究了2种vision Transformer的内部机制,即:1. 如何更好地利用图像patch内部信息?2. 如何设计更灵活的位置编码?TNT通过将Patch内部信息与Patch之间的信息融合,提升了DeiT/ViT的性能。CPVT通过PEG模块代替传统的位置编码,实现了灵活的位置表示和更高效的位置信息编码,提升了DeiT/ViT的性能。值得一提的是,这2种方法与其他的改进措施是正交的,可以在不相互影响的条件下直接完成迁移。