对比学习

参考

对比学习(Contrastive Learning)作为一种强大的无监督和自监督学习方法,近年来受到了广泛关注。它通过对比数据样本之间的相似性和差异性,从未标记的数据中提取有意义的表示,从而为下游任务提供强大的特征支持

对比学习用一句话概括就是:构造正样本对(Positive Pairs)负样本对(Negative Pairs),拉近正样本对之间的距离,拉远负样本对之间的距离,从而学习到数据的高效表示

对比学习的一个重要问题就是如何构造正负样本对

工作原理

  • 数据增强
    • 对于图像,进行诸如这些变换:裁剪,翻转,旋转,颜色变换等,来生成同一实例的不同视图。这些增强视图作为正样本对输入模型,而来自不同实例的视图则作为负样本对输入模型
    • 对于文本,可以进行同义词替换,回译(从A语言->B语言->A语言),打乱顺序等做法,来生成同一实例的不同视图。这些增强视图作为正样本对输入模型,而来自不同实例的视图则作为负样本对输入模型
  • 编码器网络
    • 编码器网络的作用是将输入数据映射到一个潜在的表示空间。通常,编码器网络是一个深度神经网络,例如卷积神经网络(CNN)用于图像数据,或循环神经网络(RNN)用于序列数据。编码器网络提取输入数据的高级特征,为后续的对比学习提供基础
  • 投影网络
    • 将编码器网络的输出进一步投影到一个更低维度的空间,称为嵌入空间;投影网络的作用是增强特征的判别能力,降低数据的复杂性和冗余
  • 对比学习目标
    • 最大化正样本对之间的一致性,同时最小化负样本对之间的一致性
  • 损失函数
    • 常见的损失函数包括对比损失(Contrastive Loss)、三重态损失(Triplet Loss)、N对损失(N-pair Loss)、InfoNCE损失和逻辑损失(Logistic Loss)

损失函数

对比损失

目标是最大化正样本对之间的一致性,最小化负样本对之间的一致性;通常基于距离度量来计算

$$
\begin{align}
L = \frac{1}{2N}\sum_{i=1}^{N}[y_i\cdot d_i^2\cdot max(margin-d_i,0)^2]\
y_i:1表示正样本对,0表示负样本对\
d_i:样本对的欧氏距离\
margin:超参数,表示负样本之间的最小距离\
\end{align}
$$

三重态损失

它通过形成三元组(锚点实例、正样本、负样本)来优化模型。三重态损失的目标是确保锚点与正样本之间的距离小于锚点与负样本之间的距离。然而,三重态损失对三元组的选择较为敏感,且在大规模数据集上计算成本较高。

N对损失

N对损失是三重态损失的扩展,它考虑了给定锚点的多个正样本和负样本。N对损失的目标是最大化锚点与所有正样本之间的一致性,同时最小化锚点与所有负样本之间的一致性。这种方法可以捕获更复杂的模式,但在处理大规模数据集时计算成本较高

InfoNCE损失

它通过将对比学习问题视为二元分类问题来优化模型。给定一个正样本对和一组负样本对,模型需要学习区分正样本和负样本。InfoNCE损失通过概率方法(如softmax函数)来衡量样本之间的相似性,从而优化模型的学习过程。

$$
\begin{align}
L = -\frac{1}{N}\sum_{i=1}^{N}log\frac{exp(f(x_i)\cdot f(x_i^{+}))}{exp(f(x_i)\cdot exp(f(x_i^{+})+\sum_{j=1}^{K}exp(f(x_i)\cdot f(x_j^{-}))))}\
N:样本总数\
f(x_i):x_i的表示\
x_i^{+}:正样本对\
x_j^{-}:负样本对\
K:负样本对数
\end{align}
$$

逻辑损失

逻辑损失是一种概率性损失函数,适用于对比学习中的细粒度差异建模。它通过估计两个实例在嵌入空间中属于同一类的概率来优化模型。逻辑损失的目标是最大化正样本对的相似性概率,同时最小化负样本对的相似性概率。、

第一阶段

InstDisc,CPC,CMC等,在这个阶段,方法模型、目标函数、代理任务都还没有统一

InstDisc-Unsupervised Feature Learning via Non-parametric Instance Discrimination

