【点云处理之论文狂读扩展版1】——Momentum Contrast for Unsupervised Visual Representation Learning

摘要

  1. 将对比学习看作是字典查找,构建了一个字典,该字典包含了一个队列(a queue)一个移动-平均编码器(a moving-averaged encoder),尽量保证了字典中的特征又大又具有一致性。
  2. MoCo在一些任务上的性能已经超过了监督任务。
  3. 代码详见https://github.com/facebookresearch/moco

1. 引言

  1. 无监督在NLP领域中的应用很成功,在CV中却一直不温不火,这是因为信号空间(signal space)不同。NLP中的信号是离散的,变成tokenized后很容易构建字典,就变相地变成了有监督学习。而CV中的信号是离散的、高维度的
  2. 大部分的对比学习都构建了动态字典——基于已经了解对比学习的基础上理解“key”和“query”
  3. 字典在训练的时候一定要大(采集到的样本更好,会提供更好的近似),并且还要具有一致性(使用相同或相似的encoder进行编码)
  4. MoCo = Queue + Momentum-based moving average encoder来实现上述字典
  5. 选择==个体判别(instance discrimination)==任务来完成无监督学习——代理任务(pretext task)
  6. 相比较于其他模型,MoCo的应用范围可能会更广泛,更有可能取代监督的地位。

2. 相关工作

Loss functions

  1. 生成式损失函数:预测改变,目标固定
  2. 判别式损失函数:预测改变,目标固定
  3. 对比式损失函数:预测改变,目标改变
  4. 对抗式损失函数:预测改变,目标改变

Pretext tasks

  1. denoising auto-encoders
  2. context auto-encoders
  3. cross-channel auto-encoders

3. 方法

3.1 Contrastive Learning as Dictionary Look-up

字典查询任务

查询特征为 q q q和字典的一组关键特征为 { k 0 , k 1 , k 2 , … } \left\{k_{0}, k_{1}, k_{2}, \ldots\right\} {k0,k1,k2,}

假设字典中有一个关键特征 k + k_{+} k+ q q q相匹配,那么对比损失应当在 q q q k + k_{+} k+相似 以及 q q q和其它关键特征不相似的情况下值最小,用IfoNCE(Noise Contrastive Estimation)表示为:

L q = − log ⁡ exp ⁡ ( q ⋅ k + / τ ) ∑ i = 0 K exp ⁡ ( q ⋅ k i / τ ) \mathcal{L}_{q}=-\log \frac{\exp \left(q \cdot k_{+} / \tau\right)}{\sum_{i=0}^{K} \exp \left(q \cdot k_{i} / \tau\right)} Lq=logi=0Kexp(qki/τ)exp(qk+/τ)

乍一看,这个式子还蛮熟悉的,和softmax有点像。其中 q q q表示query的特征, k i k_i ki表示dictionary中key的特征, k + k_+ k+表示 q q q在dictionary中的一个正样本(假设有且只有一个), τ \tau τ 是一个超参数,用于调整上述loss的。细看的话,其实这和交叉熵本质就是一样的,区别在于,通过query与各个key点乘运算,获得他们的相似性(与正常的分类模型的FC raw outputi对应),但是点乘后的结果的量级不适合softmax运算,通过一个 τ \tau τ系数控制。所以这个loss的本质就是希望网络对于相同图片采样出来的输入,得到的feature要尽可能的相似。

该损失函数就是 ( K + 1 ) (K+1) (K+1)分类,用于将 q q q 分到 k + k_{+} k+类中。

  1. 查询特征的表示方法为 q = f q ( x q ) q=f_{\mathrm{q}}\left(x^{q}\right) q=fq(xq),其中 f q f_{\mathrm{q}} fq 是编码器, x q x^{q} xq是查询样本
  2. 关键特征的表示方法为 k = f k ( x k ) k=f_{\mathrm{k}}\left(x^{k}\right) k=fk(xk),其中 f k f_{\mathrm{k}} fk是编码器, x k x^{k} xk是关键样本

