锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

[论文阅读](对比学习系列)

时间:2022-11-17 10:30:00 u型光式传感器系列传感器fq50

文章目录

  • 第一阶段:百花齐放
    • [2018] (CVPR) INstDisc
    • [2019] (CVPR)Unsupervised Embedding Learning via Invariant and Spreading Instance Feature
    • [2018] CPC
    • CMC
  • 第二阶段:CV双雄
    • [2020] (CVPR)MoCo_v1
      • 标题和作者
      • 摘要
      • 导论
      • 结论
      • 相关工作
      • MoCo方法
        • Contrastive Learning as Dictionary Look-up
        • Momentum Contrast
        • Pretext Task
      • 实验
      • 总结
    • [2020] (ICML)SimCLR_v1
      • 摘要
      • Introduction
      • 方法
        • The Contrastive Learning Framework
        • Training with Large Batch Size
      • 比较表征学习的数据增强
        • 数据增强操作的组合对学习良好的表现至关重要
        • 比监督学习需要更强的数据增强
      • Architectures for Encoder and Head
        • 无监督比较学习(更多)受益于更大的模型
        • 非线性投影头提高了前面层的质量
      • Loss Functions
        • 计算余弦相似性
        • 损失的计算
    • [2020] MOCO_V2
    • [2020] (NIPS) simCLR_V2
    • [2020] (NIPS) SwAV
  • 第三阶段:无负样本
  • 第四阶段:基础Transformer
  • [2021] MAE
    • 与之前读过的文章的关系
    • 1、标题 作者
    • 2、摘要
    • 3、关键图
    • 4、结论
    • 5、导言
    • 6、相关工作
    • 7MAE模型
    • 8、实验
    • 9、评论


第一阶段:百花齐放

[2018] (CVPR) INstDisc