受到了有监督学习结果的启发,将一张豹子的图片输入给用有监督学习方法训练好的分类器中,会给出最后的分类分数,我们会发现分数靠前的都是与豹子相关的,排名靠后的是与豹子不搭嘎的

通过很多这样的现象,引出了InstDisc的基本思想:

让这些图片聚集在一起的原因,并不是它们的语义标签相似,而是因为图片中的内容(object)本来就长得太像了;
分数高的object之间就是很相似,它们跟分数低的object就是不相似;

采用无监督学习的个体判别任务,把每一个instance(在这里指的是每张图片)都看成一个类别,目标是学习一种特征,从而能让我们把每一张图片都区分开来

方法

概况:将所有输入的图片经CNN Encoder编码成一个特征,希望这些特征在最后的特征空间中尽可能被分开

如何训练这个CNN Encoder?使用对比学习

  • 正样本
    • 图片本身+数据增强
  • 负样本
    • 数据集中所有其他图片
memory bank

大量的负样本特征应该怎么存储?
于是我们引入了memory bank这一部件

memory bank用于存储所有图片的特征;对于imageNet,需要存储大量的样本,因此出于存储代价的考量,其维度不能太高(128D)

Proximal Regularization

给模型的训练增加了一个约束,从而能让memory bank中的特征进行动量式更新

NCE Loss

作为CNN训练的目标函数;利用它去更新CNN的参数,更新完CNN参数后,会将CNN输出的编码结果替换掉原来memory bank中的存储;循环往复更新CNN的参数与memory bank的存储,直至模型收敛

超参数设置
  • 温度τ=0.07
  • 训练次数epochs=200
  • 负样本个数=4096(memory bank中抽取)
  • 初始学习率=0.03

InvaSpread - Unsupervised Embedding Learning via Invariant and Spreading Instance Feature

InvaSpread

SimCLR的前身;没有使用额外的数据结构去存储大量的负样本

对于正负样本,来源于同一个mini-batch;只使用一个Encoder进行端到端的学习

  • base idea:
    • 同样的图片经Encoder处理后,输出的特征应该很类似,而不一样的图片经Encoder处理后,输出的特征差别大(不类似)
    • 即相似的物体/图片,它们的特征应该保持不变性(Invariant),不相似的物体/图片,它们的特征应该尽可能分散开(Spreading)

具体它是怎么做的呢?

代理任务选取了个体判别任务

重点:正负样本选取与个数

  • 前向过程

首先输入batch_size=256张图片,经数据增强后又得到batch_size张新图片

$$
\begin{align}
对于图片x_1来说,\hat{x}_1是它的正样本,剩下的所有图片[x_2,\hat{x}_2,x_3,\hat{x}_3,\cdots]等都是它的负样本\
即x_1有(batch_size-1)\times2个负样本
\end{align}
$$
为什么InvaSpreading不像InstDisc那样使用一个额外的数据结构memory bank单独抽取负样本,而是从同一个mini-batch
中抽取正负样本呢?

  • 这样就可以用一个Encoder做端到端学习(?)
    • 传播过程中不需要停下来回写更新数据给memory bank

目标函数:NCE-Loss的一个变体

不足:对比学习需要足够多的负样本,但是这里的负样本仅有500多个

CPC-Representation Learning with Contrastive Predictive Coding

与前面两种属于判别式范畴的方法(代理任务为个体判别任务)不同,CPC是属于生成式任务范畴的(代理任务为预测)

# 论文地址

图中以音频作为输入的例子,但是其实这个模型架构还可以用在图像,文本乃至强化学习

idea:输入的内容是一个有时序的序列x

$$
\begin{align}
x = [\cdots,x_{t-3},x_{t-2},x_{t-1},x_t,x_{t+1},x_{t+2},x_{t+3},\cdots]\
把x_t及之前时刻的输入扔给编码器g_{enc},它会返回一个特征\
把这些特征扔进自回归(auto regressive)模型g_{ar},常见的是RNN,LSTM等\
最后得到一个上下文特征表示C_t\
如果C_t足够好,那么它就可以用来预测未来时刻的特征输出z_{t+1},z_{t+2},\cdots\
对比学习体现在何处呢?\
正样本:未来时刻的输入经g_{enc}得到的未来时刻的输出特征\
C_t相当于query,真正的未来时刻输出特征是由未来时刻的输入决定的\
负样本:很广泛,任意选取输入,经g_{enc}得到的输出都应该和预测不相似
\end{align}
$$

