transformer的具体应用:图像分类
在上面的结构图中可以看到,输入Encoder的最左侧部分添加了一个0*这个Token,这个就是额外添加的一个[class]token,单独用来处理类别信息,经过Encoder之后,需要单独将这个Token再提取出来,输入到MLP Head之中再输出分类结果。这一步的操作在论文中是直接采用切割的处理办法,但是在后面的代码实现中,采用了一种更巧妙的解决思路,就是利用一个卷积核大小为16x16,步距为1
模型结构/算法流程
ViT的处理流程大致可以分为以下几个步骤:
1.图片预处理
预处理这个步骤在论文里并没有详细说明,但是对于ViT这个结构而言,输入的图片尺寸并不是自定义的,ViT-B/16为例,输入的图片尺寸必须为224x224。
因此,首先需要对输入图片进行尺寸变化,具体方式可以是直接缩放(Resize),也可以进行随机裁剪(RandomResizedCrop),在后面复现的代码中,使用的是随机裁剪的方式。
2.图片切割
Transformer需要输入的是一维的Token,对于二维的图像,一种朴素的想法就是把一个个像素点拉平,这样就成了一个一维序列。但是这样造成的一个后果是计算量太庞大,比如一张224x224的图片,变成1维度之后就成了50176,相当于直接输入一篇五万字的文章,模型难以计算。
那么,一个改进的想法就是把一张图片分成nxn个Patch,每一个Patch作为一个Token,这样计算量就大大减小了。
以ViT-B/16为例,将输入图片(224x224)按照16x16大小的Patch进行划分,划分后可以得到共( 224 / 16 ) 2 = 196 (224/16)^2=196(224/16)
2
=196个Patch。每个Patch是三通道的小图片,shape为(16, 16, 3),将其展平就变成了一个长度为768的向量。
每一个向量作为一个单独的输入,那样我们总共有196个向量,在代码中,可以变成一个[196,768]的矩阵,进行并行输入。
这一步的操作在论文中是直接采用切割的处理办法,但是在后面的代码实现中,采用了一种更巧妙的解决思路,就是利用一个卷积核大小为16x16,步距为16,卷积核个数为768的卷积层来进行实现。
再来回顾我们的卷积层计算公式:
输入为[224,244,3],经过卷积层变成[14,14,768],再映射为[196,768]。
这样,就完成了从图片到Token之间的转换。
3.添加[class]token
在上面的结构图中可以看到,输入Encoder的最左侧部分添加了一个0*这个Token,这个就是额外添加的一个[class]token,单独用来处理类别信息,经过Encoder之后,需要单独将这个Token再提取出来,输入到MLP Head之中再输出分类结果。
这也是为什么结构图中MLP Head的位置是和这个[class]token对齐。
4.添加位置编码
在Transformer中,位置编码的作用是为了记忆输入的语序信息。ViT中,同样需要位置编码来记录各图像块之间的位置信息。
这里主要有两种位置编码思路,一种思路是在转换之前(14,14)的图像块矩阵添加二维(2-D)位置编码,另一种思路是在转换后(196+1)这个维度上添加一维(1-D)位置编码。
作者也对其做了实验,实验结果如下表所示:
可以看到,添加一维位置编码和二维位置编码并没有太大的差异。作者随后也对一维位置编码的结果进行了可视化,结果如下图所示:
上图中是每一个Patch中各位置的位置编码相似性度量,越接近黄色的位置代表越靠近位置编码的中心位置,可以看到,即使是一维位置编码,同样可以比较好地记录二维信息。
5.Transformer Encoder
ViT虽然采用的是Transformer Encoder的结构,但是和Transformer原始的Encoder还是有所区别,我将两者的结构进行对比,如下图所示,左侧为Transformer原始的Encoder结构。
可以看到,大致上两者结构是相同的,主要区别在于Norm层的顺序,原始Transformer的Norm层在多头注意力和前馈网络之后,而ViT将其放到前面,这里的原因,论文里没有做解释。
关于Norm层,ViT仍是采用Transformer中用到Layer Normalization,计算公式如下:
Norm层之后同样是多头注意力层(Multi-Head Attention),和Transformer中的一样。
后面的MLP是个单独的结构,论文里没有绘制其详细结构,看到博主太阳花的小绿豆[1]对其进行了绘制,这里转贴在此。MLP也并不复杂,就是两个线性层+GELU激活函数+Dropout的结构,具体细节可以看后面的代码。
6.MLP Head
在Transformer Encoder输出结果之后,需要再将第一个添加的Class Token提取出来,然后输入到MLP Head进行分类。在论文中,作者先是在ImageNet21K上进行预训练,MLP Head结构由Linear+tanh激活函数+Linear组成,但是迁移到其它数据集训练时,只需要用一个一个Linear即可。
输出结果之后,再和真实标签做交叉熵损失,这样就可以完成ViT的训练过程。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)