ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ViT深度解读:图像如何变成Token序列?从Patch Embedding到Transformer

ViT深度解读:图像如何变成Token序列?从Patch Embedding到Transformer 这篇论文我前前后后读过五六遍也照着官方代码手写过一版实现。第一次读到标题“An Image is Worth 16x16 Words”时我在“Words”这个词上卡了很久。图像里哪来的单词后来想通了这里的Words指的不是自然语言里的词而是Transformer处理的基本单位——token。只要能把图像拆成一组token序列Transformer就能处理图像根本不需要修改架构。这就是ViT最核心的表述也是整篇论文里最容易误解、也最值得展开的地方。接下来我围绕这篇《ViT: An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale》展开。这里的Image Recognition对应图像识别在ViT的语境里基本等价于图像分类比如ImageNet、CIFAR这类任务。除了把关键段落逐句解释一遍我还会专门说说读中文翻译时容易踩的坑以及从论文到代码复现时的实操细节。适合谁看准备入门视觉Transformer、想搞懂图像tokenization机制、或者正在看DETR/MAE等后续工作却因为基础概念模糊而卡住的人这篇都能当你的第一块跳板。1. 标题里藏着整篇论文的压缩逻辑16x16和Words标题是最值得先读的部分。很多翻译版本把它处理成“一张图像相当于16x16个词”这个说法不算错但会让人误以为图像被切成了16个词而不是每16x16像素一小块。正确的读法应该是一张图像等价于若干个尺寸为16x16的小块每个小块在后续模型中扮演“单词”token的角色。1.1 token这个翻译到底怎么理解最顺NLP里的token可以是一个单词、一个子词甚至一个字符。在ViT里token就是一个图像patch经过线性投影后得到的向量。整张224x224的图按16x16的patch尺寸去切可以得到14x14196个patch。每个patch展平后是16x16x3768维向量经过投影变成D维向量这个D维向量就是视觉token。动手算一遍更容易建立体感如果输入是224x224x3patch size是16N(224/16)^2196那么输入序列长度就是196。如果patch size改成14序列长度变成256改成32序列长度变成49。序列越长self-attention的复杂度平方级增长所以patch size的选取直接影响训练成本。ViT的核心变体多用16是精度和计算量的折中。1.2 为什么不用卷积也能做视觉任务传统CNN有三个天然假设局部性、平移等变性、层级结构。卷积核只在局部滑窗所以天然关注邻近像素权重共享让同一特征不管出现在图像哪里都能被检测到池化和步长逐渐扩大感受野。这三个假设在小数据集上非常好用因为它们把很多视觉先验直接内建到网络结构里不需要大量数据去学。ViT的做法是把这个先验全部抽掉。Patch Embedding处虽然还保留了一点局部性一个patch内部的像素直接被线性映射但之后的Transformer层里每个token都能看到所有其他token注意力权重完全由数据学习得到。这样做的代价是在小数据集上很难学会“相邻像素往往属于同一物体”这类基本规律所以需要海量数据来补偿。这也引出了论文里最重要的实验结论规模是关键。从工程视角看标题里的“Words”其实是在给后来的所有视觉Transformer工作定调任何能token化的信号都能塞进Transformer。图像切成patch后续的视频切成tube、点云切成group都是同一套逻辑。所以读懂ViT不只是读懂一个分类模型而是读懂一种处理思路的范式转变。2. Patch Embedding图像变成词序列的那道工序论文在Method部分的第一句就定义了输入变换。原文大概是这样的“The standard Transformer receives as input a 1D sequence of token embeddings. To handle 2D images, we reshape the image x∈R^{H×W×C} into a sequence of flattened 2D patches x_p∈R^{N×(P^2·C)}, where (H,W) is the resolution of the original image, C is the number of channels, (P,P) is the resolution of each image patch, and NHW/P^2 is the number of patches.”翻译过来就是标准Transformer接收的是一维token embedding序列。为了处理二维图像我们把图像x重塑为展平的二维patch序列N是patch数量。然后每个patch通过可学习的线性投影映射到D维。这一整段其实就是整个ViT最核心的“输入接口”。2.1 三种等价写法建议掌握前两种在实际代码里Patch Embedding有三种常见写法手动切patch再线性投影也就是最贴近论文公式的写法。用einops的rearrange可以一行完成x rearrange(x, b c (h p1) (w p2) - b (h w) (p1 p2 c), p1p, p2p) x nn.Linear(p * p * c, embed_dim)(x)用卷积代替我个人最推荐。因为kernel_size和stride都等于patch size卷积天然实现了“不重叠地切patch并投影”这个操作x nn.Conv2d(in_channelsc, out_channelsembed_dim, kernel_sizep, stridep)(x) x x.flatten(2).transpose(1, 2)用Unfoldim2col再线性映射工程上少见但理论上是等价的一种实现。三种方式数学等价但第二种工程效率最高。我实测在同样batch下卷积实现的patch embedding比手动切patch的内存更友好。timm库的ViT实现也默认用Conv2d算是社区验证过的选择。注意线性投影的输入维度是P^2·C输出维度是D也就是transformer的hidden size。D不是随便定的它决定整个模型宽度。ViT-Base用D768ViT-Large用D1024ViT-Huge用D1280。这个投影矩阵是在整个训练过程中学习的本质上是让模型自己判断哪些像素组合对后续分类最有用。2.2 class token为什么要在序列最前面插一个特殊向量光有196个patch tokenTransformer输出后怎么得到整图的分类结果最简单的做法是全局平均池化但ViT选择了另一个方案复制BERT的[CLS] token设计在序列最前面加一个可学习的class token。这个class token本身不来自图像它只是一个随机初始化的向量和所有patch token一起进Attention。由于它没有固定语义模型会通过训练慢慢把对分类有用的全局信息“汇聚”到这个位置上。最后分类时只取class token对应的输出向量过一个LayerNorm和一个线性分类头。论文实验表明class token和全局池化效果接近但class token的优势在于和Transformer的逻辑闭环输入是序列输出也是序列不需要引入额外的池化算子。2.3 位置编码用1D还是2D结果出人意料Transformer本身不区分顺序所以必须给序列里的每个token一个位置标记。ViT用的是一维可学习位置编码直接加到token向量上z_0 [x_class; x_p^1 E; x_p^2 E; ... ; x_p^N E] E_pos。很多读者第一次看到这里会疑惑图像明明是二维的为什么用一维位置编码论文里专门比较了一维位置编码、二维位置编码、相对位置编码结论是三种方案性能差不多所以作者选择最简单的1D方案。原因可以这样理解patch序列本身已经是二维图像被强行拉直后的结果模型完全有能力通过注意力机制去还原patch之间的空间关系位置编码只需要提供一个不重复的位置线索具体怎么解释这个线索模型会自己学。值得注意的是训练完的position embedding可视化之后会有明显的空间结构——相邻patch的编码向量更相似这相当于模型在自己摸索“像素在二维平面上有邻居关系”这一事实。复现时建议把这个编码矩阵画成热力图看看你会更直观地理解位置编码到底在做什么。3. Transformer Encoder为什么“原封不动”反而是最聪明的决定ViT在架构上的最大“卖点”其实就是“没改”。它没有像早期工作那样在CNN里塞注意力模块也没有设计图像专用的attention变体而是直接把NLP的Transformer Encoder搬了过来。3.1 从公式看Encoder内部的四个部件论文把每个Encoder block描述得很清楚zl MSA(LN(z{l-1})) z_{l-1} z_l MLP(LN(z_l)) z_l其中LN是LayerNormMSA是多头自注意力MLP是两层带GELU激活的全连接。每个block内部结构可以拆成四个部分LayerNorm先归一化再进注意力/MLP这种叫Pre-LN和原始Transformer的Post-LN不同。Pre-LN在训练深层模型时更稳定梯度更容易流动。多头自注意力每个token的向量分别映射成Query、Key、Value计算所有token两两之间的注意力权重然后加权聚合。残差连接每个子层都有一条“短路”让信息能直接跨层传递。MLP对每个token独立做非线性变换通常是先升维再降维。比如D768时MLP内部先升到3072维再降回768维。原文里MLP是两层线性层中间夹一个GELU。GELU比ReLU更平滑在Transformer类模型里几乎成了标配。激活函数的差别看起来小但在深层次、大规模训练下会影响收敛速度这是从NLP迁移过来的经验不是写论文时顺手选的。3.2 和原版Transformer的关键差异只有三个没有Decoder。ViT只用了Encoder来编码整张图像分类head直接建在Encoder输出上。它不需要像机器翻译那样生成序列所以Decoder是多余的。激活函数用GELU而不是ReLU。原始Transformer里FFN用的是ReLUViT跟随了BERT的GELU习惯。Norm位置前置。Post-LN是在每个子层输出之后加normPre-LN是在子层之前加norm。ViT采用Pre-LN便于扩大模型规模。这三处差异单独看都不大但合在一起决定了ViT能以几十亿甚至几百亿参数在大规模数据上稳定训练。如果你自己改过Transformer结构就会知道Norm位置这个细节经常被忽略却是最容易出现“换了个激活就发散”的坑。3.3 模型配置表Base/Large/Huge各是什么量级论文给出了三档模型配置参数规模从千万到几亿再到几亿不等。以patch size16时最常见的几档为例模型层数Hidden SizeMLP Size注意力头数参数量ViT-B/161276830721286MViT-L/16241024409616307MViT-H/14321280512016632M模型命名里末尾的数字代表patch size。ViT-B/16就是Base规模、patch size 16。同样隐藏层配置下patch越小序列越长计算量越大但空间信息越完整。所以ViT-L/16和ViT-L/32相比前者精度通常更好但训练更贵。阅读翻译版时看到“模型变体”这个词可以自动映射到这张表。3.4 归纳偏置到底去哪了论文里有一句经常被引用的话Vision Transformer比CNN的归纳偏置少。所谓归纳偏置就是结构里预先刻好的假设。CNN通过卷积核天然假设邻近像素有关系、特征平移了仍算同一个特征ViT没有这些假设所有关系都靠注意力学习。别小看这句话它直接解释了为什么ViT在第一篇论文的小规模实验里表现一般没有先验可用只能靠大数据来学。用一句生活化的话总结CNN像一个出生就带着地方方言的人学习语言快但天花板锁死ViT像一个语言天才起步难给足语料后能说得比谁都好。数据规模就是那个临界点。4. 训练策略大规模数据不是锦上添花是必要条件如果只看架构很容易觉得ViT和CNN只是在“归纳偏置”上有差别。真正让ViT成立的是训练策略整套流程一句话概括先在巨量数据上预训练再在下游任务上微调。4.1 预训练数据集为什么至少要ImageNet-21k起步论文里有一个非常扎眼的实验结论只在ImageNet-1k约128万张图上训练时ViT精度低于同量级ResNet在ImageNet-21k约1400万张图上训练两者打平在JFT-300M约3亿张图上训练ViT明显超过ResNet。作者甚至把论文标题后半截的“at Scale”直接写出来说明他们自己很清楚效果的前提是大规模。为什么规模如此关键因为Transformer的全局注意力给了模型非常大的自由度。小数据下模型容易把注意力浪费在大量无关像素组合上数据足够大时模型才能从海量样本里统计出“哪些远距离关系是有用的”。这就像让一个没有任何先验知识的人学辨认物体你至少得让他看足够多的样本。CNN的卷积结构相当于提前告诉他“邻近像素关系更重要”所以小样本下占优势ViT不做这个假设就必须用数据量来换结构灵活度。4.2 预训练与微调的分辨率策略是最容易忽视的细节ViT的预训练通常用224x224输入微调用更高分辨率比如384x512。这里有个关键问题224分辨率下patch size16序列长度是196换到384分辨率patch size还是16序列长度变成 (384/16)^2576。问题是可学习的位置编码只有196个位置576个位置并没有在预训练里见过。ViT的解决办法是把预训练好的位置编码做二维插值扩展到新的序列长度上。这个操作看起来不起眼但复现时特别容易踩坑。我第一次做高分辨率微调时直接丢掉预训练位置编码重新随机初始化结果模型在验证集上比原来低了快两个点。后来换成双线性插值初始化新位置编码精度立刻恢复正常。原理很简单位置编码里已经包含了空间结构只差一个“按比例放大”的步骤随机初始化等于让模型从头学空间关系当然会掉点。4.3 优化器、学习率与正则这些超参组合起来才有效ViT的训练细节继承自NLP的成熟经验但做了一些适配。通常使用Adam优化器beta取(0.9, 0.999)配合warmup和cosine学习率衰减weight decay设得比较高论文里常用0.1级别还会用梯度裁剪防止训练不稳定。数据增强层面用到Mixup、CutMix、RandAugment等这些策略对防止Transformer在小数据下过拟合起了很大作用。很多复现实验做不出论文精度不是模型写错了而是训练策略没跟上。ViT对训练超参的反应比CNN更敏感尤其是学习率和数据增强强度。你在ImageNet-1k从头训练一个ViT-B/16想达到timm上85%的精度纯靠默认配置是不够的通常需要配合反复调过的augmentation pipeline、EMA和长时间训练。这和论文结论一致ViT能力上限高但不好驯服。5. 实验结果怎么读不是“ViT全面碾压CNN”而是有条分水岭论文的Results部分信息量很大但很多人只记住了“ViT在ImageNet上88.55%”这句很容易得出“Transformer就是比CNN强”的结论。实际上实验结果更微妙也更值得琢磨。5.1 数据集规模与模型精度的关系曲线论文里最关键的一张图是“不同预训练数据集大小下ViT与ResNet/BiT的精度差异”。横轴是预训练数据规模纵轴是迁移到ImageNet后的top-1精度。结果非常清晰当预训练数据只有ImageNet-1k时ViT的曲线明显低于CNN当预训练数据大到JFT-300M时ViT的曲线反超。这告诉你两件事。第一评价ViT不能脱离数据规模谈效果第二所谓“CNN的归纳偏置”并不是永远的优势在数据足够时自由度高反而让模型能摆脱先验限制学出更有效的表征。这个规律后来在MAE、CLIP等一系列工作中反复出现几乎成了视觉预训练的一条铁律。5.2 ViT在不同变体、不同迁移任务上的表现论文报告的表格很多我倾向于只看几个关键数在ImageNet上单模型top-1最好成绩88.55%在ImageNet-21k上90.72%在CIFAR-100上94.55%在VTAB由19个视觉任务组成的benchmark上77.63%。这几个指标在当年已经超过了同等训练条件下的BiTBig Transfer系列。VTAB这个榜单值得多说一句它把任务分成自然图像、专用图像比如卫星图、医学图和结构化图像比如计数、3D深度三类。ViT在这三类上的表现有差异但在整体平均上仍然领先。后续几乎每一篇视觉Transformer论文都会报告VTAB结果因为它是衡量迁移能力的重要标尺。5.3 可视化注意力论文里最有画面感的部分ViT论文里有一组可视化结果把class token对每个patch的注意力权重画成热力图叠加在原图上。你会发现模型无需任何监督就在注意力热力图里出现了类似分割的目标轮廓。有些head负责抓全局轮廓有些head负责抓局部细节这种“分工”完全是自注意力学出来的。这组图从直觉上说服了很多人视觉Transformer不是黑盒它在早期的层里已经学会了描述空间关系和物体边界。复现时你也能做同样的可视化只需要把最后一层class token对所有token的attention weight取出来reshape成14x14再插值到原图尺寸即可。这一步会帮助你更直观理解“Transformer在图像上到底看到了什么”。6. 翻译与理解几个容易让人理解偏的术语这篇论文的中文翻译流传很广但翻译过程中有几个术语特别容易产生歧义我单独列出来。6.1 token、embedding、patch 三者至少要有明确对立patch图像被切出的小块比如16x16像素的一个方块。它还是图像层面的概念。tokenpatch进入模型后的身份可以理解成序列中的一个元素。embeddingtoken对应的连续向量也就是模型内部的数值表示。三者的关系是图像被切分成patch每个patch通过线性投影变成embedding这些embedding组成的序列在Transformer里被称为token序列。翻译版如果不区分这三个词读起来会一头雾水。建议读的时候心里始终带着这条流水线。6.2 “head”不是“头”“hidden size”不是“隐层数”ViT论文里的head通常指分类头classification head也就是接在Encoder最后做类别输出的那个线性层。很多人第一次看会以为是“注意力头”但上下文里如果提到“classification head”它就是分类器“attention head”才是多头注意力里的一个头。hidden size指的是向量维度D不是隐藏层数量。MLP size指MLP中间层的宽度。这些术语在不同翻译版本里容易被混用导致理解偏差。顺带说“fine-tune”翻译成“微调”没毛病但要注意它和“从头训练”train from scratch是相对的概念。ViT如果不预训练直接ImageNet-1k从头训练效果很一般先在大数据上预训练再微调才能发挥功力。6.3 把论文摘要完整读一遍胜过零散刷片段论文摘要虽然短但信息密度极高。我直接给出一个比较贴近原意的翻译“尽管Transformer架构已经成为自然语言处理任务的事实标准它在计算机视觉中的应用仍然有限。我们证明与普遍假设相反这种架构不需要针对视觉任务做任何修改。将图像划分为16x16大小的词序列并作为输入交给一个标准Transformer就足以让模型在图像分类上达到很好的效果。当模型在足够大的数据集上预训练并迁移到多个中小规模数据集后ViT取得了与领先的卷积网络相当甚至更好的结果同时训练成本显著更低。”这段摘要里“at scale”被翻译成“大规模”没有歧义但它真正的含义是只有在足够的规模下这个结论才成立。所以论文标题本身就是一篇压缩版导读。6.4 论文里反复出现的“transfer”和“downstream”指的是什么预训练在JFT或ImageNet-21k上做完之后模型要去处理具体任务比如CIFAR-100分类、VTAB里的某个视觉任务。这个过程论文里叫transfer迁移这些具体任务叫downstream tasks下游任务。所有视觉Transformer预训练模型的精度报告都有一个“预训练数据微调任务”的完整上下文。只看微调精度不看预训练数据规模几乎无法横向比较模型好坏。7. 从ViT到Deformable DETR同一套思想的扩散ViT的另一个重要贡献是它让整个视觉社区开始用Transformer语言重新描述问题。很快DETR把目标检测变成集合预测Deformable DETR又解决了DETR收敛慢、小目标检测差的问题。想理解这些工作ViT的基本概念是第一条线索。7.1 DETR把检测从“框回归”变成“序列预测”DETR的思路是不再像Faster R-CNN那样预设大量候选框而是让Transformer直接输出固定数量的目标集合。图像经过CNN backbone得到特征展平后加上位置编码作为token序列Decoder通过注意力机制从这些token中解码出目标框和类别。从本质上看DETR就是把“图像内容”和“目标预测”都建模成序列到序列的问题和ViT的“图像到token序列”是一脉相承的。7.2 Deformable DETR把“全图注意力”改造成“稀疏多尺度注意力”Deformable DETR是DETR的重要改进它和ViT的关联在于注意力机制的理解上。DETR的attention每个query要对所有key计算权重收敛慢且计算开销大。Deformable DETR的做法是只看当前query附近的少量采样点并通过可学习的偏移量评估位置偏移。这样既保留了Transformer的全局建模能力又把计算复杂度从和feature map尺寸平方相关降到了和采样点数线性相关同时还能自然地在多尺度特征图上做注意力。如果从ViT的角度看Deformable DETR你会看到同样的骨架输入先token化attention负责建模关系位置信息通过编码注入。Deformable只是把“对所有token做注意力”换成了“对一小部分相关位置做注意力”本质是对Transformer注意力稀疏化的一种工程优化。所以不要把它们当成两种互不相干的技术它们共享的是同一个方法论只要你能把视觉输入变成有意义的token序列Transformer的注意力机制就能工作问题只是怎么让它更高效。7.3 为什么建议先读ViT再读这些工作ViT是所有视觉Transformer工作的入门锚点。它把“图像如何变成token”“position embedding怎么加”“pre-train加fine-tune的套路”这些概念都讲透了。后续的MAE、CLIP、SAM也都建立在“图像可以被token化”这一前提下。你花两天把ViT吃透之后再读Deformable DETR、DINO、SAM等论文时很多概念都不用重新学只需要关注它们各自在attention结构或训练范式上的改动。8. 复现笔记从论文到代码的几点经验最后一章说说实操。我复现ViT的时候踩过不少坑挑几个最值得记录的分享出来。8.1 位置编码插值最容易被忽视的精度杀手前文提到微调分辨率时的位置编码插值。这里再强调一次如果从224分辨率迁移到384最好把预训练的position embedding按二维网格双线性插值到新的网格尺寸。多个开源实现里都内置了这个函数但如果你自己写训练脚本很容易漏掉。漏掉之后模型不会崩只是精度悄悄掉一个点以上排查起来非常麻烦。8.2 分类头的微调策略不要整个头都随便初始化预训练时的分类头类别数和下游任务类别数往往不同微调时一般会换一个新的线性层。这里要注意可以把新head的学习率调高一些backbone的lr调低一些这样收敛快很多。ViT原论文微调时对head用了更高的学习率这个细节在中文复现教程里经常被省略。8.3 训练过程中多留几个checkpoint尤其是最后一个阶段Transformer类模型在训练后期往往会出现精度突然上涨的情况。ViT论文用了long training scheduleJFT预训练加上fine-tune的总时长不短。我建议至少每个epoch都保存一次最好保留最后几轮的checkpoint因为早停太早可能丢掉最好的模型。某些跑完实验后把中间checkpoint全删掉的操作在纯CNN时代可能无所谓在ViT这种大规模预训练范式里非常可惜。8.4 我的建议阅读顺序如果你是从零开始不要一上来就啃代码先读论文的摘要和结论再照着本文第2、3章的公式把输入输出关系写一遍然后打开官方仓库对比实现。读论文比读代码更能建立整体图景。如果只看中文翻译建议重点对照原图表理解毕竟图表比文字更不容易被翻译带偏。我这几年读下来最大的体会是ViT的“简单”其实是强者的简单——它把复杂度从结构转移到了数据和训练策略上。搞清楚这个前提你对后续所有视觉Transformer工作的理解都会快很多。
返回列表