x q x^{q} xq x k x^{k} xk的类型取决于代理任务。 f q f_{\mathrm{q}} fq f k f_{\mathrm{k}} fk可以是相同的,一部分相同,或是完全不同。

3.2 Momentum Contrast

所创建的字典是动态的,这是因为keys是随机采样的,key编码器也是随着训练不断变化。

3.2.1 Dictionary as a queue

将字典看作是一个数据采样的队列,队列的引入可以使得字典的大小和mini-batch 大小剥离开。因此,字典的大小可以作为一个超参数任意设置。

字典中的样本是逐渐被替代的。当前的mini-batch排队进入字典,最老的mini-batch就会被移除。字典表示这所有数据的采样子集,同时计算量又得到了控制。此外,移除最老的mini-batch是有好处的,因为他们的信息是过时的,与最新的mini-batch不一致。

3.2.2 Momentum update

使用队列可以使得字典变得很大,但是通过反向传播来更新key编码器又出现了问题(梯度应当是传播给队列中的所有样本)。最初的想法是将更新后的 f q f_{\mathrm{q}} fq直接复制给 f k f_{\mathrm{k}} fk,但是结果却不好,这是因为太大的变化会使得编码器减少一致性,所以就需要动量更新。

f k f_{\mathrm{k}} fk的参数为 θ k \theta_{\mathrm{k}} θk f q f_{\mathrm{q}} fq的参数为 θ q \theta_{\mathrm{q}} θq,更新 θ k \theta_{\mathrm{k}} θk的方法为:

θ k ← m θ k + ( 1 − m ) θ q \theta_{\mathrm{k}} \leftarrow m \theta_{\mathrm{k}}+(1-m) \theta_{\mathrm{q}} θkmθk+(1m)θq
其中 m ∈ [ 0 , 1 ) m \in[0,1) m[0,1)是动量参数,仅参数 θ q \theta_{\mathrm{q}} θq通过反向传播更新。动量的更新方法使得 θ k \theta_{\mathrm{k}} θk更新更为平滑。实验中,将 m m m设置为0.999的结果都要比0.9要好,进一步说明了缓慢更新 θ k \theta_{\mathrm{k}} θk是重要的。

3.2.3 Relations to previous mechanisms

  1. end-to-end:两个编码器都可以反向传播,字典里的样本一致性很高。但是字典的大小和mini-batch是一样的,在硬件条件受限的情况下,字典就会很小。
  2. memory bank:只有一个quary编码器可以反向传播,字典是线下方式生成的,所以可以做的很大。但是,每个iterate都要从字典中随机抽取特征进行更新,所以在下一个epoch时再随机抽取时,字典里的特征不具有一致性,如果样本量太大,存放字典的硬件也会接受不了。尽管memory bank中也使用了动量更新,但是更新的是表示,不是本文中的参数。

3.3 Pretext Task

本文采用的是个体判别任务:

  1. 假设quary和相匹配的key是正样本,因为他们都是来自于一个图片;其他的key都是负样本。
  2. quary和相匹配的key是通过数据增强transformation得到的。
  3. quaries和keys相继被各自的编码器编码,编码器采用卷积神经网络。

首先定义了一些参数f_q、f_k,它们分别是query和key的编码器

queue这个队列指的是字典,里面一共有k个key,所以它的维度是C*K,C指的是每个特征的维度

m是动量

t是算InfoNCE loss的时候的那个温度

整个模型的前向过程:

首先初始化两个编码器,对于query编码器f_q来说,它是随机初始化的,然后将f_q的参数直接复制给f_k,这样就把f_k也初始化了

接下来从data loader里拿一个batch的数据,这里数据用x表示,它有n个sample(在MoCo的代码中,默认的batch-size就是256,也就是n等于256,所以是非常标准的batch-size,是可以在常用的GPU上进行训练的)

接下来的第一步就是得到一个正样本对,所以从原始的数据x开始,先做一次数据增强,得到一个query的图片,然后再去随机的做一次数据增强,得到一个key的图片,因为这个query和key都是从同一个数据x得到的,它的语义不应该发生太大的变化,所以这个x_q、x_k就成为了一个正样本对

