Contrastive Learning(对比学习,MoCo,SimCLR,BYOL,SimSiam,SimCSE)

您所在的位置:网站首页 summertimesagaDNA样本怎么得到 Contrastive Learning(对比学习,MoCo,SimCLR,BYOL,SimSiam,SimCSE)

Contrastive Learning(对比学习,MoCo,SimCLR,BYOL,SimSiam,SimCSE)

2023-06-26 04:15| 来源: 网络整理| 查看: 265

在这里插入图片描述

很多大佬认为,深度学习的本质就是做两件事情:Representation Learning(表示学习)和 Inductive Bias Learning(归纳偏好学习)。在表示学习方面,如果直接对语义进行监督学习,虽然表现很好,但是它需要很多的样本并且往往是需要对特定的任务进行设计,很难具有迁移性。所以难怪各位大佬们都纷纷为自监督学习站台,自监督是未来!

自监督学习有大类方法,一个是生成方法一个对比方法,如上图。生成方法往往会对像素级损失进行约束,关于这一类博主已经在前一篇文章整理了传送门:视觉无监督学习,而对比学习在表示学习上做的事情就是:

其实模型不必要知道关于特征的细节,只要学到的特征足矣使其和其他样本区别开来就行。

Contrastive loss 对比损失Contrastive loss,简单的解释就是,利用对比正-负样本来学习表示。学习的目的为 s c o r e ( f ( x ) , f ( x + ) ) > > s c o r e ( f ( x ) , f ( x − ) ) score(f(x),f(x^{+}))>>score(f(x),f(x^{-})) score(f(x),f(x+))>>score(f(x),f(x−)) 这里x+是与x相似或相等的数据点,称为正样本。x−是与x不同的数据点,称为负样本。score函数是一个度量两个特征之间相似性的指标,直接算内积来表示: s c o r e ( f ( x ) , f ( x + ) ) = f ( x ) T f ( x + ) score(f(x),f(x^{+}))=f(x)^T f(x^{+}) score(f(x),f(x+))=f(x)Tf(x+) 然后尝试优化以下期望,即让正例样本越相似,要负例样本越远就好。 E [ − l o g e f ( x ) T f ( x + ) e f ( x ) T f ( x + ) + e f ( x ) T f ( x − ) ] E[-log \frac{e^{f(x)^Tf(x^+)}}{e^{f(x)^Tf(x^+)}+e^{f(x)^Tf(x^-)}}] E[−logef(x)Tf(x+)+ef(x)Tf(x−)ef(x)Tf(x+)​]

其实这个叫法最初似乎出自Yann LeCun “Dimensionality Reduction by Learning an Invariant Mapping”,本来是用于处理在降维空间中正样本和负样本之间的相似/不相似的远近距离关系,式子为: L = 1 2 N ∑ n = 1 N y d 2 + ( 1 − y ) m a x ( m a r g i n − d , 0 ) 2 L=\frac{1}{2N}\sum_{n=1}^Nyd^2+(1-y)max(margin-d,0)^2 L=2N1​n=1∑N​yd2+(1−y)max(margin−d,0)2其中 d = ∣ ∣ a n − b n ∣ ∣ 2 d=||a_n - b_n||_2 d=∣∣an​−bn​∣∣2​,代表两个样本特征的欧氏距离,y为两个样本是否匹配的标签,y=1代表两个样本相似或者匹配,y=0则代表不匹配,margin为设定的阈值。损失函数主要惩罚如果原本相似的样本y=1,但在特征空间的欧式距离较大,则说明当前的模型不好,损失变大。同样的如果原本不相似y=0,但其特征空间的欧式距离反而小的话,损失也会变大。 在这里插入图片描述 上图是loss与样本特征的欧式距离d之间的关系,其中红色虚线表示的是相似样本的损失值,蓝色实线表示的不相似样本的损失值。

def contrastive_loss(self, y,d,batch_size): tmp= y *tf.square(d) #tmp= tf.mul(y,tf.square(d)) tmp2 = (1-y) *tf.square(tf.maximum((1 - d),0)) return tf.reduce_sum(tmp +tmp2)/batch_size/2

而这种成对loss的思想在其他领域如搜索推荐会有其他的变体:

