我自己的原文哦~ https://blog.51cto.com/whaosoft/12443204
#小样本目标检测
这次先是关于他的小样本目标检测 , 用很少的训练示例检测新目标
论文地点:
https://openaccess.thecvf.com/content/CVPR2022/papers/Han_Few-Shot_Object_Detection_With_Fully_Cross-Transformer_CVPR_2022_paper.pdf
小样本目标检测 (FSOD) 旨在利用很少的训练示例检测新目标,最近在社区中引起了极大的研究兴趣。已经证明基于度量学习的方法利用基于双分支的孪生网络对这项任务有效,并计算图像区域和少样本示例之间的相似性以进行检测。
然而,在之前的工作中,两个分支之间的交互只限于检测头,而剩下的数百层用于单独的特性提取。受最近关于视觉转换器和视觉语言转换器的工作的开导,研究者提出了一种新颖的基于完全交织转换器(Fully Cross-Transformer)的FSOD模子 (FCT),方法是将交织转换器整合到特性主干和检测头中。提出了非对称批处理交织注意来聚合来自具有差别批处理大小的两个分支的关键信息。新模子可以通过引入多级交互来改善两个分支之间的少样本相似性学习。PASCAL VOC和MSCOCO FSOD基准的综合实验证明白我们模子的有效性。
以往小样本检测方法大致可以分为俩类:single-branch方法和two-branch方法;前者通常是基于Faster RCNN进行finetuned,需构建multi-class classifier;但该方法针对shot比较少例如1-shot时,较为容易出现过拟合情况;而后者通常时构建siamese网络,分别同时提取query特性和support特性,然后基于metric learning方法比如feature fusion,feature alignment,GCN大概non-local attention来计算俩分支的相似性,由于在Novel种别上无需构建multi-class classifier,所以泛化性更好;俩类方法大致差异如下图所示:
新框架 Task Definition
在小样本目标检测(FSOD)中,有两组类C=Cbase∪Cnovel和Cbase∩Cnovel=∅,其中基类Cbase每个类都有大量训练数据,而新类Cnovel(也称为支持类)只有每个类的训练示例很少(也称为支持图像)。对于K-shot(例如,K=1,5,10)目标检测,研究者为每个新种别c∈Cnovel准确地利用K个边界框解释作为训练数据。FSOD的目标是利用数据丰富的基类来帮忙检测少样本的新类。
Overview of Our Proposed Model (FCT)
研究者以为以往的two-branch方法只关注了detection head部分的特性交互,忽略了特性提取部分;于是这篇论文的motivation就出来了。因此研究者在Faster RCNN上提出了Fully Cross-Transformer(FCT)的小样本检测方法,在每个阶段都进行特性交互。如下图所示:
The Cross-Transformer Feature Backbone
在cross-transformer中计算Q-K-V attention时为了减少计算量,研究者接纳了PVTv2的方式。上面大致介绍了query和support特性提取,在特性交互上作者提出了 Asymmetric-Batched Cross-Attention。详细做法如下图和公式所示:
评论。研究者彻底研究了提出的模子中两个视觉分支之间的多层次交互。cross-transformer特性主干中的三个阶段使两个分支与低级、中级和高级视觉特性逐渐有效交互。
The Cross-Transformer Detection Head
在detection head部分,和以上操作相反,在每张query上提取完proposal之后颠末ROI Align可以得到ROI特性fp∈RBp∗H′∗W′∗C3,其中Bp=100,为了减少计算复杂度还是对support进行ave操作fs′=1Bs∑Bsfs,fs′∈R1∗H′∗W′∗C3,然后利用Asymmetric-Batched Cross-Attention计算俩分支attention,差别的是,query分支Bp≥1 and Bs′=1 。
实验
从上面表格的(c-d)俩行可以看出,利用三阶段训练在2-shot、10-shot上均有提拔。
#RTFormer~1
好了开始第二段 RTFormer
丢掉多头机制!百度开源RTFormer , 构建一个超快速语义分割Transformer方案
对于及时语义分割,由于 Transformer 的计算机制耗时,纯基于 CNN 的方法在该领域仍旧占主导职位。本文提出了 RTFormer,一种用于及时语义分割的高效双分辨率转换器,与基于 CNN 的模子相比,它在性能和服从之间取得了更好的平衡。
论文链接:https://arxiv.org/abs/2210.07124 [NeurIPS2022]
代码地点:https://github.com/PaddlePaddle/PaddleSeg
尽管Transformer方案在语义分割领域取得了非常惊人的性能,但在及时性方面,纯CNN方案仍占据主流职位。本文提出了一种用于及时语义分割的高效对偶分辨率Transformer方案RTFormer,它具有比CNN方案更佳的性能-服从均衡。
为达成GPU设备上的高推理服从,所提RTFormer接纳了线性复杂度的GPU友好注意力模块,同时消除了多头机制。别的,作者发现:跨注意力机制对于全局上下文信息聚合非常有效。多个主流基准数据集(Cityscapes, CamVid, COCOStuff, ADE20K)上的实验效果验证了所提RTFormer的有效性。下图给出了CAMVid数据集上差别方案的性能与推理速率对比,很显着:RTFormer具有最佳的性能-速率均衡。
出发点
ViT技术在CV领域证实其有效性后,相关技术敏捷在各个领域取得了一系列的成果。比如语义分割领域的DPT、SegFormer、HRFormer、Segmentor等均取得了非常优异的效果。但是,相比CNN方案,Transformer方案因自注意力机制题目存在高计算量、高显存占用题目,导致其推理服从显着不如CNN方案。
作者以为:注意力机制在推理服从方面的瓶颈重要源自以下两个维度:
- 现有注意力机制的计算属性对于GPU设备不够友好,如二次复杂度、多头机制;
- 仅在高分辨率特性图实施注意力大概并非最有效捕获长隔断上下文关系的方案,这是因为高分辨率特性的单个特性向量的感受野非常有限。
基于上述所提到的两个局限性,本文提出了一种GPU友好的注意力模块与跨分辨率注意力模块,并由此构建了RTFormer。
接下来,我们首先对本文所提GPU友好注意力RTFormer模块进行介绍,然后结合如何基于RTFormer模块构建RTFormer分割架构。
#RTFormer~2
上图给出了RTFormer架构示意图,它由Backbone与SegHead两部分构成:
- Backbone:为提取高分辨率特性所需的局部信息,作者将卷积与所提RTFormer模块进行了组合以构建RTFormer。详细来说,RTFormer的Stem、Stage1以及Stage2部分由卷积与残差模块构成,从Stage3开始接纳所提对偶分辨率模块以促进高分辨率分支与低分辨率分支特性的信息交互,在后三个阶段,高分辨率特性的stride保持为8不变,而低分辨率的stride则分别为16、32、32。值得阐明的是,Stage分与Stage5由本文所提RTFormer模块构成以促进高效全局上下文建模,而Stage3则仍由残差模块构建。
上表给出了RTFormer-Slim与RTFormer-Base的架构配置信息,很显着,RTFormer的骨干部分由5个stage构成,其中3-5stage由对偶分辨率特性构成,在分割头方面,RTFormer引入了DPPM模块进一步提取多标准特性。
实验
上表与图给出了Cityscapes与CamVid数据集上的性能对比,从中可以看到:
- 在Cityscapes数据集上,在全部及时分割方案中,RTFormer取得了最佳的速率-精度均衡;
- RTFormer-Slim取得了76.3%mIoU指标且推理速率高达110.0FPS,优于STDC2-Seg75与DDRNet-23-Slim。
- RTFormer-Base取得了79.3%mIoU指标且推理速率高达39.1FPS,取得了新的SOTA效果。
- 在CamVid数据集上,所提方案仅需ImageNet预训练即取得了82.5%mIoU指标且推理速率达94.FPS,优于接纳额外Cityscapes预训练的STDC2-Seg;
- RTFormer-Slim仅需4.8M参数即取得了81.4mIoU指标且推理速率高达190.7FPS,优于STDC2-Seg与DDRNet-23;
- 从视觉效果方面来看,RTFormer-Base具有更佳的细粒度分解效果。
上表与图给出了ADE20K数据集上差别方案的性能对比,从中可以看到:
- RTFormer-Base取得了42.1%mIoU指标且推理速率达71.4FPS,优于其他方案;
- 相比DDRNet-23-Slim,RTFormer-SLim取得了36.7%mIoU指标,同时保持相当的速率。
- 从视觉效果可以看到:相比DDRNet-23,所提方案的分割效果具有更好的细节与上下文信息。
代码
本文代码已经开源在PaddleSeg仓库,笔者对其核心code进行摘录如下:
- class RTFormer(nn.Layer):
- def __init__(self, ...):
- super().__init__()
- ...
- def forward(self, x):
- x1 = self.layer1(self.conv1(x))
- x2 = self.layer2(self.relu(x1))
-
- # stage-3
- x3 = self.layer3(self.relu(x2))
- x3_ = x2 + F.interpolate(self.compression3(x3), size=paddle.shape(x2)[2:], mode='bilienar')
- x3_ = self.layer3_(self.relu(x3_))
-
- # stage-4与stage-5的计算流程类似stage-3
- x4_ = ...
- x5_ = ...
-
- # SegHead, DAPPM
- x6 = self.spp(x5)
- x6 = x6 + F.interpolate(x6, size=paddle.shape(x5_), mode='bilinear')
- x_out = self.seghead(paddle.concat([x5_, x6], axis=1))
- return F.interpolate(x_out, paddle.shape(x)[2:], mode='bilinear')
- class ExternalAttention(nn.Layer):
- def __init__(self, ...)
- super().__init__()
-
- def _act_sn(self, x):
- x = x.reshape([-1, self.inter_channels, 0, 0]) * (self.inter_channels ** -0.5)
- x = F.softmax(x, axis=1)
- x = x.reshape([1, -1, 0, 0])
- def _act_dn(self, x):
- x_shape = paddle.shape(x)
- h, w = x_shape[2], x_shape[3]
- x = x.reshape([0, self.num_heads, self.inter_channels //self.num_heads, -1])
- x = F.softmax(x, axis=3)
- x = x / (paddle.sum(x, axis=2, keepdim=True) + 1e-06)
- x = x.reshape([0, self.inter_channels, h, w])
-
- def forward(self, x, cross_k=None, cross_v=None):
- x = self.norm(x)
- if not self.use_cross_kv:
- x = F.conv2d(x, self.k, bias=None, stride=2 if not self.same_in_out_chs else 1, padding=0)
- x = self._act_dn(x) # n,c_inter,h,w
- x = F.conv2d(x, self.v, bias=None, stride=1, padding=0)
- else:
- B = x.shape[0]
- x = x.reshape([1, -1, 0, 0]) # n,c_in,h,w -> 1,n*c_in,h,w
- x = F.conv2d(x, cross_k, bias=None, stride=1, padding=0, groups=B)
- x = self._act_sn(x)
- x = F.conv2d(x, cross_v, bias=None, stride=1, padding=0, groups=B)
- x = x.reshape([-1, self.in_channels, 0, 0])
- return x
复制代码
#RTFormer block
上图给出了本文所提RTFormer模块示意图,它是一种对偶分辨率模块,它包罗两种类型注意力模块。在低分辨率分支,作者接纳了GPU友好的注意力模块以捕获高层全局上下文信息,而在高分辨率分支,作者则引入了跨分辨率注意力机制对高层全局上下文信息进行传播扩散,也就是将两个分辨率的特性通过注意力模块进行聚合。
- 它使得矩阵乘操作成为一体且非常适合于GPU设备;
- 它在某种程度上了保持了多头机制的上风。
Cross-resolution Attention 已有研究证实:多分辨率融合对于稠密预测任务非常有效。对于多分辨率架构计划,我们在差别分支独立实行GAF处理,然后再进行信息交互。作者以为:直接在高分辨率特性实行注意力对于全局上下文学习不够高效。为更有效的获得全局上下文信息,作者提出了跨分辨率注意力,它可以充分利用从低分辨率分支学到的高层语义信息。该过程可描述如下:
需要注意的是:考虑到GPU设备的快速推理因素,这里同样消除了多头机制。
#CLIPPO
CLIPPO 是一种同一的模子,用单个编码器和对比丧失来实行图像、文本和多模态任务,优于传统的 NLP 基线和之前基于像素的掩码语言模子。
比年来,基于 Transformer 的大规模多模态训练促成了差别领域最新技术的改进,包罗视觉、语言和音频。特别是在计算机视觉和图像语言理解方面,单个预训练大模子可以优于特定任务的专家模子。
然而,大型多模态模子通常利用模态或特定于数据集的编码器息争码器,并相应地导致涉及的协议。例如,此类模子通常涉及在各自的数据集上对模子的差别部分进行差别阶段的训练,并进行特定于数据集的预处理,或以特定于任务的方式迁徙差别部分。这种模式和特定于任务的组件大概会导致额外的工程复杂性,并在引入新的预训练丧失或卑鄙任务时面临挑衅。
因此,开辟一个可以处理任何模态或模态组合的单一端到端模子,将是多模态学习的重要一步。本文中,来自谷歌研究院(谷歌大脑团队)、苏黎世的研究者将重要关注图像和文本。
论文地点:https://arxiv.org/pdf/2212.08045.pdf
很多关键同一加速了多模式学习的进程。首先经证实,Transformer 架构可以作为通用主干,而且在文本、视觉、音频和其他领域上表现精良。其次,很多论文探索了将差别的模态映射到单个共享嵌入空间以简化输入 / 输出接口,或开辟一个用于多个任务的单一接口。第三,模态的替代表现允许在一个领域中利用另一个领域计划的神经架构或训练程序。例如,[54] 和 [26,48] 分别表现文本和音频,通过将这些形式出现为图像(在音频的情况下为频谱图)进行处理。
本文将对利用纯基于像素的模子进行文本和图像的多模态学习进行探索。该模子是一个单独的视觉 Transformer,它处理视觉输入或文本,或两者一起,全部都出现为 RGB 图像。全部模态都利用雷同的模子参数,包罗低级特性处理;也就是说,不存在特定于模态的初始卷积、tokenization 算法或输入嵌入表。该模子仅用一个任务训练:对比学习,正如 CLIP 和 ALIGN 所推广的那样。因此模子被称作 CLIP-Pixels Only(CLIPPO)。
在 CLIP 计划用于图像分类和文本 / 图像检索的重要任务上,尽管没有特定的 tower 模态,CLIPPO 的表现也与 CLIP 相似(相似度在 1-2% 之内)。令人惊讶的是,CLIPPO 不需要任何从左到右的语言建模、掩码语言建模或显式的词级丧失,就可以实行复杂的语言理解任务。特别是在 GLUE 基准测试上,CLIPPO 优于经典的 NLP 基线,如 ELMO+BiLSTM+attention,别的,CLIPPO 还优于基于像素的掩码语言模子,并接近 BERT 的分数。
风趣的是,当简单地将图像和文本一起渲染时,CLIPPO 也可以在 VQA 上获得精良的性能,尽管从未在此类数据上进行预训练。与通例语言模子相比,基于像素的模子的一个直接上风是不需要预先确定词汇。因此,与利用经典 tokenizer 的等效模子相比,多语言检索的性能有所提高。末了,该研究还发现,在某些情况下训练 CLIPPO 时,之前观察到的模态差距有所减少。
方法概览
CLIP 已经成为一种强大的、可扩展的范式,用于在数据集上训练多用途视觉模子。详细来说,这种方法依靠于图像 /alt-text 对,这些可以从网络上大规模自动网络。因此,文本描述通常是有噪音的,而且大概由单个关键字、关键字集或潜在的冗长描述组成。利用这些数据,联合训练两个编码器,即嵌入 alt-text 的文本编码器和将相应图像嵌入共享潜在空间的图像编码器。这两个编码器利用对比丧失进行训练,鼓励相应图像和 alt-text 的嵌入相似,同时与全部其他图像和 alt-text 的嵌入差别。
一旦颠末训练,这样的编码器对可以以多种方式利用:它可以通过文本描述对固定的视觉概念集进行分类(零样本分类); 嵌入可用于检索给定文本描述的图像,反之亦然;大概,视觉编码器可以通过对标记的数据集进行微调或通过在冻结的图像编码器表现上训练头部,以有监督的方式传输到卑鄙任务。原则上,文本编码器可以作为一个独立的文本嵌入利用,不过据悉,还没有人针对这种应用展开深入探究,一些研究引用了低质量的 alt-text 导致文本编码器的语言建模性能较弱。
从前的工作表明,图像和文本编码器可以用一个共享 transformer 模子(也称为单塔模子,或 1T-CLIP)实现,其中图像利用 patch embedding 嵌入,tokenized 文本利用单独的 word embedding 嵌入。除了模态特定的嵌入外,两种模态的全部模子参数都是共享的。固然这种类型的共享通常会导致图像 / 图像 - 语言任务的性能下降,但它也使模子参数的数量减少了一半。
CLIPPO 将这一想法更进一步:文本输入出如今空白图像上,随后完全作为图像处理,包罗初始的 patch embedding(参见图 1)。通过与之前的工作进行对比训练,生成了一个单一的视觉 transformer 模子,它可以通过单一的视觉接口来理解图像和文本,并提供了一个可以用于解决图像、图像 - 语言和纯语言理解任务的单一表现。
除了多模态多功能性,CLIPPO 还减轻了文本处理的常见困难,即开辟适当的 tokenizer 和词汇表。这在大量多语言设置的上下文中特别风趣,其中文本编码器必须处理数十种语言。
可以发现,在图像 /alt-text 对上训练的 CLIPPO 在公共图像和图像语言基准上的表现与 1T-CLIP 相当,而且在 GLUE 基准上与强大的基线语言模子竞争。然而,由于 alt-texts 的质量较低,通常不是语法句子,仅从 alt-texts 学习语言理解从根本上是有限的。因此,可以在图像 /alt-texts 对比预训练中到场基于语言的对比训练。详细而言,需要考虑到从文本语料库中采样的一连句对,差别语言的翻译句对,后翻译句对,以及有单词缺失的句子对。
实验效果
视觉和视觉-语言理解
图像分类与检索。表 1 显示了 CLIPPO 的性能,可以看到,与 CLIP∗ 相比,CLIPPO 和 1T-CLIP 产生了 2-3 个百分点的绝对下降。
VQA。图 2 中陈诉了模子和基线的 VQAv2 评分。可以看到,CLIPPO 优于 CLIP∗ 、1T-CLIP,以及 ViT-B/16,获得了 66.3 的分数。
多语言视觉 - 语言理解
图 3 表明,CLIPPO 实现了与这些基线相当的检索性能。在 mT5 的情况下,利用额外的数据可以提高性能;在多语言上下文中利用这些额外的参数和数据将是 CLIPPO 将来一个风趣的方向。
语言理解
表 2 显示了 CLIPPO 和基线的 GLUE 基准测试效果。可以观察到,在 WebLI 上训练的 CLIPPO 与 BiLSTM+Attn+ELMo 基线(其具有在大型语言语料库上训练的深度词嵌入)相比具有竞争力。别的,我们还可以看到,CLIPPO 和 1T-CLIP 优于利用标准对比语言视觉预训练训练的语言编码器。
#Transformers综述
这是一篇综述吧~
Transformer整个网络布局完全由Attention机制组成,其出色的性能在多个任务上都取得了非常好的效果。本文从Transformer的布局出发,结合视觉中的成果进行了分析,能够帮助初学者们快速入门。
transformer布局是google在17年的Attention Is All You Need论文中提出,在NLP的多个任务上取得了非常好的效果,可以说如今NLP发展都离不开transformer。最大特点是抛弃了传统的CNN和RNN,整个网络布局完全是由Attention机制组成。由于其出色性能以及对卑鄙任务的友好性大概说卑鄙任务仅仅微调即可得到不错效果,在计算机视觉领域不停有人实验将transformer引入,近期也出现了一些效果不错的实验,典型的如目标检测领域的detr和可变形detr,分类领域的vision transformer等等。本文从transformer布局出发,结合视觉中的transformer成果(详细是vision transformer和detr)进行分析,希望能够帮助cv领域想相识transformer的初学者快速入门。由于本人接触transformer时间也不长,也算初学者,故如果有描述大概理解错误的地方欢迎指正。
transformer介绍
一般讲解transformer都会以机器翻译任务为例子讲解,机器翻译任务是指将一种语言转换得到另一种语言,例如英语翻译为中文任务。从最上层来看,如下所示:
早期seq2seq
机器翻译是一个历史悠久的题目,本质可以理解为序列转序列题目,也就是我们常说的seq2seq布局,也可以称为encoder-decoder布局,如下所示:
encoder和decoder在早期一般是RNN模块(因为其可以捕获时序信息),后来引入了LSTM大概GRU模块,不管内部组件是啥,其核心头脑都是通过Encoder编码成一个表现向量,即上下文编码向量,然后交给Decoder来进行解码,翻译成目标语言。一个接纳典型RNN进行编码码翻译的可视化图如下:
可以看出,其解码过程是顺序进行,每次仅解码出一个单词。对于CV领域初学者来说,RNN模块构建的seq2seq算法,理解到这个程度就可以了,不需要深入探究如何进行训练。但是上述布局实在有缺陷,详细来说是:
- 岂论输入和输出的语句长度是什么,中心的上下文向量长度都是固定的,一旦长度过长,仅仅靠一个固定长度的上下文向量显着不公道
- 仅仅利用上下文向量解码,会有信息瓶颈,长度过长时间信息大概会丢失
通俗理解是编码器与解码器的连接点仅仅是编码单元输出的隐含向量,其包罗的信息有限,对于一些复杂任务大概信息不够,如要翻译的句子较长时,一个上下文向量大概存不下那么多信息,就会造成翻译精度的下降。
基于attention的seq2seq
基于上述缺陷进而提出带有注意力机制Attention的seq2seq,同样可以应用于RNN、LSTM大概GRU模块中。注意力机制Attention对人类来说非常好理解,假设给定一张图片,我们会自动聚焦到一些关键信息位置,而不需要逐行扫描全图。此处的attention也是同一个意思,其本质是对输入的自适应加权,结合cv领域的senet中的se模块就能够理解了。
se模块终极是学习出一个1x1xc的向量,然后逐通道乘以原始输入,从而对特性图的每个通道进行加权即通道注意力,对attention进行抽象,不管啥领域其机制都可以归纳为下图:
将Query(通常是向量)和4个Key(和Q长度雷同的向量)分别计算相似性,然后颠末softmax得到q和4个key相似性的概率权重分布,然后对应权重乘以Value(和Q长度雷同的向量),末了相加即可得到包罗注意力的attention值输出,理解上应该不难。举个简单例子阐明:
- 假设世界上全部小吃都可以被标签化,例如微辣、特辣、变态辣、微甜、有嚼劲....,总共有1000个标签,如今我想要吃的小吃是[微辣、微甜、有嚼劲],这三个单词就是我的Query
- 来到东门老街一共100家小吃点,每个店铺卖的东西不一样,但是肯定可以被标签化,例如第一家小吃被标签化后是[微辣、微咸],第二家小吃被标签化后是[特辣、微臭、特咸],第二家小吃被标签化后是[特辣、微甜、特咸、有嚼劲],其余店铺都可以被标签化,每个店铺的标签就是Keys,但是每家店铺由于卖的东西不一样,单品种类也不一样,所以被标签化后每一家的标签List不一样长
- Values就是每家店铺对应的单品,例如第一家小吃的Values是[烤羊肉串、炒花生]
- 将Query和全部的Keys进行一一比对,相当于计算相似性,此时就可以知道我想买的小吃和每一家店铺的匹配情况,末了有了匹配列表,就可以去店铺里面买东西了(Values和相似性加权求和)。终极的情况大概是,我在第一家店铺买了烤羊肉串,然后在第10家店铺买了个玉米,末了在第15家店铺买了个烤面筋
以上就是完备的注意力机制,接纳我心中的标准Query去和被标签化的全部店铺Keys一一比对,此时就可以得到我的Query在每个店铺中的匹配情况,终极去差别店铺买差别东西的过程就是权重和Values加权求和过程。简要代码如下:
- # 假设q是(1,N,512),N就是最大标签化后的list长度,k是(1,M,512),M可以等于N,也可以不相等
- # (1,N,512) x (1,512,M)-->(1,N,M)
- attn = torch.matmul(q, k.transpose(2, 3))
- # softmax转化为概率,输出(1,N,M),表示q中每个n和每个m的相关性
- attn=F.softmax(attn, dim=-1)
- # (1,N,M) x (1,M,512)-->(1,N,512),V和k的shape相同
- output = torch.matmul(attn, v)
复制代码 带有attention的RNN模块组成的ser2seq,解码时间可视化如下:
在没有attention时间,差别解码阶段都仅仅利用了同一个编码层的末了一个隐含输出,到场attention后可以通过在每个解码时间步输入的都是差别的上下文向量,以上图为例,解码阶段会将第一个开启解码标记<START>(也就是Q)与编码器的每一个时间步的隐含状态(一系列Key和Value)进行点乘计算相似性得到每一时间步的相似性分数,然后通过softmax转化为概率分布,然后将概率分布和对应位置向量进行加权求和得到新的上下文向量,末了输入解码器中进行解码输出,其详细解码可视化如下:
通过上述简单的attention引入,可以将机器翻译性能大幅提拔,引入attention有以下几个好处:
- 注意力明显提高了机器翻译性能
- 注意力允许解码器以差别程度的权重利用到编码器的全部信息,可以绕过瓶颈
- 通过检查注意力分布,可以看到解码器在关注什么,可表明性强
基于transformer的seq2seq
基于attention的seq2seq的布局固然说解决了很多题目,但是其依然存在不敷:
- 不管是接纳RNN、LSTM还是GRU都不利于并行训练和推理,因为相关算法只能从左向右依次计算大概从右向左依次计算
- 长依靠信息丢失题目,顺序计算过程中信息会丢失,固然LSTM号称有缓解,但是无法彻底解决
最大题目应该是无法并行训练,不利于大规模快速训练和部署,也不利于整个算法领域发展,故在Attention Is All You Need论文中抛弃了传统的CNN和RNN,将attention机制发挥到底,整个网络布局完全是由Attention机制组成,这是一个比较大的进步。
google所提基于transformer的seq2seq团体布局如下所示:
其包罗6个布局完全雷同的编码器,和6个布局完全雷同的解码器,其中每个编码器息争码器计划头脑完全雷同,只不过由于任务差别而有些许区别,团体详细布局如下所示:
第一眼看有点复杂,其中N=6,由于基于transformer的翻译任务已经转化为分类任务(目标翻译句子有多长,那么就有多少个分类样本),故在解码器末了会引入fc+softmax层进行概率输出,训练也比较简单,直接接纳ce loss即可,对于接纳大量数据训练好的预训练模子,卑鄙任务仅仅需要训练fc层即可。上述布局看起来有点复杂,一个稍微抽象点的图示如下:
看起来比基于RNN大概其余布局构建的seq2seq简单很多。下面结合代码和原理进行深入分析。
transformer深入分析
前面写了一大堆,没有理解没有关系,对于cv初学者来说实在只需要理解QKV的寄义和注意力机制的三个计算步骤 和全部K计算相似性;对相似性接纳softmax转化为概率分布;将概率分布和V进行一一对应相乘,末了相加得到新的和Q一样长的向量输出即可,重点是下面要讲的transformer布局。
下面按照 编码器输入数据处理->编码器运行->解码器输入数据处理->解码器运行->分类head 的现实运行流程进行讲解。
编码器输入数据处理
(1) 源单词嵌入
以上面翻译任务为例,原始待翻译输入是三个单词:
输入是三个单词,为了能够将文本内容输入到网络中肯定需要进行向量化(否则单词如何计算?),详细是接纳nlp领域的embedding算法进行词嵌入,也就是常说的Word2Vec。对于cv来说知道是干嘛的就行,不必相识细节。假设每个单词都可以嵌入成512个长度的向量,故此时输入即为3x512,注意Word2Vec操作只会输入到第一个编码器中,背面的编码器担当的输入是前一个编码器输出。
为了便于组成batch(差别训练句子单词个数肯定不一样)进行训练,可以简单统计全部训练句子的单词个数,取最大即可,假设统计后发现待翻译句子最长是10个单词,那么编码器输入是10x512,额外填充的512维向量可以接纳固定的标记编码得到,例如$$。
(2) 位置编码positional encoding
接纳颠末单词嵌入后的向量输入到编码器中还不够,因为transformer内部没有类似RNN的循环布局,没有捕捉顺序序列的能力,大概说无论句子布局怎么打乱,transformer都会得到类似的效果。为相识决这个题目,在编码词向量时会额外引入了位置编码position encoding向量表现两个单词i和j之间的隔断,简单来说就是在词向量中到场了单词的位置信息。
到场位置信息的方式非常多,最简单的可以是直接将绝对坐标0,1,2编码成512个长度向量即可。作者现实上提出了两种方式:
提前假设单词嵌入而且组成batch后,shape为(b,N,512),N是序列最大长度,512是每个单词的嵌入向量长度,b是batch
(a) 网络自动学习
- self.pos_embedding = nn.Parameter(torch.randn(1, N, 512))
复制代码 比较简单,因为位置编码向量需要和输入嵌入(b,N,512)相加,所以其shape为(1,N,512)表现N个位置,每个位置接纳512长度向量进行编码
(b) 自己定义规则
自定义规则做法非常多,论文中接纳的是sin-cos规则,详细做法是:
pos即0~N,i是0-511
- 将向量的512维度切分为奇数行和偶数行
- 偶数行接纳sin函数编码,奇数行接纳cos函数编码
- 然后按照原始行号拼接
- def get_position_angle_vec(position):
- # d_hid是0-511,position表示单词位置0~N-1
- return [position / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)]
- # 每个单词位置0~N-1都可以编码得到512长度的向量
- sinusoid_table = np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)])
- # 偶数列进行sin
- sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2]) # dim 2i
- # 奇数列进行cos
- sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2]) # dim 2i+1
复制代码 上面例子的可视化如下:
如此编码的优点是能够扩展到未知的序列长度,例如前向时间有特别长的句子,其可视化如下:
作者为啥要计划如此复杂的编码规则?原因是sin和cos的如下特性:
假设k=1,那么下一个位置的编码向量可以由前面的编码向量线性表现,等价于以一种非常容易学会的方式告诉了网络单词之间的绝对位置,让模子能够轻松学习到相对位置信息。注意编码方式不是唯一的,将单词嵌入向量和位置编码向量相加就可以得到编码器的真正输入了,其输出shape是(b,N,512)。
编码器前向过程
编码器由两部分组成:自注意力层和前馈神经网络层。
其前向可视化如下:
注意上图没有绘制出单词嵌入向量和位置编码向量相加过程,但是是存在的。
(1) 自注意力层
通过前面分析我们知道自注意力层实在就是attention操作,而且由于其QKV来自同一个输入,故称为自注意力层。我想各人应该能想到这里attention层作用,在参考资料1博客里面举了个简单例子来阐明attention的作用:假设我们想要翻译的输入句子为The animal didn't cross the street because it was too tired,这个“it”在这个句子是指什么呢?它指的是street还是这个animal呢?这对于人类来说是一个简单的题目,但是对于算法则不是。当模子处理这个单词“it”的时间,自注意力机制会允许“it”与“animal”建立联系即随着模子处理输入序列的每个单词,自注意力会关注整个输入序列的全部单词,帮助模子对本单词更好地进行编码。现实上训练完成后确实如此,google提供了可视化工具,如下所示:
上述绘制的不是矩阵形式,更好理解而已。对于第一个单词的编码过程是:将q1和全部的k进行相似性计算,然后除以维度的平方根(论文中是64,本文可以以为是512)使得梯度更加稳固,然后通过softmax转达效果,这个softmax分数决定了每个单词对编码当下位置(“Thinking”)的贡献,末了对加权值向量求和得到z1。
这个计算很显着就是前面说的注意力机制计算过程,每个输入单词的编码输出都会通过注意力机制引入其余单词的编码信息。
上述为了方便理解才拆分这么过细,现实上代码层面接纳矩阵实现非常简单:
上面的操作很不错,但是还有改进空间,论文中又增长一种叫做“多头”注意力(“multi-headed” attention)的机制进一步美满了自注意力层,并在两方面提高了注意力层的性能:
- 它扩展了模子专注于差别位置的能力。在上面的例子中,固然每个编码都在z1中有或多或少的表现,但是它大概被现实的单词自己所支配。如果我们翻译一个句子,比如“The animal didn’t cross the street because it was too tired”,我们会想知道“it”指的是哪个词,这时模子的“多头”注意机制会起到作用。
- 它给出了注意力层的多个“表现子空间",对于“多头”注意机制,有多个查询/键/值权重矩阵集(Transformer利用8个注意力头,因此我们对于每个编码器/解码器有8个矩阵集合)。
简单来说就是类似于分组操作,将输入X分别输入到8个attention层中,得到8个Z矩阵输出,末了对效果concat即可。论文图示如下:
先忽略Mask的作用,左边是单头attention操作,右边是n个单头attention构成的多头自注意力层。
代码层面非常简单,单头attention操作如下:
- class ScaledDotProductAttention(nn.Module):
- ''' Scaled Dot-Product Attention '''
- def __init__(self, temperature, attn_dropout=0.1):
- super().__init__()
- self.temperature = temperature
- self.dropout = nn.Dropout(attn_dropout)
- def forward(self, q, k, v, mask=None):
- # self.temperature是论文中的d_k ** 0.5,防止梯度过大
- # QxK/sqrt(dk)
- attn = torch.matmul(q / self.temperature, k.transpose(2, 3))
- if mask is not None:
- # 屏蔽不想要的输出
- attn = attn.masked_fill(mask == 0, -1e9)
- # softmax+dropout
- attn = self.dropout(F.softmax(attn, dim=-1))
- # 概率分布xV
- output = torch.matmul(attn, v)
- return output, attn
复制代码 再次复习下Multi-Head Attention层的图示,可以发如今前面讲的内容基础上还到场了残差计划和层归一化操作,目的是为了防止梯度消失,加快收敛。