接下来将query的数据通过query的编码器做一次前向,从而得到真正的特征q,q的特征维度是NC,也就是256128

为了便于理解,这里再具体化一些,假如编码器f_q、f_k就是一个Res50的网络,Res50等到最后一层做完global average pooling之后会得到一个2048维的特征

一般如果是在ImageNet数据集上去做有监督训练的时候,会加一个分类头,将这个2048维变成1000维,这样就可以做分类了

这里作者就是将1000换成了128,也就是说从2048维变成了128维

这里为什么使用128?其实是为了跟之前的工作保持一致,也就是之前memory bank的工作(文献61)。memory bank这篇文章中,为了让整个memory bank变得尽可能的小,所以特征的维度选的也相对比较小,只有128

通过编码器f_k得到正样本的那个key的维度也是256*128

因为是pytorch的代码,所以用了detach操作将gradient去掉,这样对于key来说就没有梯度回传了,反正也是要放到队列中去的

下一步就是计算logit,也就是之前公式1中算InfoNCE loss的时候的分子( q × k + q × k_+ q×k+),这样就得到了正样本的logit,它的特征维度就变成了N* 1

如何计算负样本的logit?首先从队列中把负样本拿出来,也就是代码中的queue,接下来就是计算公式1中InfoNCE的分母,也就是对query * ki求和(i是从0到k的),做完了这一步的乘法之后,就得到了负样本的logit,也就是n * k,所以就是256 * 65536(因为MoCo中默认的字典大小是65536)

最后总体的logit(既有正样本又有负样本,所有的logit拼接起来)就变成了256 *65537的一个向量,也就是像公式1中所说的,现在所做的其实就是k + 1路的分类问题

一旦有了正负样本的logit,接下来就是算loss了,这里其实就是用了一个cross entropy loss去实现的,既然是交叉熵loss,肯定就得有一个ground truth(作者在这里巧妙地设计了一个全零的向量作为ground truth,之所以使用全零,是因为按照作者的这种实现方式,所有的正样本永远都是在logit的第一个位置上,也就是位置0,所以对于正样本来说,如果找对了那个key,在分类任务中得到的正确的类别就是类别0,所以巧妙地使用了这种方式创建了一个ground truth,从而计算出了对比学习的loss)

有了loss之后,自然就是先做一次梯度回传,有了梯度之后就可以去更新query的编码器了

接下来就到了MoCo的第二个贡献(动量更新),因为不想让f_k变得太快,所以f_k的参数大部分都是从上一个时刻的参数直接搬过来的,只有非常少的一部分是从当前更新过的f_q中拿过来的,这样就保证了key network是缓慢更新的

最后一步是更新队列,将新算的key放进队列中,然后将最老的key从队列中移出

3.3.1 技术细节

Encoder:ResNet + 最后一层输出为128维,输出先进行L2-norm

τ \tau τ:0.07

Data augmentation:224×224的裁剪,然后进行random color jittering, random horizontal flip, and random grayscale conversion

3.3.1 Shuffling BN

标准Resnet中的BN由于batch内的通信(mean,veriable),造成信息泄露,导致模型找一个看似低的loss欺骗前置任务,难以学习到一种真正好的表示,所以本文采用shuffle BN来解决这个问题。

  1. 多个GPU训练,并在每个GPU的样本上独立执行BN
  2. 对于key encoder ​ f k f_k fk ,在分配到每个GPU之前,shuffle每个mini-batch中的样本顺序(在编码之后会还原)
  3. query encoder​ f q f_q fq中的样本顺序不变

以上做法确保了用于计算query的批统计信息batch stastistic与其positive key来自两个不同的子集。

4. 实验

以后再写…

5. 结论

  1. MoCo在从ImageNet-1M到Instagram-1B的过程中提升的精度很少,可能更好的代理任务会解决这些问题。
  2. Mask auto-encoding作为代理任务,可能性能会更好——属于是跟MAE前后呼应了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值