CMC-Contrastive Multiview Coding

# 论文地址

思想引入:
人观察世界通过很多的传感器(眼睛,耳朵等感官),这些不同的传感器给我们的大脑传递着不同的信号
每个视角都是带有噪声且可能不完整的,但是最重要的信息却在这些信息中共享

比如:对于一条修勾,既可以被听到、看到,也可以被感受到;

因此引入了CMC的一个目标:

学习一个很强大的特征,具有多视角下的不变性;比如对于刚刚的修勾,无论是听到它的叫声,看到它,还是触碰它的外形,我们都能准确判断出它是修勾而不是其他动物

CMC工作目的就是增大不同视角间的互信息(mutual information)

CMC是怎样工作的呢?

选取了NYU RGBD数据集

这个数据集有4个view:

  • 原始图像
  • 图像对应的深度信息
    • 每个物体离观察者有多远
  • surface normal
  • 分割图像

虽然这4个不同的输入来自不同的传感器/模态,但是这些所有的输入都是对应着一张图片,则它们互为正样本

CMC是比较早做多视角工作的对比学习,不仅证明了对比学习的灵活性,而且证明了多视角多模态的可行性

局限性在于,不同的视角下可能需要不同的Encoder,会导致训练模型的计算代价偏高

基于CMC,openAI于2021年推出了CLIP模型

思想:将一张图片及对其描述的文本作为正样本对;通过对比学习的方式,学习图像和文本描述之间的关联

任务:图文匹配任务

第二阶段

MoCo v1,SimCLR v1,MoCo v2,SimCLR v2,CPC、CMC他们的延伸工作,swAV;这个阶段发展非常迅速

MoCo

# 论文地址

MoCo这个名字来源于动量对比学习Momentum Contrast的缩写

动量可以理解成一种加权移动平均

$y_t=my_{t-1}+(1-m)x_t$

$y_t为当前时刻输出,y_{t-1}为前一时刻的输出,x_t为当前时刻输入,m为动量$

MoCo的主要贡献就是把之前对比学习的一些方法,都归纳总结成为了一个字典查询问题

正负样本

假设选取了x1,对x1进行transformation后得到两个不同的图片x11和x12,x11和x12被称为正样本对

把x11当作基准点anchor,那么x12就是相对于anchor的正样本,经编码器E12(可以与E11相同也可以不同,MoCo中使用的编码器不同)后得到特征f12

负样本也使用E12作为编码器,因为所有的正样本和负样本都是基于anchor:x11得到的

字典查询

字典(dictionaries)中的条目(keys)是从数据中抽样出来的,然后用一个编码器Encoder去表示;
也就是说,keys对应的是特征f而不是图像

$把f_{11}作为query,其他的特征[f_{12},f_2,\cdots,f_N]作为keys,那么就转换成字典查询问题了$

MoCo的目标就是训练一些编码器进行字典的查找:让query与和它匹配的那个key尽可能相似

$x_{11}通常用x^q表示,x_{12}通常用x^k表示$

$特征f_{11}使用q表示,其他特征f_{12},f_2,\cdots,f_N使用k_0,k_1,\cdots,k_N表示$

好的字典需要满足:足够大,训练时保持尽可能的一致性

  • 足够大:更好地从高维的连续视觉空间做抽样,key越多,表示的视觉特征越丰富
  • 一致性:keys应该由相同或相似的编码器得到,这样和query对比时才能尽可能保持一致;如果不这样,与query匹配的key可能仅仅只是使用了和query相似的编码器,而不是真的与query相似

贡献

提出了两个东西,从而去形成一个又大又一致的字典:

  • 队列queue
    • 为什么引入queue?
      • 受限于存储开销,作者希望字典的大小能与每次前向传播的batch的batch_size解耦
    • 做法:
      • queue_size可以比较大,但是更新queue却是一点一点进行的
      • 新进入的mini_batch会把最早的mini_batch挤出queue
    • 优点:
      • queue大小(字典大小)与batch_size解耦合,每个迭代过程都只更新一小部分,只用普通的GPU就可以训练出一个很好的模型
  • 动量编码器momentum encoder
    • 前面做法的缺点:当前batch是由相同编码器得到,而之前的key是由不同的编码器得到的(?)
      • 前面提到了对于keys,尽量要保持编码器的一致性;可是对于每个batch都要进行反向传播参数更新,编码器就逐渐不一致了
    • 改进:
      • $对于x^q的编码器我们称为\theta_q;对于动量编码器\theta_k,我们让\theta_k = m\theta_{k-1}+(1-m)\theta_q$
      • 如果我们选择了一个很大的动量m,那么$\theta_k$的更新是很缓慢的

实现细节

Encoder使用了残差网络ResNet,每个图片的特征维度为128D,对所有特征做了L2归一化(将向量除以L2范数)

$$
\begin{align}
L2归一化:对于X=[x_1,x_2,\cdots,x_n]\
其L2范数||X|| = \sqrt{x_1^2+\cdots+x_n^2}\
\hat{X} = \frac{X}{||X||}
\end{align}
$$
目标函数使用InfoNCE

$L_q= -log\frac{exp(qk/\tau)}{\sum_{i=1}^Kexp(qk_i/\tau)}$

$\tau是超参数-温度$

  • τ较小
    • 模型会更关注将正样本和负样本区分开,但是可能导致模型泛化能力下降
  • τ较大
    • 所有样本的差异可能”平滑化”,学习的力度减弱。这会得到一个更“均匀”的表示空间,但可能缺乏判别性,无法捕捉细微差异

SimCLR

# 论文地址

SimCLR即视觉表征对比学习简单框架(A Simple Framework for Contrastive Learning of Visual Representations)

正负样本

如果有一个mini-batch的图片x,对mini-batch中的所有图片做不同的的数据增强,分别得到两张图片

在论文中称这两张经数据增强后的图片为正样本对(positive pair)

如何理解视频中所说batch_size和正样本、负样本数量的关系(?)

对同一个图片延伸得到的两个图片为正样本对;也就是说如果batch_size=n,那么正样本数也为n(因为positive pair=(anchor,positive),即正样本对中一个是锚点anchor,一个是基于锚点的正样本),基于锚点anchor的负样本数为2(n-1)

特征表示

数据增强后得到的xi,xj经过共享权重的编码器f(一种ResNet,相当于共用一个Encoder)后,得到特征表示h

创新点

SimCLR的重大创新是:在得到特征表示之后又加了一个projector,就是图中所示的g函数;但是,这个g只在训练阶段使用,在下游任务(downstream tasks)时应该扔掉g;也就是说这个projector仅是为了让模型训练得更好

简单地说,projector就是一个mlp(1层全连接层+ReLU),输出的最终的特征表示zi,zj为128D,为了和之前的工作一致

目标函数

最后使用 NT-Xent(the normalized temperature-scaled cross entropy loss) 衡量正样本之间能否达到最大的一致性

$$
\begin{align}
相似度sim(u,v) = u^Tv / ||u||\ \ ||v||\
L_{i,j} =-log\frac{exp(sim(z_i,z_j)/\tau)}{\sum_{k=1}^{2N}\mathbb{1}{[k \neq j]}exp(sim(x_i.x_k)/\tau)}\
\mathbb{1}
{[k \neq j]}:当k\neq j时为1\
normalize体现在对相似度函数sim()做了L2归一化
\end{align}
$$

贡献

  • 在编码器后加上一个mlp层的做法对后续的研究方法产生了长远的影响力

对于经过Encoder(Res50)出来的特征h(shape=2048D),图例的non-linear表示的就是projector,linear指的是没有ReLU的projector,None就是不要projector
- 通过图得到的结论:
- projector对准确率的提升显著
- z最后的维度对最终结果没有什么影响

  • 更多的数据增强方法

最有效的数据增强是裁剪crop和颜色失真/扭曲color distort

MoCo v2

# 技术报告地址

MoCo v2 即 “Improved Baselines with Momentum Contrastive Learning”

MoCo v2是借鉴了SimCLR而做的优化,比如引入mlp层以及使用更多的数据增强

MoCo v2对比MoCo主要有以下4个改动:

  • 加了一个MLP projection head
  • 使用更多的数据增强
  • 训练时使用cosine的learning rate schedule
  • 训练的epochs从200增加到800

MoCo v2对比SimCLR的优点主要体现在:

  • 在小batch_size上表现更佳
  • 增大epochs带来的效果提升更明显
  • 硬件要求更低

SimCLR v2

# 论文地址

SimCLR v2 即 Big Self-Supervised Models are Strong Semi-Supervised Learners,这体现了SimCLR v2的主要思想:

大的自监督模型很适合做半监督学习

作者提出一种从少量的带标签数据和大量的不带标签的数据中进行学习的方案:无监督预训练+有监督微调

这个方案分为3个部分:

  • SimCLR v2:怎样自监督去训练出一个大的模型
  • 用少量有标签数据做有监督的微调,得到一个teacher模型
  • teacher模型在之前的无标签数据集上生成伪标签,然后训练一个student模型进行自监督训练

主要改进

  • 使用更大的模型,从ResNet50替换为ResNet152 + selective kernel
    • selective kernel是一种用于CNN的机制,允许网络在每一层”有选择地”决定使用多大尺寸的卷积核来处理信息
  • 加深projection head,从1层变为2层
  • 使用了MoCo的动量编码器,但是效果提升并不明显(1个点左右),这是因为SimCLR已经有了非常大的mini_batch(size=4096/8196)了,负样本已经相当多了,所以无论是从字典大小还是字典特征一致性,SimCLR v2都已经做的很好了

SwAV - Unsupervised Learning of Visual Features by Contrasting Cluster Assignments

# 论文地址

SwAVSwap Assignment Views的缩写,意思就是给定同样一张图片,如果去生成不同的视角View,可以用一个视角得到的特征去预测另一个视角得到的特征;因为所有视角下的特征应该是非常接近的

这篇文章具体的做法是:将对比学习与聚类结合在了一起

动机

对比学习的做法就是将同一张图片做不同的数据增强得到的特征直接进行对比,这样做有点原始而且浪费资源,因为所有的图片都是自己的类

作者考虑,能不能不做近似(比如MoCo在ImageNet上训练那就有128万类,即使在计算loss时取近似,只是取队列编码器里的作为负样本,那负样本也有6万多个),能不能借助一些先验信息,不去和大量的负样本对比,而是去跟一些更简洁的东西对比;由此作者提出了与聚类中心C进行比较

具体做法

$$
\begin{align}
引入K个聚类中心prototypes{C_1,\cdots,C_k},表示为C \in R^{D\times K}\
D:特征维度,K:聚类中心数量(K=3000)\
得到特征z_1,z_2的过程与左图中的对比学习无异\
与左图不同的是,得到特征z_1,z_2后并不是直接在上面做对比学习的loss,而是通过clustering方法生成目标Q_1,Q_2\
Q_1,Q_2相当于ground\ \ truth(基准)\
如果x_1,x_2是正样本,那么特征z_1,z_2会很相似,按道理就可以互相做预测\
代理任务为:\
z_1和C点乘预测Q_2,z_2和C点乘预测Q_1\
点乘的结果就是我们的预测,ground\ \ truth就是前面经clustering得到的Q_1,Q_2
\end{align}
$$
通过换位预测(Swap Prediction)的方法,SwAV可以对模型进行训练

聚类的优点:

  • 减少计算成本
    • 对比学习中需要和巨量的负样本进行对比,即便如此也只是近似,而如果是跟聚类中心进行对比,那么只需要3000个聚类中心
  • 更合理有效
    • 聚类中心是有明确语义含义的;而前面的对比学习只是随机抽样负样本,随机抽取的负样本中,有的可能还是正样本(本身就相似的图片),而且有的时候抽出来的负样本类别也不均衡

Multi-crop: Augmenting views with smaller images

之前的对比学习都是用的两个crop(将原图片resize到256×256,再随机裁下来2个224×224的图片作为正样本对x1,x2)

由于224×224相对256×256这个尺寸很大,所以学习到的是整个场景的特征
如果使用多个crop,那么就能更好地抓住局部的特征

为了平衡计算量与学习局部特征,作者的做法是:

随机取2个160×160的crop学习全局特征,为了学习局部特征和增加正样本数量,再随机取4个96×96的crop
此时就有6个视角而不是原来的2个视角

Multi-crop这个方法对其他的对比学习方法也有用

第三阶段

