4

CV 领域的自监督

 2 years ago
source link: https://yuanjie-ai.github.io/2021/12/28/SSL-intro/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

CV 领域的自监督

发表于

2021-12-28 更新于 2022-01-03

阅读次数: 2 本文字数: 3.6k 阅读时长 ≈ 3 分钟

还是要认真的学习一会儿,降低多巴胺的分泌。在使用 triplet loss 学表示的时候,出现了模型坍塌的情况,也就是说,模型对任何输入的输出都是一样的,损失恒定的现象。在网上搜了一些解决方案后,需要用户去花精力构造正负样本,我不喜欢这样的东西,所以开始看了自监督的论文,毕竟都是学表示。也发现自监督会在一段时间内成为未来的视觉领域的主流,正好我做的东西和自监督也算相关,做一个论文整理。包括了 MoCo,SimCLR,SimSiam 和 Barlow Twins。

模型崩塌,也就是模型为了偷懒,无论什么图片都会输出同样表示,这样结果 loss 很小,然后却没学到任何东西。

图像领域的自监督主要由两部分组成,对比损失和数据增强,而那四篇论文也是基于这两个东西去做的,无非是如何对比。

首先是 MoCo,简单的看一下模型的结构

TsYsxS.png

对同一个样本做两次数据增强,会得到两个样本 xq 和 xk,创建两个 encoder,左边那个 encoder 接入 xq,右边的 encoder 接入 xk,使得这两个的相似性越高越好,与此同时,期望 xq 与负样本的相似性越低越好。那么如何衡量相似性呢?使用的是 InfoNCE 这个损失函数。

LInfoNCE=−EX[log⁡fk(xt+k,ct)∑xj∈Xfk(xj,ct)]

分子是正样本的相似度,分母是负样本的相似度,这个比值越大,log 就越大,对应的损失函数就越小。MoCo 的改动如下,用点积来衡量 xq 和正负样本的距离。也就是说,使用对比损失来区分图像的高维特征。

(1)Lq=−log⁡exp⁡(q⋅k+/τ)∑i=0Kexp⁡(q⋅ki/τ)

那么与众不同的地方呢?换句话说,负样本从哪里来呢?模型会设计一个队列,队列负责维护将刚进入的样本视为负样本放入队列(最开始没负样本的话,用的是随机数),并弹出之前的负样本,这就需要很大的显存以及代码编写的难度,我不是很喜欢这两点。不信你来看他们官方的程序

此外需要注意的是,作者提出了动量的更新方式,来更新右侧的网络:

(2)θk←mθk+(1−m)θq,m∈[0,1)

也许你会有疑问,都计算好梯度了,更新左侧的网络就没事,更新右侧的网络就有事,莫非在耍流氓?这个梯度给谁不是给,为什么不用梯度更新两个网络?论文上写的是:由于样本很多,右侧网络不容易更新。很多网上的论文解析也是人云亦云,看了代码就知道存储负样本的队列和右侧的网络没半毛钱关系,队列在计算梯度的时候已经 detach 了。所以,论文写的更新困难并不是梯度回传困难,那么为什么不能用梯度更新右侧的网络呢?

先来解释为什么不把左侧网络的参数拷贝给右侧网络的参数。这么做的目的是因为不同 epoch 之间数据分布差异可能很大,encoder 的参数有可能会发生突变,不能将多个 epoch 的数据特征近似成一个静止的大 batch 数据特征,因此就使用了这么一个类似于滑动平均的方法来更新右侧网络。

再来回答为什么不用梯度更新右侧的网络。xk 经过右侧网络后,然后我们就得到了它们相对应的表示。而对于这个表示,它们不光包含了这个 mini-batch 的表示,也包含了一些之前处理过的 mini-batch 中的表示。换句极端的话说,右侧的网络掌握了全部数据的表示,来调控左侧网络该学到什么样的表示。这些表示通过右侧网络后放入队列中,相对于只在本 mini-batch 内做比较的方法而言,moco 能用较小的管理开销来获得更多的负样本。如果只用一个 mini-batch 的梯度更新右侧网络,或者说右侧网络只掌握一个 mini-batch 的表示,那右侧网络直接输出和左侧网络一样的东西不就行了,这样损失很小,但什么也没学到。因此,不能用一个 mini-batch 的梯度去更新右侧网络。

