1. MoCo v1
论文名称: Momentum Contrast for Unsupervised Visual Representation Learning
开源地址:https://github.com/facebookresearch/moco
 大佬详细解读:https://zhuanlan.zhihu.com/p/382763210
motivation
原始的端到端自监督方法:
 给定样本 
     
      
       
        
        
          x 
         
        
          q 
         
        
       
      
        x_q 
       
      
    xq,数据增强得到正样本 
     
      
       
        
        
          x 
         
        
          k 
         
        
       
      
        x_k 
       
      
    xk,batch内的其余样本作为负样本
原始样本 x q x_q xq输入到Encoder f q f_q fq中,正样本和负样本均输入到Encoder f k f_k fk中,通过Contrastive loss来更新2个Encoder f q f_q fq和 f k f_k fk的参数
Contrastive loss一般为InfoNCE:
 
毫无疑问,batch size 越大效果越好,但是受显存影响(2个encoder的全量数据都用于更新两个encoder的参数),batchsize不能设置过大,如何获得更多的负样本?
MoCo v1之前的做法:
 正样本的生成方式不变(数据增强),采用一个较大的memory bank 用来存储负样本,每次训练从中采样一批负样本出来 
     
      
       
        
        
          k 
         
         
         
           s 
          
         
           a 
          
         
           m 
          
         
           p 
          
         
           l 
          
         
           e 
          
         
        
       
      
        k_{sample} 
       
      
    ksample,loss只更新Encoder 
     
      
       
        
        
          f 
         
        
          q 
         
        
       
      
        f_q 
       
      
    fq 的参数,和几个采样的 
     
      
       
        
        
          k 
         
         
         
           s 
          
         
           a 
          
         
           m 
          
         
           p 
          
         
           l 
          
         
           e 
          
         
        
       
      
        k_{sample} 
       
      
    ksample值 。因为这时候没有了 Encoder 
     
      
       
        
        
          f 
         
        
          k 
         
        
       
      
        f_k 
       
      
    fk的反向传播,所以支持memory bank容量很大。
上述方法存在的弊端:
 Encoder 
     
      
       
        
        
          f 
         
        
          q 
         
        
       
      
        f_q 
       
      
    fq每个step都会更新,但是某一个 
     
      
       
        
        
          k 
         
         
         
           s 
          
         
           a 
          
         
           m 
          
         
           p 
          
         
           l 
          
         
           e 
          
         
        
          i 
         
        
       
      
        k_{sample}^i 
       
      
    ksamplei可能很多个step才被采样到更新一次,而且一个epoch只会更新一次。这样最新的 query 采样得到的 key 可能是好多个step之前的编码器编码得到的 key,因此丧失了一致性。
 
MoCo的解决方法
momentum (移动平均更新模型权重) 与queue (字典)
 
假设 Batch size 的大小是 N N N ,现在有个队列 Queue,这个队列的大小是 K ( K > N ) K(K>N) K(K>N),为了方便更新, K K K一般是 N N N的整数倍(代码里面 K = 65536 K=65536 K=65536)。
如上图所示,有俩网络,一个是 Encoder ,另一个是Momentum Encoder 。这两个模型的网络结构是一样的,初始参数也是一样的 (但是训练开始后两者参数将不再一样了)。 f q f_q fq与 f m k f_{mk} fmk是将输入信息映射到特征空间的网络。
样本 
     
      
       
       
         x 
        
       
      
        x 
       
      
    x经过两种数据增强分别得到样本 
     
      
       
        
        
          x 
         
        
          q 
         
        
       
      
        x_q 
       
      
    xq和 
     
      
       
        
        
          x 
         
        
          k 
         
        
       
      
        x_k 
       
      
    xk, 
     
      
       
        
        
          x 
         
        
          q 
         
        
       
      
        x_q 
       
      
    xq经过Encoder 
     
      
       
        
        
          f 
         
        
          q 
         
        
       
      
        f_q 
       
      
    fq得到特征 
     
      
       
       
         q 
        
       
      
        q 
       
      
    q(维度 
     
      
       
       
         N 
        
       
         × 
        
       
         C 
        
       
      
        N×C 
       
      
    N×C), 
     
      
       
        
        
          x 
         
        
          k 
         
        
       
      
        x_k 
       
      
    xk经过Encoder 
     
      
       
        
        
          f 
         
         
         
           m 
          
         
           k 
          
         
        
       
      
        f_{mk} 
       
      
    fmk得到特征 
     
      
       
       
         k 
        
       
      
        k 
       
      
    k(维度 
     
      
       
       
         N 
        
       
         × 
        
       
         C 
        
       
      
        N×C 
       
      
    N×C), 
     
      
       
       
         q 
        
       
      
        q 
       
      
    q和 
     
      
       
       
         k 
        
       
      
        k 
       
      
    k为两个正样本特征
 其中k=k.detach(),不使用梯度更新 
     
      
       
        
        
          f 
         
         
         
           m 
          
         
           k 
          
         
        
       
      
        f_{mk} 
       
      
    fmk的参数
 
 moco伪代码
