名称:An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale
研究团队:Google Research, Brain Team
原文:原文
参考笔记:笔记1
自注意力及多头注意力讲解笔记
李沐vitb站视频笔记
transformer的self att和multi-head att:视频讲解霹雳吧啦Wz
vit理解笔记1
vit理解笔记2
transformer:
使用注意力、做序列转录的模型,把之前在 encoder - decoder 的结构换成了 multi-headed self-attention.
区别:
CNN(局部像素–>全部像素;多通道 --> multi-head)
Transformer 仅依赖 self-attention 计算输入输出的表征
cnn缺点:
1 CNN 对较长的序列难以建模。因为卷积计算的时候看一个比较小的窗口,i.e., 3 * 3 窗口,如果 2 个像素隔得比较远,需要用很多 3 * 3 的卷积层、一层一层的叠加上去,才能把隔得很远的 2个像素联系起来。
Transformer 的 attention mechanism 每一次看到所有的像素,一层能够看到整个序列。
2 cnn多个输出通道,每个通道可以识别不同的模式。
Transformer 的 multi-head self-attention 模拟 CNNs 多通道输出的效果。
B站讲解视频笔记
李沐
query:查询 ;key:关键字;键值:value
1 注意力函数: 一个将一个 query 和一些 key - value 对 映射成一个输出的函数,其中所有的 query、key、value 和 output 都是一些向量。
2 输出:output 是 value 的一个加权和 --> 输出的维度 == value 的维度。
3 权重:output 中 value 的权重 = 查询 query 和对应的 key 的相似度 or compatibility function,
通常用内积实现,用来衡量每个key对每个query的影响大小
4 注意力的具体计算是:对每一个 query 和 key 做内积,然后把它作为相似度
attention = softmax( 两个向量的内积值 / sqrt(dk)) * V ,dk 是向量的长度
使用 softmax :一个 query 给 n 个 key - value pair ,这个 query 会跟每个 key - value pair 做内积,会产生 n 个相似度值。传入 softmax 得到 n 个非负、求和为 1 的权重值。把 softmax 得到的权重值 与 value 矩阵 V 相乘 得到 attention 输出。
因为 softmax 最后的结果是希望 softmax 的预测值,置信的地方尽量靠近,不置信的地方尽量靠近零,以保证收敛差不多了。这时候梯度就会变得比较小,那就会跑不动。
5 输入:encoder 的注意力层,有三个输入,它分别表示的是key、value 和 query。
一根线过来,它复制成了三下:同样一个东西,既 key 也作为 value 也作为 query,所以叫做自注意力机制。key、value 和 query 其实就是一个东西,就是自己本身。
6 总结:输入了 n 个 query,每个 query 会得到一个输出,那么会有 n 个输出。
输出 是 value 加权和(权重是 query 和 key 的相似度),输出的维度 == d – > 输入维度 == 输出维度
与其做一个单个的注意力函数,不如说把整个 query、key、value 整个投影 project 到 1个低维,投影 h 次。然后再做 h 次的注意力函数,把每一个函数的输出 拼接在一起,然后 again projected,会得到最终的输出。
multi-head attention 给 h 次机会去学习 不一样的投影的方法,使得在投影进去的度量空间里面能够去匹配不同模式需要的一些相似函数,然后把 h 个 heads 拼接起来,最后再做一次投影。
将原始图片进行均匀的分成若干个patch,,每个小块可以看做成NLP当中的词,把patch展平成序列,再把分割后的patch输入到原始Transformer模型的编码器部分中,最后通过正常的全连接层对图片进行分类
主要包括以下几个部分:数据处理部分,Patch编码,位置编码,分类等模块。
Multi-Head Attention,是由多个Self-Attention组成的,
Norm 表示 Layer Normalization,用于对每一层的激活值进行归一化。
LayerNorm:对每个样本做 Normalization(把每一行变成 均值为 0、方差为 1),不是对每个特征做 normalization。
1 将输入的图像进行patch的划分
2 Linear Projection of Flatted Patches,将patch拉平并进行线性映射
3 生成CLS token特殊字符*,生成Position Embedding,Patch+Position Embedding相加作为inputs token
4 Transformer Encoder编码,特征提取
5 MLP Head进行分类输出结果
我们假设原始的输入数据是 HxWxC的大小,对原始的数据,先分块,再展平。假设每个Patch的长宽为P,那么分块的数目为:N=HxW/(PxP)
对每个Patch都展平成一维的向量,每个向量可以表示成:PxPxC
乘以C是因为有C个通道,那总的输入的经过展平后的维数为:NxPxPxC
将图像分成16×16的patch(小方块),每个patch块可以看做是一个token(词向量),
共有(224/16=14)14×14=196个token,每个token的维度为16×16×3=768。
patch块大小是16×16,每个patch块是RGB三通道的彩色图像)
接着对每个向量都做一个线性变换(即全连接层),压缩维度为D,这里我们称其为 Patch Embedding。
**一个patch块它的维度是16×16×3=768,我们把它flatten拉平成行向量它的长度就为768,**一共有14×14=196个patch,所以输入的维度是[196, 768],我们经过一个Linear Projection(映射)到指定的维度,比如1024或2048,我们用全连接层来实现,但映射的维度我们任然选择为768,那么此时的输出是[196, 768]。
数据维度转换
对于标准的Transformer模块,要求输入的是token(向量)序列,即二维矩阵[num_token, token_dim],
如下图,token0-9对应的都是向量,以ViT-B/16为例,每个token向量长度为768。
对于图像数据而言,其数据格式为[H, W, C]是三维矩阵明显不是Transformer想要的。
所以需要先通过一个Embedding层来对数据做个变换。
如下图所示,
1.首先将一张图片按给定大小分成一堆Patches。
以ViT-B/16为例,将输入图片(224x224)按照16x16大小的Patch进行划分,
划分后会得到(224/16)^2=196个Patches。
2.接着通过线性映射将每个Patch映射到一维向量中
以ViT-B/16为例,每个Patche数据shape为[16, 16, 3]通过映射得到一个长度为768的向量(后面都直接称为token)。[16, 16, 3] -> [768]
输出的结果维度是[196, 768]
== 在代码实现中,直接通过一个卷积层来实现 ==。
以ViT-B/16为例,直接使用一个卷积核大小为16x16,步距为16,卷积核个数为768的卷积来实现。
通过卷积,[224, 224, 3] -> [224/16=14, 224/16=14, 768],
然后把H以及W两个维度展平即可[14, 14, 768] -> [196, 768],此时正好变成了一个二维矩阵,正是Transformer想要的。
但是有大佬就不服啦,反正都是线性映射,为啥我要这么复杂,把它flatten后在用全连接层呢?
聪明的大佬就想到,我直接使用卷积来实现,用16×16大小的卷积核,步长stride=16,维度设为768,输入[3, 224, 224]->[768, 14, 14],然后交换并合并一下维度不就得到结果了吗?[196, 768]
在输入Transformer Encoder之前注意需要加上[class]token以及Position Embedding。 在原论文中,作者说参考BERT,在刚刚得到的一堆tokens中插入一个专门用于分类的[class]token,
这个[class]token是一个可训练的参数,数据格式和其他token一样都是一个向量,
以ViT-B/16为例,就是一个长度为768的向量,与之前从图片中生成的tokens拼接在一起,Cat([1, 768], [196, 768]) -> [197, 768]。
关于Position Embedding就是之前Transformer中讲到的Positional Encoding,这里的Position Embedding采用的是一个可训练的参数(1D Pos. Emb.),是直接叠加在tokens上的(add),所以shape要一样。
以ViT-B/16为例,刚刚拼接[class]token后shape是[197, 768],那么这里的Position Embedding的shape也是[197, 768]
假设我们按照论文切成了9块,但是在输入的时候变成了10个向量。这是人为增加的一个向量。因为传统的Transformer采取的是类似seq2seq编解码的结构。而ViT只用到了Encoder编码器结构,缺少了解码的过程,假设你9个向量经过编码器之后,你该选择哪一个向量进入到最后的分类头呢?
因此这里作者给了额外的一个**用于分类的向量,与输入进行拼接。**同样这是一个可学习的变量。
patch embedding的维度为[196, 768]
1.首先生成一个cls token它的维度为[1, 768],然后拼接到输入的path embedding,得到的维度为[197, 768]
2.对197个patch都生成一个位置信息,它的维度同patch维度为[197, 768]
3.Patch+Position Embedding,直接相加为新的的token作为encoder的输入
疑惑解答
cls_token = nn.Parameter(torch.zeros(1, 768))
pos_embedding = nn.Parameter(torch.zeros(197, 768))
cls token的作用是为了同NLP领域的Transformer保持一致,最后直接提取cls token作为网络提取得到的特征,作为类别预测的输出,放入MLP进行分类
不使用cls token也是可以的,对196个维度为768的patch进行全局均值池化,得到结果维度为[196,1]后作为类别预测的输出,放入MPL进行分类
通过学习率的调整可以让cls token和对patch全局均值池化两种方法获得相近似的结果,但为尽可能接近原始Transformer模型,选择使用额外的cls标记
位置信息编码是为了给patch加上相对位置,不然在后面的特征提取中丢掉位置信息可就不好了
位置信息编码被用于增加patch embedding的原始位置信息
位置信息编码有可学习参数类型,有通过公式计算的方法,可以是一维、二维;但不使用性能会差点
没有位置编码性能差距会很大,但选择不同位置编码几乎没有差异,原因是Transformer是直接在patch上操作而不是基于像素级,因此,空间信息编码方式差异没那么重要
Transformer Encoder其实就是重复堆叠Encoder Block L次,下图是我自己绘制的Encoder Block,主要由以下几部分组成:
Encoder输入的维度为[197, 768],输出的维度为[197, 768],可以把中间过程简单的理解成为特征提取的过程
分类头的意思就是:特征提取工作已经全部完成,现在你要做分类就加上对应的操作。(同CNN特征提取层后的Linear层+Softmax进行分类预测)
上面通过Transformer Encoder后输出的shape和输入的shape是保持不变的,
以ViT-B/16为例,输入的是[197, 768]输出的还是[197, 768]。注意,在Transformer Encoder后其实还有一个Layer Norm没有画出来,后面有我自己画的ViT的模型可以看到详细结构。
这里我们只是需要分类的信息,所以我们只需要提取出[class]token生成的对应结果就行,即[197, 768]中抽取出[class]token对应的[1, 768]。接着我们通过MLP Head得到我们最终的分类结果。MLP Head原论文中说在训练ImageNet21K时是由Linear+tanh激活函数+Linear组成。但是迁移到ImageNet1K上或者你自己的数据上时,只用一个Linear即可。
整个Encoder的输出为[197, 768]我们仅仅保留最前面的CLS token作为全连接的输入[1, 768],然后接上全连接层及分类数n_class,使用交叉熵损失函数计算损失,反向传播更新网络的权重和参数。
分类头很简单,加入了LayerNorm和两层全连接层实现的,采用的是GELU激活函数。
一个图片224x224,分成了196个16x16的patch;
对这么多的patch做embedding,成196个768向量;
再拼接一个cls_tokens,变成197个768向量;
再加上pos_embedding,还是197个768向量;
这些向量输入到transformer中进行自注意力的特征提取;
输出的是197个768向量
在论文4.1章节的Model Variants中有比较详细的讲到Hybrid混合模型,就是将传统CNN特征提取和Transformer进行结合。
下图绘制的是以ResNet50作为特征提取器的混合模型,但这里的Resnet与之前讲的Resnet有些不同。首先这里的R50的卷积层采用的StdConv2d不是传统的Conv2d,然后将所有的BatchNorm层替换成GroupNorm层。在原Resnet50网络中,stage1重复堆叠3次,stage2重复堆叠4次,stage3重复堆叠6次,stage4重复堆叠3次,但在这里的R50中,把stage4中的3个Block移至stage3中,所以stage3中共重复堆叠9次。
通过R50 Backbone进行特征提取后,得到的特征矩阵shape是[14, 14, 1024],
接着再输入Patch Embedding层,注意Patch Embedding中卷积层Conv2d的kernel_size和stride都变成了1,只是用来调整channel。后面的部分和前面ViT中讲的完全一样,就不在赘述。
在论文的Table1中有给出三个模型(Base/ Large/ Huge)的参数,
在源码中除了有Patch Size为16x16的外还有32x32的。
其中的Layers就是Transformer Encoder中重复堆叠Encoder Block的次数,
Hidden Size就是对应通过Embedding层后每个token的dim(向量的长度),
MLP size是Transformer Encoder中MLP Block第一个全连接的节点个数(是Hidden Size的四倍),
Heads代表Transformer中Multi-Head Attention的heads数。