前言

虽然会大模型远远不等同于会具身,但“大模型”转“具身”会很顺畅,我们就是这么过来的(PS,长沙分部很快 要折腾两个新的具身项目了,到时候就是第二轮、第三轮的突飞猛进,至于第一轮突飞猛进是今25年6.4-7.19日)

对于视觉相关的应用,无论是多模态大模型还是最新的检测、分割技术,本文所介绍的ViT都是没法回避的,而本文

  • 一开始是属于2023年4月份写的此文《图像生成的奠基与起源:从AE、VAE、VQ-VAE到扩散模型DDPM(含加噪、去噪全过程)、DDIM(含U-Net的简介)》的
  • 再后来,又写在此文《ViT及自监督ViT DINO的发展史——从ViT、Swin transformer到Meta发布的DINO、DINOv2、通用视觉特征提取器DINOv3》中

但ViT实在是太太重要了,完全值得单独成文,更何况其在整个CV的发展史上有着里程碑式的地位,完全没必要屈居于任何一个模型之下,故有了本文

第一部分 Vision Transformer:用标准的Transformer直接干CV任务

1.1 ViT的提出:把整个图片切分成一个个图片块(比如分割为九宫格)

2020年10月,Google Research, Brain Team的Alexey Dosovitskiy, Lucas Beyer, Alexander Kolesnikov, Dirk Weissenborn, Xiaohua Zhai(本毕南大、14年北大博毕之后便去了Google,不过后于24年12月初与左边标粗的两位伙伴同去了OpenAI )、Neil Houlsby等12人提出ViT

这个工作是怎么一步步出来的呢?自从Google在2017年发布的Transformer在NLP领域大杀四方的时候,就一直不断有人想把如此强大且充满魔力的transformer用到CV领域中,但前路曲折啊

  1. 一开始面对的问题就是,当把Transformer中对NLP的各个token之间两两互相做相似度计算的self-attention引入到图片各个像素点之间两两做self-attention时,你会发现计算复杂度瞬间爆炸「transformer 的计算复杂度是序列长度 n 的 平方 O(n^2)

    原因很简单,一句话才多少个token(顶多几百而已),但一张图片呢?比如一张像素比较低的224 \times 224分辨率的图片,就已经达到了 50176 个像素点,再考虑到RGB三个维度,直接就是15万起步,一张图片动不动就15万个像素点做self-attention,这计算量,你品..
  2. 兴高采烈之下理想遇挫,才发现没那么简单,咋办呢,那就降低序列长度呗
    比如
    \rightarrow  要么像CVPR Wang et al. 2018的工作把网络中间的特征图当做transformer的输入,毕竟ResNet 50 最后一个 stage, res4 的 feature map也就14 \times 14 = 196的大小
    \rightarrow  要么借鉴CNN的卷积机制用一个局部窗口去抓图片的特征,从而降低图片的复杂度

    但这一系列工作虽然逻辑上通畅,但因为硬件上无法加速等,导致模型没法太大
  3. 总之,之前的工作要么把CNN和self-attention结合起来,要么把self-attention取代CNN,但都没取得很好的扩展效果,看来得再次冲击Transformer for CV
    (为何如此执着?还不是因为理想实在过于美丽,要不然人到中年 每每听到beyond的 “原谅我这一生不羁放纵爱自由” 便如此共鸣强烈)

    不管如何,还是得回到开头的老大难问题:如何处理图片的复杂度
    于此,Vision Transformer(ViT)来了:不再一个一个像素点的处理,而是把整个图片切分成一个个图片块(比如分割为九宫格),这些图片块作为transformer的输入

下面,我们来仔细探究下ViT到底是怎么做的

1.2 ViT的架构:Embedding层 + Transformer Encoder + MLP Head

简单而言,Vision Transformer(ViT)的模型框架由三个模块组成:Embedding层、Transformer Encoder、MLP Head

1.2.1 Embedding层(线性投射层Linear Projection of Flattened Patches)

首先,以ViT_base_patch16为例,一张224 x 224的图片先分割成 “尺寸大小为16 x 16” 的 patch

  • 很显然会因此而存在 (224\times 224)/(16\times 16)=196 个 patch
  • 这个patch数如果泛化到一般情况就是图片长宽除以patch的长宽,即N = HW/P^2
    即图片的长宽由原来的224  x 224 变成:14  x 14(因为224/16 = 14)

你可能还没意识到这个操作的价值,这相当于把图片需要处理的像素单元从5万多直接降到了14x14 = 196个像素块,如果一个像素块当做一个token,那针对196个像素块/token去做self-attention不就轻松多了么

16*16 16*16 16*16 16*16 16*16 16*16 16*16 16*16 16*16 16*16 16*16 16*16 16*16 16*16
16*16
16*16
16*16
...

第14个

16*16

顺带提一句,其实在ViT之前,已经有人做了类似的工作,比如ICLR 2020的一篇paper便是针对CIFAR-10中32\times 32的图片抽 2\times 2的像素块
  1. 其次,对于图片而言,还得考虑RGB channel这个因素,故每个 patch 的 维度便是 [16, 16, 3],但标准Transformer的输入是一个一维的token序列,所以需要把这个三维的维度通过线性映射linear projection成一维的维度,从而使得每个 patch 最终的输出维度是:16\times 16\times 3 = 768

    这样图片就由原来的 [224, 224, 3] 的大小变成了 [14, 14, 768] 的大小相当于之前图片横竖都是224个像素点的,现在横竖只是14个像素点了,而每个像素点的维度(相当于每个token的序列长度)为768
  2. 之后经过 Flatten 就得到 [196, 768],接着再经过一个维度为768 \times 768的Linear projection (本质上就是一个全连接层,用大写 E表示,这个768的维度即embedding_dim可以变,简写为D,比如原始的transformer设置的维度为512),故最终的维度还是为196\times 768
     

    至于在代码实现中,可通过一个卷积层来实现,卷积核大小为16,步长为16,输入维度是3,通过对整个图片进行卷积操作:[224, 224, 3] -> [14, 14, 768],然后把H以及W两个维度展平即可[14, 14, 768] -> [196, 768]

    Conv2d(in_c, embed_dim, kernel_size=patch_size, stride=patch_size)
  3. 接下来,为了做最后的分类,故在所有tokens的前面加一个可以通过学习得到的 [class] token作为这些patchs的全局输出,相当于BERT中的分类字符CLS (这里的加是concat拼接),得益于self-attention机制,所有token两两之间都会做交互,故这个[class] token也会有与其他所有token交互的信息

    且为了保持维度一致,[class] token的维度为 [1, 768] ,通过Concat操作,[196, 768]  与 [1, 768] 拼接得到 [197, 768]
  4. 由于self-attention本身没有考虑输入的位置信息,无法对序列建模。而图片切成的patches也是有顺序的,打乱之后就不是原来的图片了,故随后和transformer一样,就是对于这些 token 添加位置信息,也就是 position embedding

    VIT的做法是在每个token前面加上位置向量position embedding(这里的加是直接向量相加即sum,不是concat),这里和 transformer 一致,都是可训练的参数,因为要加到所有 token 上,所以维度也是 [197, 768]

1.2.2 Transformer Encoder

接下来的流程为

  1. 维度为[197, 768]的embedded patches进来后,先做一次Layer Norm

  2. 然后再做Multi-head Attention,通过乘以三个不同的Q K V矩阵得到三个不同的Q K V向量,且ViT_base_patch16设计的是12个头,故每个头的维度为:[197, 768/12] = [197, 64]

    最后把12个头拼接起来,会再次得到[197, 768]的维度
  3. 接着再做Norm

最后是MLP,维度上先放大4倍到[197, 3072],之后又缩小回去恢复到[197, 768]的维度

1.2.3 MLP Head(最终用于分类的层结构)

MLP里面,是用tanh作为一个非线性的激活函数,去根据[class] token做分类的预测

此外,上述这三个阶段的过程最终可以用如下4个公式表达

  • 对于公式(1),x_{p}^{N}表示图片patch,总共N个patch,x_{p}^{N} E则表示patch embedding,x_{class}表示class embedding,因为需要用它做最后的输出/分类
  • 对于公式(2),z'_l则表示多头注意力的结果(z_{l-1}先Norm再multi-head attention,得到的结果再与z_{l-1}做残差连接)
  • 对于公式(3),z_l表示最终整个transformer decoder的输出(z'_l先做Norm再做MLP,得到的结果再与z'_l做残差连接)

1.3 ViT的多种尺寸及与CNN的对比

1.3.1 ViT的多种尺寸——ViT-B/16,代表Base 模型,patch 大小为 16×16

值得一提的是,ViT有多种不同尺寸大小的模型,下面几种是比较常用的

模型规格

层数 (Layers)

隐藏层维度 (Hidden Size)

MLP 维度

多头注意力头数 (Heads)

参数量 (Params)

命名示例

常见输入图像尺寸

ViT-Base

12

768

3072

12

86 M

ViT-B/16,代表Base 模型,patch 大小为 16×16

ViT-B/32,代表Base 模型,patch 大小为 32×32

224×224、384×384

ViT-Large

24

1024

4096

16

307 M

ViT-L/16、ViT-L/32

224×224、384×384

ViT-Huge

32

1280

5120

16

632 M

ViT-H/14

224×224、384×384

最后,再通过小绿豆根据ViT的源码画的这个图总结一下

vit-b/16

1.3.2 与CNN在先验知识上的对比

ViT不像CNN那样对图像有比较多的先验知识,即没有用太多的归纳偏置

  1. 具体来说,CNN的局部性locality (以滑动窗口的形式一点一点在图片上进行卷积,故会假设图片上相邻的区域会有近似的特征)

    平移等变性translation equivariance「无论先做平移还是先做卷积,最后的 结果都是一样的,类似f(g(x)) = g(f(x)),毕竟卷积核就像一个模板一样,输入一致的情况下,不论图片移动到哪里,最后的输出都是一样的
    贯穿整个CNN模型的始终
  2. 而对于ViT而言,也就在最后的MLP用到了局部且平移等变性,以及针对每个图片的patch加了位置编码,除这两点之外,ViT没有再专门针对CV问题做任何额外的处理,说白了,就是干:直接拿transformer干CV

所以在中小型的数据集上训练的结果不如CNN也是可以理解的。既如此,transformer的全局建模能力比较强,而CNN又不用那么多的训练数据,那是否可以把这个模型的优势给结合起来呢?

比如做一个混合网络,前头是CNN,后头是transformer呢,答案是:也是可以的! 但这是不是就类似上文介绍过的DETR呢?读者可以继续深入思考下

MAE

 // 待更

第二部分 Swin Transformer:多尺度的ViT(在检测、分割上也能取得不错的效果)

2.1 多尺度的ViT

swin transformer作为多尺度的ViT更加适合处理视觉问题,且证明transformer不但能在ViT所证明的分类任务上取得很好的效果,在检测、分割上也能取得很好的效果,而在结构上,swin transformer主要做了以下两点改进

  1. 获取图像多尺寸的特征
    对于ViT而言,经过12层每一层的transformer都是16×16的patch块(相当于16倍下采样率),虽然通过transformer全局的自注意力操作可以达到全局的建模能力,但它对多尺寸特征的把握则相对弱些,而这个多尺寸的特征有多重要呢,比如对于目标检测而言,用的比较广的一个方法叫FPN,这个方法用的一个分层式的CNN,而每一个卷积层因为不同的感受野则会获取到不同尺寸的特征
    而swin transformer就是为解决ViT只有单一尺寸的特征而来的
  2. 降低序列长度是图像处理中一个很关键的问题,虽然ViT把整张图片打成了16×16的patch,但但图像比较大的时候,计算的复杂度还是比较高
    而Swin Transformer使用窗口Window的形式将16×16的特征图划分成了多个不相交的区域(比如16个4×4的,4个8×8的),并且只在每个小窗口(4×4或8×8)内进行多头注意力计算,大大减少计算量

    之所以能这样操作的依据在于借鉴了CNN中locality先验知识(CNN是以滑动窗口的形式一点一点地在图片上进行卷积,原因在于图片上相邻的区域会有相邻的特征),即同一个物体的不同部位在一个小窗口的范围内是临近着的,从而在小窗口内算自注意力够用

    且swin transformer使用patch merging,可以把相邻的四个小的patch合成一个大的patch(即patch merging),提高了感受野,这样就能获取多尺度的特征(类似CNN中的池化效果),这些特征通过FPN结构就可以做检测,通过UNet结构就可以做分割了