f_k.params = f_q.params # 初始化
for x in loader: # 输入一个图像序列x,包含N张图,没有标签
    x_q = aug(x) # 用于查询的图(数据增强得到)
    x_k = aug(x) # 模板图(数据增强得到),自监督就体现在这里,只有图x和x的数据增强才被归为一类
    q = f_q.forward(x_q) # 提取查询特征,输出NxC
    k = f_k.forward(x_k) # 提取模板特征,输出NxC
    # 不使用梯度更新f_k的参数,这是因为文章假设用于提取模板的表示应该是稳定的,不应立即更新
    k = k.detach() 
    # 这里bmm是分批矩阵乘法
    l_pos = bmm(q.view(N,1,C), k.view(N,C,1)) # 输出Nx1,也就是自己与自己的增强图的特征的匹配度
    l_neg = mm(q.view(N,C), queue.view(C,K)) # 输出Nxk,自己与上一批次所有图的匹配度(全不匹配)
    logits = cat([l_pos, l_neg], dim=1) # 输出Nx(1+k)
    labels = zeros(N)   # 正样本全在第0位
    # NCE损失函数,就是为了保证自己与自己衍生的匹配度输出越大越好,否则越小越好
    loss = CrossEntropyLoss(logits/t, labels) 
    loss.backward()
    update(f_q.params) # f_q使用梯度立即更新
    # 由于假设模板特征的表示方法是稳定的,因此它更新得更慢,这里使用动量法更新,相当于做了个滤波。
    f_k.params = m*f_k.params+(1-m)*f_q.params 
    enqueue(queue, k) # 为了生成反例,所以引入了队列
    dequeue(queue)
下图展示的是logits某一行的信息,这里的 K=2。
 
训练参数:
- 优化器:SGD,weight decay: 0.0001,momentum: 0.9。
- Batch size: 256
- 初始学习率: 0.03,200 epochs,在第120和第160 epochs时分别乘以0.1,结束时是0.0003。
附上一些思考:
🌱 Momentum Encoder输入的是正样本 
     
      
       
        
        
          x 
         
        
          k 
         
        
       
      
        x_k 
       
      
    xk和负样本queue中的全量样本(区别于memory bank 采样输入负样本)
 🌱 负样本队列queue的更新条件是什么?
 队列存满会把最旧的样本batch替换成最新的batch,队列长度K不是样本总数量,而是远远小于总数量的(65535 vs 几百万),所以queue内存在和query正样本的概率比较小
 🌱 数据增强的方式:
- Randomly resized image + random color jittering
- Random horizontal flip
- Random grayscale conversion
此外,作者还把 BN 替换成了 Shuffling BN,因为 BN 会欺骗 pretext task,轻易找到一种使得 loss 下降很快的方法。
 
🌱 对比端到端自监督的方法,MoCo的负样本数量为 
     
      
       
       
         K 
        
       
      
        K 
       
      
    K(queue长度),端到端负样本数量为 
     
      
       
       
         b 
        
       
         a 
        
       
         t 
        
       
         c 
        
       
         h 
        
       
         s 
        
       
         i 
        
       
         z 
        
       
         e 
        
       
         − 
        
       
         1 
        
       
      
        batchsize - 1 
       
      
    batchsize−1
 🌱 为什么momentum encoder参数更新m=0时会训练失败而端到端自监督训练时却没有失败?
 对于动量参数更新m的消融实验:
 
 m=0说明两个encoder的参数完全一致,而端到端的训练,是两个网络均使用梯度更新,网络参数不一定一致,所以训练不会失败
🌱 源码中一些比较巧妙的点:
 1) queue实际存的是负样本的embbeding,为了方便计算loss 所以用的是tensor形式,负样本进出队列实际使用一个索引 ptr 显示的样本替换位置
 
 2)对于 
     
      
       
        
        
          x 
         
        
          k 
         
        
       
      
        x_k 
       
      
    xk使用了shuffle 与unshuffle
 



