在这里插入图片描述
本文提出了个人判断任务memory bank

  • 图1:鼓励我们监督非监督方法的学习成果。对于来自美洲豹的图像,训练有素的神经网络分类器得到最高响应的类别都是视觉相关的,比如美洲豹和猎豹。不是语义标记,而是数据本身的明显相似性使某些类比其他类更接近。我们的无监督方法充分发挥了类级监督的作用,学习了一种能够区分个体实例的特征。*
  1. 图1:本文的方法是受到监督学习结果的启发,如果给已经喂豹子的图片监督学习方法训练的分类器,会发现他给的前几个分类结果都与豹子有关,包括猎豹和雪豹。简言之,从图片上看,这些物体非常相似;排名较低的判断通常与豹子无关
  2. 通过许多这样的现象,作者认为这些图片聚集在一起的原因不是因为它们有相似的语义标签,而是因为它们看起来太相似了,有些 object 它们与其他人非常相似。 object 有些分类分数很高,而有些分数很低
  3. 最后,根据这一观察,作者提出了个人判断任务:无监督的学习方法是将监督信号按类别推到极致,现在每一个 instance它们被视为一个类别,即每张图片都被视为一个类别。目标是学习一个特征,区分每张图片
  4. 因此,画得很好,起到了一石二鸟的作用:不仅简单介绍了研究动机,自然引入了问题,还以一句话的形式引入了个人来判断代理任务
  5. 图二讲述了文章中的方法
  6. 所有的图片都通过卷积神经网络编码成特征(这些特征可以在最终的特征空间中尽可能分离,因为每张图片都是个人判断任务的类别,所以每张图片应该尽可能与其他图片分离
  7. 训练这个卷积神经网络使用比较学习,因此需要有正负样本,根据个体判别这个任务,样本是图片本身(可能是通过一些数据增强),负样本是数据集中的所有其他图片
  8. 对比学习应该存在大量的负样本特征?本文使用了它 memory bank 形式:即保存所有图片的所有特征memory bank 也就是字典(ImageNet数据集有128万张图片,也就是说memory bank存储128万行意味着每个特征的维度不能太高,否则存储成本太高,本文使用128维)

前向过程:

  1. 假如batch size是256,也就是说,有256张图片片进入编码器 Res 50.最终特征维度为2048维,然后将其降低到128维,即每张图片的特征尺寸
  2. batch size 是 256 这意味着有256个正样本。负样本从何而来? memory bank 随机抽取一些负样本。本文抽取了4096个负样本
  3. 有了正样本和负样本,就可以用了NCE loss 计算和比较学习的目标函数
  4. 这个网络一旦更新,就可以了 mini batch数据样本对应的特征在于 memory bank 更换里面,这样 memory bank 更新
  5. 下一步是重复这个过程,不断更新这个编码 t,不断更新这个 memory bank,最后,尽可能区分这一特征

本文的方法和很多细节都设计得非常巧妙

  1. 比如说 proximal regularization:它对模型的训练增加了约束,使其能够 memory bank 进行这些特征动量式更新,跟 MoCo 想法很一致

  2. 此外,设置内部超参数的设置,如计算 loss 的时候温度的设置是0.07,选择4000个负样本,训练200个epochs,batch size 是256,起始的 learning rate 是0.03之后,其他论文(尤其是 MoCo)所有这些实验细节,MoCo 严格按照 Inst Disc 来的,这些超参数都没有进行更改。所以说 Inst Disc 这篇论文也是一项里程碑式的工作:它不仅提出了个人对代理任务的判断,还使用了代理任务和 NCE loss比较学习取得了良好的无监督表征学习成果。同时,它还提出了如何更新其他数据结构中的大量负样本,以及如何更新特征,因此,它在促进以后的比较和学习中发挥了至关重要的作用

[2019] (CVPR)Unsupervised Embedding Learning via Invariant and Spreading Instance Feature


这是一篇 CVPR 19的论文,跟今天要说的其它论文相比,它的影响力可能不是那么大,之所以提一下这篇论文,是因为它可以被理解成是 SimCLR 的一个前身,它没有使用额外的数据结构去存储大量的负样本,它的正负样本就是来自于同一个 minibach,而且它只用一个编码器进行端到端的学习

这篇文章也没有给自己起名字,所以就像 Inst Disc 一样就叫它 Inva Spread 好了,所以写论文的时候,最好还是给自己的方法起个名字,而不是叫 ours,这样方便别人记住也方便别人引用,也是一个双赢的事情

本文的想法其实就是最基本的对比学习

  1. 如图1所示,同样的图片通过编码器以后,它的特征应该很类似,不同的图片,它的特征出来就应该不类似,这就是题目中说的invariant和 spreading,就是说对于相似的图片、相似的物体,特征应该保持不变性,但是对于不相似的物体或者完全不沾边的物体,特征应该尽可能的分散开

具体做法:

  1. 代理任务也是选取了个体判别这个任务

前向过程:

  1. 如果 batch size 是256,也就是说一共有256个图片,经过数据增强,又得到了256张图片
  2. 对于 x1 这张图片来说, x1’ 就是它的正样本,它的负样本是所有剩下的这些图片(包括原始的图片以及经过数据增强后的图片),也就是说正样本是256,负样本是256减12,就是除去样本本身之外 mini-batch 剩下的所有样本以及它经过数据增强后的样本,也就是这里为什么要2,这些都是负样本
  3. 它和 Inst Disc d的区别:Inst Disc中,正样本虽然是256,它的负样本是从一个 memory bank 里抽出来的,它用的负样本是4096甚至还可以更大
  4. 本文为什么要从同一个 mini-batch 里去选正负样本?因为这样就可以用一个编码器做端到端的训练了,这也就是MoCo里讲过的端到端的学习方式
  5. 剩下的前向过程都是差不多的,就是过完编码器以后,再过一层全连接层就把这个特征的维度降的很低,就变成128了,正样本比如说上图中绿色的球在最后的特征空间上应该尽可能的接近,但是这个绿色的球跟别的颜色的特征应该尽可能的拉远
  6. 本文所用的目标函数也是 NCE loss 的一个变体
  7. 所以说之所以讲这篇论文,是因为它刚好属于另一个流派,也就是端到端的学习,而且只用一个编码器,不需要借助外部的数据结构去存储大量的负样本,它的正负样本都来自于同一个 minibach
  8. 既然它跟 SimCLR 这么像,为什么它没有取得那么好的结果呢?就是之前在MoCo那篇论文里反复强调过的,就是这个字典必须足够大,也就是说在做对比学习的时候,负样本最好是足够多,而本文的作者是没有 TPU 的,所以说它的 batch size 就是256,也就意味着它的负样本只有500多个,再加上它还缺少像 SimCLR 那样那么强大的数据增广以及最后提出的那个 mlp projector,所以说呢这篇论文的结果没有那么炸裂,自然也就没有吸引大量的关注,但事实上它是可以理解成 SimCLR 的前身

[2018] CPC

一般机器学习分为判别式模型和生成式模型,个体判别显然是属于判别式范畴的,那肯定就会有一些生成式的代理任务,比如最常见的预测型的任务

cpc 这篇论文其实非常厉害,因为它是一个很通用的结构

  1. 图1中描述的是CPC不仅可以处理音频,还可以处理图片、文字以及在强化学习里使用
  2. 这里为了简单,它用的是一个音频的信号作为输入
  3. 本文的想法:假如说有一个输入 x(一个持续的序列),t表示当前时刻,t-i表示过去的时刻,t+i表示未来的时刻。把之前时刻的输入全都扔给一个编码器,这个编码器就会返回一些特征,然后把这些特征喂给一个自回归的模型(gar,auto regressive),一般常见的自回归模型,就是 RNN 或者 LSTM的模型,所以每一步最后的输出,就会得到图中红色的方块(ct,context representation,代表上下文的一个特征表示),如果这个上下文的特征表示足够好(它真的包含了当前和之前所有的这些信息),那它应该可以做出一些合理的预测,所以就可以用ct预测未来时刻的这个zt +1、zt + 2(未来时刻的特征输出)
  4. 对比学习在哪里体现的呢?正样本其实就是未来的输入通过编码器以后得到的未来时刻的特征输出,这相当于做的预测是 query,而真正未来时刻的输出是由输入决定的,也就是说它们相对于预测来说是正样本;负样本的定义其实很广泛,比如可以任意选取输入通过这个编码器得到输出,它都应该跟预测是不相似的,这就是cpc定义正负样本的方式
  5. 这套思想是很朴实的,把输入序列换成一个句子,也可以说用前面的单词来预测后面的单词的特征输出;如果把这个序列想象成一个图片的patch块从左上到右下形成一个序列,就可以用上半部分的图片特征去预测后半部分的图片特征,总之是非常灵活

CMC

cpc是用预测的代理任务做对比学习

cmc这篇论文定义正样本的方式就更为广泛了:一个物体的很多个视角都可以被当做正样本

cmc 的摘要写的非常好:

  1. 人观察这个世界是通过很多个传感器,比如说眼睛或者耳朵都充当着不同的传感器来给大脑提供不同的信号
  2. 每一个视角都是带有噪声的,而且有可能是不完整的,但是最重要的那些信息其实是在所有的这些视角中间共享,比如说基础的物理定律、几何形状或者说它们的语音信息都是共享的
  3. 在这里举了个很好的例子:比如一个狗既可以被看见,也可以被听到或者被感受到
  4. 基于这个现象作者就提出:他想要学一个非常强大的特征,它具有视角的不变性(不管看哪个视角,到底是看到了一只狗,还是听到了狗叫声,都能判断出这是个狗)
  5. cmc工作的目的就是去增大互信息(所有的视角之间的互信息)
  6. 如果能学到一种特征能够抓住所有视角下的关键的因素,那这个特征就很好了,至少解决分类问题不在话下

  1. cmc到底是怎么样去形成正样本和负样本从而去做对比学习的呢?如图一所示,它选取的是 NYU RGBD 这个数据集(这个数据集有同时4个view,也就是有四个视角:原始的图像、这个图像对应的深度信息(每个物体离观察者到底有多远)、SwAV ace normal、这个物体的分割图像)
  2. cmc 的意思是说,虽然这些不同的输入来自于不同的传感器或者说不同的模态,但是所有的这些输入其实对应的都是一整图片,都是一个东西,那它们就应该互为正样本也就是说,当有一个特征空间的时候,比如图中圆圈所示的特征空间,这四个绿色的点在这个特征空间里就应该非常的接近。这时候如果随机再去挑一张图片,不论是用图片还是用风格的图像(总之属于一个不配对的视角)的话,这个特征就应该跟这些绿色的特征远离
  3. 这就是 cmc 定义正负样本的方式,它的正样本来自于多个视角,一旦定义好了正负样本,剩下的工作就大差不差了
    cmc是第一个或者说比较早的工作去做这种多视角的对比学习,它不仅证明了对比学习的灵活性,而且证明了这种多视角、多模态的这种可行性。所以说接下来open AI,很快就出了clip模型:也就是说如果有一个图片,还有一个描述这个图片的文本,那这个图像和文本就可以当成是一个正样本对,就可以拿来做多模态的对比学习
  4. cmc原班作者人马用对比学习的思想做了一篇蒸馏的工作:不论用什么网络,不论这个网络是好是坏是大是小,只要你的输入是同一张图片,那得到的这个特征就应该尽可能的类似,也就意味着想让 teacher 模型的输出跟 student 模型的输出尽可能的相似,它就通过这种方式把 teacher和student做成了一个正样本对,从而可以做对比学习
  5. 所以说让大家意识到对比学习如此灵活,可以应用到不同的领域,cmc功不可没
  6. 一个小小的局限性:当处理不同的视角或者说不同的模态时候,可能需要不同的编码器,因为不同的输入可能长得很不一样,这就有可能会导致使用几个视角,有可能就得配几个编码器,在训练的时候这个计算代价就有点高(比如说在 clip 这篇论文里,它的文本端就是用一个大型的语言模型,比如说 bert,它的图像端就是用一个 vit,就需要有两个编码器),这样其实又回到了刚开始讲ViT时候所说的说这个Transformer的好处–Transformer有可能能同时处理不同模态的数据
  7. 事实上现在已经有人这么做了,今年的ICLR就有一篇ma clip,它就用一个Transformer去同时处理两个输入模态,效果反而更好,所以说这可能才是 Transformer 真正吸引人的地方:一个网络能处理很多类型的数据,而不用做针对每个数据特有的改进

第一阶段大概讲了这四篇论文,可以看到

  1. 它们使用的代理任务是不一样的,有个体判别,有预测未来,还有多视角多模态
  2. 它们使用的目标函数也不尽相同,有 NCE,有infoNCE,还有NCE的其它变体
  3. 它们使用的模型也都不一样,比如说invariant spread用了一个编码器;Inst Disc用一个编码器和memory bank;cpc有一个编码器,还有一个自回归模型;cmc可能有两个甚至多个编码器
  4. 它们做的任务从图像到视频到音频到文字到强化学习,非常的丰富多彩

第二阶段:CV双雄

[2020] (CVPR)MoCo_v1


MoCo

Momentum Contrast for Unsupervised Visual Representation Learning

MoCo是CVPR2020的最佳论文提名,算是视觉领域中使用对比学习的一个里程碑式的工作

  1. 对比学习作为从19年开始一直到现在,视觉领域乃至整个机器学习领域最炙手可热的方向之一,它简单、好用、强大,以一己之力盘活了从2017年开始就卷的非常厉害的机器视觉领域,涌现了一大批优秀的工作,MoCo就是其中之一
  2. MoCo作为一个无监督的表征学习的工作,不仅在分类任务上逼近了有监督的基线模型,而且在很多主流的视觉任务(比如检测、分割、人体关键点检测)上,都超越了有监督预训练的模型,也就是ImageNet上预训练的模型,在有的数据集上甚至是大幅度超越
  3. 所以说MoCo的出现从某种意义上来说,是给视觉领域吃了一个定心丸:无监督学习是真的可以的,有可能真的不需要大规模的标好的数据去做预训练。这个结论也从侧面上证明了之前Yannn LeCun在NeurlPS 2016做演讲的时候用的一张图,如下图所示

  1. 它的意思是说如果将机器学习比做一个蛋糕的话,那强化学习只能算是这个蛋糕上的一个小樱桃,有监督学习最多就算这个蛋糕上的那层糖霜,只有无监督学习才是这块蛋糕的本质,才是我们真正想要的
  2. 而现在也确实是这样,不光是在自然语言处理里很多我们耳熟能详的大模型都是用自监督的预训练得到的,那么在视觉里也快了

标题和作者

Momentum Contrast for Unsupervised Visual Representation Learning

MoCo的名字来源于前两个单词的前两个字母

动量对比的方法去做无监督的表征学习

动量从数学上可以理解成一种加权移动平均:

  1. yt = m * y( t - 1 )+( 1 - m )* xt
  2. m就是动量的超参数
  3. y( t - 1 )是上一个时刻的输出
  4. yt是这一时刻想要改变的输出
  5. xt是当前时刻的输入
  6. 简单来说,就是不想让当前时刻的输出完全依赖于当前时刻的输入,所以这里引入了之前的输出,并且给了他一个权重
  7. 因为这里动量的超参数m是介于0和1之间的数,如果m很大(趋近于1)的时候,yt的改变是非常缓慢的,因为后面的1-m基本就趋近于0,也即是说不怎么依赖当前的输入;反过来说,如果m很小的话,那就是说当前的输出更多地依赖于当前的输入
  8. MoCo利用了动量的这种特性,从而缓慢地更新一个编码器,让中间学习的字典中的特征尽可能地保持一致

对比学习最广为应用的代理任务:个体判别

摘要

本文提出了MoCo去做无监督的表征学习,虽然是基于对比学习的,但是本文是从另外一个角度来看对比学习,也就是说把对比学习看作是一个字典查询的任务

具体来说,就是做一个动态的字典,这个动态的字典由两个部分组成

  1. 第一个部分是一个队列,因为队列中的样本不需要做梯度回传,所以就可以往队列中放很多负样本,从而使得这个字典变得很大
  2. 第二个部分是一个移动平均的编码器,使用这个移动平均的编码器的目的是想让字典中的特征尽量的保持一致。作者发现在训练的过程中,如果能有一个很大而且比较一致的字典,会对无监督的对比学习非常有好处

这篇论文主要的亮点在于它的结果,所以剩下大篇幅的摘要留给了结果:

分类:

  1. 在ImageNet数据集上,如果使用大家普遍采用的linear protocol去做测试的话,MoCo能够取得跟之前最好的无监督学习方式差不多或者更好一点的结果

  2. linear protocol:如果先预训练好一个骨干网络,当把它用到不同的数据集上的时候,将它的骨干网络冻住(backbone freeze),然后只去学习最后的那个全连接层,也就是分类头,这样就相当于把提前训练好的预训练模型当成了一个特征提取器,只用它来抽取特征,这样就可以间接证明之前预训练好的那个模型的特征到底好不好
    更重要的是MoCo学习到的特征能够很好地迁移到下游的任务,这才是整篇文章的精髓

  3. 之所以想做大规模的无监督预训练,就是想去学到一个很好的特征,然后这个特征拥有很好的迁移性,就可以在没有那么多标注数据的下游任务里获得很好的结果

MoCo作为一个无监督的预训练模型,能够在7个下游任务(分割、检测等)上,而且比如说在VOC、COCO这些数据集上超越之前的有监督的预训练模型,有时候甚至是大幅度超越

  1. 这里的counterpart是说模型使用的是一样的,比如说都是Res50,只是训练的方式不一样,一个是用有监督的带标签的数据去训练,一个是用无监督不带标签的数据去训练

最后作者总结说,这就意味着对于很多视觉任务来说,无监督和有监督的表征学习中间的鸿沟已经填上了

  1. 之前虽然有一些无监督的工作能够在某个数据集或者是某个任务上能够比它对应的有监督预训练模型好一点,但是MoCo是第一个能够在这么多主流视觉任务上,全面地让无监督训练的模型比有监督训练的模型表现要好

导论

最近的一些研究[61,46,36,66,35,56,2]提出了使用与对比损失[29]相关的方法进行无监督视觉表征学习的有前途的结果。尽管受到各种动机的驱动,但可以将这些方法视为构建动态字典。字典中的keys(tokens)从数据(例如,图像或补丁)中采样,并由编码器网络表示。无监督学习训练编码器执行字典查找:一个encoded “query"应该与它的matching key相似,而与其他查询不同。学习被定义为最小化对比损失[29]。

从这个角度来看,我们假设我们希望构建的词典是:(i)大的,(ii)在培训过程中,它们的演变是一致的。直观上,一个更大的字典可以更好地对底层的连续、高维视觉空间进行采样,而字典中的键应该由相同或类似的编码器表示,以便它们与查询的比较是一致的。然而,使用对比损失的现有方法可以限制在这两个方面之一(稍后在上下文中讨论)。

我们提出了动量对比(MoCo)作为一种构建具有对比损失的大型一致字典的方法(图1)。我们将字典维护为一个数据样本队列:当前小批量的编码表示被加入队列,而最老的被退出队列队列将字典大小与小批大小解耦,允许其较大。此外,由于字典关键字来自前几个小批量,提出了一种慢进度的关键字编码器,实现为一个基于动量的移动平均的查询编码器,以保持一致性

结论

MoCo的最后一部分不光是结论,主要的内容都围绕在了讨论上面,结论其实就是一句话,MoCo在一系列的方法和数据集上都取得了比较好的效果

接下来就是讨论了两个比较有趣的问题

1、通过实验,作者发现当预训练的数据集从ImageNet换到Instagram的时候,提升虽然是有的,但是都相对来说比较小,只有零点几个点或者一个点,但是数据集是从一百万增加到10亿了,扩大了一千倍,这样的提升着实是有点小,所以作者觉得是因为大规模的数据集没有被很好地利用起来,可能一个更好的代理任务有可能解决这个问题

2、除了这个简单的个体判别任务,有没有可能把MoCo和另外一个代理任务(Masked auto-encoding)结合起来用,就像NLP中的BERT一样,用masked language modeling完形填空去自监督地训练模型(MAE)

最后作者希望MoCo能够对其他那些使用对比学习的代理任务有帮助,之所以强调对比学习是因为MoCo设计的初衷就是去构造一个大的字典,从而让正负样本能够更有效地进行对比,提供一个稳定地自监督信号,最后去训练模型

相关工作

无监督或半监督学习通常包含两个方面的研究内容:pretext task和loss function。

  1. pretext task:表明真正要解决的并不是任务本身而是学习优秀的特征表示。

  2. loss function:MoCo的研究内容。
    2.1 Loss function
    定义损失函数的方式有两种:

  3. 常见的方式是比较模型预测结果与真实值的差异,例如cross-entropy loss或margin-based loss;

  4. Contrastive Loss是在特征表示空间测量样本对间的相似性

  5. Adversarial Loss衡量的是概率分布间的差异。
    2.2 Contrastive Learning vs Pretext Task
    很多前置任务可基于对比学习。

MoCo方法

Contrastive Learning as Dictionary Look-up

之前的对比学习以及最新的一些效果比较好的变体,它们都可以想象成是训练一个编码器,从而去做一个字典查找的任务

假设有一个编码好的query q,也就是特征,还有一系列已经编码好的样本,也就是k0、k1、k2等,这些可以看作是字典中的那些key

  1. 这里做了一个假设:在这个字典中只有一个key是跟query是配对的,也就是说它们两个互为正样本对,这个key叫做key positive
  2. 之所以有这个假设,前面在讲个体判别任务的时候也提到过,这个代理任务就是从一个图片经过两种变换得到两种图片,其中一个作为基准图片,另外一个作为正样本,所以就是只有一个正样本对。当然理论上是可以使用多个正样本对,之后也有工作证明使用多个正样本对有可能会提升任务的性能

一旦定义好了正样本和负样本,接下来就需要一个对比学习的目标函数,这个对比学习的目标函数最好能满足以下要求

  1. 当query q和唯一的正样本k plus相似的时候,它的loss值应该比较低
  2. 当query q和其他所有的key都不相似的时候,这个loss的值也应该比较低

这个也是训练模型的目标,如果已经能够达到这么一种状态,就说明模型差不多训练好了,当然希望目标函数的loss值尽可能的低,就不要再去更新模型了;反之,如果q和正样本key plus不相似,或者说query q和本来应该是负样本的那些key相似,那么这个目标函数的loss值就应该尽可能的大,从而去惩罚这个模型,让模型加速更新它的参数

在本文中,采取了一个叫做InfoNCE的对比学习函数来训练整个模型
对比学习损失(InfoNCE loss)与交叉熵损失的联系,以及温度系数的作用

  1. InfoNCE:先看下图中右边手写的式子,它是softmax的操作,那如果是在有监督学习的范式下,也就是有一个one-hot向量来当作ground truth
  2. 其实在前面加上一个-log,整个其实就是cross entry loss,也就是交叉熵目标函数,但是需要注意的是红色圆圈圈出来的k在有监督学习中指的是这个数据集有多少个类别(比如说ImageNet就是1000类,k就是1000,是一个固定的数)
  3. 回到对比学习,其实对比学习理论上来说是可以用上面这个公式去计算loss的,但是实际上行不通。如果说像大部分对比学习的工作一样,就使用instance discrimination这个代理任务去当自监督信号的话,那这里的类别数k将会是一个非常巨大的数字,就拿ImageNet数据集来举例,这里的k就不再是1000了,而是128万,就是有多少图片就有多少类
  4. softmax在有这么多类别的时候,它其实是工作不了的,同时因为这里还有exponential操作,当向量的维度是几百万的时候,计算复杂度是相当高的,如果每个训练的iteration都要这样去计算loss,那么训练的时间将会大大延长

所以就有了NCE loss(noise contrastive estimation的缩写),之前因为类别太多所以没办法计算softmax,从而没办法计算目标函数,NCE的做法是:将这么多的类简化成一个二分类问题,现在只有两个类别了

  1. 一个是数据类别data sample

  2. 一个是噪声类别noisy sample
    那么每次只需要拿数据样本和噪声样本做对比就可以了,也就是文中所说的noise contrastive。但是如果还是将整个数据集剩下的图片都当做负样本,那么其实noise contrastive estimatation解决了类别多的问题,但是计算复杂度还是没有降下来,那么该如何让loss计算的更快一点呢?没有别的办法,只有取近似了

  3. 意思就是说,与其在整个数据集上算loss,不如就从这个数据集中选一些负样本来算loss就可以了,这也是这里estimation的含义,它只是估计、近似

  4. 但是按照一般的规律,如果选取的样本很少,就没有那么近似了,结果自然就会比较差。选的样本越多,自然就跟使用整个数据集的图片的结果更加近似,效果自然也会更好,所以这也是MoCo一直强调的希望字典足够大,因为越大的字典,越能够提供更好的近似

总的来说,NCE这个loss就是把一个超级多类分类的问题变成了一系列的二分类的问题,从而让大家还是可以使用softmax操作

这里说的InfoNCE其实就是NCE的一个简单的变体,作者认为如果只把问题看作是一个二分类(只有数据样本和噪声样本)的话,可能对模型学习不是很友好,毕竟在那么多的噪声样本中,大家很有可能不是一个类,所以还是把它看成一个多分类的问题比较合理,于是NCE就变成了InfoNCE,公式如下图所示

  1. 公式中的q * k,其实就相当于是logit,也可以类比为上面softmax中的z,也是模型出来的logit

  2. 公式中的τ是一个温度的超参数,这个温度一般是用来控制分布的形状的τ的值越大,1/τ就越小,就相当于将分布中的数值都变小了,尤其是经过exponential之后就变得更小了,最后就会导致这个分布变得更平滑;相反,如果τ取得值越小,也就是1/τ越大,那么分布里的值也就相应的变大,经过exponential之后,原来大的值变得更大了,就使得这个分布更集中,也就变得更加peak了。

  3. 所以说温度这个超参数的选择也是很有讲究的,如果温度设的越大,那么对比损失对所有的负样本都一视同仁,导致学习的模型没有轻重;如果温度的值设的过小,又会让模型只关注那些特别困难的样本,其实那些负样本很有可能是潜在的正样本,如果模型过度地关注这些特别困难的负样本,会导致模型很难收敛,或者学好的特征不好去泛化。

  4. 但是温度这个超参数终究只是一个标量,如果忽略掉它,就会发现,其实这个InfoNCE loss就是cross entropy loss,唯一的区别就在于在cross entropy loss中k指代的是数据集里类别的多少,但是在对比学习的InfoNCE loss中,k指的是负样本的数量

  5. 公式下面分母中的求和其实是在一个正样本和k个负样本上做的,因为是从0到k,所以是k+1个样本,也就指的是字典里所有的key

  6. 如果直观地想一下,InfoNCE loss就是一个cross entropy loss,它做的就是一个k+1类的分类任务,它的目的就是想把q这个图片分成k plus这个类。所以到这里可以发现,InfoNCE也不是那么难理解,和之前常用的cross entropy loss是有很大联系的

  7. 如果直接跳到后面MoCo的伪代码的话,也会发现MoCo这个loss的实现就是基于cross entropy loss实现的

既然已经有了这个代理任务提供的正负样本,也有了可以用来训练模型的目标函数输入和模型大概是什么?(这里作者还是从一个自顶向下的方式去写的)

普遍来说,query q是一个输入xq通过一个编码器fq得到的,同理所有的k的表示也都是key的输入通过了一个key的编码器,输入和模型具体的实现是由具体的代理任务决定

  1. 在代理任务不一样的时候,输入xq和xk既可以是图片,也可以是图片块,或者是含有上下文的一系列的图片块
    对于模型,query的编码器和key的编码器既可以是相等的(就是说模型的架构一样,参数也是完全共享的),或者说它们的参数是部分共享的,也可以是彻底不一样的两个网络

Momentum Contrast

作者说从以上角度,对比学习是一种在高维的连续的输入信号上去构建字典的一种方式

  1. 这里的高维和连续其实指的就是图片

这个字典是动态的

  1. 之所以是动态的是因为这个字典中的key都是随机取样的,而且给这些key做编码的编码器在训练的过程中也是在不停的改变
  2. 这和之前所讲的无论是在有监督还是无监督的方法都不太一样,因为之前的那些工作最后学习的target都是一个固定的目标,所以作者认为,如果想学一个好的特征,这个字典就必须拥有两个特性(大,大的字典能够包含很多语义丰富的负样本,从而有助于学到更有判别性的特征一致性主要是为了模型的训练,能够便面学到一些trivial solution,也就是一些捷径解

所以基于以上的研究动机,作者就提出了momentum contrast

**文章的第一个贡献:如何把一个字典看成队列 **

作者说,方法的核心其实就是把一个字典用队列的形式表现出来

  1. 队列其实就是一种数据结构,如果有一个队列,里面有很多元素,假如新来的元素从下面进来,为了维持这个队列的大小,最老的数据会从队列中出去,所以说队列一般被称作是一个fifo(first in first out,先进先出)的数据结构
  2. 作者在这里是用一个队列去代表一个字典,也就是说整个的队列就是一个字典,里面的元素就是放进去的key
  3. 在模型训练的过程中,每一个mini-batch就会有新的一批key被送进来,同时也会有一批老的key移出去,所以用队列的好处是可以重复使用那些已经编码好的key,而这些key是从之前的那些mini-batch中得到的,比如说队尾指的是当前mini-batch送进来的新key,那么紧挨着它上面的那些key就是在它之前的那些mini-batch编码好送进来的
  4. 这样使用了队列之后,就可以把字典的大小和mini-batch的大小彻底剥离开了,就可以在模型的训练过程中使用一个比较标准的mini-batch size,一般是128或者是256,但是字典的大小可以变得非常大,它的大小非常灵活,而且可以当作一个超参数一样单独设置

这个字典一直都是所有数据的一个子集

  1. 在算对比学习目标函数的时候只是取一个近似,而不是在整个数据集上算loss

  2. 使用队列的数据结构,可以让维护这个字典的计算开销非常小

  3. 事实也是如此,如果把字典的大小从几百变到几千或者上万,整体的训练时间基本是不变的
    最后作者又强调了为什么要使用队列这个数据结构:因为队列有先进先出的特性,这样每次移出队列的都是最老的那些mini-batch,也就是最早计算的那些mini-batch,这样对于对比学习来说是很有利的,因为从一致性的角度来说,最早计算的那些mini-batch的key是最过时的,也就是说跟最新的这个mini-batch算的key是最不一致的

文章的第二个贡献:如何使用动量的思想去更新编码器

用队列的形式当然可以使这个字典变得更大,但是也因为使用了非常大的字典,也就是非常长的队列,就导致没办法给队列中所有的元素进行梯度回传了,也就是说,key的编码器无法通过反向传播的方式去更新它的参数

  1. 如果想更新这个key的编码器,其实有一个非常简单的方法:就是每个训练iteration结束之后,将更新好的编码器参数fq直接复制过来给key的编码器fk就可以了
  2. 这个想法简单确实是简单,但是作者紧接着说这种方式的结果并不好,原因是一个快速改变的编码器降低了这个队列中所有key的特征的一致性
  3. 假如现在有一个队列如上图所示,新的元素从左边进来,旧的元素从右边出去,假设mini-batch size就是1,也就是说每次只更新一个key,k1、k2等这些所有的key都是由不同的编码器产生的,这样的话这些快速改变的编码器就会降低所有key之间的一致性

所以作者提出了动量更新的方式来解决这个问题

  1. 如果将key编码器fk参数设为θk,query编码器的参数fq设为θq,那θk就是以以下公式所示的动量改变方式进行更新的
  2. 上式中m是动量参数,它是一个0到1之间的数
  3. θq,也就是query编码器,是通过梯度反向回传来更新它的模型参数的
  4. θk除了刚开始是用θq初始化以外,后面的更新大部分主要是靠自己,因为如果动量参数m设的很大,那么θk更新就非常缓慢,所以作者接下来说,因为使用了这种动量更新的方式,随意虽然在队列里的key都是由不同的编码器产生得到的,但是因为这些编码器之间的区别极小,所以产生的key的一致性都很强
  5. 为了强调这一观点,作者说,他们在实验中使用了一个相对比较大的动量0.999,意思就是每次θk更新的时候,99.9%都是来自原来的编码器参数,只有0.1%是从更新好的θq中借鉴过来的,自然而然的,θk的更新就非常缓慢了
  6. 作者还做了对比实验,当他把这个动量变小一点,变成0.9的时候(其实0.9也不算小了),作者说使用一个相对较大的动量0.999比0.9效果要好得多,所以就意味着,如果想充分利用好队列,一个缓慢更新的key编码器是至关重要的,因为它可以保证这个队列里所有的key是相对一致的

到这里,其实MoCo的主要贡献旧已经讲完了,但是如果对对比学习不是很熟的人来说可能还是不太理解MoCo的前向过程到底是什么样子的,可惜的是这篇论文并没有提供一个很直观、形象的模型总览图,取而代之的是伪代码,写得相当简洁明了,理解和复现都比较容易

Relations to previous mechanisms
作者在引言中提到过,之前的那些对比学习方法都可以看作是字典查找,但是它们都或多或少受限于字典的大小和字典的一致性的问题,这里作者将之前的方法总结了一下,归纳成了两种架构

1、第一种就是比较直接的端到端学习的方式

  1. 端到端学习,顾名思义就是编码器都是可以通过梯度回传来更新模型参数的
  2. 下图的标题中也写道编码器q和k可以是不同的网络,但是之前很多工作都用的是同样的网络,为了简单起见,MoCo实验中编码器q和k是同一个模型,也就是一个Res50

  1. 这里为什么可以使用同一个模型?因为模型的正负样本都是从同一个mini-batch里来的,也就是xq和xk都是从同一个batch中来的,它做一次forward就能得到所有样本的特征,而且这些样本是高度一致的,因为都是来自一个编码器
  2. 听起来确实很美好,编码器都能用反向回传学习了,特征也高度一致了,但是它的局限性就在于字典的大小因为在端到端的学习框架中,字典的大小和mini-batch size的大小是等价的,如果想要一个很大的字典,里面有成千上万个key的话,也就意味着mini-batch size的大小必须也是成千上万的,这个难度就比较高了。因为现在的GPU是塞不下这么大的batch-size的,而且就算有内存够大的硬件,能塞下这么大的batch-size,但是大的batch-size的优化也是一个难点,如果处理不得当,模型是很难收敛的
  3. 所以作者说这种端到端学习的方式受限于字典的大小,之所以选择读MoCo这篇论文而不是SimCLR,是因为SimCLR就是这种端到端的学习方式,它还用了更多的数据增强,而且提出了在编码器之后再用一个projector,会让学到的特征大大变好,但是总体来说SimCLR就是端到端的学习方式,之所以这么做,是因为google有TPU,TPU内存大,所以可以无脑上batch-size,SimCLR中选用了8192当作batch-size,这样最后就会有10000多个负样本,这个负样本的规模就足够对比学习了,所以SimCLR就可以用这么简单的方式直接去做端到端的学习
  4. 端到端学习的优点在于编码器是可以实时更新的,所以导致它字典里的那些key的一致性是非常高的,但是它的缺点在于因为它的字典大小(就是batch-size的大小),导致这个字典不能设置的过大,否则硬件内存吃不消

2、memory bank(更关注字典的大小,而牺牲一些一致性)

  1. 在memory bank中其实就只有一个编码,也就是query的编码器是可以通过梯度回传来进行更新学习的

  2. 但是对于字典中的key是没有一个单独的编码器的

  3. memory bank把整个数据集的特征都存到了一起,对于ImageNet来说,memory bank中就有128万个特征(看上去好像很大,但是memory bank的作者在论文中说,因为每一个特征只有128维,所以即使整个memory bank中有128万个key,最后也只需要600M的空间就能把所有的这些key存下来了,即使是对整个数据集的特征做一遍最近邻查询,在泰坦XGPU上也只需要20毫秒,所以是非常高效的)

  4. 一旦有了这个memory bank,在每次模型做训练的时候,只需要从memory bank中随机抽样很多的key出来当作字典就可以了,就相当于下图中红色圆圈圈出来的整个右边的那些操作都是在线下执行的,所以完全不用担心硬件内存的问题,也就是字典可以抽样抽的很大,但是有得必有舍,memory bank的做法在特征一致性上就处理的不是很好

  5. 假如说有一个memory bank如下图所示,里面有128万个key,它在训练的时候是随机抽样很多样本出来当作字典,这里为了方便讲解,将它简化为顺序抽样而且字典的大小就是3,也就是说当在做对比学习的时候,当前用的mini-batch可以先把k1、k2、k3这三个key抽出来当作负样本,然后去和query计算loss

  6. 算完这个loss,回传的梯度更新了左边的编码器encoder之后,memory bank的做法就是会用新的编码器在原来对应的位置上,也就是k1、k2、k3的位置上的那些样本去生成新的特征,也就是下图中蓝色的新的k1、k2、k3,然后把新的key放回到memory bank中就把memory bank更新了

  7. 依此类推,下一次假如是将4、5、6这三个key抽出来做一次模型更新,然后再把新的key4、5、6放回去,那么4、5、6也就被更新了

  8. 这里也就有一个问题:因为这里的特征都是在不同时刻的编码器得到的,而且这些编码器都是通过梯度回传来进行快速更新的,这也就意味着这里得到的特征都缺乏一致性

  9. 而且memory bank还有另外一个问题,因为memory bank中存储了所有的图片,也就意味着模型训练了整整一个epoch才能把整个memory bank更新一遍,那也就意味着,当开始下一个epoch训练的时候,假如选了三个key,那这三个key的特征都是上一个epoch不知道哪个时间点算出来的特征了,这也就导致query的特征和key的特征差的特别远

  10. 所以总结一下,就是**memory bank的做法就是牺牲了特征的一致性,**从而获得了可以构造很大的字典的特权

显然,无论是端到端的学习还是memory bank的方法,都和作者说的一样,受限于字典大小和特征一致性这两方面中的至少一个,所以为了解决之前这两种做法的局限性,作者就提出了MoCo

  1. MoCo 采用队列的形式去实现字典 ,从而使得它不像端到端的学习一样受限于batch-size的大小
  2. 同时为了提高字典中特征的一致性,MoCo使用了动量编码器
  3. 其实从整体上来看,MoCo跟Memory bank的方法是更加接近的,它们都只有一个编码器(query的编码器,它是通过梯度回传来更新模型参数的),它们的字典都是采取了额外的数据结构进行存储从而和batch-size剥离开了,memory bank中使用了memory bank,而MoCo中使用了队列
  4. memory bank这篇论文(文献61)也意识到了特征不一致性所带来的坏处了,所以作者当时还加了另外一个loss(proximal optimization),目的就是为了让训练变得更加平滑,这其实和MoCo中的动量更新是有异曲同工之效的
  5. MoCo的作者也提到memory bank这篇论文中也提到了动量更新,只不过它的动量更新的是特征,而MoCo中动量更新的是编码器,而且MoCo的作者还补充说MoCo的扩展性很好,他可以在上亿级别的图像库上进行训练
  6. 但是对于特别大的数据集memory bank的方法就捉襟见肘了,因为它需要将所有的特征都存到一个memory bank中,对于ImageNet这种百万规模的数据集来说,存下的特征只需要600兆的空间,但是对于一个拥有亿级图片规模的数据,存储所有的特征就需要几十G甚至上百G的内存了,所以memory bank的扩展性不如MoCo好

总之,MoCo既简单又高效,而且扩展性还好,它能同时提供一个又大而且又一致的字典,从而进行对比学习

Pretext Task

作者为了简单起见,使用了个体判别的任务,个体判别任务的定义如下图所示 作

MoCo论文中的小trick:Shuffling BN

(这个操作在接下来很多论文里,甚至包括作者团队接下来的工作,比如SimSiam也没有再用Shuffling NB这个操作了)

因为用了BN以后,很有可能造成当前batch里的样本中间的信息的泄露,因为BN要计算这些样本的running mean和running variance,也就是说,它能通过这些泄露信息很容易地找到正样本,而不需要真正地去学一个好的模型(也就是作者所说的模型会走一条捷径)

如何解决这个问题?

  1. 因为BN这个操作大部分时候都是在当前GPU上计算的,所以作者在做多卡训练之前,先把样本的顺序打乱再送到所有的GPU上去,算完了特征之后再回复顺序去算最后的loss,这样的话对loss是没有任何影响的,但是每个GPU上的BN计算就不一样了,就不会再存在信息泄露的问题了

  2. 类似的BN操作还在后续BYOL那篇论文中引起了一段很有意思的乌龙事件,总之,BN操作让人又爱又恨,用的好威力无穷,但是90%的情况都是属于用的不好的,会带来各种莫名其妙的问题,而且很难去debug,所以现在换成transformer也好,这样直接就用layer norm,就能暂时不用理会BN了

实验

  1. 作者做了一个grid search,然后发现最佳的学习率是30.这个其实是很不可思议的,因为除了在神经网络搜索(NAS)的那些工作里可能会搜到一些比较夸张的学习率之外,过去几十年里用神经网络的工作,都没有用超过1的学习率,因为已经预训练好了一个网络,所以只需要做微调就可以了,所以学习率最大可能也就是设个0.1,然后0.01、0.001这样往下降,很少有人会设一个比1大的学习率,但是在这里竟然是30.所以说做无监督学习或者说是做对比学习可以看一下学习率,如果结果不好,很有可能是学习率没有设置对
  2. 基于这个现象,作者也做了总结,这个学习率确实比较诡异,它暗示了这种无监督对比学习学到的特征分布跟有监督学习学到的特征的分布是非常不同的
  3. 动量使用一个相对较大的值0.999或者0.9999的时候性能是最好的,差不多都是59,这就说明了一个变化非常缓慢的编码器是对对比学习有好处的,因为它能够提供一个一致性的特征
  4. 但是当把动量逐渐变小,变到0.99或者是0.9的时候,性能的下降就比较明显了,尤其是当直接去掉动量,直接将快速更新的query编码器拿过来当key编码器用的时候,就会发现不光是性能下降的问题,整个模型甚至都不能收敛,loss一直在震荡,从而导致训练失败
  5. 这个table非常有力地证明了作者的论点,就是要建立一个一致性的字典

迁移学习的效果:

文章的最后一个章节,也是全文的点睛之笔,作者就是想验证一下MoCo预训练模型得到的特征到底在下游任务上能不能有好的迁移学习效果

无监督学习最主要的目标就是学习一个可以迁移的特征

用ImageNet做有监督的预训练,它最有用、最有影响力的时候就是在当在下游任务上做微调,可以用这个预训练模型做模型的初始化,从而当下游任务只有很少的标注数据的时候也能获得很好的效果

作者用视觉领域中最常见、应用最广的检测任务来做无监督的MoCo预训练模型和ImageNet的有监督预训练模型之间的比较

归一化

  1. 如果拿MoCo学出来的模型直接当一个特征提取器的话,那它在做微调的时候的最佳学习率是30,也就说明MoCo学到的特征跟有监督学到的特征的分布是非常不一样的,但是现在需要拿这个特征去做下游的任务,不可能在每个下游任务上都去做一遍grid search找一下它最佳的学习率是多少,这个相对来讲比较麻烦,也失去了无监督预训练的意义,所以作者就想,如果可以拿之前有监督的预训练已经设置好的超参数来做微调的话,既可以做公平对比,而且也不用做grid search。
  2. 一般来说,当分布不一致的时候,最常想到的方法就是归一化,所以作者这里使用了特征归一化的方法,具体来说就是整个模型现在都在微调,而且尤其是BN层(作者用的是sync BN,也就是synchronized batch norm,就是将多级训练的所有GPU上的batch norm的统计量都合起来,算完running mean、running variance之后再做BN层的更新,这样就会让特征的规划做的更彻底一点,也会让模型的训练更稳定一些)。为了解决这个问题,我们在微调时采用了特征归一化的方法:我们用训练过的(并且在gpu[49]上同步的)BN进行微调,而不是用仿射层[33]冻结它
  3. 在新加的层中,比如说在做检测的时候都会用fbn的结构,作者在这里也用了BN,目的就是去调整一下值域的大小,从而便于做特征的归一化
  4. 一旦做完归一化之后,作者发现就可以拿这些有监督训练用的超参数来做微调了

schedule

  1. 作者之前有一篇论文(文献31)发现:当下游任务的数据集足够大的时候,可以不需要预训练,直接从随机初始化开始从头训练,最后的效果一样可以很好。有了这个结论,无论是有监督的预训练还是无监督的预训练就都无所谓了,反正已经不需要预训练的模型去做初始化了,这样就没办法体现MoCo的优越性了
  2. 但是在文献31中说的是当训练足够长的时候,也就是训练6或者9的学习时长的时候,如果训练的短的话,预训练模型还是有用的,那么就在预训练时长短的时候做比较就可以了
  3. 所以在本文中,作者说只用1或者2的学习时长,这个时候预训练还是非常有用的,就可以比较到底是MoCo预训练的好还是有监督的ImageNet的训练好了

总之,归一化和学习时长都是为了铺垫:当使用MoCo的预训练模型去做微调的时候,微调也是跟有监督预训练模型在微调时候的方式是一样的,这样做的好处是当在不同的数据集或者不同的任务上做微调的时候,就不用再去调参了,就用之前调好的参数就行了

总结

作者最后总结:MoCo在很多的下游任务上都超越了ImageNet的有监督预训练模型,但是在零零星星的几个任务上,MoCo稍微差了一点,主要是集中在实例分割和语义分割的任务上,所以接下来大家也都怀疑对比学习的方式是不是不太适合做dence prediction的task,就是这种每个像素点都要去预测的任务,所以后续也涌现了很多基于这个出发点的工作,比如dence contrast或者是pixel contrast之类

文章最后的结论:

MoCo在很多的视觉任务上,已经大幅度的把无监督和有监督之间的坑给填上了

[2020] (ICML)SimCLR_v1

摘要

本文介绍了一个简单的视觉表征对比学习框架SimCLR。我们简化了最近提出的对比自监督学习算法,而不需要专门的架构或存储库。为了理解是什么使得对比预测任务能够学习有用的表示,我们系统地研究了我们框架的主要组成部分。我们表明(1)数据增强的组成在定义有效的预测任务中起着至关重要的作用,(2)在表示和对比损失之间引入一个可学习的非线性转换( a learnable nonlinear transformation),大大提高了学习表示的质量,(3)对比学习比监督学习受益于更大的批处理规模和更多的训练步骤(larger batch sizes and more training steps)。通过结合这些发现,我们能够大大超过以前在ImageNet上进行自我监督和半监督学习的方法。一个由SimCLR学习的自我监督表示训练的线性分类器达到了76.5%的top-1准确率,这比以前的最先进水平相对提高了7%,与有监督的ResNet-50的性能匹配。当只对1%的标签进行微调时,我们实现了85.8%的top-5准确率,在减少100个标签的情况下优于AlexNet。

Introduction

  1. 在没有人类监督的情况下学习有效的视觉表示是一个长期存在的问题。大多数主流的方法可以分为两类:生成式的和判别式的

  2. 生成方法学习在输入空间中生成或建模像素(Hinton等人)。然而,像素级生成的计算成本很高,可能不是表示学习所必需的。 判别方法使用与监督学习类似的目标函数来学习表示,但训练网络执行借口任务(pretext tasks),其中输入和标签都来自于一个未标记的数据集。许多此类方法依赖启发式来设计借口任务,这可能会限制学习表征的普遍性。在潜在空间中,基于对比学习的判别方法最近显示出巨大的前景,取得了最新的成果

  3. 在本研究中,我们介绍了一个简单的视觉表征对比学习框架,我们称之为SimCLR。SimCLR不仅优于以前的工作(图1),而且它也更简单,不需要专门的架构,也不是一个存储库。

  4. 为了理解是什么促成了良好的对比表征学习,我们系统地研究了我们的框架的主要组成部分,并展示了这一点:

  5. 在定义产生有效表示的对比预测任务时,多个数据增强操作的组合至关重要。此外,无

相关文章