
一、VIT的整体架构图说实话只看这个整体架构图即使看了论文还是不明白这个框架是什么所以直接debug github 上的 vit-pytorch 中的VIT结构先学习模型结构再深入理解背后含义二、VIT模型结构分解代码2.1 图片变成transformer能吃的token序列图片分割成很多小path输入是Bx3x224x224 patch 大小是16x16那么把Bx3x224x224每个通道都进行path的分割那么可以变成 Bx3x14x16x14x16每个path的大小是16x16x3一共有Bx14x14个如果我们按照词向量token的说法来看把每个path拉成一个向量patch 16x16x3768**patch内部进行归一化**对每个path多归一化让数值稳定防止训练爆炸**patch维度线性投影:**使用nn.Linear(pathc_dim,dim) 进行投影投影前每个path的768个值是像素值例如pixel(0,0,R), pixel(0,0,G), pixel(0,0,B),pixel(0,1,R), pixel(0,1,G), pixel(0,1,B), nn.Linear 的每个输出维度都是所有像素的加权组合。我之前理解的是把像素空间上升到语义空间GhatGPT纠正说是像素空间映射到模型可以学习的表示空间。我觉得这里后面可以再深入理解。最后输入 1x3x224 → 1x196x10242.2 CLS token分类 token创建一个可学习的token专门用来做分类self.cls_token是 1x1024 的随机矩阵这里的1024 就是 第一步中的1024两个维度要一致将分类token和图片token序列放一起x torch.cat((cls_tokens, x), dim1)最后输入 1x196x1024 → 1x197x10242.2 位置编码给每个token 包括CLS path 分配一个 可学习的位置向量pos_embedding的大小是 197x1024pos_embedding[0] → CLS token 的位置pos_embedding[1] → patch 1为什么需要它因为transformer 有一个致命特点对输入顺序不敏感如果你打乱pathtransformer本身看不出来但是图像时有空间结构的左上角 和右下角时完全不同的语义所以位置编码的作用告诉模型每个token在图像中的位置x x self.pos_embedding[:seq]这句话就是给每个token加上位置信息偏置疑问transformer 位置编码如果仔细看其实就是一个和已经切片过的输入一样大小的参数例如输入是x 197x1024 位置编码就是 pos 197x1024的需要学习的参数然后 x x pos 到底是什么手段就给pos 赋予了位置编码的意义这个问题是我看到位置编码时候的一个疑问我觉得任何先进的技术都是从之前的前驱者进化而来的这个进化的过程就是对基本理论的理解后对当前问题的深入探讨后的结果因此有时候看论文第一遍是这是什么后面就是一遍一遍的问为什么然后慢慢的去溯源。如果我是设计者在1024后面扩展一为用做位置id有197个patch位置id就是0~196这样就发现一个问题就是数值尺度不匹配前1024个维度每个维度是像素值的加权组合得到的数值在[-1,1]之前位置编码在0到196位置维度越来越占主导。使用one-hot1024维后面加197维度的one-hot借鉴NLP界的词向量意思是单词不要用ID来表示而是用连续向量来表示例如不要用cat 17 dog 18 而是cat [0.3,0.2…] dog [0.4, 0.7…] 等可以让cat 和 dog 在向量空间接近那么如果单词ID 可以向量化那么位置是不是也可以向量化最后输入 1x197x1024 → 1x197x10242.3 Transformer2.3.1 参数解析depth :深度有几个transformer blockheads:dim_head:2.3.2 Attention 模块归一化稳定训练让每个token的分布一致生成Q/K/V:self.to_qkv nn.Linear(dim, inner_dim * 3, bias False), 输入 的 1x197x1024 通过一个线性函数输入是 1x197x1024 输出是 1x197x(3x1024) 然后通过chunk函数切成q: 1x197x1024, k:1x197x1024, v:1x197x1024因为有16个头所有将上面的q,k,v 都分到16个头上变成q: 1x16 x197x64, k:1x16 x197x64, v:1x16 x197x64;计算 Attention Score核心:dots torch.matmul(q, k.transpose(-1, -2)) * self.scale数学形式 Attention score Q · K^T其中q: 1x16 x197x64, k:1x16 x197x64,1x16 x197x64 * 1x16x64x197 1x16x197x197, 可以理解计算每个patch 之间的关系有多少个path 就有多大的矩阵关系。 scale的作用是防止 结果太大softmax 饱和 梯度消失。Softmax注意力权重softmax(dim -1)对 dots 在横向维度上经softmax 计算也就是每个token对其他token的注意力分布dropout正则化attn self.dropout(attn) 防止过度依赖某些token加权求和核心输出 out torch.matmul(attn, v)其实就是对v进行加权求和得到out的尺度是1x16 x197x64最后将out变换为1x197x1024的大小输出投影return self.to_out(out)Linear(D → D)融合多头信息2.3.3 FeedForward模块FeedForward 模块 是在self-attention 后面增加一个这个模块self-attention建模token之间关系FeedForward增强token自身表达能力 self.net nn.Sequential( nn.LayerNorm(dim), nn.Linear(dim, hidden_dim), nn.GELU(), nn.Dropout(dropout), nn.Linear(hidden_dim, dim), nn.Dropout(dropout))最后输入 1x197x1024 → 1x197x10242.4 VIT 中可以有多层transformer最后输入 1x197x1024 → 1x197x10242.5 VIT 中最终分类向量x x.mean(dim 1) if self.pool ‘mean’ else x[:, 0]用第197个 1024的向量做最终分类向量最后输入 1x197x1024 → 1x10242.6 VIT 中占位符self.to_latent nn.Identity()x self.to_latent(x)ViT 结构里其实是一个非常标准的工程设计占位符hook / switch点当前不做任何变换但预留一个可替换的接口Identity的真实作用不是计算而是留一个未来可插拔的层。2.7 VIT 中类别估计self.mlp_head(x)self.mlp_head nn.Linear(dim, num_classes) if num_classes 0 else None这个就是我们常见的估计类别的操作很好理解三、VIT论文阅读理解论文里面关于transformer 结构的部分这里就不再重复描述了重点是论文里面说的image-specific inductive biasCNN有很强的图像先验局部性CNN的卷积核只看一个小窗口比如3x3意味着模型一开始就被设计成只关心附近像素不会一上来就看全图着非常符合自然图像一个物体的边缘、纹理都是局部结构二维领域结构卷积操作时严格在2D网格上滑动的左右时空间邻近上下也是空间临近CNN天生知道图像是二维的平移等变性注意和平移不变区分CNN的严格性质是平移等变性即如果输入平移了输出特征图也会平移平移不变性是猫在左边输出是猫猫在右边输出还是猫CNN没有严格的不变性但是可以通过结构操作变得近似不变pooling 层丢掉精确位置信息GAP强烈增强不变性分类头最终输出不关心空间位置。但是VIT只有一堆tokentoken之间可以任意交互空间关系完全没定义没有空间感的语言模式所有VIT需要特别更大的数据量