Pairwise Ranking Loss: L ( r 0 , r 1 , y ) = y ∣ ∣ r 0 − r 1 ∣ ∣ + ( 1 − y ) m a x ( 0 , m − ∣ ∣ r 0 − r 1 ∣ ∣ ) L(r_0,r_1,y)=y||r_0-r_1||+(1-y)max(0,m-||r_0-r_1||) L(r0​,r1​,y)=y∣∣r0​−r1​∣∣+(1−y)max(0,m−∣∣r0​−r1​∣∣)Triplet Ranking Loss: L ( r a , r p , r n ) = m a x ( 0 , m + d ( r a , r p ) − d ( r a , r n ) ) L(r_a,r_p,r_n)=max(0,m+d(r_a,r_p)-d(r_a,r_n)) L(ra​,rp​,rn​)=max(0,m+d(ra​,rp​)−d(ra​,rn​))

而马上要总结的MoCo使用的其实是Contrastive loss一种变体InfoNCE: L q = − l o g e x p ( q k + / τ ) ∑ i = 0 K e x p ( q k i / τ ) L_q=- log \frac{exp(qk_{+}/\tau)}{\sum^K_{i=0} exp(qk_{i}/\tau)} Lq​=−log∑i=0K​exp(qki​/τ)exp(qk+​/τ)​一个正例k+,K个负例ki,这样可以使只有真正匹配(与query q算点积)的样本更相似,并且同时不匹配的不相似时,loss才低。 τ \tau τ是温度系数,一般设置为0.1或者0.2,它的作用主要是可以是损失函数感知负例的难度。最初出自Contrastive Predictive Coding,据说使用InfoNCE,可以同时优化encoder和自回归模型…

对比学习为什么有效? 对比损失函数是一个具备困难负样本自发现性质的损失函数,这一性质对于学习高质量的自监督表示是至关重要的,不具备这个性质的损失函数会大大恶化自监督学习的性能。关注困难样本的作用就是:对于那些已经远离的样本,不需要继续让其远离,而主要聚焦在如何使没有远离的那些的样本远离,从而使得到的表示空间更均匀(uniformity)。

alignment:正例之间表示保持较近距离。距离越小,alignment的程度越高。uniformity:随机样例的表示应分散在超球面上。数据越均匀,保留的信息越多 在这里插入图片描述 温度系数的作用是调节对困难样本的关注程度:越小的温度系数越关注于将本样本和最相似的其他样本分开),因此往往可以得到更均匀的表示,但过分强迫与困难样本分开会破坏学到的潜在语义结构。

如何选择正-负例pair? Easy negative example比较容易识别,所以相对来说找一些较难的pair是有利于训练的。一般可分为

Offline mining。计算所有的数据的embedding,然后计算所以pair之间的距离判断其难易程度,主要选择hard或者semi-hard的数据。Online mining。为每一batch动态挖掘有用的数据,将一个batch输入到神经网络中,得到这个batch数据的embedding,Batch all的方式还是会计算所有的合理的,Batch hard偏向于选择距离最大的正样本和距离最小的负样本。

这里需要思考的问题是这种pair对究竟多少数量是合适的?

一般来说,对比方法在有更多的负样本的情况下效果更好,因为假定更多的负样本可以更有效地覆盖底层分布,从而给出更好的训练信号。

所以回到MoCo的图了,既然样本数量对于学习到的样本质量有很大的影响,那么我们就扩展负样本的数量就好!但是目前对于batch size是没有很好的解决办法的,实际上如下图a,loss的梯度会流过编码器的正样本q和负样本k的Encoder θ k ← m θ k + ( 1 − m ) θ q \theta_k \leftarrow m\theta_k+(1-m)\theta_q θk​←mθk​+(1−m)θq​这意味着样本的数量被限制在mini-batch的尺寸上,即我们并不能采样无穷多的样本,GPU负载能力有限。

在这里插入图片描述 对于查询正样本xq,要在一个batch中(dictionary size = mini-batch size)的所有K中区别开来,有上图三种方法:

end-to-end:先编码encoder(可同可不同),然后内积算loss再梯度。但是这种方法由于dictionary size 和 mini-batch 的强耦合性(负例样本对也会为 loss 产生贡献,也会回传梯度),在batch大的时候优化难,而在batch小的时候,batch之间的参数会不一样,也就是GPU大小限制了模型的性能。memory bank:把dictionary size 从 mini-batch 中解耦出来,即先把所有样本的特征保存下来bank,然后每次随机采样,再梯度query的encoder的参数。但是这样只有当所有key被sample完以后才会更新memory bank,不同的key在和query是不一致的和滞后的,因为每一次sample encoder都会更新虽有memory bank后面也加入了momentum,但是是针对sample来的,在更新memory bank时会保留一部分上一轮的特征值。MoCo:是以上两者的融合版本,将 dictionary 作为一个 queue 进行维护当前的negative candidates pool,且它是改成了queue的动态更新机制,每sample一个batch key(所以一个trick就是会使用Shuffling BN,打乱再BN),进队后相对于一些最早进入队列的 mini-batch 对应的 key 进行出队操作,这样保证一些过时的、一致性较弱的 key 可以被清除掉。这样就同样是解耦,K是队列长度,K可以设置很大,同时更新也不会有问题。

在这里插入图片描述 按照以上伪码,可以简单看看MoCo的三个比较重要的函数

@torch.no_grad() def _momentum_update_key_encoder(self): """ key encoder的Momentum update """ for param_q, param_k in zip(self.encoder_q.parameters(), self.encoder_k.parameters()): param_k.data = param_k.data * self.m + param_q.data * (1. - self.m) @torch.no_grad() def _dequeue_and_enqueue(self, keys): """ 完成对队列的出队和入队更新 """ # 在更新队列前得到keys keys = concat_all_gather(keys)#合并所有keys batch_size = keys.shape[0] ptr = int(self.queue_ptr) assert self.K % batch_size == 0 # for simplicity # 出队入队完成队列的更新 self.queue[:, ptr:ptr + batch_size] = keys.T ptr = (ptr + batch_size) % self.K # 用来移动的指针 self.queue_ptr[0] = ptr def forward(self, im_q, im_k): # 计算query features q = self.encoder_q(im_q) # queries: NxC q = nn.functional.normalize(q, dim=1) # 计算key features with torch.no_grad(): # 对于keys是没有梯度的反向的 self._momentum_update_key_encoder() # 用自己的来更新key encoder # 执行shuffle BN im_k, idx_unshuffle = self._batch_shuffle_ddp(im_k) k = self.encoder_k(im_k) # keys: NxC k = nn.functional.normalize(k, dim=1) # 还原shuffle k = self._batch_unshuffle_ddp(k, idx_unshuffle) # 计算概率 # positive logits: Nx1 l_pos = torch.einsum('nc,nc->n', [q, k]).unsqueeze(-1) #用爱因斯坦求和来算sum # negative logits: NxK l_neg = torch.einsum('nc,ck->nk', [q, self.queue.clone().detach()]) # logits: Nx(1+K) logits = torch.cat([l_pos, l_neg], dim=1) # 平滑softmax的分布,T越大越平 logits /= self.T # labels是正例index labels = torch.zeros(logits.shape[0], dtype=torch.long).cuda() # 出队入队更新 self._dequeue_and_enqueue(k) return logits, labels

完整的中文源码阅读笔记可以参考:https://github.com/nakaizura/Source-Code-Notebook/tree/master/MoCo

原paper:https://arxiv.org/abs/1911.05722 原code:https://github.com/facebookresearch/moco

SimCLR MoCo 强调pair对的样本数量对对比学习很重要,SimCLR 认为构建负例的方式也很重要。先说结论:

多个数据增强方法组合对于对比预测任务产生有效表示非常重要。此外,与有监督学习相比,数据增强对于无监督学习更加有用;在表示和对比损失之间引入一个可学习的非线性变换可以大幅提高模型学到的表示的质量;与监督学习相比,对比学习得益于更大的批量和更多的训练步骤。

模型过程如下: 在这里插入图片描述

先sample一些图片(batch)对batch里的image做不同的data augmentation,如图上的x˜i 和 x˜j,将其视为正对一个基本的神经网络编码器 f(·),从增强数据中提取表示向量, 作者使用ResNet-50 ;一个小的神经网络投射头(projection head)g(·),将表示映射到对比损失的空间;目标是希望同一张图片、不同augmentation的结果相近,并互斥其他结果。

作者认为多种数据增强操作的组合是学习良好表示的关键,论文里面主要讨论过的有如下: 在这里插入图片描述 (推荐有一个github用于数据增强很好用,pip install imgaug,https://github.com/aleju/imgaug)

为什么要用非线性的projection head? 由图可知在representation与contrastive loss间使用了可学习的non-linear projection,这个其实是非常简单的单层MLP+ReLU的架构。其优势在于对于已经强增广的pair来说可能差距比较大,为了避免计算 similarity 的 loss function在训练时丢掉一些重要的feature,用mlp可以改善一些细粒度个性特征的表示质量,也方便做下游任务(其实这也是个经验结果,用了两次变换之后 效果就是会很好…)。

损失函数NT-Xent (the normalized temperature-scaled cross entropy loss),zi 和 zj 是从 Projection Head 获得的输出矢量,output∈{0,1} if k≠i,τ 表示温度参数可以用来放缩概率。

在这里插入图片描述 值得注意的一个trick就是会算两次(即公式中间的2N,会把i-j的计算,用j-i成对的再算一次)

做完训练后,特征表示可以拿去下游做微调,比如用于图像分类等下游任务。整体的框架图如下:

在这里插入图片描述 paper:https://arxiv.org/abs/2002.05709 code:https://github.com/google-research/simclr 注:他们用了128块GPU/TPU,来处理每个minibatch 9000个以上样本(这是为了获得足够的负样本对比,所以必须要比普通的batch要大),并完成1000轮的训练…

MoCo v2 在MoCo的基础上加入了SimCLR的 projection head 和多种数据增强手段如模糊等…ImageNet 任务提升了 6%。

SimCLR v2 结合无监督预训练、半监督训练、有监督的微调和未标记数据的蒸馏等等一系列的训练手段。具体如下图:

左边,非监督的方法学习一个任务无关的通用的表征,这里直接用SimCLR,不同点在于网络变大和也借用了MoCo部分架构。中间,用监督的方法进行fine-turning右边,在unlabeled大数据集上进行蒸馏 在这里插入图片描述

这种架构显然很适合在工业界落地。

BYOL 无需负样本也能够取得好的效果?!出自DeepMind的NIPS20’的Bootstrap Your Own Latent(BYOL),BYOL认为之前的方法都基于negative pairs,而它们很大程度上取决于图像增强的选择,所以为什么不直接从图像增强视角出发呢?框架图如下: 在这里插入图片描述 没有pair,但是BYOL 使用两个相互交互并相互学习的非对称神经网络,分别称为在线网络和目标网络。架构如上:

上面的分支是 online network,包括了embedding ,projection 以及 prediction ,其中嵌入的使我们最要想要的模块。下面的分支是 target network,包括 embedding 和 projection,此处梯度不直接回传。online 网络参数使用L2的梯度进行更新,而 target 网络直接通过 online 的momentum得到,这里target的就充当了之前负样本的功能。

即target可以随机开始得到输出比如一开始的结果为1.4%非常差,此时新开一个分支训练online去预测同一图像在不同增强视角下的target的表示(从一个分支直接预测了另一个分支的输出,用滚动编码方法更新),此时结果居然就可以到非常高的程度了。也正是 BYOL主打其 不需要进行 negative 样本的idea。所以因此它的性能对 batch size 的大小不是特别敏感,在同等参数量的情况下,BYOL 的效果也是非常好。

为什么BYOL有效? 非对称解构可以防止模式崩塌。最近有一篇论文对其做了细致的测试,其中最关键的结论就是:BYOL移除BN之后的表现就和随机瞎猜一样了。由于BN的出现本来就是为了克服domain和target的差异问题,即预防mode collapse,可以将正负样本的距离拉开,所以BYOL可能也是做了这样的事情,做了对图片均值和方差的学习,然后重新分配结果和特征值。

BYOL和MoCo、SimCLR的区别

MoCo、SimCLR更偏向于问这两张图片之间有何差异?BYOL可能是在问这张图片与这些图片的平均有什么差异?

几种流行对比学习的模式 主要按防止模型坍塌可分为:

基于负例基于对比聚类基于不对称网络结构基于冗余消除损失函数

paper:https://arxiv.org/abs/2006.07733

Exploring Simple Siamese Representation Learning(SimSiam) 孪生网络已成为无监督表达学习领域的通用架构,现有方法通过最大化同一图像的两者增广之后的相似性使其避免“崩溃解(collapsing solutions)”问题。即在训练网络的时候,网络会很迅速找了一个退化解并达到了最小可能损失-1。

但是在kaiming大神的这篇文章中,他们提出的Simple Siamese(SimSiam)网络不仅可以没有negative sample pairs;没有arge batch;甚至没有momentum encoders就学到有意义的特征表达。

主要是强调stop-grad的概念(非对称结构防止模型坍塌),结构如下: 在这里插入图片描述 前面的部分基本相同,输入两个随机变换的x1和x2,通过相同的孪生网络提取特征并变换到高维空间,然后可以看到左边的分支有个projection head h得到p1,之后再与右边得到的z2,两者的结果进行匹配使cosine最小化: D ( p 1 , z 2 ) = − p 1 ∣ ∣ p 1 ∣ ∣ 2 ⋅ z 2 ∣ ∣ z 2 ∣ ∣ 2 D(p_1,z_2)=-\frac{p_1}{||p_1||_2} \cdot \frac{z_2}{||z_2||_2} D(p1​,z2​)=−∣∣p1​∣∣2​p1​​⋅∣∣z2​∣∣2​z2​​ L = 1 2 D ( p 1 , z 2 ) + 1 2 D ( p 2 , z 1 ) L=\frac{1}{2} D(p_1,z_2)+\frac{1}{2} D(p_2,z_1) L=21​D(p1​,z2​)+21​D(p2​,z1​) 而重点的Stop-gradient,意思是在loss的第一项的时候,x2不会从z2接收梯度信息;同时在计算第二项,则会从p2接收梯度信息,即loss变为: L = 1 2 D ( p 1 , s t o p g r a d ( z 2 ) ) + 1 2 D ( p 2 , s t o p g r a d ( z 1 ) ) L=\frac{1}{2} D(p_1,stopgrad(z_2))+\frac{1}{2} D(p_2,stopgrad(z_1)) L=21​D(p1​,stopgrad(z2​))+21​D(p2​,stopgrad(z1​))

# Algorithm1 SimSiam Pseudocode, Pytorch-like # f: backbone + projection mlp。f是backbone+projection head部分组成 # h: prediction mlp for x in loader: # load a minibatch x with n samples x1, x2 = aug(x), aug(x) # random augmentation,随机增强后的x1和x2 #分别做两次投影操作 z1, z2 = f(x1), f(x2) # projections, n-by-d p1, p2 = h(z1), h(z2) # predictions, n-by-d #计算不对称的两个D得到loss L 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,在这里使用detach做stopgrad的操作 p = normalize(p, dim=1) # l2-normalize z = normalize(z, dim=1) # l2-normalize return -(p*z).sum(dim=1).mean()

其实stopgrad的本质就是一个交替方案(固定一个,求解另一个)的近似求解。

paper: https://arxiv.org/abs/2011.10566

最后再看个对比方便分清楚: 在这里插入图片描述 (这篇文章没有整理的SwAV的主要贡献是用对比聚类的思想,即用snkhorn-knopp算法计算哪个样本属于哪个prototypes作为伪标签,同类为正不同类为负的隐形负样本思路)

Simple Contrastive Learning of Sentence Embeddings(SimCSE) 简洁而优雅,只用dropout替换了传统的数据增强方法,即将同一个输入dropout两次作为对比学习的正例,效果就足够好了!而具体的做法更为简单,因为比如BERT(文章做sentence的)内部每次dropout都随机会生成一个不同的dropout mask,所以只需要将同一个句子喂给模型两次,得到的两个向量就是应用两次不同dropout mask的结果了。 具体可分为两种形式:

Unsupervised SimCSE:正例是用预训练语言模型编码两次得到的两个向量,负例使用in-batch negatives的方式,即随机采样一个batch中另一个输入作为的负例。Supervised SimCSE:直接引入了NLI任务来监督,即如果两个句子存在蕴含关系为正例对,矛盾关系则是负例对。

paper:https://arxiv.org/pdf/2104.08821.pdf code:https://github.com/princeton-nlp/SimCSE

博主的个人总结,欢迎讨论:

对比学习目前更偏实验科学,如何做理论分析(如BYOL这么简单的模型为什么不坍塌?)如何进一步利用负样本,如何进一步强增广伪标签的设计继续设计loss应用到下游

下一篇博文整理了一些对比学习在其他具体领域的应用,传送门:

对比学习的应用(CLCaption,C-SWM,CMC),主要是InfoNCE相关的应用模型。对比学习的应用(LCGNN,GraphCL,XMC-GAN),主要是MoCo和SimCLR的应用模型。对比学习用于推荐系统问题(SSL,S^3-Rec,SGL,DHCN),用于推荐系统的应用。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3