Multi-Head Attention实如今ScaledDotProductAttention基础上构建:
- class MultiHeadAttention(nn.Module):
- ''' Multi-Head Attention module '''
- # n_head头的个数,默认是8
- # d_model编码向量长度,例如本文说的512
- # d_k, d_v的值一般会设置为 n_head * d_k=d_model,
- # 此时concat后正好和原始输入一样,当然不相同也可以,因为后面有fc层
- # 相当于将可学习矩阵分成独立的n_head份
- def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1):
- super().__init__()
- # 假设n_head=8,d_k=64
- self.n_head = n_head
- self.d_k = d_k
- self.d_v = d_v
- # d_model输入向量,n_head * d_k输出向量
- # 可学习W^Q,W^K,W^V矩阵参数初始化
- self.w_qs = nn.Linear(d_model, n_head * d_k, bias=False)
- self.w_ks = nn.Linear(d_model, n_head * d_k, bias=False)
- self.w_vs = nn.Linear(d_model, n_head * d_v, bias=False)
- # 最后的输出维度变换操作
- self.fc = nn.Linear(n_head * d_v, d_model, bias=False)
- # 单头自注意力
- self.attention = ScaledDotProductAttention(temperature=d_k ** 0.5)
- self.dropout = nn.Dropout(dropout)
- # 层归一化
- self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
- def forward(self, q, k, v, mask=None):
- # 假设qkv输入是(b,100,512),100是训练每个样本最大单词个数
- # 一般qkv相等,即自注意力
- residual = q
- # 将输入x和可学习矩阵相乘,得到(b,100,512)输出
- # 其中512的含义其实是8x64,8个head,每个head的可学习矩阵为64维度
- # q的输出是(b,100,8,64),kv也是一样
- q = self.w_qs(q).view(sz_b, len_q, n_head, d_k)
- k = self.w_ks(k).view(sz_b, len_k, n_head, d_k)
- v = self.w_vs(v).view(sz_b, len_v, n_head, d_v)
- # 变成(b,8,100,64),方便后面计算,也就是8个头单独计算
- q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
- if mask is not None:
- mask = mask.unsqueeze(1) # For head axis broadcasting.
- # 输出q是(b,8,100,64),维持不变,内部计算流程是:
- # q*k转置,除以d_k ** 0.5,输出维度是b,8,100,100即单词和单词直接的相似性
- # 对最后一个维度进行softmax操作得到b,8,100,100
- # 最后乘上V,得到b,8,100,64输出
- q, attn = self.attention(q, k, v, mask=mask)
- # b,100,8,64-->b,100,512
- q = q.transpose(1, 2).contiguous().view(sz_b, len_q, -1)
- q = self.dropout(self.fc(q))
- # 残差计算
- q += residual
- # 层归一化,在512维度计算均值和方差,进行层归一化
- q = self.layer_norm(q)
- return q, attn
复制代码 如今pytorch新版本已经把MultiHeadAttention当做nn中的一个类了,可以直接调用。
(2) 前馈神经网络层
这个层就没啥说的了,非常简单:
- class PositionwiseFeedForward(nn.Module):
- ''' A two-feed-forward-layer module '''
- def __init__(self, d_in, d_hid, dropout=0.1):
- super().__init__()
- # 两个fc层,对最后的512维度进行变换
- self.w_1 = nn.Linear(d_in, d_hid) # position-wise
- self.w_2 = nn.Linear(d_hid, d_in) # position-wise
- self.layer_norm = nn.LayerNorm(d_in, eps=1e-6)
- self.dropout = nn.Dropout(dropout)
- def forward(self, x):
- residual = x
- x = self.w_2(F.relu(self.w_1(x)))
- x = self.dropout(x)
- x += residual
- x = self.layer_norm(x)
- return x
复制代码 (3) 编码层操作团体流程
可视化如下所示:
单个编码层代码如下所示:
- class EncoderLayer(nn.Module):
- def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
- super(EncoderLayer, self).__init__()
- self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
- self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)
- def forward(self, enc_input, slf_attn_mask=None):
- # Q K V是同一个,自注意力
- # enc_input来自源单词嵌入向量或者前一个编码器输出
- enc_output, enc_slf_attn = self.slf_attn(
- enc_input, enc_input, enc_input, mask=slf_attn_mask)
- enc_output = self.pos_ffn(enc_output)
- return enc_output, enc_slf_attn
复制代码 将上述编码过程重复n遍即可,除了第一个模块输入是单词嵌入向量与位置编码的和外,其余编码层输入是上一个编码器输出即背面的编码器输入不需要位置编码向量。如果考虑n个编码器的运行过程,如下所示:
- class Encoder(nn.Module):
- def __init__(
- self, n_src_vocab, d_word_vec, n_layers, n_head, d_k, d_v,
- d_model, d_inner, pad_idx, dropout=0.1, n_position=200):
- # nlp领域的词嵌入向量生成过程(单词在词表里面的索引idx-->d_word_vec长度的向量)
- self.src_word_emb = nn.Embedding(n_src_vocab, d_word_vec, padding_idx=pad_idx)
- # 位置编码
- self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)
- self.dropout = nn.Dropout(p=dropout)
- # n个编码器层
- self.layer_stack = nn.ModuleList([
- EncoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)
- for _ in range(n_layers)])
- # 层归一化
- self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
- def forward(self, src_seq, src_mask, return_attns=False):
- # 对输入序列进行词嵌入,加上位置编码
- enc_output = self.dropout(self.position_enc(self.src_word_emb(src_seq)))
- enc_output = self.layer_norm(enc_output)
- # 作为编码器层输入
- for enc_layer in self.layer_stack:
- enc_output, _ = enc_layer(enc_output, slf_attn_mask=src_mask)
- return enc_output
复制代码 到如今为止我们就讲完了编码部分的全部流程和代码细节。如今再来看整个transformer算法就会感觉亲切很多了:
解码器输入数据处理
在分析解码器布局前先看下解码器团体布局,方便理解:
其输入数据处理也要区分第一个解码器和后续解码器,和编码器类似,第一个解码器输入不仅包罗末了一个编码器输出,还需要额外的输出嵌入向量,而后续解码器输入是来自末了一个编码器输出和前面解码器输出。
(1) 目标单词嵌入
这个操作和源单词嵌入过程完全雷同,维度也是512,假设输出是i am a student,那么需要对这4个单词也利用word2vec算法转化为4x512的矩阵,作为第一个解码器的单词嵌入输入。
(2) 位置编码
同样的也需要对解码器输入引入位置编码,做法和编码器部分完全雷同,且将目标单词嵌入向量和位置编码向量相加即可作为第一个解码器输入。
和编码器单词嵌入差别的地方是在进行目标单词嵌入前,还需要将目标单词即是i am a student右移动一位,新增长的一个位置接纳提前定义好的标记位BOS_WORD代替,如今就酿成[BOS_WORD,i,am,a,student],为啥要右移?因为解码过程和seq2seq一样是顺序解码的,需要提供一个开始解码标记,。否则第一个时间步的解码单词i是如何输出的呢?详细解码过程实在是:输入BOS_WORD,解码器输出i;输入前面已经解码的BOS_WORD和i,解码器输出am...,输入已经解码的BOS_WORD、i、am、a和student,解码器输出解码结束标记位EOS_WORD,每次解码都会利用前面已经解码输出的全部单词嵌入信息
下面有个非常清晰的gif图,一目了然:
上图没有绘制BOS_WORD嵌入向量输入,然后解码出i单词的过程。
解码器前向过程
仔细观察解码器布局,其包罗:带有mask的MultiHeadAttention、MultiHeadAttention和前馈神经网络层三个组件,带有mask的MultiHeadAttention和MultiHeadAttention布局和代码写法是完全雷同,唯一区别是是否输入了mask。
为啥要mask?原因依然是顺序解码导致的。试想模子训练好了,开始进行翻译(测试),其流程就是上面写的:输入BOS_WORD,解码器输出i;输入前面已经解码的BOS_WORD和i,解码器输出am...,输入已经解码的BOS_WORD、i、am、a和student,解码器输出解码结束标记位EOS_WORD,每次解码都会利用前面已经解码输出的全部单词嵌入信息,这个测试过程是没有题目,但是训练时间我肯定不想接纳上述顺序解码类似rnn即一个一个目标单词嵌入向量顺序输入训练,肯定想接纳类似编码器中的矩阵并行算法,一步就把全部目标单词预测出来。要实现这个功能就可以参考编码器的操作,把目标单词嵌入向量组成矩阵一次输入即可,但是在解码am时间,不能利用到背面单词a和student的目标单词嵌入向量信息,否则这就是作弊(测试时间不大概能未卜先知)。为此引入mask,目的是构成下三角矩阵,右上角全部设置为负无穷(相当于忽略),从而实现当解码第一个字的时间,第一个字只能与第一个字计算相关性,当解出第二个字的时间,只能计算出第二个字与第一个字和第二个字的相关性。详细是:在解码器中,自注意力层只被允许处理输出序列中更靠前的那些位置,在softmax步骤前,它会把背面的位置给隐去(把它们设为-inf)。
还有个非常重要点需要知道(看图示可以发现):解码器内部的带有mask的MultiHeadAttention的qkv向量输入来自目标单词嵌入大概前一个解码器输出,三者是雷同的,但是背面的MultiHeadAttention的qkv向量中的kv来自末了一层编码器的输入,而q来自带有mask的MultiHeadAttention模块的输出。
关于带mask的注意力层写法实在就是前面提到的代码:
- class ScaledDotProductAttention(nn.Module):
- ''' Scaled Dot-Product Attention '''
- def __init__(self, temperature, attn_dropout=0.1):
- super().__init__()
- self.temperature = temperature
- self.dropout = nn.Dropout(attn_dropout)
- def forward(self, q, k, v, mask=None):
- # 假设q是b,8,10,64(b是batch,8是head个数,10是样本最大单词长度,
- # 64是每个单词的编码向量)
- # attn输出维度是b,8,10,10
- attn = torch.matmul(q / self.temperature, k.transpose(2, 3))
- # 故mask维度也是b,8,10,10
- # 忽略b,8,只关注10x10的矩阵,其是下三角矩阵,下三角位置全1,其余位置全0
- if mask is not None:
- # 提前算出mask,将为0的地方变成极小值-1e9,把这些位置的值设置为忽略
- # 目的是避免解码过程中利用到未来信息
- attn = attn.masked_fill(mask == 0, -1e9)
- # softmax+dropout
- attn = self.dropout(F.softmax(attn, dim=-1))
- output = torch.matmul(attn, v)
- return output, attn
复制代码 可视化如下:

整个解码器代码和编码器非常类似:
- class DecoderLayer(nn.Module):
- ''' Compose with three layers '''
- def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1):
- super(DecoderLayer, self).__init__()
- self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
- self.enc_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout)
- self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)
- def forward(
- self, dec_input, enc_output,
- slf_attn_mask=None, dec_enc_attn_mask=None):
- # 标准的自注意力,QKV=dec_input来自目标单词嵌入或者前一个解码器输出
- dec_output, dec_slf_attn = self.slf_attn(
- dec_input, dec_input, dec_input, mask=slf_attn_mask)
- # KV来自最后一个编码层输出enc_output,Q来自带有mask的self.slf_attn输出
- dec_output, dec_enc_attn = self.enc_attn(
- dec_output, enc_output, enc_output, mask=dec_enc_attn_mask)
- dec_output = self.pos_ffn(dec_output)
- return dec_output, dec_slf_attn, dec_enc_attn
复制代码 考虑n个解码器模块,其团体流程为:
- class Decoder(nn.Module):
- def __init__(
- self, n_trg_vocab, d_word_vec, n_layers, n_head, d_k, d_v,
- d_model, d_inner, pad_idx, n_position=200, dropout=0.1):
- # 目标单词嵌入
- self.trg_word_emb = nn.Embedding(n_trg_vocab, d_word_vec, padding_idx=pad_idx)
- # 位置嵌入向量
- self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position)
- self.dropout = nn.Dropout(p=dropout)
- # n个解码器
- self.layer_stack = nn.ModuleList([
- DecoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout)
- for _ in range(n_layers)])
- # 层归一化
- self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
- def forward(self, trg_seq, trg_mask, enc_output, src_mask, return_attns=False):
- # 目标单词嵌入+位置编码
- dec_output = self.dropout(self.position_enc(self.trg_word_emb(trg_seq)))
- dec_output = self.layer_norm(dec_output)
- # 遍历每个解码器
- for dec_layer in self.layer_stack:
- # 需要输入3个信息:目标单词嵌入+位置编码、最后一个编码器输出enc_output
- # 和dec_enc_attn_mask,解码时候不能看到未来单词信息
- dec_output, dec_slf_attn, dec_enc_attn = dec_layer(
- dec_output, enc_output, slf_attn_mask=trg_mask, dec_enc_attn_mask=src_mask)
- return dec_output
复制代码 分类层
在进行编码器-解码器后输出依然是向量,需要在背面接fc+softmax层进行分类训练。假设当前训练过程是翻译任务需要输出i am a student EOS_WORD这5个单词。假设我们的模子是从训练会集学习一万个差别的英语单词(我们模子的“输出词表”)。因此softmax后输出为一万个单元格长度的向量,每个单元格对应某一个单词的分数,这实在就是普通多分类题目,只不过维度比较大而已。
依然从前面例子为例,假设编码器输出shape是(b,100,512),颠末fc后酿成(b,100,10000),然后对末了一个维度进行softmax操作,得到bx100个单词的概率分布,在训练过程中bx100个单词是知道label的,故可以直接接纳ce loss进行训练。
- self.trg_word_prj = nn.Linear(d_model, n_trg_vocab, bias=False)
- dec_output, *_ = self.model.decoder(trg_seq, trg_mask, enc_output, src_mask)
- return F.softmax(self.model.trg_word_prj(dec_output), dim=-1)
复制代码 前向流程
以翻译任务为例:
- 将源单词进行嵌入,组成矩阵(加上位置编码矩阵)输入到n个编码器中,输出编码向量KV
- 第一个解码器先输入一个BOS_WORD单词嵌入向量,后续解码器担当该解码器输出,结合KV进行第一次解码
- 将第一次解码单词进行嵌入,联合BOS_WORD单词嵌入向量构成矩阵再次输入到解码器中进行第二次解码,得到解码单词
- 不停循环,每次的第一个解码器输入都差别,其包罗了前面时间步长解码出的全部单词
- 直到输出EOS_WORD表现解码结束大概逼迫设置最大时间步长即可
这个解码过程实在就是标准的seq2seq流程。到如今为止就描述完了整个标准transformer训练和测试流程。
视觉领域的transformer
在理解了标准的transformer后,再来看视觉领域transformer就会非常简单,因为在cv领域应用transformer时间各人都有一个共识:只管不改动transformer布局,这样才气和NLP领域发展对齐,所以各人理解cv里面的transformer操作黑白常简单的。
分类vision transformer
论文题目:An Image is Worth 16x16 Words:Transformers for Image Recognition at Scale
论文地点:https://arxiv.org/abs/2010.11929
github: https://github.com/lucidrains/vit-pytorch
其做法超级简单,只含有编码器模块:
本文出发点是彻底抛弃CNN,从前的cv领域固然引入transformer,但是或多或少都用到了cnn大概rnn,本文就比较纯粹了,整个算法几句话就说清晰了,下面直接分析。
图片分块和降维
因为transformer的输入需要序列,所以最简单做法就是把图片切分为patch,然后拉成序列即可。假设输入图片大小是256x256,打算分成64个patch,每个patch是32x32像素
- x = rearrange(img, 'b c (h p1) (w p2) -> b (h w) (p1 p2 c)', p1=p, p2=p)
复制代码 这个写法是接纳了爱因斯坦表达式,详细是接纳了einops库实现,内部集成了各种算子,rearrange就是其中一个,非常高效。不懂这种语法的请自行百度。p就是patch大小,假设输入是b,3,256,256,则rearrange操作是先酿成(b,3,8x32,8x32),末了酿成(b,8x8,32x32x3)即(b,64,3072),将每张图片切分成64个小块,每个小块长度是32x32x3=3072,也就是说输入长度为64的图像序列,每个元素接纳3072长度进行编码。
考虑到3072有点大,故作者先辈行降维:
- # 将3072变成dim,假设是1024
- self.patch_to_embedding = nn.Linear(patch_dim, dim)
- x = self.patch_to_embedding(x)
复制代码 仔细看论文上图,可以发现假设切成9个块,但是终极到transfomer输入是10个向量,额外追加了一个0和_。为啥要追加?原因是我们如今没有解码器了,而是编码后直接就进行分类预测,那么该解码器就要负责一点点解码器功能,那就是:需要一个类似开启解码标记,非常类似于标准transformer解码器中输入的目标嵌入向量右移一位操作。试下如果没有额外输入,9个块输入9个编码向量输出,那么对于分类任务而言,我应该取哪个输出向量进行后续分类呢?选择任何一个都说不通,所以作者追加了一个可学习嵌入向量输入。那么额外的可学习嵌入向量为啥要计划为可学习,而不是类似nlp中接纳固定的token代替?个人不负责任的推测这应该就是图片领域和nlp领域的差异,nlp里面每个词实在都有详细寄义,是离散的,但是图像领域没有这种真正意义上的离散token,有的只是一堆一连特性大概图像像素,如果不设置为可学习,那还真不知道应该设置为啥内容比较合适,全0和全1也说不通。自此如今就是酿成10个向量输出,输出也是10个编码向量,然后取第0个编码输出进行分类预测即可。从这个角度看可以以为编码器多了一点点解码器功能。详细做法超级简单,0就是位置编码向量,_是可学习的patch嵌入向量。
- # dim=1024
- self.cls_token = nn.Parameter(torch.randn(1, 1, dim))
- # 变成(b,64,1024)
- cls_tokens = repeat(self.cls_token, '() n d -> b n d', b=b)
- # 额外追加token,变成b,65,1024
- x = torch.cat((cls_tokens, x), dim=1)
复制代码 位置编码
位置编码也是必不可少的,长度应该是1024,这里做的比较简单,没有接纳sincos编码,而是直接设置为可学习,效果差不多
- # num_patches=64,dim=1024,+1是因为多了一个cls开启解码标志
- self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim))
复制代码 对训练好的pos_embedding进行可视化,如下所示:
相邻位置有相近的位置编码向量,团体出现2d空间位置排布一样。
将patch嵌入向量和位置编码向量相加即可作为编码器输入
- x += self.pos_embedding[:, :(n + 1)]
- x = self.dropout(x)
复制代码 编码器前向过程
作者接纳的是没有任何改动的transformer,故没有啥说的。
- self.transformer = Transformer(dim, depth, heads, mlp_dim, dropout)
复制代码 假设输入是(b,65,1024),那么transformer输出也是(b,65,1024)
分类head
在编码器后接fc分类器head即可
- self.mlp_head = nn.Sequential(
- nn.LayerNorm(dim),
- nn.Linear(dim, mlp_dim),
- nn.GELU(),
- nn.Dropout(dropout),
- nn.Linear(mlp_dim, num_classes)
- )
- # 65个输出里面只需要第0个输出进行后续分类即可
- self.mlp_head(x[:, 0])
复制代码 到如今为止就全部写完了,是不黑白常简单,外层团体流程为:
- class ViT(nn.Module): def __init__(self, *, image_size, patch_size, num_classes, dim, depth, heads, mlp_dim, channels=3, dropout=0.,emb_dropout=0.): super().__init__() # image_size输入图片大小 256 # patch_size 每个patch的大小 32 num_patches = (image_size // patch_size) ** 2 # 一共有多少个patch 8x8=64 patch_dim = channels * patch_size ** 2 # 3x32x32=3072 self.patch_size = patch_size # 32 # 1,64+1,1024,+1是因为token,可学习变量,不是固定编码 self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim)) # 图片维度太大了,需要先降维 self.patch_to_embedding = nn.Linear(patch_dim, dim) # 分类输出位置标记,否则分类输出不知道应该取哪个位置 self.cls_token = nn.Parameter(torch.randn(1, 1, dim)) self.dropout = nn.Dropout(emb_dropout) # 编码器 self.transformer = Transformer(dim, depth, heads, mlp_dim, dropout) # 输出头 self.mlp_head = nn.Sequential( nn.LayerNorm(dim), nn.Linear(dim, mlp_dim), nn.GELU(), nn.Dropout(dropout), nn.Linear(mlp_dim, num_classes) ) def forward(self, img, mask=None): p = self.patch_size # 先把图片酿成64个patch,输出shape=b,64,3072 x = rearrange(img, 'b c (h p1) (w p2) -> b (h w) (p1 p2 c)', p1=p, p2=p) # 输出 b,64,1024 x = self.patch_to_embedding(x) b, n, _ = x.shape # 输出 b,1,1024 cls_tokens = repeat(self.cls_token, '() n d -> b n d', b=b) # 额外追加token,酿成b,65,1024 x = torch.cat((cls_tokens, x), dim=1) # 加上位置编码1,64+1,1024 x += self.pos_embedding[:, :(n + 1)] x = self.dropout(x) x = self.transformer(x, mask) # 分类head,只需要x[0]即可 # x = self.to_cls_token(x[:, 0]) x = x[:, 0] return self.mlp_head(x)
复制代码 实验分析
作者得出的结论是:cv领域应用transformer需要大量数据进行预训练,在同等数据量的情况下性可否则cnn。一旦数据量上来了,对应的训练时间也会加长很多,那么就可以轻松逾越cnn。