不用负样本也可以进行对比学习;主要是BYOL这个方法及其后续改进;SimSiam框架总结了前面的方法,是用卷积神经网络做对比学习的总结性工作

BYOL - Bootstrap Your Own Latent

# 论文地址

BYOL是论文题目的缩写,Bootstrap是在已有的东西上进行改造,Latent代指特征(latent,hidden,feature,embedding都是特征的表示),简单地说,BYOL的意思就是自己跟自己学,左脚踩右脚上天

BYOL是一种全新的自监督学习方法,完全没有使用任何形式的负样本

为什么就只是不使用负样本就这么新奇?
在对比学习中,负样本是一个约束,我们的目标是让相似的物体它们的特征也尽可能相似,如果在计算目标函数时只有正样本,那么就会有一个捷径,那就是给定什么输入,输出都一样,那么最终的特征就都一模一样,计算loss就永远为0,模型直接躺平不学了,这个现象称为model collapse/learning collapse

前向过程具体做法

  • 对于输入的mini_batch x,进行两个不同的数据增强得到 $v,v’$
  • 图片通过两个架构完全相同、参数不同的编码器得到两个特征 $y_{\theta},y_{\xi}’$
    • $online分支:f_{\theta}随梯度更新而更新$
    • $target分支:f_{\xi}采用动量编码器的更新形式,与MoCo相同$
  • 使用了和SimCLR相同的projection head
    • $g_{\xi}和g_{\theta}结构相同,但是g_{\xi}通过动量方式更新$
  • $z_{theta}后又接了一个新结构predictor,q_{\theta};与g_{\theta}结构相同,得到新特征q_{\theta}(z_{\theta})$
  • target层使用一个叫sg(stop-gradient)的东西输出最终的特征,最终用 $q_{\theta}(z_{\theta})去预测sg(z_{\xi}’)进行对比学习,使用MSE作为loss$

online层可以理解成MoCo的query,target可以理解成MoCo的key,但是代理任务不同;BYOL相当于是用自己的一个视角的特征去预测另一个视角的特征

当训练完成后,$除了f_{\theta}外的组件将被丢弃,y_{\theta}用来做下游任务$

不同对比学习模型的projection head结构对比

SimCLR:

MLP的结构为:线性变换+Batch Norm+ReLU+线性变换+Batch Norm

MoCo v2:

没有Batch Norm操作

BYOL:

MLP的结构为:线性变换+Batch Norm+ReLU+线性变换

为什么BYOL不使用负样本,模型学习却不坍塌?

有一篇博客复现BYOL使用了MoCo v2的projection结构,导致了模型坍塌,作者做了实验:

不使用Batch Norm的表现与随机初始化的ResNet的表现相差无几,因此博客作者认为导致BYOL学习模型不坍塌的原因是MLP中有Batch Norm操作

Batch Norm这么神奇吗?

$$
\begin{align}
给定输入张量X \in R^{Batch_size\times Dim}\
均值:\mu = \frac{1}{D}\sum_{i=1}^{Dim}X[i,:]\
方差:\sigma = \frac{1}{D}\sum_{i=1}^{Dim}(X[i,:]-\mu)^2\
归一化:\hat{X}[i,:] = \frac{X[i,:]-\mu}{\sqrt{\sigma^2-\epsilon}}\
\epsilon:防止除零的小常数(如 1e−5)
\end{align}
$$

从它的计算公式入手,我们会发现计算时,其实会看到batch中其他样本的特征,存在信息泄露

作者引入了一个概念叫平均图片(mode,众数,中值),是之前很多图片的总结量,由Batch Norm产生

BYOL学习时不光是自己跟自己学,存在隐式的负样本-平均图片,与平均图片对比着学,类似于SwAV

但是BYOL作者又做了实验,表示Batch Norm只是让模型变得稳定,在模型初始化较好的情况下,不使用Batch Norm也不会导致模型坍塌

SimSiam - Simple Siamese Representation Learning

# 论文地址

SimSiam意思是是简单孪生网络

SimSiam不需要用负样本,不需要大的batch_size,也不需要动量编码器

孪生指的就是这两个encoder结构相同,参数共享

与BYOL不同的地方是:没有使用动量编码器

为什么BYOL不会模型坍塌?
因为stop-gradient;因为stop gradient操作将一套模型参数被人为劈成了两份(避免了梯度回传,而导致一套参数受另一套参数反馈影响,导致他们共谋捷径使模型坍塌),即需要解决两个子问题,模型的更新其实也是在交替进行的

伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Algorithm 1 SimSiam Pseudocode, PyTorch-like
# f: backbone + projection mlp
# h: prediction mlp

for x in loader: # load a minibatch x with n samples
x1, x2 = aug(x), aug(x) # random augmentation
z1, z2 = f(x1), f(x2) # projections, n-by-d
p1, p2 = h(z1), h(z2) # predictions, n-by-d
L = D(p1, z2)/2 + D(p2, z1)/2 # loss
L.backward() # back-propagate
update(f, h) # SGD update
def D(p, z): # negative cosine similarity
z = z.detach() # stop gradient
p = normalize(p, dim=1) # l2-normalize
z = normalize(z, dim=1) # l2-normalize
return -(p * z).sum(dim=1).mean()

前向过程为:

  • image x经过两次数据增强得到x1,x2
  • x1,x2分别经过编码器f得到特征z1,z2
  • z1,z2经过predictor得到h1,h2
  • 计算对称性loss(p1预测z2,同时p2预测z1)

所有孪生网络结构、做法对比

  • SimCLR
    • 是end2end的学习,所以两个分支都有梯度回传,做的是对比任务
  • SwAV
    • 做对比任务,与聚类中心(由SK算法得到)对比
  • BYOL
    • 引入新结构predictor,预测任务

第四阶段

使用Transformer,主要是MoCo v3DINO

在介绍MoCo v3之前,先简单地过一下ViT是什么

ViT - Visual Transformer

参考

# 论文地址

ViT

前言

先回顾一下Transformer,Transformer的核心就是自注意力机制,元素间两两互相去做自注意力操作,复杂度为O(n^2)
在NLP中,目前硬件支持的输入序列长度最大也就是几百上千

在视觉领域使用Transformer首要解决的就是如何把多维的输入(比如图片)转换为1D的序列/集合

如果把每个像素值拉直拉平变成序列,那样序列太长了,计算复杂度过高

简介

ViT 通过将图像分成一系列图块(patches),并将每个图块转换为向量表示作为输入序列。然后这些向量通过多层的Transformer Encoder进行处理,这样就可以捕捉到图像中不同位置的上下文依赖关系。最后通过对Transformer Encoder的输出做分类或回归,可以完成特定的视觉任务

整体架构

为了使图像仿照NLP的输入序列,我们先将图像分为块(patch),再将这些图像块平铺后输入到网络中,然后通过Transformer进行特征提取,最后接一个MLP对特征分类,其实就是把以往CNN分类任务的网络骨架(backbone)换成了Transformer

工作流程

  • 输入一张图片,分成一个一个的patch
  • 将每一个patch输入到 Linear Projection of Flattened Patches 层(也就是Embedding层),通过一个可学习的线性变换将patch变成一个向量token
  • 在这一系列token前面再加上一个专门用于分类的token(图中的*,[class]embedding)
  • 对每一个token加上位置编码,对应图中的0,1,2,…,9
  • 把token输入Transformer Encoder中
  • 取出class token,输入MLP中得到分类结果

MoCo v3 - An Empirical Study of Training Self-Supervised Vision Transformers

# 论文地址

无监督的预训练(BERT/GPT等)已经彻底改变了NLP,自从Vision Transformer成功之后,将ViT引入CV领域的自监督训练已经是大势所趋了。但是使用ViT作为backbone会导致训练很不稳定,这种不稳定性是造成模型准确率降低的一个主要问题。

MoCo v3只是一种架构,CNN可以用,Visual Transformer也可以用

MoCo v3这篇论文主要在介绍如何通过一些方法提高ViT的稳定性,这也是题目所说的 “An Empirical Study(一个实验性学习)”

架构&伪代码

论文中并没有直接给模型总览图,我们直接看伪代码

MoCo v3的架构,其实相当于MoCo v2与SimSiam的合体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# f_q: encoder: backbone + proj mlp + pred mlp
# f_k: momentum encoder: backbone + proj mlp
# m: momentum coefficient 动量系数
# tau: temperature
for x in loader: # load a minibatch x with N samples minibatch x,batch_size=N
x1, x2 = aug(x), aug(x) # augmentation 数据增强
q1, q2 = f_q(x1), f_q(x2) # queries: [N, C] each
k1, k2 = f_k(x1), f_k(x2) # keys: [N, C] each
loss = ctr(q1, k2) + ctr(q2, k1) # symmetrized
loss.backward()
update(f_q) # optimizer update: f_q
f_k = m*f_k + (1-m)*f_q # momentum update: f_k
# contrastive loss
def ctr(q, k):
logits = mm(q, k.t()) # [N, N] pairs
labels = range(N) # positives are in diagonal
loss = CrossEntropyLoss(logits/tau, labels)
return 2 * tau * loss

整体的架构仍是有两个网络,query编码器f_q,key动量编码器f_k,使用对比学习的目标函数,从这个角度,它是MoCo v2
但是f_q除了backbone,还有projection head和prediction head,同时计算loss是对称性的,这个角度又说明它是SimSiam

从总体结构看,MoCo v3就是MoCo v2和SimSiam的一个延伸工作

因为ViT的成功,作者就像尝试自监督学习+ViT会不会也取得成功,于是将backbone的ResNet换成了ViT

这是将backbone从ResNet换成ViT之后,在不同batch_size下的自监督训练曲线

在batch_size大的情况下,会出现准确率突然掉点,虽然能很快恢复,但是不如原来的准确率高(掉点程度变大)

针对这个问题,MoCo v3的作者提出了一个trick

在训练时,去观察每一层梯度回传的情况;每次loss大幅振动导致准确率大幅下降的时候,梯度也会有一个波峰,这个波峰发生在第一层(patch projection,把图片分割成一个一个patch,通过可学习的线性变换将patch变换为向量)

但是总是梯度不正常,那么这个patch projection不如直接冻住不让它学习训练

这样就神奇的解决了问题

这个trick对BYOL也有用,把BYOL的ResNet换成ViT后,冻住patch projection一样可以获取更好的结果

DINO - Emerging Properties in Self-Supervised Vision Transformers

# 论文地址

这篇文章的主要卖点是ViT在自监督训练的情况下,会有一些非常有趣的特性

下图是可视化的自注意力图,可以发现它非常准确地抓住了物体的轮廓

DINO的名字来源于 Self-distillation with no labels,无标签的自蒸馏方法

DINO延续了BYOL,把online网络叫成student网络,把target网络叫成teacher网络,使用student网络预测teacher网络

架构

DINO前向过程与BYOL和SimSiam类似

  • $x经两个不同的数据增强得到两个视角x_1,x_2$
  • $x_1,x_2经过两个结构相同,参数不同,包含projection\ \ head和prediction\ \ head的编码器g_{\theta_s},g_{\theta_t}$
  • $teacher的编码器g_{\theta_t}是动量更新的,为了避免模型坍塌,DINO做了一步centering,就是计算batch中样本的均值,然后减去均值$
  • $student和teacher经过softMax得到概率分布p_1,p_2,用p_1预测p_2,loss=-p_2logp_1$
伪代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# gs, gt: student and teacher networks
# C: center (K)
# tps, tpt: student and teacher temperatures
# l, m: network and center momentum rates
gt.params = gs.params
for x in loader: # load a minibatch x with n samples
x1, x2 = augment(x), augment(x) # random views

s1, s2 = gs(x1), gs(x2) # student output n-by-K
t1, t2 = gt(x1), gt(x2) # teacher output n-by-K

loss = H(t1, s2)/2 + H(t2, s1)/2
loss.backward() # back-propagate

# student, teacher and center updates
update(gs) # SGD
gt.params = l*gt.params + (1-l)*gs.params
C = m*C + (1-m)*cat([t1, t2]).mean(dim=0)

def H(t, s):
t = t.detach() # stop gradient
s = softmax(s / tps, dim=1)
t = softmax((t - C) / tpt, dim=1) # center + sharpen
return - (t * log(s)).sum(dim=1).mean()

可以看到前向过程真的非常像MoCo v3

对比学习总结

InstDisc提出了个体判别任务以及提出了使用memory bank这样一个额外的数据结构存储负样本,

不用额外数据结构的另一条路就是端到端学习,InvaSpread

使用InfoNCE作为loss:CPC

把任务拓展到多视角:CMC