2.1.1 如何让4个互相独立不重叠的窗口彼此交互做自注意力操作:经过一系列shift操作

接下来,我们看下swin transformer中移动窗口的设计,图中灰色的小patch就是4×4的大小,然后最左侧的4个红色小窗口中均默认有7×7=49个小patch(当然,示意图中只展示了16个小patch),如果做接下来几个操作

  1. 把最左侧的整个大窗口layer 1向右下角整体移动两个小patch,并把移动之后的大窗口的最右侧的宽为2个小patch、高为6个小patch的部分平移到大窗口之外的左侧
  2. 且同时把移动之后的大窗口最底部宽为8个小patch、高为2个小patch部分的一分为二:
    \rightarrow  一方面 其左侧部分(宽为6个小patch 高为2个小patch)整体平移到大窗口之外的最上方
    \rightarrow  二方面 最后遗留下来的右下角的2个小patch移动到大窗口之外的最左上角

则成为图中右侧所示的大窗口layer 1+1,从而使得之前互相独立不重叠的4个小窗口在经过这一系列shift操作之后,彼此之间可以进行互动做自注意力的计算了

貌似还是有点抽象是不?没事,我画个图 就一目了然了


如下所示,在右侧加粗的4个新的小窗口内部,每个小窗口都有其他小窗口的信息了(每个小窗口都由之前的单一颜色的patch组成,变成了由4种不同的颜色patch组成,相当于具备了全局的注意力,够直观吧?!)

2.1.2 Swin Transformer模型总览图

以下是整个swin transformer模型的总览图

 从左至右走一遍整个过程则是

  1. stage 1
    patch partition
    对于一张原始图片224×224×3,打成4×4的patch(则每个patch的维度是4×4×3 = 48,其中3是图片的RGB通道),从而会存在224^2/4^2 = 56\times 56个patch,相当于整个图片由[224,224,3]的维度变成了[56,56,48]的维度
  2. linear embedding
    为了变成transformer能接受的值,[56,56,48]的维度变成[56,56,96],最前面的两个维度拉直之后,则维度变成了[3136,96],很明显在ViT里在这一步对应的维度是[196,768],故这个3136的维度太大了,咋办呢?
  3. swin transformer block:基于7×7个小patch的小窗口计算自注意力
    好在swin transformer引入了基于窗口的自注意力机制,而每个窗口默认只有七七四十九个patch,所以序列长度就只有49了,也就解决了计算复杂度的问题
  4. stage 2
    patch mergeing(很像lower level任务中很常用的一个上采样方式:pixel shuffle)
    patch mergeing的作用在于把临近的小patch合并成一个大patch,比如针对下图中维度为[H,W,C]的张量
    由于下采样两倍,所以选点的时候,是每隔一个点选一个,最终整个大张量变成了4个小张量(每隔张量大小为[H/2,W/2])
    之后把这4个张量的在C的维度上拼接起来,拼接之后的张量的维度则就变成了[H/2,W/2,4C]
    接着,在C这个维度上通过一个1×1的卷积操作把张量的维度降了下来(这个1×1的卷积操作类似于linear的作用),最终变成了[H/2,W/2,2C]

    所以对应到模型总览图上则是:[56,56,96]的维度变成了[28,28,192]的维度

  5. stage 3
    维度上从[28,28,192]变成[14,14,384]
  6. stage 4
    维度上从[14,14,384]变成[7,7,768] 

整个前向传播过程走完了之后,可能有读者问,swin transformer如何做分类呢?它为了和CNN保持一致,没有像ViT在输入序列上加一个用于最后分类的CLS token,而是在得到最后的特征图之后,用了一个golbal average polling(即全局池化)的操作,直接把[7,7,768]中的7×1取平均并拉直成1,使得最终的维度变成[1,768]

// 待更..

Logo

惟楚有才,于斯为盛。欢迎来到长沙!!! 茶颜悦色、臭豆腐、CSDN和你一个都不能少~

更多推荐