同时应用transformer,一个突出优点是可表明性比较强:

目标检测detr
论文名称:End-to-End Object Detection with Transformers
论文地点:https://arxiv.org/abs/2005.12872
github:https://github.com/facebookresearch/detr
detr是facebook提出的引入transformer到目标检测领域的算法,效果很好,做法也很简单,符合其一向的简洁优雅计划做法。

对于目标检测任务,其要求输出给定图片中全部远风景体的种别和bbox坐标,该任务现实上是无序集合预测题目。针对该题目,detr做法非常简单:给定一张图片,颠末CNN进行特性提取,然后酿成特性序列输入到transformer的编解码器中,直接输出指定长度为N的无序集合,集合中每个元素包罗物体种别和坐标。其中N表现整个数据会集图片上最多物体的数量,因为整个训练和测试都Batch进行,如果不设置最大输出集合数,无法进行batch训练,如果图片中物体不够N个,那么就接纳no object填充,表现该元素是背景。
整个头脑看起来非常简单,相比faster rcnn大概yolo算法那就简单太多了,因为其不需要设置先验anchor,超参几乎没有,也不需要nms(因为输出的无序集合没有重复情况),而且在代码程度相比faster rcnn那就不知道简单多少倍了,通过简单修改就可以应用于全景分割任务。可以推测,如果transformer真正大规模应用于CV领域,那么对初学者来说就是福音了,理解transformer就几乎等于理解了整个cv领域了(当然也大概是坏事)。
detr核心头脑分析
相比faster rcnn等做法,detr最大特点是将目标检测题目转化为无序集合预测题目。论文中特意指出faster rcnn这种设置一大堆anchor,然后基于anchor进行分类和回归实在属于代理做法即不是最直接做法,目标检测任务就是输出无序集合,而faster rcnn等算法通过各种操作,并结合复杂后处理终极才得到无序集合属于绕路了,而detr就比较纯粹了。
尽管将transformer引入目标检测领域可以制止上述各种题目,但是其依然存在两个核心操作:
- 无序集合输出的loss计算
- 针对目标检测的transformer改进
detr算法实现细节
下面结合代码和原理对其核心环节进行深入分析。
无序集合输出的loss计算
在分析loss计算前,需要先明白N个无序集合的target构建方式。作者在coco数据集上统计,一张图片最多标注了63个物体,所以N应该要不小于63,作者设置的是100。为啥要设置为100?有人推测是和coco评估指标只取前100个预测效果算法指标有关系。
如今核心题目来了:输出的bx100个检测效果是无序的,如何和gt bbox计算loss?这就需要用到经典的双边匹配算法了,也就是常说的匈牙利算法,该算法广泛应用于最优分配题目,在bottom-up人体姿态估计算法中进行分组操作时间也经常利用。detr中利用匈牙利算法先辈行最优一对一匹配得到匹配索引,然后对bx100个效果进行重排就和gt bbox对应上了(对gt bbox进行重排也可以,没啥区别),就可以算loss了。
匈牙利算法是一个标准优化算法,详细是组合优化算法,在scipy.optimize.linear_sum_assignmen函数中有实现,一行代码就可以得到最优匹配,网上解读也非常多,这里就不写细节了,该函数核心是需要输入A集合和B集合两两元素之间的连接权重,基于该重要性进行内部最优匹配,连接权重大的优先匹配。
上述描述优化过程可以接纳如下公式表达:
Hungarian意思就是匈牙利,也就是前面的L_match,上述意思是需要计算M个gt bbox和N个输出集合两两之间的广义隔断,隔断越近表现越大概是最优匹配关系,也就是两者最密切。广义隔断的计算考虑了分类分支和bbox分支,下面结合代码直接阐明,比较简单。
- # detr分类输出,num_queries=100,shape是(b,100,92)
- bs, num_queries = outputs["pred_logits"].shape[:2]
- # 得到概率输出(bx100,92)
- out_prob = outputs["pred_logits"].flatten(0, 1).softmax(-1)
- # 得到bbox分支输出(bx100,4)
- out_bbox = outputs["pred_boxes"].flatten(0, 1)
- # 准备分类target shape=(m,)里面存储的是类别索引,m包括了整个batch内部的所有gt bbox
- tgt_ids = torch.cat([v["labels"] for v in targets])
- # 准备bbox target shape=(m,4),已经归一化了
- tgt_bbox = torch.cat([v["boxes"] for v in targets])
- #核心
- #bx100,92->bx100,m,对于每个预测结果,把目前gt里面有的所有类别值提取出来,其余值不需要参与匹配
- #对应上述公式,类似于nll loss,但是更加简单
- cost_class = -out_prob[:, tgt_ids]
- #计算out_bbox和tgt_bbox两两之间的l1距离 bx100,m
- cost_bbox = torch.cdist(out_bbox, tgt_bbox, p=1)
- #额外多计算一个giou loss bx100,m
- cost_giou = -generalized_box_iou(box_cxcywh_to_xyxy(out_bbox), box_cxcywh_to_xyxy(tgt_bbox))
- #得到最终的广义距离bx100,m,距离越小越可能是最优匹配
- C = self.cost_bbox * cost_bbox + self.cost_class * cost_class + self.cost_giou * cost_giou
- # bx100,m--> batch,100,m
- C = C.view(bs, num_queries, -1).cpu()
- #计算每个batch内部有多少物体,后续计算时候按照单张图片进行匹配,没必要batch级别匹配,徒增计算
- sizes = [len(v["boxes"]) for v in targets]
- #匈牙利最优匹配,返回匹配索引
- indices = [linear_sum_assignment(c[i]) for i, c in enumerate(C.split(sizes, -1))]
- return [(torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices]
复制代码 在得到匹配关系后算loss就水到渠成了。分类分支计算ce loss,bbox分支计算l1 loss+giou loss
- def loss_labels(self, outputs, targets, indices, num_boxes, log=True):
- #shape是(b,100,92)
- src_logits = outputs['pred_logits']
- #得到匹配后索引,作用在label上
- idx = self._get_src_permutation_idx(indices)
- #得到匹配后的分类target
- target_classes_o = torch.cat([t["labels"][J] for t, (_, J) in zip(targets, indices)])
- #加入背景(self.num_classes),补齐bx100个
- target_classes = torch.full(src_logits.shape[:2], self.num_classes,
- dtype=torch.int64, device=src_logits.device)
- #shape是(b,100,),存储的是索引,不是one-hot
- target_classes[idx] = target_classes_o
- #计算ce loss,self.empty_weight前景和背景权重是1和0.1,克服类别不平衡
- loss_ce = F.cross_entropy(src_logits.transpose(1, 2), target_classes, self.empty_weight)
- losses = {'loss_ce': loss_ce}
- return losses
- def loss_boxes(self, outputs, targets, indices, num_boxes):
- idx = self._get_src_permutation_idx(indices)
- src_boxes = outputs['pred_boxes'][idx]
- target_boxes = torch.cat([t['boxes'][i] for t, (_, i) in zip(targets, indices)], dim=0)
- #l1 loss
- loss_bbox = F.l1_loss(src_boxes, target_boxes, reduction='none')
- losses = {}
- losses['loss_bbox'] = loss_bbox.sum() / num_boxes
- #giou loss
- loss_giou = 1 - torch.diag(box_ops.generalized_box_iou(
- box_ops.box_cxcywh_to_xyxy(src_boxes),
- box_ops.box_cxcywh_to_xyxy(target_boxes)))
- losses['loss_giou'] = loss_giou.sum() / num_boxes
- return losses
复制代码 针对目标检测的transformer改进
分析完训练最关键的:双边匹配+loss计算部分,如今需要考虑在目标检测算法中transformer如何计划?下面按照算法的4个步骤讲解。

transformer细节如下:

(1) cnn骨架特性提取
骨架网络可以是任何一种,作者选择resnet50,将末了一个stage即stride=32的特性图作为编码器输入。由于resnet仅仅作为一个小部分且已经颠末了imagenet预训练,故和通例操作一样,会进行如下操作:
- resnet中全部BN都固定,即接纳全局均值和方差
- resnet的stem和第一个stage不进行参数更新,即parameter.requires_grad_(False)
- backbone的学习率小于transformer,lr_backbnotallow=1e-05,其余为0.0001
假设输入是(b,c,h,w),则resnet50输出是(b,1024,h//32,w//32),1024比较大,为了节流计算量,先接纳1x1卷积降维为256,末了转化为序列格式输入到transformer中,输入shape=(h'xw',b,256),h'=h//32
- self.input_proj = nn.Conv2d(backbone.num_channels, hidden_dim, kernel_size=1)
- # 输出是(b,256,h//32,w//32)
- src=self.input_proj(src)
- # 变成序列模式,(h'xw',b,256),256是每个词的编码长度
- src = src.flatten(2).permute(2, 0, 1)
复制代码 (2) 编码器计划和输入
编码器布局计划没有任何改变,但是输入改变了。
a) 位置编码需要考虑2d空间
由于图像特性是2d特性,故位置嵌入向量也需要考虑xy方向。前面说过编码方式可以接纳sincos,也可以设置为可学习,本文接纳的依然是sincos模式,和前面说的一样,但是需要考虑xy两个方向(前面说的序列只有x方向)。
- #输入是b,c,h,w
- #tensor_list的类型是NestedTensor,内部自动附加了mask,
- #用于表示动态shape,是pytorch中tensor新特性https://github.com/pytorch/nestedtensor
- x = tensor_list.tensors # 原始tensor数据
- # 附加的mask,shape是b,h,w 全是false
- mask = tensor_list.mask
- not_mask = ~mask
- # 因为图像是2d的,所以位置编码也分为x,y方向
- # 1 1 1 1 .. 2 2 2 2... 3 3 3...
- y_embed = not_mask.cumsum(1, dtype=torch.float32)
- # 1 2 3 4 ... 1 2 3 4...
- x_embed = not_mask.cumsum(2, dtype=torch.float32)
- if self.normalize:
- eps = 1e-6
- y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale
- x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale
- # 0~127 self.num_pos_feats=128,因为前面输入向量是256,编码是一半sin,一半cos
- dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device)
- # 归一化
- dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats)
- pos_x = x_embed[:, :, :, None] / dim_t
- pos_y = y_embed[:, :, :, None] / dim_t
- # 输出shape=b,h,w,128
- pos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3)
- pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3)
- pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)
- # 每个特征图的xy位置都编码成256的向量,其中前128是y方向编码,而128是x方向编码
- return pos # b,n=256,h,w
复制代码 可以看出对于h//32,w//32的2d图像特性,不是类似vision transoformer做法简单的将其拉伸为h//32 x w//32,然后从0-n进行长度为256的位置编码,而是考虑了xy方向同时编码,每个方向各编码128维向量,这种编码方式更符合图像特定。
还有一个细节需要注意:原始transformer的n个编码器输入中,只有第一个编码器需要输入位置编码向量,但是detr里面对每个编码器都输入了同一个位置编码向量,论文中没有写为啥要如此修改。
b) QKV处理逻辑差别
作者设置编码器一共6个,而且位置编码向量仅仅加到QK中,V中没有到场位置信息,这个和原始做法不一样,原始做法是QKV都加上了位置编码,论文中也没有写为啥要如此修改。
其余地方就完全雷同了,故代码就没须要贴了。总结下和原始transformer编码器差别的地方:
- 输入编码器的位置编码需要考虑2d空间位置
- 位置编码向量需要到场到每个编码器中
- 在编码器内部位置编码仅仅和QK相加,V不做任那边理
颠末6个编码器forward后,输出shape为(h//32xw//32,b,256)。
c) 编码器部分团体运行流程
6个编码器团体forward流程如下:
- class TransformerEncoder(nn.Module):
- def __init__(self, encoder_layer, num_layers, norm=None):
- super().__init__()
- # 编码器copy6份
- self.layers = _get_clones(encoder_layer, num_layers)
- self.num_layers = num_layers
- self.norm = norm
- def forward(self, src,
- mask: Optional[Tensor] = None,
- src_key_padding_mask: Optional[Tensor] = None,
- pos: Optional[Tensor] = None):
- # 内部包括6个编码器,顺序运行
- # src是图像特征输入,shape=hxw,b,256
- output = src
- for layer in self.layers:
- # 每个编码器都需要加入pos位置编码
- # 第一个编码器输入来自图像特征,后面的编码器输入来自前一个编码器输出
- output = layer(output, src_mask=mask,
- src_key_padding_mask=src_key_padding_mask, pos=pos)
- return output
复制代码 每个编码器内部运行流程如下:
- def forward_post(self,
- src,
- src_mask: Optional[Tensor] = None,
- src_key_padding_mask: Optional[Tensor] = None,
- pos: Optional[Tensor] = None):
- # 和标准做法有点不一样,src加上位置编码得到q和k,但是v依然还是src,
- # 也就是v和qk不一样
- q = k = src+pos
- src2 = self.self_attn(q, k, value=src, attn_mask=src_mask,
- key_padding_mask=src_key_padding_mask)[0]
- src = src + self.dropout1(src2)
- src = self.norm1(src)
- src2 = self.linear2(self.dropout(self.activation(self.linear1(src))))
- src = src + self.dropout2(src2)
- src = self.norm2(src)
- return src
复制代码 (3) 解码器计划和输入
解码器布局计划没有任何改变,但是输入也改变了。
a) 新引入Object queries
object queries(shape是(100,256))可以简单以为是输出位置编码,其作用重要是在学习过程中提供目标对象和全局图像之间的关系,相当于全局注意力,必不可少非常关键。代码形式上是可学习位置编码矩阵。和编码器一样,该可学习位置编码向量也会输入到每一个解码器中。我们可以实验通俗理解:object queries矩阵内部通过学习建模了100个物体之间的全局关系,例如房间里面的桌子旁边(A类)一般是放椅子(B类),而不会是放一头大象(C类),那么在推理时间就可以利用该全局注意力更好的进行解码预测输出。
- # num_queries=100,hidden_dim=256
- self.query_embed = nn.Embedding(num_queries, hidden_dim)
复制代码 论文中指出object queries作用非常类似faster rcnn中的anchor,只不过这里是可学习的,不是提前设置好的。
b) 位置编码也需要
编码器环节接纳的sincos位置编码向量也可以考虑引入,且该位置编码向量输入到每个解码器的第二个Multi-Head Attention中,背面有是否需要该位置编码的对比实验。
c) QKV处理逻辑差别
解码器一共包罗6个,和编码器中QKV一样,V不会到场位置编码。上述说的三个操作,只要看下网络布局图就一目了然了。
d) 一次解码输出全部无序集合
和原始transformer顺序解码操作差别的是,detr一次就把N个无序框并行输出了(因为任务是无序集合,做成顺序推理有序输出没有很大须要)。为了阐明如何实现该功能,我们需要先回忆下原始transformer的顺序解码过程:输入BOS_WORD,解码器输出i;输入前面已经解码的BOS_WORD和i,解码器输出am...,输入已经解码的BOS_WORD、i、am、a和student,解码器输出解码结束标记位EOS_WORD,每次解码都会利用前面已经解码输出的全部单词嵌入信息。如今就是一次解码,故只需要初始化时间输入一个全0的查询向量A,类似于BOS_WORD作用,然后第一个解码器担当该输入A,解码输出向量作为下一个解码器输入,不停推理即可,末了一层解码输出即为我们需要的输出,不需要在第二个解码器输入时间考虑BOS_WORD和第一个解码器输出。
总结下和原始transformer解码器差别的地方:
- 额外引入可学习的Object queries,相当于可学习anchor,提供全局注意力
- 编码器接纳的sincos位置编码向量也需要输入解码器中,而且每个解码器都输入
- QKV处理逻辑差别
- 不需要顺序解码,一次即可输出N个无序集合
e) 解码器团体运行流程
n个解码器团体流程如下:
- class TransformerDecoder(nn.Module):
- def forward(self, tgt, memory,
- tgt_mask: Optional[Tensor] = None,
- memory_mask: Optional[Tensor] = None,
- tgt_key_padding_mask: Optional[Tensor] = None,
- memory_key_padding_mask: Optional[Tensor] = None,
- pos: Optional[Tensor] = None,
- query_pos: Optional[Tensor] = None):
- # 首先query_pos是query_embed,可学习输出位置向量shape=100,b,256
- # tgt = torch.zeros_like(query_embed),用于进行一次性解码输出
- output = tgt
- # 存储每个解码器输出,后面中继监督需要
- intermediate = []
- # 编码每个解码器
- for layer in self.layers:
- # 每个解码器都需要输入query_pos和pos
- # memory是最后一个编码器输出
- # 每个解码器都接受output作为输入,然后输出新的output
- output = layer(output, memory, tgt_mask=tgt_mask,
- memory_mask=memory_mask,
- tgt_key_padding_mask=tgt_key_padding_mask,
- memory_key_padding_mask=memory_key_padding_mask,
- pos=pos, query_pos=query_pos)
- if self.return_intermediate:
- intermediate.append(self.norm(output))
- if self.return_intermediate:
- return torch.stack(intermediate) # 6个输出都返回
- return output.unsqueeze(0)
复制代码 内部每个解码器运行流程为:
- def forward_post(self, tgt, memory,
- tgt_mask: Optional[Tensor] = None,
- memory_mask: Optional[Tensor] = None,
- tgt_key_padding_mask: Optional[Tensor] = None,
- memory_key_padding_mask: Optional[Tensor] = None,
- pos: Optional[Tensor] = None,
- query_pos: Optional[Tensor] = None):
- # query_pos首先是可学习的,其作用主要是在学习过程中提供目标对象和全局图像之间的关系
- # 这个相当于全局注意力输入,是非常关键的
- # query_pos是解码器特有
- q = k = tgt+query_pos
- # 第一个自注意力模块
- tgt2 = self.self_attn(q, k, value=tgt, attn_mask=tgt_mask,
- key_padding_mask=tgt_key_padding_mask)[0]
- tgt = tgt + self.dropout1(tgt2)
- tgt = self.norm1(tgt)
- # memory是最后一个编码器输出,pos是和编码器输入中完全相同的sincos位置嵌入向量
- # 输入参数是最核心细节,query是tgt+query_pos,而key是memory+pos
- # v直接用memory
- tgt2 = self.multihead_attn(query=tgt+query_pos,
- key=memory+pos,
- value=memory, attn_mask=memory_mask,
- key_padding_mask=memory_key_padding_mask)[0]
- tgt = tgt + self.dropout2(tgt2)
- tgt = self.norm2(tgt)
- tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt))))
- tgt = tgt + self.dropout3(tgt2)
- tgt = self.norm3(tgt)
- return tgt
复制代码 解码器终极输出shape是(6,b,100,256),6是指6个解码器的输出。
(4) 分类和回归head
在解码器输出基础上构建分类和bbox回归head即可输出检测效果,比较简单:
- self.class_embed = nn.Linear(256, 92)
- self.bbox_embed = MLP(256, 256, 4, 3)
- # hs是(6,b,100,256),outputs_class输出(6,b,100,92),表示6个分类分支
- outputs_class = self.class_embed(hs)
- # 输出(6,b,100,4),表示6个bbox坐标回归分支
- outputs_coord = self.bbox_embed(hs).sigmoid()
- # 取最后一个解码器输出即可,分类输出(b,100,92),bbox回归输出(b,100,4)
- out = {'pred_logits': outputs_class[-1], 'pred_boxes': outputs_coord[-1]}
- if self.aux_loss:
- # 除了最后一个输出外,其余编码器输出都算辅助loss
- out['aux_outputs'] = self._set_aux_loss(outputs_class, outputs_coord)
复制代码 作者实验发现,如果对解码器的每个输出都到场辅助的分类和回归loss,可以提拔性能,故作者除了对末了一个编码层的输出进行Loss监督外,还对其余5个编码器接纳了同样的loss监督,只不过权重设置低一点而已。
(5) 团体推理流程
基于transformer的detr算法,作者特意夸大其突出优点是部署代码不超过50行,简单至极。
当然上面是简化代码,和现实代码不一样。详细流程是:
- 将(b,3,800,1200)图片输入到resnet50中进行特性提取,输出shape=(b,1024,25,38)
- 通过1x1卷积降维,酿成(b,256,25,38)
- 利用sincos函数计算位置编码
- 将图像特性和位置编码向量相加,作为编码器输入,输出编码后的向量,shape不变
- 初始化全0的(100,b,256)的输出嵌入向量,结合位置编码向量和query_embed,进行解码输出,解码器输出shape为(6,b,100,256)
- 将末了一个解码器输出输入到分类和回归head中,得到100个无序集合
- 对100个无序集合进行后处理,重要是提取远景种别和对应的bbox坐标,乘上(800,1200)即可得到终极坐标,后处理代码如下:
- prob = F.softmax(out_logits, -1)
- scores, labels = prob[..., :-1].max(-1)
- # convert to [x0, y0, x1, y1] format
- boxes = box_ops.box_cxcywh_to_xyxy(out_bbox)
- # and from relative [0, 1] to absolute [0, height] coordinates
- img_h, img_w = target_sizes.unbind(1)
- scale_fct = torch.stack([img_w, img_h, img_w, img_h], dim=1)
- boxes = boxes * scale_fct[:, None, :]
- results = [{'scores': s, 'labels': l, 'boxes': b} for s, l, b in zip(scores, labels, boxes)]
复制代码 既然训练时间对6个解码器输出都进行了loss监督,那么在测试时间也可以考虑将6个解码器的分类和回归分支输出效果进行nms合并,稍微有点性能提拔。
实验分析
(1) 性能对比
Faster RCNN-DC5是指的resnet的末了一个stage接纳空洞率=stride设置代替stride,目的是在不进行下采样基础上扩大感受野,输出特性图分辨率保持不变。+号代表接纳了额外的技巧提拔性能例如giou、多标准训练和9xepoch训练策略。可以发现detr效果稍微好于faster rcnn各种版本,证明白视觉transformer的潜力。但是可以发现其小物体检测能力远远低于faster rcnn,这是一个比较大的弊端。
(2) 各个模块分析
编码器数量越多效果越好,但是计算量也会增长很多,作者终极选择的是6。
可以发现解码器也是越多越好,还可以观察到第一个解码器输出预测效果比较差,增长第二个解码器后性能提拔非常多。上图中的NMS操作是指既然我们每个解码层都可以输入无序集合,那么将全部解码器无序集合全部保存,然后进行nms得到终极输出,可以发现性能稍微有提拔,特别是AP50。
作者对比了差别类型的位置编码效果,因为query_embed(output pos)是必不可少的,所以该列没有进行对比实验,始终都有,末了一行效果最好,所以作者接纳的就是该方案,sine at attn表现每个注意力层都到场了sine位置编码,相比仅仅在input增长位置编码效果更好。
(3) 注意力可视化
前面说过transformer具有很好的可表明性,故在训练完成后终极提出了几种可视化形式
a) bbox输出可视化
这个就比较简单了,直接对预测进行后处理即可
- probas = outputs['pred_logits'].softmax(-1)[0, :, :-1]
- # 只保留概率大于0.9的bbox
- keep = probas.max(-1).values > 0.9
- # 还原到原图,然后绘制即可
- bboxes_scaled = rescale_bboxes(outputs['pred_boxes'][0, keep], im.size)
- plot_results(im, probas[keep], bboxes_scaled)
复制代码 b) 解码器自注意力层权重可视化
这里指的是末了一个解码器内部的第一个MultiheadAttention的自注意力权重,实在就是QK相似性计算后然后softmax后的输出可视化,详细是:
- # multihead_attn注册前向hook,output[1]指的就是softmax后输出
- model.transformer.decoder.layers[-1].multihead_attn.register_forward_hook(
- lambda self, input, output: dec_attn_weights.append(output[1])
- )
- # 假设输入是(1,3,800,1066)
- outputs = model(img)
- # 那么dec_attn_weights是(1,100,850=800//32x1066//32)
- # 这个就是QK相似性计算后然后softmax后的输出,即自注意力权重
- dec_attn_weights = dec_attn_weights[0]
- # 如果想看哪个bbox的权重,则输入idx即可
- dec_attn_weights[0, idx].view(800//32, 1066//32)
复制代码 c) 编码器自注意力层权重可视化
这个息争码器操作完全雷同。
- model.transformer.encoder.layers[-1].self_attn.register_forward_hook(
- lambda self, input, output: enc_attn_weights.append(output[1])
- )
- outputs = model(img)
- # 最后一个编码器中的自注意力模块权重输出(b,h//32xw//32,h//32xw//32),其实就是qk计算然后softmax后的值即(1,25x34=850,850)
- enc_attn_weights = enc_attn_weights[0]
- # 变成(25, 34, 25, 34)
- sattn = enc_attn_weights[0].reshape(shape + shape)
- # 想看哪个特征点位置的注意力
- idxs = [(200, 200), (280, 400), (200, 600), (440, 800), ]
- for idx_o, ax in zip(idxs, axs):
- # 转化到特征图尺度
- idx = (idx_o[0] // fact, idx_o[1] // fact)
- # 直接sattn[..., idx[0], idx[1]]即可
- ax.imshow(sattn[..., idx[0], idx[1]], cmap='cividis', interpolation='nearest')
复制代码 detr团体做法非常简单,根本上没有改动原始transformer布局,其明显优点是:不需要设置啥先验,超参也比较少,训练和部署代码相比faster rcnn算法简单很多,理解上也比较简单。但是其缺点是:改了编解码器的输入,在论文中也没有表明为啥要如此计划,而且很多操作都是实验对比才确定的,比较迷。算法层面训练epoch次数远远大于faster rcnn(300epoch),在同等epoch下显着性能不如faster rcnn,而且训练占用内存也大于faster rcnn。
团体而言,固然效果不错,但是整个做法还是显得比较原始,很多地方感觉是实验后得到的做法,没有很好的表明性,而且最大题目是训练epoch非常大和内存占用比较多,对应的就是收敛慢,期待后续作品。
#Transformer细节
为什么想通过十八个题目的方式总结Transformer?
有两点原因:
第一,Transformer是既MLP、RNN、CNN之后的第四大特性提取器,也被称为第四大基础模子;最近爆火的chatGPT,其最底层原理也是Transformer,Transformer的重要性可见一斑。
第二,希望通过问题目这种方式,更好的帮助各人理解Transformer的内容和原理。
1.2017年深度学习领域的重大突破是什么?
Transformer。有两方面的原因:
1.1 一方面,Transformer是深度学习领域既MLP、RNN、CNN之后的第4大特性提取器(也被称为基础模子)。什么是特性提取器?大脑是人与外部世界(图像、文字、语音等)交互的方式;特性提取器是计算机为了模仿大脑,与外部世界(图像、文字、语音等)交互的方式,如图1所示。举例而言:Imagenet数据会集包罗1000类图像,人们已经根据自己的履历把这一百万张图像分好1000类,每一类图像(如美洲豹)都有独特的特性。这时,神经网络(如ResNet18)也是想通过这种分类的方式,把每一类图像的特有特性尽大概提取或识别出来。分类不是终极目的,而是一种提取图像特性的手段,掩码补全图像也是一种提取特性的方式,图像块顺序打乱也是一种提取特性的方式。
图1 神经网络为了模仿大脑中的神经元
1.2 另一方面,Transformer在深度学习领域扮演的角色:第3次和第4次热潮的基石,如下图2所示。
图2 深度学习发展的4个阶段
2. Transformer的提出背景是什么?
2.1 在领域发展背景层面:当时时处2017年,深度学习在计算机视觉领域火了已经几年。从Alexnet、VGG、GoogLenet、ResNet、DenseNet;从图像分类、目标检测再到语义分割;但在自然语言处理领域并没有引起很大反响。
2.2 技术背景层面:(1)当时主流的序列转录任务(如机器翻译)的解决方案如下图3所示,在Sequence to Sequence架构下(Encoder- Decoder的一种),RNN来提取特性,Attention机制将Encoder提取到的特性高效转达给Decoder。(2)这种做法有两个不敷之处,一方面是在提取特性时的RNN天生从前向后时序转达的布局决定了其无法并行运算,其次是当序列长度过长时,最前面序列的信息有大概被忘记掉。因此可以看到,在这个框架下,RNN是相对单薄急需改进的地方。
图3 序列转录任务的主流解决方案
3. Transformer到底是什么?
3.1 Transformer是一种由Encoder和Decoder组成的架构。那么什么是架构呢?最简单的架构就是A+B+C。
3.2 Transformer也可以理解为一个函数,输入是“我爱学习”,输出是“I love study”。
3.3 如果把Transformer的架构进行分拆,如图4所示。
图4 Transformer的架构图
4. 什么是Transformer Encoder?
4.1 从功能角度,Transformer Encoder的核心作用是提取特性,也有利用Transformer Decoder来提取特性。例如,一个人学习跳舞,Encoder是看别人是如何跳舞的,Decoder是将学习到的履历和记忆,展现出来
4.2 从布局角度,如图5所示,Transformer Encoder = Embedding + Positional Embedding + N*(子Encoder block1 + 子Encoder block2);
子Encoder block1 = Multi head attention + ADD + Norm;
子Encoder block2 = Feed Forward + ADD + Norm;
4.3 从输入输出角度,N个Transformer Encoder block中的第一个Encoder block的输入为一组向量 X = (Embedding + Positional Embedding),向量维度通常为512*512,其他N个TransformerEncoder block的输入为上一个 Transformer Encoder block的输出,输出向量的维度也为512*512(输入输出大小雷同)。
4.4 为什么是512*512?前者是指token的个数,如“我爱学习”是4个token,这里设置为512是为了囊括差别的序列长度,不够时padding。后者是指每一个token生成的向量维度,也就是每一个token利用一个序列长度为512的向量表现。人们常说,Transformer不能超过512,否则硬件很难支持;实在512是指前者,也就是token的个数,因为每一个token要做self attention操作;但是后者的512不宜过大,否则计算起来也很慢。
图5 Transformer Encoder的架构图
5. 什么是Transformer Decoder?
5.1 从功能角度,相比于Transformer Encoder,Transformer Decoder更擅长做生成式任务,尤其对于自然语言处理题目。
5.2 从布局角度,如图6所示,Transformer Decoder = Embedding + Positional Embedding + N*(子Decoder block1 + 子Decoder block2 + 子Decoder block3)+ Linear + Softmax;
子Decoder block1 = Mask Multi head attention + ADD + Norm;
子Decoder block2 = Multi head attention + ADD + Norm;
子Decoder block3 = Feed Forward + ADD + Norm;
图6 Transformer Decoder的架构图
5.3 从(Embedding+Positional Embedding)(N个Decoder block)(Linear + softmax) 这三个每一个单独作用角度:
Embedding + Positional Embedding :以机器翻译为例,输入“Machine Learning”,输出“机器学习”;这里的Embedding是把“机器学习”也转化成向量的形式。
N个Decoder block:特性处理和转达过程。
Linear + softmax:softmax是预测下一个词出现的概率,如图7所示,前面的Linear层类似于分类网络(ResNet18)末了分类层前接的MLP层。
图7 Transformer Decoder 中softmax的作用
5.4 Transformer Decoder的输入、输出是什么?在Train和Test时是差别的。
在Train阶段,如图8所示。这时是知道label的,decoder的第一个输入是begin字符,输出第一个向量与label中第一个字符利用cross entropy loss。Decoder的第二个输入是第一个向量的label,Decoder的第N个输入对应的输出是End字符,到此结束。这里也可以看到,在Train阶段是可以进行并行训练的。
图8 Transformer Decoder在训练阶段的输入和输出
在Test阶段,下一个时刻的输入时是前一个时刻的输出,如图9所示。因此,Train和Test时间,Decoder的输入会出现Mismatch,在Test时间确实有大概会出现一步错,步步错的情况。有两种解决方案:一种是train时偶然给一些错误,另一种是Scheduled sampling。
图9 Transformer Decoder在Test阶段的输入和输出
5.5 Transformer Decoder block内部的输出和输出是什么?
前面提到的是在团体train和test阶段,Decoder的输出和输出,那么Transformer Decoder内部的Transformer Decoder block,如图10所示,的输入输出又是什么呢?
图10 Transformer Decoder block的架构图
对于N=6中的第1次循环(N=1时):子Decoder block1 的输入是 embedding +Positional Embedding,子Decoder block2 的输入的Q来自子Decoder block1的输出,KV来自Transformer Encoder末了一层的输出。
对于N=6的第2次循环:子Decoder block1的输入是N=1时,子Decoder block3的输出,KV同样来自Transformer Encoder的末了一层的输出。
总的来说,可以看到,无论在Train还是Test时,Transformer Decoder的输入不仅来自(ground truth大概上一个时刻Decoder的输出),还来自Transformer Encoder的末了一层。
训练时:第i个decoder的输入 = encoder输出 + ground truth embedding。
预测时:第i个decoder的输入 = encoder输出 + 第(i-1)个decoder输出.
6. Transformer Encoder和Transformer Decoder有哪些差别?
6.1 作用上,Transformer Encoder常用来提取特性,Transformer Decoder常用于生成式任务。Transformer Encoder和Transformer Decoder是两条差别的技术路线,Bert接纳的前者,GPT系列模子接纳的是后者。
6.2 布局上,Transformer Decoder block包罗了3个子Decoder block,而Transformer Encoder block 包罗2个子Encoder block,且Transformer Decoder中利用了Mask multi-head Attention。
6.3 从二者的输入输出角度,N个Transformer Encoder运算完成之后,它的输出才正式输入进Transformer Decoder,作为QKV中的K和V,给Transformer Decoder利用。那么TransformerEncoder末了层的输出是如何送给Decoder呢?如图11所示。
图11 Transformer Encoder和Transformer Decoder交互的方式
那么,为什么Encoder和Decoder必须要用这种交互的方式呢?实在也并不肯定,后续有差别交互方式的提出,如图12。
图12 Transformer Encoder和Decoder的交互方式
7. 什么是Embedding?
7.1 Embedding在Transformer架构中的位置如图13所示。
7.2 提出背景: 计算机无法直接处理一个单词大概一个汉字,需要把一个token转化成计算机可以识别的向量,这也就是embedding过程。
7.3 实现方式: 最简单的embedding操作就是one hot vector,但one hot vector有一个弊端就是没有考虑词语前后之间的关系,后来也就产生了WordEmbedding,如图13。
图13 Embedding的一些阐明,从左往右依次为:embedding在Transformer中的位置,one hot vector,Word embedding。
8. 什么是Positional Embedding?
8.1 Positional Embedding在Transformer架构中的位置如图14所示。
8.2 提出背景: RNN作为特性提取器,是自带词的前后顺序信息的;而Attention机制并没有考虑先后顺序信息,但前后顺序信息对语义影响很大,因此需要通过Positional Embedding这种方式把前后位置信息加在输入的Embedding上。
8.3 实现方式: 传统位置编码和神经网络自动训练得到。
图14 Positional Embedding的一些阐明,从左往右依次为:positional embedding在Transformer中的位置,传统位置编码的实现方式,传统位置编码ei得到的图像,每一列为一个token的位置编码。
9. 什么是Attention?
9.1 介绍Transformer,为什么要介绍Attention呢?因为在Transformer中最多的multi head attention和Mask multi head attention来自Scaled dot product attention,而scaled dot product attention来自self attention,而self attention是attention的一种,因此首先需要相识Attention,如图15所示。
图15 Attention与Transformer的关系
9.2 Attention到底是什么意思呢?
对于图像而言,attention就是人们看到图像中的核心关注的区域,是图像中的重点,如图16所示。对于序列而言,Attention机制本质上是为了找到输入中差别token之间的相互关系,通过权重矩阵来自发地找到词与词之间的关系。
图16 图像中的attention
9.3 Attention是如何实现的呢?
是通过QKV实现的。
那么什么是QKV呢?Q是query,K是keys,V是values。如图17所示,举例而言,Q是大脑发出的信号,我口渴了;K是环境信息,眼睛看到的世界;V是对环境中差别的物品赋予差别的比重,水的比重加大。
总之,Attention就是通过计算QK的相似度,与V相乘得到注意力数值。
图17 Attention的实现方式
9.4 为什么必须要有QKV三者?
为什么不是只有Q?因为Q1与Q2之间的关系权重,不止需要a12,也需要a21。你大概会问?我们让a12=a21不行吗?也可以实验,但从原理上讲效果应该没有a12和a21效果好。
为什么不是只有QK?求得的权重系数需要放到输入中,可以乘Q,也可以乘K,为什么要重新乘V呢?我觉得大概是多了一组可训练参数WV,使网络具有更强的学习能力。
10. 什么是Self attention?
10.1 介绍Transformer,为什么要介绍self Attention呢?因为在Transformer中最多的multi head attention和Mask multi head attention来自Scaled dot product attention,而scaled dot product attention来自self attention,如图15所示。
10.2 什么是self attention呢?self attention和local attention、stride attention都是attention的一种;self attention是每一个Q与每一个K依次计算注意力系数,如图18所示,而像local attention是Q只与相邻的K计算注意力系数,stride attention是Q通过跳连的方式与K计算注意力系数。
图18 从左至右依次为:self attention、local attention、stride attention
10.3 Self attention为什么可以用于处理像机器翻译这种序列数据?
输入序列中的每一个位置的数据,可以关注其他位置的信息,由此通过Attention score来提取特性大概捕获输入序列每一个token之间的关系。
10.4 Self attention是如何详细实现的? 总共分为4步,如图19所示
图19 Self attention的实现过程
11. 什么是Scaled dot product attention?
11.1 self attention最常见的有两种,一种是dot product attention、另一种是additive attention,如图20所示,前者的计算服从更高。
图20 dot product attention与additive attention的区别
11.2 什么是Scaled ?
scaled的详细实现方式如图21所示,这一操作的目的是为了防止内积过大,从梯度角度考虑,制止靠近1,易训练;与batch normalization有一些相似的功能。
图21 scaled操作在attention中的位置
12. 什么是Multi head attention?
12.1 Multi head attention在Transformer架构中的位置如图15所示。
12.2 提出背景:CNN具有多个channel,可以提取图像差别维度的特性信息,那么Self attention是否可以有类似操作,可以提取差别隔断token的多个维度信息呢?
12.3 什么是group 卷积?如图22所示,将输入的特性多个channel分成几个group单独做卷积,末了再进行con c操作。
图22 group卷积
12.4 Multi head attention的实现方式?与self attention根本差别是什么?
如图23所示,以2个head的为例,将输入的Q、K、V分成两份,每一小份的Q与对应的K、V分别操作,末了计算得到的向量再进行conc操作,由此可以看出,Multi head attention与group卷积有着相似的实现方式。
图23 Multi head attention与self attention的区别
12.5 如何从输入输出维度,角度来理解Multi head attention?如图24所示。
图24 Multi head attention的输入输出维度
13. 什么是Mask Multi head attention?
13.1 Mask Multi head attention在transformer架构中的位置如图15所示。
13.2 为什么要有Mask这种操作?
Transformer预测第T个时刻的输出,不能看到T时刻之后的那些输入,从而保证训练和预测同等。
通过 Masked 操作可以防止第 i 个单词知道 i+1 个单词之后的信息,如图25所示。
图25 Mask操作在Transformer中的位置
13.3 Mask操作是如何详细实现的呢?
Q1只跟K1计算,Q2只跟K1、K2计算,而对于K3、K4等,在softmax之前给一个非常大的负数,由此颠末softmax之后变为0,其在矩阵上的计算原理实现如图26所示。
图26 Mask操作的矩阵计算上的实现方式
14. 什么是ADD?
14.1 Add就是残差连接,由2015年ResNet这篇文章发扬光大(如今引用量已超过16万),与Skip connection的区别在于需要大小维度全部雷同。
14.2 作为大道至简想法的极致,几乎每一个深度学习模子都会用到这个技术,可以防止网络退化,常用于解决多层网络难训练的题目。
图27 ADD在Transformer架构中的位置(左)与残差连接原理示意图(右)
15. 什么是Norm?
15.1 Norm就是layer normalization。
15.2 核心作用:为了训练更加稳固,和batch normalization有雷同的作用,都是为了使输入的样本均值为零,方差为1。
15.3 为什么不利用batch normalization,利用的是layer normalization呢?因为一个时序数据,句子输入长度有长有短,如果利用batch normalization,则很容易造成因样本黑白不一造成“训练不稳固”。BN是对同一个batch内的全部数据的同一个特性数据进行操作;而LN是对同一个样本进行操作。
图28 layer Normalization在Transformer架构中的位置(左)与batch normalization的区别(右)
16. 什么是FFN?
16.1 FFN就是feed forward networks。
16.2 为什么有了Self attention层,还要有FFN?Attention已经有了想要的序列信息特性,MLP的作用是把信息投影到特定的空间里,再做一次非线性映射,和Self attention交替利用。
16.3 布局上:包罗两层MLP,第一层的维度为512*2048,第二层的维度为2048*512,且第二层MLP没有利用激活函数,如图29所示。
17. Transformer是如何训练出来的?
17.1 数据上,在Transformer论文中有提到,用到了4.5M和36M的翻译句子对。
17.2 硬件上,base模子是8个P100 GPU训练了12个小时,大模子是训练了3.5天。
17.3 模子参数和调参层面:
第一,可训练的参数包罗WQ、WK、WV、WO,换包罗FFN层的参数。
第二,可调的参数包罗:每一个token向量表现的维度(d_model)、head的头数、Encoder和Decoder中block重复的次数N、FFN中心层向量的维度、Label smoothing(置信度0.1)和dropout(0.1)。
18. Transformer为什么效果好?
18.1 固然题目是Attention is all you need,但后续一些研究表明,Attention、残差连接、layer normalization、FFN,这些因素共同成就了Transformer。
18.2 Transformer优点包罗:
第一,提出深度学习既MLP、CNN、RNN后的第4大特性提取器。
第二,一开始用在机器翻译,随着GPT和Bert彻底出圈;是一个迁徙转变点,在这个点之后,NLP领域快速发展,之后多模态、大模子、视觉Transformer等开始兴起。
第三,给人们信心,原来CNN和RNN之后,还可以有效果更好的特性提取器。
18.3 Transformer的不敷之处?
第一,计算量大,对硬件要求高。
第二,因为无归纳偏置,需要很多数据才可以取得很好的效果。
#Train_Transformers_with_INT4
清华朱军团队提出的INT4算法,解决了超低INT精度训练的挑衅。LLM训练服从要起飞了!
将激活、权重和梯度量化为4位,有望加速神经网络训练。
然而,现有的4位训练方法需要自定义数字格式,而当代硬件不支持这种格式。
最近,清华朱军团队提出了一种利用INT4算法实现全部矩阵乘法的Transformer训练方法。
利用超低INT4精度进行训练,黑白常具有挑衅性的。为了实现这一目标,研究者仔细分析了Transformer中激活和梯度的详细布局,为它们提出专用的量化器。
对于前向传播,研究者确定了异常值的挑衅,并提出了Hadamard量化器来克制异常值。
对于后向传播,他们通过提出位分割,来利用梯度的布局希罕性,并利用分数采样技术来准确量化梯度。
这种新的算法,在自然语言理解、机器翻译和图像分类等广泛任务上,都实现了具有竞争力的准确性。
原型线性算子运算速率比FP16同类算子快2.2倍,训练速率提高了35.1%。
论文地点:https://arxiv.org/abs/2306.11987
代码地点:https://github.com/xijiu9/Train_Transformers_with_INT4
全新的INT 4训练算法
训练神经网络对计算的要求很高。利用低精度算术进行训练(完全量化训练/FQT)有望提高计算和内存服从。
FQT方法在原来的全精度计算图中添加了一些量化器和反量化器,并用消耗更小的低精度浮点运算,代替了消耗更高的浮点运算。
FQT的研究旨在低落训练数值精度,而不捐躯太多的收敛速率或精度。
所需的数值精度已从FP16低落到FP8、INT32+INT8和INT8+INT5。
FP8训练是在带有Transformer引擎的Nvidia H100 GPU中实现的,加速了大规模Transformer的训练。最近的训练数值精度,已经降到了4位。
然而,这些4位训练方法不能直接用于加速,因为它们需要自定义数字格式,而当代硬件不支持这些格式。
首先,前向传播中的不可微量化器,会使丧失情况变得坎坷不平,基于梯度的优化器很容易陷入局部最优。
其次,梯度仅仅以低精度近似计算。这种不精确的梯度会减慢训练过程,甚至导致训练不稳固或发散。
而在这项工作中,研究者为Transformer提出了一种新颖的INT4训练算法。
训练Transformer的全部高消耗的线性运算,都可以写在矩阵乘法(MM)的形式中。
这种MM形式,可以让我们计划更机动的量化器,通过利用Transformer中激活、权重和梯度的特定布局,就可以更好地近似于FP32矩阵乘法。
随机数值线性代数 (RandNLA) 领域的进步,被这种量化器充分利用。
对于前向传播,研究者发现,激活中的异常值是精度下降的重要原因。
为了克制异常值,他们提出了Hadamard量化器,它会对激活矩阵的变动版本进行量化。这种变动是块对角Hadamard矩阵,它将离群值中携带的信息传播到矩阵的邻近条目,从而缩小了离群值的数值范围。
对于后向传播,他们利用了激活梯度的布局希罕性。研究者发现,一些token的梯度非常大。同时,其余大多数token的梯度非常匀称,甚至比较大梯度的量化残差更匀称。
因此,与其计算全部梯度,不如节流计算较大梯度残差的计算资源。
为了利用这种希罕性,研究者提出了位分割,将每个token的梯度分割为高4位和低4位。
然后,通过杠杆分数采样(leverage score sampling)来选择信息最丰富的梯度,这是RandNLA的一种重要采样技术。
结合前向和后向传播的量化技术,研究者提出了一种利用INT4MM进行Transformer中全部线性运算的算法, 而且评估了在各种任务上训练Transformer的算法,包罗自然语言理解、问答、机器翻译和图像分类。
与现有的4位训练算法相比,他们的算法实现了有竞争力的或更高的精度。
别的,这种算法与GPU等当代硬件兼容,因为它不需要FP4或对数格式等自定义的数字格式。
这种原型量化+INT4 MM算子实现,速率比FP16MM基线快2.2倍,而且将训练速率提高了35.1%。
相关工作完全量化训练
完全量化训练 (FQT) 方法通过将激活、权重和梯度量化为低精度来加速训练,因此训练期间的线性和非线性算子可以用低精度算术来实现。
FQT的研究计划了新颖的数值格式和量化算法,可以更好地逼近全精度张量。
如今的研究前沿是4位FQT。由于梯度的数值范围很大以及从头开始训练量化网络的优化题目,FQT具有挑衅性。
由于这些挑衅,现有的4位FQT 算法在某些任务上的精度仍旧下降了1-2.5%,而且无法支持当代硬件。
其他有效的训练方法
混合专家在不增长训练预算的情况下提高了模子容量。
布局性dropout利用计算有效的方法来正则化模子。高效的注意力低落了计算注意力的二次时间复杂度。
分布式训练体系通过利用更多的计算资源,减少了训练时间。
研究者低落数值精度的工作与这些方向具有正交性。
前向传播
神经网络训练是一个迭代优化过程,通过前向和后向传播计算随机梯度。
研究团队利用4位整数(INT4)算法加速前向和后向传播。
正向传播能以线性和非线性(GeLU, normalization, softmax等)算子的组合来实现。
在我们的训练过程中,我们用INT4算术加速全部线性运算符,并将全部计算量较小的非线性运算符保存在16位浮 点(FP16)格式中。
Transformer中的全部线性运算都可以写成矩阵乘法(MM)的形式。
为了便于表述,本文考虑以下简单矩阵乘法的加速:
这种MM的最重要用例是全连接层。
考虑一个输入形状为(批量大小S,序列长度T,维度D)的Transformer。
全连接层可以表述成上边的公式,其中X是N = STtoken的激活,W是权重矩阵。
对于注意力层,大概需要批量矩阵乘法(BMMS)。
我们提出的技术可以应用于BMMS。
学习步长量化(Learned Step Quantization)
为了加速训练,必须利用整数运算来计算前向传播。
研究职员为此目的,利用学习步长量化器(LSQ)。
LSQ是静态量化,他的量化标准不依靠于输入的方法,因此比动态方法消耗更小,量化方法,需要在每次迭代时动态计算量化标准。
激活异常值
简单地将LSQ应用到具有4位激活/权重的FQT会导致精度下降,因为会激活异常值。
如上图所示,激活有一些离群值条目,它们是其规模比其他条目大得多。
不幸的是,Transformers倾向于将信息存储在这些异常值中,而且这样的截断会严峻损害准确性。
当训练任务是在一些新的卑鄙任务上微调预训练模子时,异常值题目尤为显着。
因为预训练模子比随机初始化包罗更多的异常值 。
Hadamard量化
我们提出了Hadamard量化(HQ)来解决异常值题目。
其重要头脑是将另一个具有较少异常值的线性空间中的矩阵进行量化。
激活矩阵中的异常值形成了一个特性布局(feature-wise structure)。
他们通常会集在几个维度上,也就是说X中只有几列明显大于其他列。
哈达玛变动(Hardamand transform)是一个线性变动,它可以将异常值分摊到其他条目中。
后向传播
如今我们考虑利用INT4操作来加速线性层的后向传播。
我们将在本节中讨论激活梯度/权重梯度的计算。
梯度的布局希罕性
我们注意到,在训练过程中梯度矩阵往往非常希罕。
而且希罕性具有这样的布局:
这种布局希罕性源于当代神经网络的严峻过度参数化。
几乎在整个训练过程中,网络都以超参数化方案运行,除了一些困难的例子之外,它可以很好地适应大多数训练数据。
因此,对于拟合精良的数据点,(激活)梯度将接近于零。
研究职员发现对于预训练任务,例如,颠末几个训练周期后,布局希罕性很快就会出现。
对于微调任务,梯度整个训练过程中始终是希罕的。
位分割(Bit Splitting)和杠杆分数采样(Leverage Score Sampling)
如何计划梯度量化器,以利用布局希罕性在反向传播期间准确计算MM呢?
高级的思路是:梯度的很多行都是如此小,对参数梯度影响很小,但浪费了大量的计算量。
另一方面,大行无法用INT4精确表现。
我们放弃掉一些小行并利用节流下来的计算能力来更准确地表现大行。
实验
研究职员在包罗语言模子在内的各种任务上评估我们的INT4训练算法微调、机器翻译和图像分类。
研究职员用CUDA和cutlass实行了他们提出的HQ-MM和LSS-MM算法。
研究职员用INT4实现替换全部浮点线性运算符,但没有简单地利用LSQ来嵌入层,并保持末了一个分类器层的精度。
末了研究职员对全部评估的模子接纳了默认架构、优化器、调度器和超参数。
收敛模子精度
研究职员在下表中比较了收敛模子在各种任务上的准确性。
作为对照的方法包罗全精度训练(FP)、INT8训练(INT8)、FP4训练(「超低」),利用LSQ进行激活和权重(LSQ+LUQ)的4 位对数量化,以及我们这种利用HQ进行前向传播,利用LSS进行反向传播(HQ+LSS)的算法。
「超低」没有公开的实现,因此我们仅列出了它在机器上的原始论文中的性能翻译任务。
除了大型机器翻译任务和大型视觉Transformer任务之外,我们将每次运行重复三次,并将标准差陈诉为表中的下标。
研究职员没有进行任何类型的知识蒸馏或数据增强。
消融实验
研究职员进行的消融实验目的是展示前向和后向方法的有效性。
研究差别量化器的前向传播的有效性,我们将后向传播留在FP16中。
效果如下图所示。
计算和内存服从
末了,研究职员通过评估他们的原型实现,展示了他们的方法加速神经网络训练的潜力。
而且他们的实施还没有完全优化。
研究职员也没有将线性算子与非线性和归一化进行融合。
因此,效果不能完全反映INT4训练算法的潜力。
完全优化的实施需要大量工程,超出了我们论文的讨论范围。
结论
研究职员提出了一种对硬件很友好的Transformer INT4的训练方法。
通太过析Transformer中MM的属性,研究职员提出了HQ和LSS方法来量化激活和梯度,同时保持准确性。
在几个重要任务上,我们的方法与现有的INT4方法表现相当,甚至更好。
研究职员的这些工作大概会扩展到除了Transformers之外的其他MM架构中,例如 MLP-Mixer、图神经网络和循环神经网络网络。
这是他们将来的研究方向。
更广泛的影响:研究职员的算法可以提高服从并减少训练神经网络的能源消耗,这有助于减少深度学习造成的碳排放。
但是,高效的训练算法还大概促进那些,对于人来安全存在隐患的大语言模子和恶意人工智能应用程序的开辟。
比如,会被用于虚假内容生成的相关模子和应用。
限制:这项工作的重要限制是它只能加速具有较大规模的矩阵乘法(线性层)的大模子,但不能加速卷积层。
而且,所提出的方法还不能很好地适用于OPT-175B等超大模子。
据我们所知,即使是INT8训练对于这些超大型模子来说仍旧是尚待解决的题目。
参考资料:
https://arxiv.org/abs/2306.11987
加速AGI落地!利用4-bit整数训练Transformer,比FP16快2.2倍,提速35.1%
论文地点:https://arxiv.org/pdf/2306.11987.pdf
项目地点:https://github.com/xijiu9/Train_Transformers_with_INT4
将激活、权重和梯度量化为4-bit有望加速神经网络训练。然而,现有的4-bit训练方法需要定制的数字格式,这是当代硬件所不支持的。
在这项工作中,研究者提出了一种用INT4算法实现全部矩阵乘法的transformers的训练方法。超低INT4精度的训练极具挑衅性。为了实现这一点,我们仔细分析了transformer中激活和梯度的详细布局,为它们提出了专用的量化器。对于前向传播,我们识别了异常值的挑衅,并提出了一种Hadamard量化器来克制异常值。对于反向传播,我们通过提出比特分割和利用分数采样技术来精确量化梯度,从而利用梯度的布局希罕性。我们的算法在包罗自然语言理解、机器翻译和图像分类在内的广泛任务中实现了具有竞争力的准确性。
【QLoRA自己讲的是模子自己用4bit加载,训练时把数值反量化到bf16后进行训练,利用LoRA[2]可以锁定原模子参数不到场训练,只训练少量LoRA参数的特性使得训练所需的显存大大减少。例如33B的LLaMA模子颠末这种方式可以在24 GB的显卡上训练,也就是说单卡4090、3090都可以实现,大大低落了微调的门槛】QLORA: Efficient Finetuning of Quantized LLMs
与从前的4-bit训练方法差别,我们的算法可以在当前一代的GPU上实现。我们的原型线性算子实现速率是FP16的2.2倍,训练速率提高了35.1%。
训练神经网络在计算上要求很高。低精度算术训练(也称为全量化训练或FQT)有望提高计算和记忆服从。FQT方法在原来的全精度计算图中添加了一些量化器和反量化器,并用便宜的低精度运算取代了昂贵的浮点运算。FQT的研究旨在低落训练的数值精度,而不捐躯太多的收敛速率或精度。所需的数值精度已从FP16低落到FP8、INT32+INT8和INT8+INT5。FP8训练是在英伟达的H100 GPU和变压器引擎中实现的,为大型变压器的训练实现了令人印象深刻的加速。
最近,训练数值精度已被低落到4位。Sun等人成功地用INT4激活/权重和FP4梯度训练了几个当代网络;和Chmiel等人提出了一种自定义的4位对数数字格式,以进一步提高精度。然而,这些4位训练方法不能直接用于加速,因为它们需要当代硬件不支持的自定义数字格式。在极低的4位程度上训练神经网络存在重大的优化挑衅。首先,前向传播中的不可微量化器使丧失景观变得坎坷不平,其中基于梯度的优化器很容易陷入局部最优。其次,梯度仅以低精度近似计算。这种不精确的梯度减缓了训练过程,甚至导致训练不稳固或偏离。
Fully Quantized Training
全量化训练(FQT)方法通过将激活、权重和梯度量化到低精度来加速训练,因此训练过程中的线性和非线性算子可以用低精度算法实现。FQT的研究计划了新的数值格式和量化算法,可以更好地逼近全精度张量。如今的研究前沿是4位FQT。由于梯度的巨大数值范围和从头开始训练量化网络的优化题目,FQT具有挑衅性。由于这些挑衅,现有的4位FQT算法在某些任务上的精度仍有1-2.5%的下降,而且它们无法支持当代硬件。
Other Efficient Training Methods
Mixture-of-experts【Outrageously large neural networks: The sparsely-gated mixture-of-experts layer】在不增长训练预算的情况下提高了模子的能力。布局丢弃利用计算上有效的方法来正则化模子。有效的注意力减少了计算注意力的二次时间复杂度。分布式训练体系通过利用更多的计算资源来减少训练时间。我们低落数值精度的工作与这些方向正交。
新框架
神经网络训练是一种迭代优化过程,通过前向和后向传播计算随机梯度。我们利用4位整数(INT4)算法加速正向和反向传播。首先描述我们的训练程序的正向传播。前向传播可以公式化为线性和非线性(GeLU、归一化、softmax等)算子的组合。在我们的训练过程中,我们利用INT4算法加速全部线性算子,并将全部计算麋集度较低的非线性算子保存为16位浮点(FP16)格式。变压器中的全部线性运算都可以写成矩阵乘法(MM)形式。
Hadamard Quantization
我们提出了一种Hadamard量化器(HQ)来解决异常值题目。它的重要头脑是在另一个具有较少异常值的线性空间中量化矩阵。激活矩阵中的异常值形成了一个特性布局。它们通常会集在几个维度上,即只有少数X列比其他列大得多。Hadamard变动是一种线性变动,可以将异常值摊销为其他条目。详细地说,Hadamard变动Hk是2k×2k矩阵,其中:
实验
#Transformer~x1
固然是30 但是个合集啊~~ 包罗给60个模子建目录
如果说过去几年是什么在支持着大规模模子的发展,那肯定是Transformer了!
基于Transformer,大量模子在各个领域如同雨后春笋般不停涌现,每个模子都有差别的架构,差别的细节,以及一个不容易表明的名字。
最近有作者对近几年发布的全部流行的Transformer模子进行了一次全面的分类和索引,尽大概提供一个全面但简单的目录(catalog),文中包罗对Transformer创新的简介,以及发展脉络梳理。
论文链接:https://arxiv.org/pdf/2302.07730.pdf
图灵奖得主Yann LeCun表现认可。
文章作者Xavier (Xavi) Amatriain于2005年博士毕业于西班牙庞培法布拉大学,如今是LinkedIn工程部副总裁,重要负责产品人工智能战略。
什么是Transformer?
Transformer是一类深度学习模子,具有一些独特的架构特性,最早出如今谷歌研究职员于2017年发表的著名的「Attention is All you Need」论文中,该论文在短短5年内积累了惊人的38000次引用。
Transformer架构也属于编码器-解码器模子(encoder-decoder),只不过在此之前的模子,注意力只是其中的机制之一,大多都是基于LSTM(黑白时记忆)和其他RNN(循环神经网络)的变体。
提出Transformer的这篇论文的一个关键见解如标题所说,注意力机制可以作为推导输入和输出之间依靠关系的唯一机制,这篇论文并不打算深入研究Transformer架构的全部细节,感兴趣的朋侪可以搜刮「The Illustrated Transformer」博客。
下面只简要地描述最重要的一些组件。
编码器-解码器架构
一个通用的编码器/解码器架构由两个模子组成,编码器担当输入并将其编码为一个固定长度的向量;解码器吸收该向量并将其解码为输出序列。
对编码器息争码器进行联合训练以最小化条件对数似然。训练完成后,编码器/解码器就可以根据给定输入序列的生成一个输出,大概可以给一对输入/输出序列打分。
在最初的Transformer架构下,编码器息争码器都有6个雷同的层,在这6层中的每一层,编码器有两个子层:一个多头注意层,和一个简单的前馈网络,每个子层都有一个残差连接和一个层归一化。
编码器的输出大小为512,解码器增长了第三个子层,即在编码器输出上的另一个多头注意层。别的,解码器中的另一个多头层被mask掉,以防止对后续位置应用注意力,造成信息泄露。
注意力机制
从上面的描述中可以看出,模子布局中唯一「奇异」的元素是多头的注意力,也正是该模子的全部力量所在。
注意力函数是query和一组key-value pairs到输出之间的映射,输出的计算为数值的加权和,其中分配给每个数值的权重是由query与相应的key的compatibility函数计算的。
Transformer利用多头注意力(multi-head attention),即对一组注意力函数的并行计算,也称为缩放点积注意力。
与递归和卷积网络相比,注意力层有几个上风,比较重要的是其较低的计算复杂性和较高的连接性,对学习序列中的长期依靠关系特别有用。
Transformer可以做什么?为什么流行起来了?
最初的Transformer是为语言翻译而计划的,重要是从英语翻译到德语,但是初版论文的实验效果已经表明,该架构可以很好地推广到其他语言任务。
这一特别的趋势很快就被研究界注意到了。
在接下来的几个月里,任何与语言相关的ML任务的排行榜都完全被某个版本的Transformer架构所占据,比如问答任务Squad很快就被各种Transformer模子屠榜了。
Transofrmer能够如此敏捷地霸占大多数NLP排行榜的关键原因之一是:它们能够快速适应其他任务,也就是迁徙学习;预先训练好的Transformer模子可以非常容易和敏捷地适应它们没有被训练过的任务,相比其他模子有巨大的上风。
作为一个ML从业者,你不再需要在一个巨大的数据集上从头训练一个大型模子,只需要在手头任务上重新利用预训练过的模子,也许只是用一个小得多的数据集对其稍作调解。
用来使预训练的模子适应差别任务的详细技术是所谓的微调(fine-tuning)。
事实证明,Transformer适应其他任务的能力是如此之强,固然它们最初是为语言相关的任务而开辟的,但它们很快就对其他任务有用了,从视觉或音频和音乐应用不停到下棋或做数学。
当然,如果不是因为有无数的工具,使任何能写几行代码的人都能随时利用这些工具,全部这些应用就不大概实现。
Transformer不仅很快被整合到重要的人工智能框架中(即Pytorch和TensorFlow),还有一些完全为Transformer而生的公司。
Huggingface,一家到今天为止已经筹集了超过6000万美元的创业公司,几乎完全是围绕着将他们的开源Transformer库贸易化的想法而建立的。
GPT-3是OpenAI在2020年5月推出的Transformer模子,是他们早期GPT和GPT-2的后续版本。该公司在一份预印本中介绍了该模子,引起了很大的惊动,论文中声称该模子非常强大,以至于他们没有资格向世界发布。
而且,OpenAI不仅没有发布GPT-3,而且通过和微软之间的一个非常大的搭档关系实现了贸易化。
如今,GPT-3为300多个差别的应用程序提供底层技术支持,而且是OpenAI贸易战略的基础。对于一个已经获得超过10亿美元资金的公司来说,这是很重要的。
RLHF
从人类反馈(或偏好)中强化学习,又称RLHF(或RLHP),最近已经成为人工智能工具箱的一个巨大补充。
这个概念最早来自2017年的论文「来自人类偏好的深度强化学习」,但最近它被应用于ChatGPT和类似的对话智能体中,取得了相当好的效果,又引起了大众的关注。
文中的想法黑白常简单的,一旦语言模子被预训练后,就可以对对话产生差别的回应,并让人类对效果进行排名,可以利用这些排名(又称偏好或反馈)利用强化学习机制来训练奖励。
扩散模子Diffusion
扩散模子已经成为图像生成的新的SOTA,大有取代GANs(生成对抗网络)的趋势。
扩散模子是一类颠末训练的变分推理(varitional inference)的潜变量模子,在实践中的意思就是训练一个深度神经网络来对用某种噪声函数模糊的图像进行去噪。
以这种方式训练的网络现实上是在学习这些图像所代表的潜空间。
看完介绍,快开启Transformer的回溯之旅吧!
#VDT
2 月 16 日,OpenAI Sora 的发布无疑标记着视频生成领域的一次重大突破。Sora 基于 Diffusion Transformer 架构,和市面上大部分主流方法(由 2D Stable Diffusion 扩展)并不雷同。
为什么 Sora 坚持利用 Diffusion Transformer,其中的原因从同时期发表在 ICLR 2024(VDT: General-purpose Video Diffusion Transformers via Mask Modeling)的论文可以窥见一二。
这项工作由中国人民大学研究团队主导,并与加州大学伯克利分校、香港大学等进行了互助,最早于 2023 年 5 月公开在 arXiv 网站。研究团队提出了基于 Transformer 的 Video 同一生成框架 - Video Diffusion Transformer (VDT),并对接纳 Transformer 架构的原因给出了详细的表明。
- 论文标题:VDT: General-purpose Video Diffusion Transformers via Mask Modeling
- 文章地点:Openreview: https://openreview.net/pdf?id=Un0rgm9f04
- arXiv地点: https://arxiv.org/abs/2305.13311
- 项目地点:VDT: General-purpose Video Diffusion Transformers via Mask Modeling
- 代码地点:https://github.com/RERV/VDT
1.VDT 的精良性与创新之处
研究者表现,接纳 Transformer 架构的 VDT 模子,在视频生成领域的精良性表如今:
- 与重要为图像计划的 U-Net 差别,Transformer 能够借助其强大的 token 化和注意力机制,捕捉长期或不规则的时间依靠性,从而更好地处理时间维度。
- 只有当模子学习(或记忆)了世界知识(例如空间时间关系和物理法则)时,才气生成与现实世界符合的视频。因此,模子的容量成为视频扩散的一个关键组成部分。Transformer 已经被证明具有高度的可扩展性,比如 PaLM 模子就拥有高达 540B 的参数,而当时最大的 2D U-Net 模子大小仅 2.6B 参数(SDXL),这使得 Transformer 比 3D U-Net 更适合应对视频生成的挑衅。
- 视频生成领域涵盖了包罗无条件生成、视频预测、插值和文本到图像生成等多项任务。以往的研究往往聚焦于单一任务,常常需要为卑鄙任务引入专门的模块进行微调。别的,这些任务涉及多种多样的条件信息,这些信息在差别帧和模态之间大概有所差别,这就需要一个能够处理差别输入长度和模态的强大架构。Transformer 的引入能够实现这些任务的同一。
VDT 的创新之处,重要包罗如下几个方面:
- 将 Transformer 技术应用于基于扩散的视频生成,展现了 Transformer 在视频生成领域的巨大潜力。VDT 的上风在于其出色的时间依靠性捕获能力,能够生成时间上连贯的视频帧,包罗模仿三维对象随时间的物理动态。
- 提出同一的时空掩码建模机制,使 VDT 能够处理多种视频生成任务,实现了技术的广泛应用。VDT 机动的条件信息处理方式,如简单的 token 空间拼接,有效地同一了差别长度和模态的信息。同时,通过与该工作提出的时空掩码建模机制结合,VDT 成为了一个通用的视频扩散工具,在不修改模子布局的情况下可以应用于无条件生成、视频后续帧预测、插帧、图生视频、视频画面补全等多种视频生成任务。
2.VDT 的网络架构详细解读
VDT 框架与 Sora 的框架非常相似,包罗以下几部分:
输入 / 输出特性。VDT 的目标是生成一个 F×H×W×3 的视频片断,由 F 帧大小为 H×W 的视频组成。然而,如果利用原始像素作为 VDT 的输入,尤其是当 F 很大时,将导致计算量极大。为解决这个题目,受潜在扩散模子(LDM)的开导,VDT 利用预训练的 VAE tokenizer 将视频投影到潜在空间中。将输入和输出的向量维度减少到潜在特性 / 噪声的 F×H/8×W/8×C,加速了 VDT 的训练和推理速率,其中 F 帧潜在特性的大小为 H/8×W/8。这里的 8 是 VAE tokenizer 的下采样率,C 表现潜在特性维度。
线性嵌入。遵循 Vision Transformer 的方法,VDT 将潜在视频特性表现分别为大小为 N×N 的非重叠 Patch。
时空 Transformer Block。受到视频建模中时空自注意力成功的开导,VDT 在 Transformer Block 中插入了一个时间注意力层,以获得时间维度的建模能力。详细来说,每个 Transformer Block 由一个多头时间注意力、一个多头空间注意力和一个全连接前馈网络组成,如上图所示。
对比 Sora 最新发布的技术陈诉,可以看到 VDT 和 Sora 在实现细节上仅存在一些渺小差异。
首先,VDT 接纳的是在时空维度上分别进行注意力机制处理的方法,而 Sora 则是将时间和空间维度合并,通过单一的注意力机制来处理。这种分离注意力的做法在视频领域已经相当常见,通常被视为在显存限制下的一种妥协选择。VDT 选择接纳分离注意力也是出于计算资源有限的考虑。Sora 强大的视频动态能力大概来自于时空团体的注意力机制。
其次,差别于 VDT,Sora 还考虑了文本条件的融合。之前也有基于 Transformer 进行文本条件融合的研究(如 DiT),这里推测 Sora 大概在其模块中进一步到场了交织注意力机制,当然,直接将文本和噪声拼接作为条件输入的形式也是一种潜在的大概。
在 VDT 的研究进程中,研究者将 U-Net 这个常用的基础骨干网络替换为 Transformer。这不仅验证了 Transformer 在视频扩散任务中的有效性,展现了便于扩展和增强一连性的上风,也引发了他们对于其潜在价值的进一步思考。
随着 GPT 模子的成功和自回归(AR)模子的流行,研究者开始探索 Transformer 在视频生成领域的更深层次应用,思考其是否能为实现视觉智能提供新的途径。视频生成领域有一个与之密切相关的任务 —— 视频预测。将预测下一个视频帧作为通往视觉智能的路径这一想法看似简单,但它现实上是很多研究者共同关注的题目。
基于这一考虑,研究者希望在视频预测任务上进一步适配和优化他们的模子。视频预测任务也可以视为条件生成,这里给定的条件帧是视频的前几帧。VDT 重要考虑了以下三种条件生成方式:
自适应层归一化。实现视频预测的一种直接方法是将条件帧特性整合到 VDT Block 的层归一化中,类似于我们如何将时间信息整合到扩散过程中。
交织注意力。研究者还探索了利用交织注意力作为视频预测方案,其中条件帧用作键和值,而噪声帧作为查询。这允许将条件信息与噪声帧融合。在进入交织注意力层之前,利用 VAE tokenizer 提取条件帧的特性并 Patch 化。同时,还添加了空间和时间位置嵌入,以帮助我们的 VDT 学习条件帧中的对应信息。
Token 拼接。VDT 模子接纳纯粹的 Transformer 架构,因此,直接利用条件帧作为输入 token 对 VDT 来说是更直观的方法。研究者通过在 token 级别拼接条件帧(潜在特性)和噪声帧来实现这一点,然后将其输入到 VDT 中。接下来,他们将 VDT 的输出帧序列分割,并利用预测的帧进行扩散过程,如图 3 (b) 所示。研究者发现,这种方案展示了最快的收敛速率,与前两种方法相比,在终极效果上提供了更优的表现。别的,研究者发现即使在训练过程中利用固定长度的条件帧,VDT 仍旧可以担当任意长度的条件帧作为输入,并输出同等的预测特性。
在 VDT 的框架下,为了实现视频预测任务,不需要对网络布局进行任何修改,仅需改变模子的输入即可。这一发现引出了一个直观的题目:我们可否进一步利用这种可扩展性,将 VDT 扩展到更多样化的视频生成任务上 —— 例如图片生成视频 —— 而无需引入任何额外的模块或参数。
通过回顾 VDT 在无条件生成和视频预测中的功能,唯一的区别在于输入特性的类型。详细来说,输入可以是纯噪声潜在特性,大概是条件和噪声潜在特性的拼接。然后,研究者引入了 Unified Spatial-Temporal Mask Modeling 来同一条件输入,如下图 4 所示:
3.VDT 的性能评测
通过上述方法,VDT 模子不仅可以无缝地处理无条件视频生成和视频预测任务,还能够通过简单地调解输入特性,扩展到更广泛的视频生成领域,如视频帧插值等。这种机动性和可扩展性的表现,展示了 VDT 框架的强大潜力,为将来的视频生成技术提供了新的方向和大概性。
风趣的是,除 text-to-video 外,OpenAI 也展示了 Sora 非常惊艳的其他任务,包罗基于 image 生成,前后 video predict 以及差别 video clip 相融合的例子等,和研究者提出的 Unified Spatial-Temporal Mask Modeling 所支持的卑鄙任务非常相似;同时在参考文献中也引用了 kaiming 的 MAE。所以,这里推测 Sora 大概率底层也利用了类 MAE 的训练方法。
研究者同时探索了生成模子 VDT 对简单物理规律的模仿。他们在 Physion 数据集上进行实验,VDT 利用前 8 帧作为条件帧,并预测接下来的 8 帧。在第一个示例(顶部两行)和第三个示例(底部两行)中,VDT 成功模仿了物理过程,包罗一个沿抛物线轨迹运动的球和一个在平面上滚动并与圆柱体碰撞的球。在第二个示例(中心两行)中,VDT 捕捉到了球的速率 / 动量,因为球在碰撞圆柱体前停了下来。这证明白 Transformer 架构是可以学习到肯定的物理规律。
VDT 对网络布局进行部分消融。可以发现模子性能和 GFlops 强相关,模子布局自己的一些细节反而影响不是很大,这个和 DiT 的发现也是同等的。
研究者还对 VDT 模子进行了一些布局上的消融研究。效果表明,减小 Patchsize、增长 Layers 的数量以及增大 Hidden Size 都可以进一步提高模子的性能。Temporal 和 Spatial 注意力的位置以及注意力头的数量对模子的效果影响不大。在保持雷同 GFlops 的情况下,需要一些计划上的权衡,总体而言,模子的性能没有明显差异。但是,GFlops 的增长会带来更好的效果,这展示了 VDT 大概 Transformer 架构的可扩展性。
VDT 的测试效果证明白 Transformer 架构在处理视频数据生成方面的有效性和机动性。由于计算资源的限制,VDT 只在部分小型学术数据集上进行了实验。我们期待将来研究能够在 VDT 的基础上,进一步探索视频生成技术的新方向和应用,也期待中国公司能早日推出国产 Sora 模子。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |