前提说明:
- 本文不打算讲解Vision Transformer的理论部分,而是从代码实现出发去贯通其中的理论知识,但是推荐一篇写的比较好的博客,并且博客中还附有博主在B站中讲解的视频。请大家耐心看完推荐的博客后再来看本文。推荐博客链接:Transformer的理论和Vision Transformer的理论。
- 写本文的初心是再看完了博主太阳花的小绿豆的理论讲解后,理论是懂了,可是对于一些代码为什么要这样实现理论中阐述的功能仍然不明所以。
如图是我截取的部分评论,说明了大家确实有存在前提2中的疑惑。 - 本文将作为一个引子,今后我将介绍更多底层视觉中的Transformer应用。
问题一:Embedding层为什么要使用一个卷积层来实现,以及卷积层的各个参数在实现Embedding功能时所代表的意义。
首先回顾一下在理论讲解中Embedding在做什么?Embedding是为了将图像的三维数据[channel,height,width]转换为能够喂入Transformer的二维Token[num_token,token_dim]。理论中也讲解了,首先是将一张图片按给定大小划分为一堆patch,接着通过线性映射将每个patch映射到一维向量中。比如输入图片为(224x224)按照(16x16)大小的patch进行划分,划分后将会得到(224/16)*(224/16) = 196个patch。每个patch数据的shape为[16,16,3],那么通过映射就会得到一个长度为768的向量。
通过上面的理论回顾,可以得到两个信息:
- 在Embedding层中,输入数据的shape为[3,224,224],输出数据的shape为[196,768],其中196为划分patch的数量,也即num_token,而768是来自一个patch通过映射得到的信息,也即token_dim。
- 768来自且仅来自一个patch中的信息,而这一点就给定了可以采用一个卷积实现Embedding层的可能。
下面我们来正式看下卷积的实现,也即本文的重点:
self.proj = nn.Conv2d(in_c, embed_dim, kernel_size=patch_size, stride=patch_size)
其中in_c=3,embed_dim=768,patch_size=16,stride=16
其实,除了embed_dim这个参数的设置外,其他参数的设置都是很容易理解的。in_c即卷积核的通道数,要与输入数据的通道数相等,而本次的输入数据为RGB的图像,因此是设置为3。kernel_size设置为patch_size,因为我们需要在原图像的每个patch大小的区域进行卷积操作。stride设置为patch_size因为最终映射得到的信息仅来自一个patch(前面讲过),为此卷积滑动的间隔应该设置为patch的尺寸大小。最后的难点就是为什么卷积核的个数要设置为768呢?它为什么要与token_dim的数量相等?为什么相等后可以实现Embedding的功能,而不仅仅是输出数据在shape上的相等而已?(不知道你们有没有理解最后一个问题是在阐述什么,我再更通俗的讲一下。Embeding层在数据shape的角度上讲是实现了将输入数据的shape从[3,224,224]转换成[196,768],而通过卷积层是很容易仅在shape上做出改变的,只需要调参就可以,任何shape都可以匹配到。而更重要的是我们需要用卷积来实现Embeding层的功能意义,即输出[196,768]后,196是代表patch的个数,而768是映射一个patch内的像素信息得到的。token_dim)
下面我来解决这个疑问,上面也说了,我们采用卷积层实现Embedding层,并使用768个拥有3通道的卷积核,卷积核的尺寸为(16,16),步长为(16,16),首先看下多通道的2D卷积是如何工作的。如下图所示,假设输入数据是一个[5,5,3]的tensor,有3个通道,使用的卷积层共有3个拥有3通道的卷积核,卷积核的尺寸为(3,3),步长为(1,1)。首先每个卷积核的每个通道的kernel作用于输入tensor对应通道的数据,这里共产生3个尺寸为(3,3)的通道数据。
然后将3个通道相加(逐个元素相加)以形成一个单通道[3,3,1]的数据,这是一个卷积核作用输入tensor的结果,我们共有3个3通道的卷积核,因此最终产生的输出数的shape为[3,3,3]。
再回到我们的问题,在Embedding层中,我们使用了768个拥有3通道的卷积核,卷积核的尺寸为(16,16),卷积核的步长为(16,16)。先看单个卷积核,为简便表示,单个卷积核的shape为(3,16,16),3为通道数,16为尺寸,后一个16为步长。当它在[3,224,224]的输入tensor上进行卷积操作时,产生了一个shape为[1,14,14]的tensor(对照2D卷积的工作原理,这个shape是很好计算和理解的)。由于shape为[1,14,14]以我们可以看见的数值表示,它相当于是一个二维矩阵,行和列数都是14,无非在外层再套了一层"[]"而已。而这里二维矩阵中的每一个元素都是通过上面的卷积核映射一个patch得到的像素信息。每一个元素都是映射一个patch得来的像素信息。每一个元素都是映射一个patch得来的像素信息。每一个元素都是映射一个patch得来的像素信息。(说三遍)。最后推广至768个卷积核作用于[3,224,224]的输入tensor,将会产生一个[768,14,14]的tensor,而每个[768,1,1]的数据就是通过一个patch映射而来的。在[3,224,224]的输入tensor中,每个patch的shape为[3,16,16],按照Embedding层实现的功能需要将其映射成二维的数据即[768,1]。而通过768个3通道的卷积核,恰好可以满足最终映射为[768,1,1]。推广至所有的patch,即得到[768,14,14]的输出数据,最终通过展平(flatten函数实现),得到[768,196]的输出数据,即共有196个token,每个token的数据维数为768。