深度学习利用篇-计算机视觉-图像分类[3]:ResNeXt、Res2Net、Swin Transformer、Vision Transformer等模型构造、实现、模型特点具体介绍
1.ResNet
相较于VGG的19层和GoogLeNet的22层,ResNet能够提供18、34、50、101、152甚至更多层的网络,同时取得更好的精度。然而为什么要应用更深层次的网络呢?同时,如果只是网络层数的重叠,那么为什么前人没有取得ResNet一样的胜利呢?
1.1. 更深层次的网络?
从实践上来讲,加深深度学习网络能够晋升性能。深度网络以端到端的多层形式集成了低/中/高层特色和分类器,且特色的档次可通过加深网络档次的形式来丰盛。举一个例子,当深度学习网络只有一层时,要学习的特色会非常复杂,但如果有多层,就能够分层进行学习,如 图1 所示,网络的第一层学习到了边缘和色彩,第二层学习到了纹理,第三层学习到了部分的形态,而第五层已逐步学习到全局特色。网络的加深,实践上能够提供更好的表达能力,使每一层能够学习到更细化的特色。
1.2. 为什么深度网络不仅仅是层数的重叠?
1.2.1 梯度隐没 or 爆炸
但网络加深真的只有重叠层数这么简略么?当然不是!首先,最显著的问题就是梯度隐没/梯度爆炸。咱们都晓得神经网络的参数更新依附梯度反向流传(Back Propagation),那么为什么会呈现梯度的隐没和爆炸呢?举一个例子解释。如 图2 所示,假如每层只有一个神经元,且激活函数应用Sigmoid函数,则有:
$$z_{i+1} = w_ia_i+b_i\\a_{i+1} = \sigma(z_{i+1})$$
其中,$\sigma(\cdot)$ 为sigmoid函数。
依据链式求导和反向流传,咱们能够失去:
$$\frac{\partial y}{\partial a_1} = \frac{\partial y}{\partial a_4}\frac{\partial a_4}{\partial z_4}\frac{\partial z_4}{\partial a_3}\frac{\partial a_3}{\partial z_3}\frac{\partial z_3}{\partial a_2}\frac{\partial a_2}{\partial z_2}\frac{\partial z_2}{\partial a_1} \\= \frac{\partial y}{\partial a_4}\sigma^{'}(z_4)w_3\sigma^{'}(z_3)w_2\sigma^{'}(z_2)w_1$$
Sigmoid 函数的导数 $\sigma^{'}(x)$ 如 图3 所示:
咱们能够看到sigmoid的导数最大值为0.25,那么随着网络层数的减少,小于1的小数一直相乘导致 $\frac{\partial y}{\partial a_1}$ 逐步趋近于零,从而产生梯度隐没。
那么梯度爆炸又是怎么引起的呢?同样的情理,当权重初始化为一个较大值时,尽管和激活函数的导数相乘会减小这个值,然而随着神经网络的加深,梯度呈指数级增长,就会引发梯度爆炸。然而从AlexNet开始,神经网络中就应用ReLU函数替换了Sigmoid,同时BN(Batch Normalization)层的退出,也根本解决了梯度隐没/爆炸问题。
1.2.2 网络进化
当初,梯度隐没/爆炸的问题解决了是不是就能够通过重叠层数来加深网络了呢?Still no!
咱们来看看ResNet论文中提到的例子(见 图4),很显著,56层的深层网络,在训练集和测试集上的体现都远不如20层的浅层网络,这种随着网络层数加深,accuracy逐步饱和,而后呈现急剧下降,具体表现为深层网络的训练成果反而不如浅层网络好的景象,被称为网络进化(degradation)。
为什么会引起网络进化呢?依照实践上的想法,当浅层网络成果不错的时候,网络层数的减少即便不会引起精度上的晋升也不该使模型成果变差。但事实上非线性的激活函数的存在,会造成很多不可逆的信息损失,网络加深到肯定水平,过多的信息损失就会造成网络的进化。
而ResNet就是提出一种办法让网络领有恒等映射能力,即随着网络层数的减少,深层网络至多不会差于浅层网络。
1..3. 残差块
当初咱们明确了,为了加深网络结构,使每一次可能学到更细化的特色从而进步网络精度,须要实现的一点是恒等映射。那么残差网络如何可能做到这一点呢?
恒等映射即为 $H(x) = x$,已有的神经网络构造很难做到这一点,然而如果咱们将网络设计成 $H(x) = F(x) + x$,即 $F(x) = H(x) - x$,那么只须要使残差函数 $F(x) = 0$,就形成了恒等映射 $H(x) = F(x)$。
残差构造的目标是,随着网络的加深,使 $F(x)$ 迫近于0,使得深度网络的精度在最优浅层网络的根底上不会降落。看到这里你或者会有疑难,既然如此为什么不间接选取最优的浅层网络呢?这是因为最优的浅层网络结构并不易找寻,而ResNet能够通过减少深度,找到最优的浅层网络并保障深层网络不会因为层数的叠加而产生网络进化。
- 参考文献
[1] Visualizing and Understanding Convolutional Networks
[2] Deep Residual Learning for Image Recognition
2. ResNeXt(2017)
ResNeXt是由何凯明团队在2017年CVPR会议上提出来的新型图像分类网络。ResNeXt是ResNet的升级版,在ResNet的根底上,引入了cardinality的概念,相似于ResNet,ResNeXt也有ResNeXt-50,ResNeXt-101的版本。那么相较于ResNet,ResNeXt的翻新点在哪里?既然是分类网络,那么在ImageNet数据集上的指标相较于ResNet有何变动?之后的ResNeXt_WSL又是什么货色?上面我和大家一起分享一下这些常识。
2.1 ResNeXt模型构造
在ResNeXt的论文中,作者提出了过后普遍存在的一个问题,如果要进步模型的准确率,往往采取加深网络或者加宽网络的办法。尽管这种办法是无效的,然而随之而来的,是网络设计的难度和计算开销的减少。为了一点精度的晋升往往须要付出更大的代价。因而,须要一个更好的策略,在不额定减少计算代价的状况下,晋升网络的精度。由此,何等人提出了cardinality的概念。
下图是ResNet(左)与ResNeXt(右)block的差别。在ResNet中,输出的具备256个通道的特色通过1×1卷积压缩4倍到64个通道,之后3×3的卷积核用于解决特色,经1×1卷积扩充通道数与原特色残差连贯后输入。ResNeXt也是雷同的解决策略,但在ResNeXt中,输出的具备256个通道的特色被分为32个组,每组被压缩64倍到4个通道后进行解决。32个组相加后与原特色残差连贯后输入。这里cardinatity指的是一个block中所具备的雷同分支的数目。
下图是InceptionNet的两种inception module构造,右边是inception module的naive版本,左边是应用了降维办法的inception module。相较于左边,右边很显著的毛病就是参数大,计算量微小。应用不同大小的卷积核目标是为了提取不同尺度的特色信息,对于图像而言,多尺度的信息有助于网络更好地对图像信息进行抉择,并且使得网络对于不同尺寸的图像输出有更好的适应能力,但多尺度带来的问题就是计算量的减少。因而在左边的模型中,InceptionNet很好地解决了这个问题,首先是1×1的卷积用于特色降维,减小特色的通道数后再采取多尺度的构造提取特色信息,在升高参数量的同时捕捉到多尺度的特色信息。
ResNeXt正是借鉴了这种“宰割-变换-聚合”的策略,但用雷同的拓扑构造组建ResNeXt模块。每个构造都是雷同的卷积核,放弃了构造的简洁,使得模型在编程上更不便更容易,而InceptionNet则须要更为简单的设计。
2.2 ResNeXt模型实现
ResNeXt与ResNet的模型构造统一,次要差异在于block的搭建,因而这里用paddle框架来实现block的代码
class ConvBNLayer(nn.Layer): def __init__(self, num_channels, num_filters, filter_size, stride=1, groups=1, act=None, name=None, data_format="NCHW" ): super(ConvBNLayer, self).__init__() self._conv = Conv2D( in_channels=num_channels, out_channels=num_filters, kernel_size=filter_size, stride=stride, padding=(filter_size - 1) // 2, groups=groups, weight_attr=ParamAttr(name=name + "_weights"), bias_attr=False, data_format=data_format ) if name == "conv1": bn_name = "bn_" + name else: bn_name = "bn" + name[3:] self._batch_norm = BatchNorm( num_filters, act=act, param_attr=ParamAttr(name=bn_name + '_scale'), bias_attr=ParamAttr(bn_name + '_offset'), moving_mean_name=bn_name + '_mean', moving_variance_name=bn_name + '_variance', data_layout=data_format ) def forward(self, inputs): y = self._conv(inputs) y = self._batch_norm(y) return yclass BottleneckBlock(nn.Layer): def __init__(self, num_channels, num_filters, stride, cardinality, shortcut=True, name=None, data_format="NCHW" ): super(BottleneckBlock, self).__init__() self.conv0 = ConvBNLayer(num_channels=num_channels, num_filters=num_filters, filter_size=1, act='relu', name=name + "_branch2a", data_format=data_format ) self.conv1 = ConvBNLayer( num_channels=num_filters, num_filters=num_filters, filter_size=3, groups=cardinality, stride=stride, act='relu', name=name + "_branch2b", data_format=data_format ) self.conv2 = ConvBNLayer( num_channels=num_filters, num_filters=num_filters * 2 if cardinality == 32 else num_filters, filter_size=1, act=None, name=name + "_branch2c", data_format=data_format ) if not shortcut: self.short = ConvBNLayer( num_channels=num_channels, num_filters=num_filters * 2 if cardinality == 32 else num_filters, filter_size=1, stride=stride, name=name + "_branch1", data_format=data_format ) self.shortcut = shortcut def forward(self, inputs): y = self.conv0(inputs) conv1 = self.conv1(y) conv2 = self.conv2(conv1) if self.shortcut: short = inputs else: short = self.short(inputs) y = paddle.add(x=short, y=conv2) y = F.relu(y) return y
2.3 ResNeXt模型特点
- ResNeXt通过管制cardinality的数量,使得ResNeXt的参数量和GFLOPs与ResNet简直雷同。
- 通过cardinality的分支构造,为网络提供更多的非线性,从而取得更准确的分类成果。
2.4 ResNeXt模型指标
上图是ResNet与ResNeXt的参数比照,能够看出,ResNeXt与ResNet简直是截然不同的参数量和计算量,然而两者在ImageNet上的体现却不一样。
从图中能够看出,ResNeXt除了能够减少block中3×3卷积核的通道数,还能够减少cardinality的分支数来晋升模型的精度。ResNeXt-50和ResNeXt-101都大大降低了对应ResNet的错误率。图中,ResNeXt-101从32×4d变为64×4d,尽管减少了两倍的计算量,但也能无效地升高分类错误率。
在2019年何凯明团队开源了ResNeXt_WSL,ResNeXt_WSL是何凯明团队应用弱监督学习训练的ResNeXt,ResNeXt_WSL中的WSL就示意Weakly Supervised Learning(弱监督学习)。
ResNeXt101_32×48d_WSL有8亿+的参数,是通过弱监督学习预训练的办法在Instagram数据集上训练,而后用ImageNet数据集做微调,Instagram有9.4亿张图片,没有通过特地的标注,只带着用户本人加的话题标签。
ResNeXt_WSL与ResNeXt是一样的构造,只是训练形式有所扭转。下图是ResNeXt_WSL的训练成果。
- 参考文献
ResNet
- 参考文献
ResNeXt
GoogLeNet
3.Res2Net(2020)
2020年,南开大学程明明组提出了一种面向指标检测工作的新模块Res2Net。并且其论文已被TPAMI2020录用。Res2Net和ResNeXt一样,是ResNet的变体模式,只不过Res2Net不止进步了分类工作的准确率,还进步了检测工作的精度。Res2Net的新模块能够和现有其余优良模块轻松整合,在不减少计算负载量的状况下,在ImageNet、CIFAR-100等数据集上的测试性能超过了ResNet。因为模型的残差块里又有残差连贯,所以取名为Res2Net。
3.1 Res2Net模型构造
模型构造看起来很简略,将输出的特色x,split为k个特色,第i+1(i = 0, 1, 2,...,k-1) 个特色通过3×3卷积后以残差连贯的形式交融到第 i+2 个特色中。这就是Res2Net的次要构造。那么这样做的目标是为什么呢?可能有什么益处呢?
答案就是多尺度卷积。多尺度特色在检测工作中始终是很重要的,自从空洞卷积提出以来,基于空洞卷积搭建的多尺度金字塔模型在检测工作上获得里程碑式的成果。不同感触野下获取的物体的信息是不同的,小的感触野可能会看到更多的物体细节,对于检测小指标也有很大的益处,而大的感触野能够感触物体的整体构造,不便网络定位物体的地位,细节与地位的联合能够更好地失去具备清晰边界的物体信息,因而,联合了多尺度金字塔的模型往往能取得很好地成果。在Res2Net中,特色k2通过3×3卷积后被送入x3所在的解决流中,k2再次被3×3的卷积优化信息,两个3×3的卷积相当于一个5×5的卷积。那么,k3就想当然与交融了3×3的感触野和5×5的感触野解决后的特色。以此类推,7×7的感触野被利用在k4中。就这样,Res2Net提取多尺度特色用于检测工作,以进步模型的准确率。在这篇论文中,s是比例尺寸的控制参数,也就是能够将输出通道数均匀等分为多个特色通道。s越大表明多尺度能力越强,此外一些额定的计算开销也能够疏忽。
3.2 Res2Net模型实现
Res2Net与ResNet的模型构造统一,次要差异在于block的搭建,因而这里用paddle框架来实现block的代码
class ConvBNLayer(nn.Layer): def __init__( self, num_channels, num_filters, filter_size, stride=1, groups=1, is_vd_mode=False, act=None, name=None, ): super(ConvBNLayer, self).__init__() self.is_vd_mode = is_vd_mode self._pool2d_avg = AvgPool2D( kernel_size=2, stride=2, padding=0, ceil_mode=True) self._conv = Conv2D( in_channels=num_channels, out_channels=num_filters, kernel_size=filter_size, stride=stride, padding=(filter_size - 1) // 2, groups=groups, weight_attr=ParamAttr(name=name + "_weights"), bias_attr=False) if name == "conv1": bn_name = "bn_" + name else: bn_name = "bn" + name[3:] self._batch_norm = BatchNorm( num_filters, act=act, param_attr=ParamAttr(name=bn_name + '_scale'), bias_attr=ParamAttr(bn_name + '_offset'), moving_mean_name=bn_name + '_mean', moving_variance_name=bn_name + '_variance') def forward(self, inputs): if self.is_vd_mode: inputs = self._pool2d_avg(inputs) y = self._conv(inputs) y = self._batch_norm(y) return yclass BottleneckBlock(nn.Layer): def __init__(self, num_channels1, num_channels2, num_filters, stride, scales, shortcut=True, if_first=False, name=None): super(BottleneckBlock, self).__init__() self.stride = stride self.scales = scales self.conv0 = ConvBNLayer( num_channels=num_channels1, num_filters=num_filters, filter_size=1, act='relu', name=name + "_branch2a") self.conv1_list = [] for s in range(scales - 1): conv1 = self.add_sublayer( name + '_branch2b_' + str(s + 1), ConvBNLayer( num_channels=num_filters // scales, num_filters=num_filters // scales, filter_size=3, stride=stride, act='relu', name=name + '_branch2b_' + str(s + 1))) self.conv1_list.append(conv1) self.pool2d_avg = AvgPool2D(kernel_size=3, stride=stride, padding=1) self.conv2 = ConvBNLayer( num_channels=num_filters, num_filters=num_channels2, filter_size=1, act=None, name=name + "_branch2c") if not shortcut: self.short = ConvBNLayer( num_channels=num_channels1, num_filters=num_channels2, filter_size=1, stride=1, is_vd_mode=False if if_first else True, name=name + "_branch1") self.shortcut = shortcut def forward(self, inputs): y = self.conv0(inputs) xs = paddle.split(y, self.scales, 1) ys = [] for s, conv1 in enumerate(self.conv1_list): if s == 0 or self.stride == 2: ys.append(conv1(xs[s])) else: ys.append(conv1(xs[s] + ys[-1])) if self.stride == 1: ys.append(xs[-1]) else: ys.append(self.pool2d_avg(xs[-1])) conv1 = paddle.concat(ys, axis=1) conv2 = self.conv2(conv1) if self.shortcut: short = inputs else: short = self.short(inputs) y = paddle.add(x=short, y=conv2) y = F.relu(y) return y
3.3 模型特点
- 可与其余构造整合,如SENEt, ResNeXt, DLA等,从而减少准确率。
- 计算负载不减少,特征提取能力更弱小。
3.4 模型指标
ImageNet分类成果如下图
Res2Net-50就是对标ResNet50的版本。
Res2Net-50-299指的是将输出图片裁剪到299×299进行预测的Res2Net-50,因为个别都是裁剪或者resize到224×224。
Res2NeXt-50为交融了ResNeXt的Res2Net-50。
Res2Net-DLA-60指的是交融了DLA-60的Res2Net-50。
Res2NeXt-DLA-60为交融了ResNeXt和DLA-60的Res2Net-50。
SE-Res2Net-50 为交融了SENet的Res2Net-50。
blRes2Net-50为交融了Big-Little Net的Res2Net-50。
Res2Net-v1b-50为采取和ResNet-vd-50一样的解决办法的Res2Net-50。
Res2Net-200-SSLD为Paddle应用简略的半监督标签常识蒸馏(SSLD,Simple Semi-supervised Label Distillation)的办法来晋升模型成果失去的。
可见,Res2Net都获得了非常不错的问题。
COCO数据集成果如下图
Res2Net-50的各种配置都比ResNet-50高。
显著指标检测数据集指标成果如下图
ECSSD、PASCAL-S、DUT-OMRON、HKU-IS都是显著指标检测工作中当初最为罕用的测试集,显著指标检测工作的目标就是宰割出图片中的显著物体,并用红色像素点示意,其余背景用彩色像素点示意。从图中能够看进去,应用Res2Net作为骨干网络,成果比ResNet有了很大的晋升。
- 参考文献
Res2Net
4.Swin Trasnformer(2021)
Swin Transformer是由微软亚洲研究院在往年颁布的一篇利用transformer架构解决计算机视觉工作的论文。Swin Transformer 在图像分类,图像宰割,指标检测等各个领域曾经屠榜,在论文中,作者分析表明,Transformer从NLP迁徙到CV上没有大放异彩次要有两点起因:1. 两个畛域波及的scale不同,NLP的token是规范固定的大小,而CV的特色尺度变动范畴十分大。2. CV比起NLP须要更大的分辨率,而且CV中应用Transformer的计算复杂度是图像尺度的平方,这会导致计算量过于宏大。为了解决这两个问题,Swin Transformer相比之前的ViT做了两个改良:1.引入CNN中罕用的层次化构建形式构建层次化Transformer 2.引入locality思维,对无重合的window区域内进行self-attention计算。另外,Swin Transformer能够作为图像分类、指标检测和语义宰割等工作的通用骨干网络,能够说,Swin Transformer可能是CNN的完满代替计划。
4.1 Swin Trasnformer模型构造
下图为Swin Transformer与ViT在解决图片形式上的比照,能够看出,Swin Transformer有着ResNet一样的残差构造和CNN具备的多尺度图片构造。
整体概括:
下图为Swin Transformer的网络结构,输出的图像先通过一层卷积进行patch映射,将图像先宰割成4 × 4的小块,图片是224×224输出,那么就是56个path块,如果是384×384的尺寸,则是96个path块。这里以224 × 224的输出为例,输出图像通过这一步操作,每个patch的特色维度为4x4x3=48的特色图。因而,输出的图像变成了H/4×W/4×48的特色图。而后,特色图开始输出到stage1,stage1中linear embedding将path特色维度变成C,因而变成了H/4×W/4×C。而后送入Swin Transformer Block,在进入stage2前,接下来先通过Patch Merging操作,Patch Merging和CNN中stride=2的1×1卷积十分相似,Patch Merging在每个Stage开始前做降采样,用于放大分辨率,调整通道数,当H/4×W/4×C的特色图输送到Patch Merging,将输出依照2x2的相邻patches合并,这样子patch块的数量就变成了H/8 x W/8,特色维度就变成了4C,之后通过一个MLP,将特色维度降为2C。因而变为H/8×W/8×2C。接下来的stage就是反复下面的过程。
每步细说:
Linear embedding
上面用Paddle代码逐渐解说Swin Transformer的架构。 以下代码为Linear embedding的操作,整个操作能够看作一个patch大小的卷积核和patch大小的步长的卷积对输出的B,C,H,W的图片进行卷积,失去的天然就是大小为 B,C,H/patch,W/patch的特色图,如果放在第一个Linear embedding中,失去的特色图就为 B,96,56,56的大小。Paddle外围代码如下。
class PatchEmbed(nn.Layer): """ Image to Patch Embedding Args: img_size (int): Image size. Default: 224. patch_size (int): Patch token size. Default: 4. in_chans (int): Number of input image channels. Default: 3. embed_dim (int): Number of linear projection output channels. Default: 96. norm_layer (nn.Layer, optional): Normalization layer. Default: None """ def __init__(self, img_size=224, patch_size=4, in_chans=3, embed_dim=96, norm_layer=None): super().__init__() img_size = to_2tuple(img_size) patch_size = to_2tuple(patch_size) patches_resolution = [ img_size[0] // patch_size[0], img_size[1] // patch_size[1] ] self.img_size = img_size self.patch_size = patch_size self.patches_resolution = patches_resolution self.num_patches = patches_resolution[0] * patches_resolution[1] #patch个数 self.in_chans = in_chans self.embed_dim = embed_dim self.proj = nn.Conv2D( in_chans, embed_dim, kernel_size=patch_size, stride=patch_size) #将stride和kernel_size设置为patch_size大小 if norm_layer is not None: self.norm = norm_layer(embed_dim) else: self.norm = None def forward(self, x): B, C, H, W = x.shape x = self.proj(x) # B, 96, H/4, W4 x = x.flatten(2).transpose([0, 2, 1]) # B Ph*Pw 96 if self.norm is not None: x = self.norm(x) return x
Patch Merging
以下为PatchMerging的操作。该操作以2为步长,对输出的图片进行采样,总共失去4张下采样的特色图,H和W升高2倍,因而,通道级拼接后失去的是B,4C,H/2,W/2的特色图。然而这样的拼接不可能提取有用的特色信息,于是,一个线性层将4C的通道筛选为2C, 特色图变为了B,2C, H/2, W/2。细细领会能够发现,该操作像极了
卷积罕用的Pooling操作和步长为2的卷积操作。Poling用于下采样,步长为2的卷积同样能够下采样,另外还起到了特色筛选的成果。总结一下,通过这个操作本来B,C,H,W的特色图就变为了B,2C,H/2,W/2的特色图,实现了下采样操作。
class PatchMerging(nn.Layer): r""" Patch Merging Layer. Args: input_resolution (tuple[int]): Resolution of input feature. dim (int): Number of input channels. norm_layer (nn.Layer, optional): Normalization layer. Default: nn.LayerNorm """ def __init__(self, input_resolution, dim, norm_layer=nn.LayerNorm): super().__init__() self.input_resolution = input_resolution self.dim = dim self.reduction = nn.Linear(4 * dim, 2 * dim, bias_attr=False) self.norm = norm_layer(4 * dim) def forward(self, x): """ x: B, H*W, C """ H, W = self.input_resolution B, L, C = x.shape assert L == H * W, "input feature has wrong size" assert H % 2 == 0 and W % 2 == 0, "x size ({}*{}) are not even.".format( H, W) x = x.reshape([B, H, W, C]) # 每次降采样是两倍,因而在行方向和列方向上,距离2选取元素。 x0 = x[:, 0::2, 0::2, :] # B H/2 W/2 C x1 = x[:, 1::2, 0::2, :] # B H/2 W/2 C x2 = x[:, 0::2, 1::2, :] # B H/2 W/2 C x3 = x[:, 1::2, 1::2, :] # B H/2 W/2 C # 拼接在一起作为一整个张量,开展。通道维度会变成原先的4倍(因为H,W各放大2倍) x = paddle.concat([x0, x1, x2, x3], -1) # B H/2 W/2 4*C x = x.reshape([B, H * W // 4, 4 * C]) # B H/2*W/2 4*C x = self.norm(x) # 通过一个全连贯层再调整通道维度为原来的两倍 x = self.reduction(x) return x
Swin Transformer Block:
上面的操作是依据window_size划分特色图的操作和还原的操作,原理很简略就是并排划分即可。
def window_partition(x, window_size): """ Args: x: (B, H, W, C) window_size (int): window size Returns: windows: (num_windows*B, window_size, window_size, C) """ B, H, W, C = x.shape x = x.reshape([B, H // window_size, window_size, W // window_size, window_size, C]) windows = x.transpose([0, 1, 3, 2, 4, 5]).reshape([-1, window_size, window_size, C]) return windowsdef window_reverse(windows, window_size, H, W): """ Args: windows: (num_windows*B, window_size, window_size, C) window_size (int): Window size H (int): Height of image W (int): Width of image Returns: x: (B, H, W, C) """ B = int(windows.shape[0] / (H * W / window_size / window_size)) x = windows.reshape([B, H // window_size, W // window_size, window_size, window_size, -1]) x = x.transpose([0, 1, 3, 2, 4, 5]).reshape([B, H, W, -1]) return x
Swin Transformer中重要的当然是Swin Transformer Block了,上面解释一下Swin Transformer Block的原理。
先看一下MLP和LN,MLP和LN为多层感知机和绝对于BatchNorm的LayerNorm。原理较为简单,因而间接看paddle代码即可。
class Mlp(nn.Layer): def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.): super().__init__() out_features = out_features or in_features hidden_features = hidden_features or in_features self.fc1 = nn.Linear(in_features, hidden_features) self.act = act_layer() self.fc2 = nn.Linear(hidden_features, out_features) self.drop = nn.Dropout(drop) def forward(self, x): x = self.fc1(x) x = self.act(x) x = self.drop(x) x = self.fc2(x) x = self.drop(x) return x
下图就是Shifted Window based MSA是Swin Transformer的外围局部。Shifted Window based MSA包含了两局部,一个是W-MSA(窗口多头注意力),另一个就是SW-MSA(移位窗口多头自注意力)。这两个是一起呈现的。
一开始,Swin Transformer 将一张图片宰割为4份,也叫4个Window,而后独立地计算每一部分的MSA。因为每一个Window都是独立的,短少了信息之间的交换,因而作者又提出了SW-MSA的算法,即采纳规定的挪动窗口的办法。通过不同窗口的交互,来达到特色的信息交换。留神,这一部分是本论文的精髓,想要理解的同学必须要看懂源代码
class WindowAttention(nn.Layer): """ Window based multi-head self attention (W-MSA) module with relative position bias. It supports both of shifted and non-shifted window. Args: dim (int): Number of input channels. window_size (tuple[int]): The height and width of the window. num_heads (int): Number of attention heads. qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set attn_drop (float, optional): Dropout ratio of attention weight. Default: 0.0 proj_drop (float, optional): Dropout ratio of output. Default: 0.0 """ def __init__(self, dim, window_size, num_heads, qkv_bias=True, qk_scale=None, attn_drop=0., proj_drop=0.): super().__init__() self.dim = dim self.window_size = window_size # Wh, Ww self.num_heads = num_heads head_dim = dim // num_heads self.scale = qk_scale or head_dim ** -0.5 # define a parameter table of relative position bias relative_position_bias_table = self.create_parameter( shape=((2 * window_size[0] - 1) * (2 * window_size[1] - 1), num_heads), default_initializer=nn.initializer.Constant(value=0)) # 2*Wh-1 * 2*Ww-1, nH self.add_parameter("relative_position_bias_table", relative_position_bias_table) # get pair-wise relative position index for each token inside the window coords_h = paddle.arange(self.window_size[0]) coords_w = paddle.arange(self.window_size[1]) coords = paddle.stack(paddle.meshgrid([coords_h, coords_w])) # 2, Wh, Ww coords_flatten = paddle.flatten(coords, 1) # 2, Wh*Ww relative_coords = coords_flatten.unsqueeze(-1) - coords_flatten.unsqueeze(1) # 2, Wh*Ww, Wh*Ww relative_coords = relative_coords.transpose([1, 2, 0]) # Wh*Ww, Wh*Ww, 2 relative_coords[:, :, 0] += self.window_size[0] - 1 # shift to start from 0 relative_coords[:, :, 1] += self.window_size[1] - 1 relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1 self.relative_position_index = relative_coords.sum(-1) # Wh*Ww, Wh*Ww self.register_buffer("relative_position_index", self.relative_position_index) self.qkv = nn.Linear(dim, dim * 3, bias_attr=qkv_bias) self.attn_drop = nn.Dropout(attn_drop) self.proj = nn.Linear(dim, dim) self.proj_drop = nn.Dropout(proj_drop) self.softmax = nn.Softmax(axis=-1) def forward(self, x, mask=None): """ Args: x: input features with shape of (num_windows*B, N, C) mask: (0/-inf) mask with shape of (num_windows, Wh*Ww, Wh*Ww) or None """ B_, N, C = x.shape qkv = self.qkv(x).reshape([B_, N, 3, self.num_heads, C // self.num_heads]).transpose([2, 0, 3, 1, 4]) q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple) q = q * self.scale attn = q @ swapdim(k ,-2, -1) relative_position_bias = paddle.index_select(self.relative_position_bias_table, self.relative_position_index.reshape((-1,)),axis=0).reshape((self.window_size[0] * self.window_size[1],self.window_size[0] * self.window_size[1], -1)) relative_position_bias = relative_position_bias.transpose([2, 0, 1]) # nH, Wh*Ww, Wh*Ww attn = attn + relative_position_bias.unsqueeze(0) if mask is not None: nW = mask.shape[0] attn = attn.reshape([B_ // nW, nW, self.num_heads, N, N]) + mask.unsqueeze(1).unsqueeze(0) attn = attn.reshape([-1, self.num_heads, N, N]) attn = self.softmax(attn) else: attn = self.softmax(attn) attn = self.attn_drop(attn) x = swapdim((attn @ v),1, 2).reshape([B_, N, C]) x = self.proj(x) x = self.proj_drop(x) return x
4.2 Swin Trasnformer模型实现
Swin Transformer波及模型代码较多,所以倡议残缺的看Swin Transformer的代码,因而举荐一下桨的Swin Transformer实现。
4.3 Swin Trasnformer模型特点
- 首次在cv畛域的transformer模型中采纳了分层构造。分层构造因为其不同大小的尺度,使不同层特色有了更加不同的意义,较浅层的特色具备大尺度和细节信息,较深层的特色具备小尺度和物体的整体轮廓信息,在图像分类畛域,深层特色具备更加有用的作用,只须要依据这个信息断定物体的类别即可,然而在像素级的宰割和检测工作中,则须要更为精密的细节信息,因而,分层构造的模型往往更实用于宰割和检测这样的像素级要求的工作中。Swin Transformer 模拟ResNet采取了分层的构造,使其成为了cv畛域的通用框架。
- 引入locality思维,对无重合的window区域内进行self-attention计算。不仅缩小了计算量,而且多了不同窗口之间的交互。
4.4 Swin Trasnformer模型成果
第一列为比照的办法,第二列为图片尺寸的大小(尺寸越大浮点运算量越大),第三列为参数量,第四列为浮点运算量,第五列为模型吞吐量。能够看出,Swin-T 在top1准确率上超过了大部分模型EffNet-B3的确是个优良的网络,在参数量和FLOPs都比Swin-T少的状况下,略优于Swin-T,然而,基于ImageNet1K数据集,Swin-B在这些模型上获得了最优的成果。另外,Swin-L在ImageNet-22K上的top1准确率达到了87.3%的高度,这是以往的模型都没有达到的。并且Swin Transformer的其余配置也获得了优良的问题。图中不同配置的Swin Transformer解释如下。
C就是下面提到的相似于通道数的值,layer numbers就是Swin Transformer Block的数量了。这两个都是值越大,成果越好。和ResNet十分相似。
下图为COCO数据集上指标检测与实例宰割的体现。都是雷同网络在不同骨干网络下的比照。能够看出在不同AP下,Swin Transformer都有大概5%的晋升,这曾经是很优良的程度了。怪不得能成为ICCV2021最佳paer。
下图为语义宰割数据集ADE20K上的体现。相较于同为transformer的DeiT-S, Swin Transformer-S有了5%的性能晋升。相较于ResNeSt-200,Swin Transformer-L也有5%的晋升。另外能够看到,在UNet的框架下,Swin Transformer的各个版本都有非常优良的问题,这充分说明了Swin Transformer是CV畛域的通用骨干网络。
- 参考文献
Swin Transformer
5.ViT( Vision Transformer-2020)
在计算机视觉畛域中,少数算法都是放弃CNN整体构造不变,在CNN中减少attention模块或者应用attention模块替换CNN中的某些局部。有研究者提出,没有必要总是依赖于CNN。因而,作者提出ViT[1]算法,仅仅应用Transformer构造也可能在图像分类工作中体现很好。
受到NLP畛域中Transformer胜利利用的启发,ViT算法中尝试将规范的Transformer构造间接利用于图像,并对整个图像分类流程进行起码的批改。具体来讲,ViT算法中,会将整幅图像拆分成小图像块,而后把这些小图像块的线性嵌入序列作为Transformer的输出送入网络,而后应用监督学习的形式进行图像分类的训练。
该算法在中等规模(例如ImageNet)以及大规模(例如ImageNet-21K、JFT-300M)数据集上进行了试验验证,发现:
- Transformer相较于CNN构造,短少肯定的平移不变性和部分感知性,因而在数据量不充沛时,很难达到等同的成果。具体表现为应用中等规模的ImageNet训练的Transformer会比ResNet在精度上低几个百分点。
- 当有大量的训练样本时,后果则会产生扭转。应用大规模数据集进行预训练后,再应用迁徙学习的形式利用到其余数据集上,能够达到或超过以后的SOTA程度。
5.1 ViT模型构造与实现
ViT算法的整体构造如 图1 所示。
5.1.1. ViT图像分块嵌入
思考到在Transformer构造中,输出是一个二维的矩阵,矩阵的形态能够示意为 $(N,D)$,其中 $N$ 是sequence的长度,而 $D$ 是sequence中每个向量的维度。因而,在ViT算法中,首先须要设法将 $H \times W \times C$ 的三维图像转化为 $(N,D)$ 的二维输出。
ViT中的具体实现形式为:将 $H \times W \times C$ 的图像,变为一个 $N \times (P^2 * C)$ 的序列。这个序列能够看作是一系列展平的图像块,也就是将图像切分成小块后,再将其展平。该序列中一共蕴含了 $N=HW/P^2$ 个图像块,每个图像块的维度则是 $(P^2*C)$。其中 $P$ 是图像块的大小,$C$ 是通道数量。通过如上变换,就能够将 $N$ 视为sequence的长度了。
然而,此时每个图像块的维度是 $(P^2*C)$,而咱们理论须要的向量维度是 $D$,因而咱们还须要对图像块进行 Embedding。这里 Embedding 的形式非常简单,只须要对每个 $(P^2*C)$ 的图像块做一个线性变换,将维度压缩为 $D$ 即可。
上述对图像进行分块以及 Embedding 的具体形式如 图2 所示。
具体代码实现如下所示。本文中将每个大小为 $P$ 的图像块通过大小为 $P$ 的卷积核来代替原文中将大小为 $P$ 的图像块展平后接全连贯运算的操作。
#图像分块、Embeddingclass PatchEmbed(nn.Layer): def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768): super().__init__() # 原始大小为int,转为tuple,即:img_size原始输出224,变换后为[224,224] img_size = to_2tuple(img_size) patch_size = to_2tuple(patch_size) # 图像块的个数 num_patches = (img_size[1] // patch_size[1]) * \ (img_size[0] // patch_size[0]) self.img_size = img_size self.patch_size = patch_size self.num_patches = num_patches # kernel_size=块大小,即每个块输入一个值,相似每个块展平后应用雷同的全连贯层进行解决 # 输出维度为3,输入维度为块向量长度 # 与原文中:分块、展平、全连贯降维保持一致 # 输入为[B, C, H, W] self.proj = nn.Conv2D( in_chans, embed_dim, kernel_size=patch_size, stride=patch_size) def forward(self, x): B, C, H, W = x.shape assert H == self.img_size[0] and W == self.img_size[1], \ "Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})." # [B, C, H, W] -> [B, C, H*W] ->[B, H*W, C] x = self.proj(x).flatten(2).transpose((0, 2, 1)) return x
5.1.2. ViT多头注意力
将图像转化为 $N \times (P^2 * C)$ 的序列后,就能够将其输出到 Transformer 构造中进行特征提取了,如 图3 所示。
Transformer 构造中最重要的构造就是 Multi-head Attention,即多头注意力构造。具备2个head的 Multi-head Attention 构造如 图4 所示。输出 $a^i$ 通过转移矩阵,并切分生成 $q^{(i,1)}$、$q^{(i,2)}$、$k^{(i,1)}$、$k^{(i,2)}$、$v^{(i,1)}$、$v^{(i,2)}$,而后 $q^{(i,1)}$ 与 $k^{(i,1)}$ 做 attention,失去权重向量 $\alpha$,将 $\alpha$ 与 $v^{(i,1)}$ 进行加权求和,失去最终的 $b^{(i,1)}(i=1,2,…,N)$,同理能够失去 $b^{(i,2)}(i=1,2,…,N)$。接着将它们拼接起来,通过一个线性层进行解决,失去最终的后果。
其中,应用 $q^{(i,j)}$、$k^{(i,j)}$ 与 $v^{(i,j)}$ 计算 $b^{(i,j)}(i=1,2,…,N)$ 的办法是缩放点积注意力 (Scaled Dot-Product Attention)。 构造如 图5 所示。首先应用每个 $q^{(i,j)}$ 去与 $k^{(i,j)}$ 做 attention,这里说的 attention 就是匹配这两个向量有多靠近,具体的形式就是计算向量的加权内积,失去 $\alpha_{(i,j)}$。这里的加权内积计算形式如下所示:
$$ \alpha_{(1,i)} = q^1 * k^i / \sqrt{d} $$
其中,$d$ 是 $q$ 和 $k$ 的维度,因为 $q*k$ 的数值会随着维度的增大而增大,因而除以 $\sqrt{d}$ 的值也就相当于归一化的成果。
接下来,把计算失去的 $\alpha_{(i,j)}$ 取 softmax 操作,再将其与 $v^{(i,j)}$ 相乘。
具体代码实现如下所示。
#Multi-head Attentionclass Attention(nn.Layer): 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 # 计算 q,k,v 的转移矩阵 self.qkv = nn.Linear(dim, dim * 3, bias_attr=qkv_bias) self.attn_drop = nn.Dropout(attn_drop) # 最终的线性层 self.proj = nn.Linear(dim, dim) self.proj_drop = nn.Dropout(proj_drop) def forward(self, x): N, C = x.shape[1:] # 线性变换 qkv = self.qkv(x).reshape((-1, N, 3, self.num_heads, C // self.num_heads)).transpose((2, 0, 3, 1, 4)) # 宰割 query key value q, k, v = qkv[0], qkv[1], qkv[2] # Scaled Dot-Product Attention # Matmul + Scale attn = (q.matmul(k.transpose((0, 1, 3, 2)))) * self.scale # SoftMax attn = nn.functional.softmax(attn, axis=-1) attn = self.attn_drop(attn) # Matmul x = (attn.matmul(v)).transpose((0, 2, 1, 3)).reshape((-1, N, C)) # 线性变换 x = self.proj(x) x = self.proj_drop(x) return x
5.1.3. 多层感知机(MLP)
Transformer 构造中还有一个重要的构造就是 MLP,即多层感知机,如 图6 所示。
多层感知机由输出层、输入层和至多一层的暗藏层形成。网络中各个暗藏层中神经元可接管相邻前序暗藏层中所有神经元传递而来的信息,通过加工解决后将信息输入给相邻后续暗藏层中所有神经元。在多层感知机中,相邻层所蕴含的神经元之间通常应用“全连贯”形式进行连贯。多层感知机能够模仿简单非线性函数性能,所模仿函数的复杂性取决于网络暗藏层数目和各层中神经元数目。多层感知机的构造如 图7 所示。
具体代码实现如下所示。
class Mlp(nn.Layer): def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.): super().__init__() out_features = out_features or in_features hidden_features = hidden_features or in_features self.fc1 = nn.Linear(in_features, hidden_features) self.act = act_layer() self.fc2 = nn.Linear(hidden_features, out_features) self.drop = nn.Dropout(drop) def forward(self, x): # 输出层:线性变换 x = self.fc1(x) # 利用激活函数 x = self.act(x) # Dropout x = self.drop(x) # 输入层:线性变换 x = self.fc2(x) # Dropout x = self.drop(x) return x
5.1.4. DropPath
除了以上重要模块意外,代码实现过程中还应用了DropPath(Stochastic Depth)来代替传统的Dropout构造,DropPath能够了解为一种非凡的 Dropout。其作用是在训练过程中随机抛弃子图层(randomly drop a subset of layers),而在预测时失常应用残缺的 Graph。
具体实现如下:
def drop_path(x, drop_prob=0., training=False): if drop_prob == 0. or not training: return x keep_prob = paddle.to_tensor(1 - drop_prob) shape = (paddle.shape(x)[0], ) + (1, ) * (x.ndim - 1) random_tensor = keep_prob + paddle.rand(shape, dtype=x.dtype) random_tensor = paddle.floor(random_tensor) output = x.divide(keep_prob) * random_tensor return outputclass DropPath(nn.Layer): def __init__(self, drop_prob=None): super(DropPath, self).__init__() self.drop_prob = drop_prob def forward(self, x): return drop_path(x, self.drop_prob, self.training)
5.1.5 根底模块
基于下面实现的 Attention、MLP、DropPath模块就能够组合出 Vision Transformer 模型的一个根底模块,如 图8 所示。
根底模块的具体实现如下:
class Block(nn.Layer): 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', epsilon=1e-5): super().__init__() self.norm1 = eval(norm_layer)(dim, epsilon=epsilon) # Multi-head Self-attention self.attn = Attention( dim, num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop) # DropPath self.drop_path = DropPath(drop_path) if drop_path > 0. else Identity() self.norm2 = eval(norm_layer)(dim, epsilon=epsilon) 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): # Multi-head Self-attention, Add, LayerNorm x = x + self.drop_path(self.attn(self.norm1(x))) # Feed Forward, Add, LayerNorm x = x + self.drop_path(self.mlp(self.norm2(x))) return x
5.1.6. 定义ViT网络
根底模块构建好后,就能够构建残缺的ViT网络了。在构建残缺网络结构之前,还须要给大家介绍几个模块:
- Class Token
假如咱们将原始图像切分成 $3 \times 3$ 共9个小图像块,最终的输出序列长度却是10,也就是说咱们这里人为的减少了一个向量进行输出,咱们通常将人为减少的这个向量称为 Class Token。那么这个 Class Token 有什么作用呢?
咱们能够设想,如果没有这个向量,也就是将 $N=9$ 个向量输出 Transformer 构造中进行编码,咱们最终会失去9个编码向量,可对于图像分类工作而言,咱们应该抉择哪个输入向量进行后续分类呢?因而,ViT算法提出了一个可学习的嵌入向量 Class Token,将它与9个向量一起输出到 Transformer 构造中,输入10个编码向量,而后用这个 Class Token 进行分类预测即可。
其实这里也能够了解为:ViT 其实只用到了 Transformer 中的 Encoder,而并没有用到 Decoder,而 Class Token 的作用就是寻找其余9个输出向量对应的类别。
- Positional Encoding
依照 Transformer 构造中的地位编码习惯,这个工作也应用了地位编码。不同的是,ViT 中的地位编码没有采纳原版 Transformer 中的 $sincos$ 编码,而是间接设置为可学习的 Positional Encoding。对训练好的 Positional Encoding 进行可视化,如 图9 所示。咱们能够看到,地位越靠近,往往具备更类似的地位编码。此外,呈现了行列构造,同一行/列中的 patch 具备类似的地位编码。
- MLP Head
失去输入后,ViT中应用了 MLP Head对输入进行分类解决,这里的 MLP Head 由 LayerNorm 和两层全连贯层组成,并且采纳了 GELU 激活函数。
具体代码如下所示。
首先构建根底模块局部,包含:参数初始化配置、独立的不进行任何操作的网络层。
#参数初始化配置trunc_normal_ = nn.initializer.TruncatedNormal(std=.02)zeros_ = nn.initializer.Constant(value=0.)ones_ = nn.initializer.Constant(value=1.)#将输出 x 由 int 类型转为 tuple 类型def to_2tuple(x): return tuple([x] * 2)#定义一个什么操作都不进行的网络层class Identity(nn.Layer): def __init__(self): super(Identity, self).__init__() def forward(self, input): return input
残缺代码如下所示。
class VisionTransformer(nn.Layer): def __init__(self, img_size=224, patch_size=16, in_chans=3, class_dim=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', epsilon=1e-5, **args): super().__init__() self.class_dim = class_dim self.num_features = self.embed_dim = embed_dim # 图片分块和降维,块大小为patch_size,最终块向量维度为768 self.patch_embed = PatchEmbed( img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim) # 分块数量 num_patches = self.patch_embed.num_patches # 可学习的地位编码 self.pos_embed = self.create_parameter( shape=(1, num_patches + 1, embed_dim), default_initializer=zeros_) self.add_parameter("pos_embed", self.pos_embed) # 人为追加class token,并应用该向量进行分类预测 self.cls_token = self.create_parameter( shape=(1, 1, embed_dim), default_initializer=zeros_) self.add_parameter("cls_token", self.cls_token) self.pos_drop = nn.Dropout(p=drop_rate) dpr = np.linspace(0, drop_path_rate, depth) # transformer self.blocks = nn.LayerList([ 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, drop_path=dpr[i], norm_layer=norm_layer, epsilon=epsilon) for i in range(depth) ]) self.norm = eval(norm_layer)(embed_dim, epsilon=epsilon) # Classifier head self.head = nn.Linear(embed_dim, class_dim) if class_dim > 0 else Identity() trunc_normal_(self.pos_embed) trunc_normal_(self.cls_token) self.apply(self._init_weights) # 参数初始化 def _init_weights(self, m): if isinstance(m, nn.Linear): trunc_normal_(m.weight) if isinstance(m, nn.Linear) and m.bias is not None: zeros_(m.bias) elif isinstance(m, nn.LayerNorm): zeros_(m.bias) ones_(m.weight) def forward_features(self, x): B = paddle.shape(x)[0] # 将图片分块,并调整每个块向量的维度 x = self.patch_embed(x) # 将class token与后面的分块进行拼接 cls_tokens = self.cls_token.expand((B, -1, -1)) x = paddle.concat((cls_tokens, x), axis=1) # 将编码向量中退出地位编码 x = x + self.pos_embed x = self.pos_drop(x) # 重叠 transformer 构造 for blk in self.blocks: x = blk(x) # LayerNorm x = self.norm(x) # 提取分类 tokens 的输入 return x[:, 0] def forward(self, x): # 获取图像特色 x = self.forward_features(x) # 图像分类 x = self.head(x) return x
5.2 ViT模型指标
ViT模型在罕用数据集上进行迁徙学习,最终指标如 图10 所示。能够看到,在ImageNet上,ViT达到的最高指标为88.55%;在ImageNet ReaL上,ViT达到的最高指标为90.72%;在CIFAR100上,ViT达到的最高指标为94.55%;在VTAB(19 tasks)上,ViT达到的最高指标为88.55%。
5.3 ViT模型特点
- 作为CV畛域最经典的 Transformer 算法之一,不同于传统的CNN算法,ViT尝试将规范的Transformer构造间接利用于图像,并对整个图像分类流程进行起码的批改。
- 为了满足 Transformer 输出构造的要求,将整幅图像拆分成小图像块,而后把这些小图像块的线性嵌入序列输出到网络。同时,应用了Class Token的形式进行分类预测。
更多文章请关注公重号:汀丶人工智能
- 参考文献
[1] An Image is Worth 16x16 Words:Transformers for Image Recognition at Scale