欢迎光临散文网 会员登陆 & 注册

MoCo----伪代码

2021-12-29 15:16 作者:如果我是泡橘子  | 我要投稿


MoCo的伪代码
  • 首先定义了一些参数fq、fk,它们分别是query和key的编码器

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

  • m是动量

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

整个模型的前向过程:

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

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

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

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

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

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

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

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

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

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

  • 下一步就是计算logit,也就是之前公式1中算InfoNCE loss的时候的分子(q * k+),这样就得到了正样本的logit,它的特征维度就变成了n * 1,256,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的第二个贡献(动量更新),因为不想让fk变得太快,所以fk的参数大部分都是从上一个时刻的参数直接搬过来的,只有非常少的一部分是从当前更新过的fq中拿过来的,这样就保证了key network是缓慢更新的

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

读完伪代码,应该是对MoCo有了一个更全面的了解,建议查看MoCo的官方代码,写的极其出色,非常简洁明了,而且基本上就跟伪代码一模一样





----end----

MoCo----伪代码的评论 (共 条)

分享到微博请遵守国家法律