损失函数解析

此外是本文较为迷惑的损失函数,文章写的没啥迷惑,迷惑的是代码。我第一眼过去,直接理解为不管是正样本还是负样本,都和 0 去接近,正样本明明越大越好,应该和 1 去接近,这么写肯定不对呀。

l_pos = torch.einsum('nc,nc->n', [q, k]).unsqueeze(-1)
l_neg = torch.einsum('nc,ck->nk', [q, self.queue.clone().detach()])
logits = torch.cat([l_pos, l_neg], dim=1)
labels = torch.zeros(logits.shape[0], dtype=torch.long)
criterion = nn.CrossEntropyLoss()
loss = criterion(logits, target)

后来仔细抠的代码才理解,torch 的交叉熵损失函数由 logsoftmaxnllloss 组成,前者完成 logsoftmax 的计算,后者 nllloss 取出对应的标签,在 nllloss 的时候,标签为 0 的意思是取出 batch 中的第一列,而这一列正好是正样本,nlloss 期望这列的值越小越好,那么回退到 logsoftmax,就是期望这列的值越大越好,也就是,正样本的相似度很高。并不是像网上那种垃圾博客说的,无监督任务都分成 0 类即可。 这里还是建议理解一下,因为自监督的损失大多是这么设计的。

SimCLR

推荐一个比较好的实现。因为反感 MoCo 那种开显存的操作,毕竟不是所有人都有 facebook 的财力,所以继续去读了其他自监督的论文,较为相似的一篇论文是 SimCLR。还是先来看模型结构图:

TsYr28.png

注意,左右两边的网络都是相同的,也就不存在梯度该传给谁的问题。一个 batch 的样本,经过数据增强得到两个 batch 的样本,这两个 batch 的样本进入网络会得到两个 batch 的表示,期待这两组表示中,同一数据的表示很接近,放大不同数据的差异。损失同样是用的类似 InfoNCE 的损失函数。不过论文中有比较奇怪的点,放一段原文:

We randomly sample a minibatch of N examples, augmented examples derived from the minibatch, resulting in 2N data points. We treat the other 2(N-1) augmented examples within a minibatch as negative examples.The final loss is computed across all positive pairs.

问题来了,一共 2N 个样本,2(N−1) 都视为负样本,哪来的 positive pairs?还是直接看代码吧,其实 MoCo 的论文写的也够晕的。看了代码损失函数的设计后发现,就是自己和自己是正样本,自己和其他的样本的关系是负样本。严格来说,对于一个样本而言,有 2(N-2) 个负样本。损失函数和 MoCo 的保持一致,都是用交叉熵损失函数实现的。推荐去看这个损失函数的实现,代码写的还是比较有意思的。但是计算量会大一些。

SimSiam

MoCo 之后,提出了更简单的网络结构:

TsYD8f.png

提前声明,相似度用的是余弦函数,因为余弦函数相似性越高数值越大,因此损失函数取了负数,最后的损失值也是负的。思路很简单,对同一个图像做两次增强得到俩个正样本,x1 和 x2,x1 经过 encoder 得到表示 z1,z1 经过 predictor 得到 p1,同理得到 p2 和 z2,计算 p1 和 z2 以及 p2 和 z1 的相似度,然后就没了。

简单吗?简单。有坑吗?有。我当时在想,如果 predictor 的权重全部是 1,那么相似度不就会很小了,但仍然是模型坍塌的情况。然后带着疑问去看代码,结果人家直接冻结了 predictor 某一层的权重,是我大意了。后来我以抄袭的形式复现的时候,没有冻结权重,效果比 SimCLR 要好一些。

Barlow Twins

到这篇论文,思路更加简单,东西和 SimCLR 比较类似,损失函数用的是大二学过的协方差矩阵,对角线越大越好,其余位置越小越好。它虽然很简洁,但我感觉他比 SimSiam 更令人喜欢。

(3)L=∑i(1−Cii)2+λ∑i∑j≠iCij2


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK