赞
踩
Vision Transformer只做了图片分类任务,表现了transformer在视觉领域的强大实力,但未表明transformer架构能cover掉CV领域的所有任务。Swin Transformer是2021年微软研究院发表在ICCV上的一篇文章,并且获得了ICCV 2021 best paper,其证明了transformer架构可以很好地用于所有的CV任务
Swin Transformer名字来自(Shifted Windows):Hierarchical Vision Transformer using Shifted Windows,即使用了移动窗口的vision transformer,即主要思想借鉴于CNN的多尺度思想,即作者想让vision transformer也能像CNN那样,可以分成几个block,也能做层级式的特征提取,也能具有多尺度的概念,从而可以作为一个通用的backbone,可以更好地服务于检测、分割等CV下游任务
transformer从NLP迁移到CV的问题:
尺度问题,由于照片拍摄远近以及实物大小的不同,代表同一个语义的词,如行人,汽车等就会具有非常不同的尺寸
正如在vision transformer中的提到过的,图片分辨率太高,如果以像素点作为基本单位,序列长度就会变得高不可攀,之前的工作要么是使用经过CNN的特征图,要么是将图片打成一个个patch,要么就是把图片拆成一个个小窗口,在窗口里去做自注意力
不同于VIT拆分成一个个patch来减少序列长度的办法,本文作者提出使用移动窗口的方式来减少序列长度,即hierarchical Transformer,将特征图划分成了多个不相交的区域(Window),并且Multi-Head Self-Attention只在每个窗口(Window)内进行。这既使得计算效率更高(只在窗口内做注意力计算,而不是计算全局,计算复杂度随图片大小线性增长,而不是平方增长),而这种窗口的移动也使得相邻的窗口之间有了交互,上下层之间就具有了cross-window connection,从而变相地达到了一种全局建模的能力
如上图右所示,VIT中的每个patch自始至终看到的尺寸都是差不多,处理的特征都是单一尺寸,自注意力计算始终都是在全局上进行,虽然它通过全局的自注意力操作,实现了全局建模的能力,但它对于多尺度特征的学习会相对弱一些,而显然从目标检测中的FPN模块和语义分割中的skip connect结构中,可以看到,对于下游任务,尤其是密集预测型的下游任务,有多尺度特征是至关重要的
此外,由于VIT是在全局上计算自注意力,所以计算量是与分辨率成平方关系的,对于检测和分割领域,如今的常用的输入尺寸已经达到800x800或者1024x1024,虽然patch_size 16能处理224x224大小的图片,但当尺寸来到800x800甚至更大,序列长度还是会达到上千,计算复杂度还是难以承受。因此综合考虑多尺度特征和计算量,VIT可能就不那么适用于CV下游任务。
也因此,综合上述挑战,作者借鉴了很多CNN的思想,提出了Swin transformer,某种程度上可以说,Swin Transformer就是披着Transformer皮的CNN。
Swin Transformer采取在小窗口内计算自注意力,如果窗口大小固定,那么自注意力计算的复杂度也是固定的,整张图的计算复杂度就会跟图片大小呈线性增长,如图片大小增大X倍,那么窗口数量也会增大X倍,计算量也就只会增大X倍。这里等价于利用了图片中局部性(Locality)的归纳偏置(Inductive bias),即同一个物体的不同部位或语义相近的不同物体还是大概率会出现在相邻的地方,所以即使仅在一个小窗口里计算自注意力,也是基本够用的。
CNN具有多尺度特征主要是由于池化操作(Pooling),带步幅的池化操作能增大每一个卷积核的感受野,从而使得每次池化后的特征能抓住不同物体的不同尺寸。基于此,Swin Transformer也提出一个类似池化的操作:patch merging,即将相邻的四个小patch合成一个大patch,合并以后的感受野就增大了,同时也能获得多尺度的特征。
下面简单从网络图(下图(a))分析一下模型的前向过程
输入图片:224*224*3(ImageNet 标准尺寸)
第一步就是像 ViT 那样把图片打成 patch,在 Swin Transformer 这篇论文里,它的 patch size 是4*4,而不是像 ViT 一样16*16,所以经过 patch partition 打成 patch 之后,得到图片的尺寸是[H/4, W/4, 48](56*56*48),因为 patch size 是4,所以向量的维度是4*4*3=48
然后通过Linear Embedding对图片做线性变换,也就是说要把向量的维度变成一个预先设置好的值,就是 Transformer 能够接受的值,在 Swin Transformer 的论文里把这个超参数设为 c,即将图像的shape由 [H/4, W/4, 48]变成了 [H/4, W/4, C],对于 Swin tiny 网络来说,也就是上图中画的网络总览图,它的 c 是96,经过Linear Embedding 之后,输入的尺寸就变成了56*56*96,前面的56*56就会拉直变成3136,变成了序列长度,后面的96就变成了每一个token向量的维度,其实 Patch Partition + Linear Embedding 就相当于是 ViT 里的Patch Projection 操作,而在代码里也是用一次卷积操作就完成了,代码实现上没有什么区别。
上述第一部分跟 ViT 其实还是没有区别的,只是patch size不一样,但紧接着区别就来了
首先序列长度是3136,对于 ViT 来说,用 patch size 16*16,它的序列长度就只有196,是相对短很多的,这里的3136就太长了,是目前来说Transformer不能接受的序列长度,所以 Swin Transformer 就引入了基于窗口的自注意力计算,每个窗口默认情况下,都只有七七四十九个 patch,所以说序列长度就只有49就相当小了,这样就解决了计算复杂度的问题
即Swin Transformer Block 是基于窗口计算自注意力的,现在暂时先把 transformer block当成是一个黑盒(后文详细分析),只关注输入和输出的维度,对于 Transformer 来说,如果不对它做更多约束的话,Transformer输入的序列长度是多少,输出的序列长度也是多少,它的输入输出的尺寸是不变的,所以说在 Stage1 中经过两层Swin Transformer block 之后,输出还是56*56*96
然后就是通过四个Stage构建不同大小的特征图,从而可以获得多尺度的特征信息,除了Stage1中先通过一个Linear Embeding层外,剩下三个stage都是先通过一个类似CNN中的Pooling:Patch Merging,进行下采样,降低分辨率,然后重复堆叠Swin Transformer Block
通过Patch Merging,与CNN类似,每经过一个stage(stage1除外),向量的高宽减半,通道数翻倍,以输入224x224x3为例,经过Patch Partition变为56x56x48,再经过Linear Embedding变为56x56x96,经过stage1变为56x56x96,stage2变为28x28x192,stage3变为14x14x384,stage4变为7x7x768,这就与CNN不同stage的特征图对应了起来
如果是用于图片分类,为了和卷积神经网络保持一致,Swin Transformer这篇论文并没有像 ViT 一样使用 CLS token,而是接上一个Layer Norm层、全局池化层以及全连接层得到最终输出。(作者这个图里并没有画,因为 Swin Transformer的本意并不是只做分类,它还会去做检测和分割,所以说它只画了backbone的部分,没有去画最后的分类头或者检测头)
如上图所示,Swin Transformer Blocks有两种结构,区别在于窗口多头自注意力的计算一个使用了W-MSA结构,一个使用了SW-MSA结构。而且这两个结构是成对使用的,先使用一个W-MSA结构再使用一个SW-MSA结构。所以堆叠Swin Transformer Block的次数都是偶数(因为成对使用)。
下面分别对Patch Merging、W-MSA、SW-MSA的做详细介绍
Patch Merging 其实在Swin Transformer之前一些工作里也有用到,它很像 Pixel Shuffle 的上采样的一个反过程,Pixel Shuffle 是 lower level 任务中很常用的一个上采样方式。在YOLO中也有用到类似于Patch Merging的操作,称为Focus,只不过在YOLO中的基本元素是一个个像素,而这里的基本元素是一个个patch。
在每个Stage中首先要通过一个Patch Merging层进行下采样(Stage1除外。Patch Merging 顾名思义就是把临近的小 patch 合并成一个大 patch,如下图所示,这里因为是想下采样两倍,所以说在选点的时候是每隔一个点选一个,这里的1、2、3、4并不是矩阵里有的值,而是给它的一个序号,同样序号位置上的 patch 就会被 merge。经过隔一个点采一个样之后,原来的这个张量就变成了四个张量,在深度方向进行concat拼接,维度从h x w x c变为h/2 x w/2 x 4c,然后在通过一个LayerNorm层。类比于CNN,每次经过pooling后通道数只会翻倍,所以这里也只想让他翻倍,而不是变成4倍,所以紧接着又再做了一次操作,就是在 c 的维度上用一个1x1的卷积(或者全连接层),把通道数降下来变成2c,最后就得到了h/2 x w/2 x 2c的输出。即通过Patch Merging层后,feature map的高和宽会减半,深度会翻倍。(当以每像素为基础应用时,1×1卷积层相当于全连接层)
这篇论文的主要贡献就是基于窗口或者移动窗口的自注意力,全局自注意力的计算会导致平方倍的复杂度,同样当去做视觉里的下游任务,尤其是密集预测型的任务,或者说遇到非常大尺寸的图片时候,这种全局算自注意力的计算复杂度就非常贵了,所以就用窗口的方式去做自注意力,也就是Windows Multi-head Self-Attention(W-MSA)
如上图所示,Muti-head Self-Attention是在全局上做自注意力,而W-MSA则是将特征图拆分成一个个不重叠的window,在window里分别做自注意力
拿第一层之前的输入来举例,它的尺寸是56x56x96,然后把它切成一个个不重叠的window,每个window的大小为m,也就是包含m*m个patch,一般m=7,即每个window包含49个patch,现在所有自注意力的计算都是在这些小窗口里完成的,就是说序列长度永远都是49,而原来大的整体特征图一共会有(56/7=8)8*8等于64个窗口,就是说会在这64个窗口里分别去算它们的自注意力
那么基于窗口的自注意力计算方式能比全局的自注意力方式节省多少计算量呢?
标准的多头自注意力:自注意力首先通过全连接层把输入x变成 q、k、v 三个向量,相当于是用一个 hxwxc 的向量乘以一个 cxc 的系数矩阵,最后得到了 hxwxc。所以每一个计算的复杂度是 h*w*c^2,因为有三次操作,所以是3*h*w*c^2。
然后,计算自注意力得分就是q:hxwxc乘以 k 的转置:cxhxw,得到hxwxhxw,这个计算复杂度就是(h*w)^2*c。
接下来,自注意力矩阵和value的乘积的计算复杂度还是 (h*w)^2*c。
最后一步,投影层也就是hxwxc乘以 cxc 变成了 hxwxc ,计算复杂度是 h*w*c^2
合并起来,就得到了下图公式(1)
基于窗口的自注意力:因为在每个窗口里算的还是多头自注意力,所以可以直接套用公式(1),只不过高度和宽度变化了,现在高度和宽度不再是 h * w,而是变成窗口有多大了,也就是 M*M,也就是说现在 h 变成了 M,w 也是 M,它的序列长度只有 M * M 这么大,所以当把 M 值带入到公式(1)之后,就得到计算复杂度是4 * M^2 * c^2 + 2 * M^4 * c,这个就是在一个窗口里算多头自注意力所需要的计算复杂度
那我们现在一共有 h/M * w/M 个窗口,现在用这么多个窗口乘以每个窗口所需要的计算复杂度就能得到公式(2)
其中:h代表feature map的高度,w代表feature map的宽度,C代表feature map的深度,M代表每个窗口(Windows)的大小
对比公式(1)和公式(2),虽然这两个公式前面这两项是一样的,只有后面从 (h*w)^2变成了 M^2 * h * w,看起来好像差别不大,但其实如果仔细带入数字进去计算就会发现,计算复杂的差距是相当巨大的,因为这里的 h*w 如果是56*56的话, M^2 其实只有49,所以是相差了几十甚至上百倍的
此外,将特征图分割成不重叠的窗口,而不是像卷积那样使用滑动窗口的方式,是因为这样可以使得不同的query共享key的集合,从而让运行更加高效,如下图所示,可以看到如果使用滑窗的方式(图左),每次滑动出来的窗口都有自己的key集合,所以即使对于同一个query点来说,每次算自注意力也要重新计算,对显存访问不友好。但移动窗口是不重叠的(图右),所以同一窗口内的点始终采用相同的邻域来进行计算,对速度更友好。
W-MSA这种基于窗口计算自注意力的方式虽然很好地解决了内存和计算量的问题,但是窗口和窗口之间没有信息传递,这样就无法更好地理解上下文,就达不到全局建模了,也就是说会限制模型的能力,所以最好还是要有一种方式能让窗口和窗口之间互相通信起来,这样效果应该会更好,因为具有上下文的信息,所以作者就提出移动窗口的方式,即Shifted Windows Multi-Head Self-Attention(SW-MSA)。
移动窗口就是把原来的窗口往右下角移动一半窗口的距离(向下取整),如果Transformer是上下两层连着做这种操作,先是 window再是 shifted window 的话,就能起到窗口和窗口之间互相通信的目的了。
所以正如前面所提到的,在 Swin Transformer里,每次都是先要做一次基于窗口的多头自注意力(W-MSA),然后再做一次基于移动窗口的多头自注意力(SW-MSA),这样就达到了窗口和窗口之间的互相通信。这两个 block 加起来其实才算是 Swin Transformer 一个基本的计算单元,这也是为什么每个stage里的Swin Transformer block 的数字总是偶数。
Shifted Windows:
如下图所示,在L层使用W-MSA,则在L+1层就使用SW-MSA,假设windows的大小为M,移动窗口就是把原来的窗口往右下角移动M/2的距离,然后重新划分窗口,分别对每个窗口内部进行MSA。如图示例,将窗口进行偏移后,由原来的4个窗口就变成9个窗口了。
综上就是Swin Transformer整体架构,作者主要的研究动机就是想要有一个层级式的 Transformer,为了这个层级式,所以使用了类似于池化的Patch Merging的操作,从而能像卷积神经网络一样把 Transformer 分成几个阶段,而为了减少计算复杂度,争取能做视觉里密集预测的任务,所以又提出了基于窗口和移动窗口的自注意力方式,也就是连在一起的两个Transformer block,最后把这些部分加在一起。
模型详细架构参数:
其中:win. sz. 表示使用的窗口(Windows)的大小(M=7,每个window包含49个patch)
dim 表示feature map的通道数(或者说token的向量长度)
head 表示多头注意力模块中head的个数
如上图所示,虽然这种基础版本的移动窗口已经能够达到窗口和窗口之间的互相通信了,但是会发现一个问题,就是原来计算的时候,特征图上只有四个窗口,但是做完移动窗口操作之后得到了9个窗口,窗口的数量增加了,而且每个窗口里的元素大小不一,比如说中间的窗口还是4*4,有16个 patch,但是别的窗口有的有4个 patch,有的有8个 patch,都不一样了,如果想做快速运算,就是把这些窗口全都压成一个 patch直接去算自注意力,就做不到了,因为窗口的大小不一样,因此这样计算效率是偏低的。
最直接的解决方式就是在这些小窗口周围再pad上0 ,把它照样pad成和中间窗口一样大的窗口,这样就有9个完全一样大的窗口,这样就还能把它们压成一个batch,就会快很多,但是这样的话,无形之中计算复杂度就提升了,因为原来如果算基于窗口的自注意力只用算4个窗口,但是现在需要去算9个窗口,计算复杂度一下就提升了两倍多。
作者提出了一个非常巧妙的方式:再次循环移位+掩码,使得计算时的窗口数量还是保持4个,而且每个窗口里的patch数量也还保持一致,从而可以压成一个batch,提高移动窗口的计算效率。
如上图所示,当通过普通的移动窗口方式,得到9个窗口之后,现在不在这9个窗口上算自注意力,先再做一次循环移位(cyclic shift)
经过这次循环移位之后,原来的窗口(虚线)就变成了现在窗口(实线)的样子,那如果在大的特征图上再把它分成四宫格的话,现在就又得到了四个窗口,意思就是说移位之前的窗口数也是4个,移完位之后再做一次循环移位得到窗口数还是4个,这样窗口的数量就固定了,也就说计算复杂度就固定了
但是新的问题就来了,虽然对于移位后左上角的窗口(也就是移位前最中间的窗口)来说,里面的元素都是互相紧挨着的,他们之间可以互相两两做自注意力,但是对于剩下几个窗口来说,它们里面的元素是由不同的window移位拼接起来的,所以它们之间,按道理来说是不应该去做自注意力,也就是说他们之间不应该有什么太大的联系。
解决这个问题就需要一个很常规的操作,也就是掩码操作,这在Transformer过去的工作里是层出不穷,很多工作里都有各式各样的掩码操作,在 Swin Transformer这篇论文里,作者也巧妙的设计了几种掩码的方式,从而能让一个窗口之中不同的区域之间也能用一次前向过程,就能把自注意力算出来,但是互相之间都不干扰,即masked Multi-head Self Attention(MSA)。
最后,在计算完多头自注意力之后,需要把循环位移再还原回去,也就是说需要把A、B、C再还原到原来的位置上去,原因是需要保持原来图片的相对位置不变的,从而使得整体图片的语义信息也是不变的,如果不把循环位移还原的话,那相当于在做Transformer的操作之中,一直在把图片往右下角移,不停的往右下角移,这样图片的语义信息很有可能就被破坏掉了。
掩码细节:如下图所示,循环移位后又变成了四个大小相同的window,其中蓝色window中的1、2,青色window中的3、6以及黄色window中的4、5、7、8均来自不同的位置的patch移位拼接,相同序号的patch代表移位之前在同一个window里。
下面以青色window为例,说明如何进行掩码多头自注意力的计算,如下图所示,先将window里的patch拉直成一个序列,一个window的大小是7,那么窗口移动一半的距离就是3,也就是说,青色window中49个patch,序号3占28个(4x7),序号6占21个(3x7),如下图所示,点积自注意力计算就是自身乘以自身的转置,而我们只需要计算移位之前在相同window里的patch之间的自注意力权重,因此对左下和右上加上掩码(mask)忽略掉即可
如下图所示,掩码就是给需要掩码的位置加上-100,不需要掩码的位置不变,由于计算得到的自注意力权重是一个非常小的值(由于LN、weight decay等的约束作用,一般模型中间的输入输出都比较小,防止过拟合),因此加上-100就会变成一个绝对值很大的负数,这样经过softmax以后,对应位置就会变成0了
类似地,对于其他拼接起来的区域,根据矩阵相乘情况的不同,需要使用不同的掩码格式
总的来说,作者针对SW-MSA的计算,提出了一种高效的、批次的计算方式。比如说本来移动窗口之后得到了9个窗口,而且窗口之间的patch数量每个都不一样,为了达到高效性,为了能够进行批次处理,先进行一次循环位移,把9个窗口变成4个窗口,然后用巧妙的掩码方式让每个窗口之间能够合理地计算自注意力,最后再把算好的自注意力还原,就完成了基于移动窗口的自注意力计算。
此外,Swin transformer没有像VIT那样在输入经过embedding后就直接加上一个位置编码,而是在每次计算自注意力时加上一个相对位置偏置(Relative Position Bias)。
在VIT中我们知道,对于图片分类任务而言,Transformer架构无论采用是一维、二维还是相对位置编码,效果都差不多。但如果去做检测、分割这类密集型预测任务的话,就需要特征对位置信息更敏感,而且更需要周围的上下文关系,在每个Transformer block都做更准确的相对位置编码,肯定是会对这类型的下游任务大有帮助的。
相对位置编码细节:
下面举个例子来说明如何添加相对位置编码,假设window的大小是2x2,计算window内的自注意力时,先计算相对位置索引,如下图所示。
再通过对行列做哈希变换,将2D的相对位置索引变为1D,作者这里采用的哈希公式是(x+M-1)*(2M-1)+(y+M-1),其中x,y为行、列号,M为window的大小。
计算过程如下所示:
1.对行、列加上M-1
2.对行标乘上2M-1
3.将行列索引相加
可以看到,经过变换,变换前相同的索引变换后仍然相同,变换前不同的索引变换后仍然不同
实际中每个window都是固定大小M=7,由49个patch组成,所以相对位置索引也是固定的。而由于相对行列索引的范围都是[-M+1, M-1](区间长度为2M-1),所以相对位置索引总共有(2M-1)*(2M-1)种,那么就可以随机生成(2M-1)*(2M-1)个随机相对位置偏置(可学参数)
如下图所示,根据相对位置索引,去获取对应的相对位置偏置,也就是公式里面的B,进行多头自注意力的计算。
ImageNet-1K(128万张图片、1000个类)上做训练,在ImageNet-1K测试集上测试
在更大的ImageNet-22K(1,400万张图片、2万多个类别)上做预训练,再在ImageNet-1K上微调,在ImageNet-1K测试集上测试
在ImageNet-1k上直接训练,swin-transformer可以和之前最好的CNN(RegNet 是之前facebook 用 NAS搜索出来的,EfficientNet 是 google 用NAS搜出来的)打成平手,对于 ViT 来说,因为它没有用很好的数据增强,而且缺少偏置归纳,所以说它的结果是比较差的,只有70多;DeiT因为用了更好的数据增强和模型蒸馏,所以说 DeiT Base 模型也能取得相当不错的结果,能到83.1
而在更大数据集上进行预训练再微调,原始标准的 ViT 的性能就有了很大提升,Swin-Transformer也取得了更好的结果,这也再次验证了Transformer强大的拟合能力,Swin Large 最后能到87.3,这个是在不使用JFT-300M,就是特别大规模数据集上得到的结果,所以还是相当高的。
在 COCO 数据集上训练并且进行测试
(a)中测试了在不同的算法框架下,Swin Transformer 到底比卷积神经网络要好多少,主要是想证明 Swin Transformer 是可以当做一个通用的backbone,所以用了 Mask R-CNN、ATSS、RepPointsV2 和Sparse R-CNN,这些都是表现非常好的一些算法,在这些算法里,将ResNet-50替换成了 Swin Tiny(Swin Tiny 的参数量和 FLOPs 跟 ResNet-50基本一致,因此用来对比还是比较公平的),可以看到,Swin Tiny 对 ResNet-50 是全方位的碾压,在四个算法上都超过了它,而且超过的幅度也是比较大的。
(b)选定算法Cascade Mask R-CNN ,然后在上面换更多的不同的backbone,比如 DeiT-S、ResNet-50 和 ResNet-101,可以看出,在相似的模型参数和相似的 Flops 之下,Swin Transformer 都是比之前的骨干网络要表现好的
(c)不追求公平比较,可以使用任何的算法和trick,如可以使用更多的数据进行预训练,可以使用更多的数据增强,甚至可以在测试的使用 test time augmentation(TTA)的方式,可以看到,之前最好的方法 Copy-paste 在 COCO Validation Set上的结果是55.9,在 Test Set 上的结果是56,最大的 Swin Transformer:Swin Large,它的结果分别能达到58和58.7,这都比之前高了两到三个点
在ADE20K数据集上进行训练和测试
可以看到之前的方法都是用的CNN,mIou都在44、45左右徘徊,SETR用了 ViT Large,取得了50.3的好成绩,Swin Transformer Large则更高,刷到了53.5 (两个“+”号的,意思是backbone在ImageNet-22K 数据集上做了预训练,所以结果才这么好)
可以看到,对于图片分类而言,移动窗口和相对位置编码提升都不是很明显,这也对应了VIT实验中不论采用何种位置编码,区别都不是很大;但是他们更大的帮助,主要是出现在下游任务里,就是 COCO 和 ADE20K 这两个数据集上,也就是目标检测和语义分割这两个任务上,用了移动窗口和相对位置编码以后,大概提升了3个点左右,提升非常显著。(正如我们前面所提到的,对于密集型预测任务,就需要特征对位置信息更敏感,而且更需要周围的上下文关系,所以说通过移动窗口提供的窗口和窗口之间的互相通信,以及在每个 Transformer block都添加更准确的相对位置编码,肯定会对这类型的下游任务很有帮助)
论文并没有展示关于cyclic shift之后的mask的消融实验,但猜想由于cyclic shift操作是人为的,强行将不同window里的patch给拼接了起来,相当于人为引入了新的分布,因此估计不用mask会掉点
Swin Transformer像CNN一样具有层级式结构,而且它的计算复杂度是跟输入图像的大小呈线性增长的。这种层级式结构使得它在下游任务上的表现非常好,如Swin Transformerr 在 COCO 和 ADE20K上的效果都非常的好,远远超越了之前最好的方法。
最大的创新就是使用了基于 Shifted Window 的自注意力,可以看到,相比于全局自注意力,它在有效地减少了的计算量的同时,还保持了很好的效果,因此对很多视觉的任务,尤其是对下游密集预测型的任务是非常有帮助的。
但是如果 Shifted Window 操作不能用到 NLP 领域里,其实在模型大一统上论据就不是那么强了,所以作者说接下来他们的未来工作就是要把 Shifted Windows用到 NLP 里面(看了下github,现在还没有实现),如果真的能做到这一点,那 Swin Transformer真的就是一个里程碑式的工作了,NLP和CV模型真正大一统的未来也就来了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。