Post

MoCo in details

MoCo in details

All generated by chatgpt-4o


🎯 问题重述:

假设一个 batch 有 64 张图,MoCo 的流程是为每张图都生成 query 和 key,那怎么一起训练、一起计算 loss呢?


✅ 回答核心:

MoCo 是并行地对每张图执行“对比任务”,然后对所有样本的 loss 做平均,一起反向传播。这是现代深度学习中很常见的“mini-batch training”。


pc

🧩 多图并行工作的流程分解(以 64 张图为例):

Step 1:数据准备(batch)

你有 64 张原始图像,记为: [img_1, img_2, ..., img_64]

对每张图分别做两次不同的数据增强,得到两个 batch:

  • query batch: [q1, q2, ..., q64]
  • key batch: [k1, k2, ..., k64]

注意:q 和 k 是成对的,来自同一原图。


Step 2:特征提取

  • 所有 query 图像(q1~q64)送入 query encoder,并行计算 → 得到 q_feat 向量:[q1_feat, …, q64_feat](比如每个是 128 维向量)
  • 所有 key 图像(k1~k64)送入 key encoder,并行计算 → 得到 k+_feat 向量:[k1_feat, …, k64_feat]

Step 3:队列参与(负样本字典)

此时队列中已经有一大堆旧的 key 特征 [k_old_1, ..., k_old_N](比如 65536 个)

对每一个 query(比如 q1),我们要做以下操作:

  • 它的正样本是 k1_feat
  • 它的负样本是整个队列里的旧 key(+ 当前 batch 的 k2~k64 除了它本身)

对每个 qi_feat,都要做:

1
loss_i = InfoNCE(qi_feat, k+_feat=ki_feat, k−_feats=queue + other k_feat in batch)

Step 4:整体 Loss 和反向传播

最终,将所有 64 个 loss 平均:

1
total_loss = (loss_1 + loss_2 + ... + loss_64) / 64

然后对这个总 loss 进行反向传播,更新 query encoder 的参数


Step 5:更新队列和 key encoder

  • 将当前 batch 的 [k1_feat, ..., k64_feat] 入队
  • 将最旧的 64 个 key 从队列中踢出去(FIFO 机制)
  • 用动量机制更新 key encoder 的参数(不是反向传播)

🔄 图示(想象)

1
2
3
4
5
6
7
8
9
10
11
12
13
For each image i in batch:

           [qi] —(query encoder)—> [qi_feat]
           [ki] —-(key encoder)—-> [ki_feat]
                        ↑
                        |   入队列
                    key_queue
                         |
                         ↓
Compare: qi_feat vs. ki_feat (positive)
         qi_feat vs. queue  (negatives)

=> InfoNCE loss → 平均 → 反向传播

🧠 总结:MoCo 如何处理多个图片?

流程多张图时怎么做
数据增强每张图增强两次,构成成对的 (q, k)
特征提取并行处理所有 query 和 key
正负对比每个 query 和自己的 key 配成正例,和队列中其他 key 构成负例
损失计算每对计算 InfoNCE loss,batch 内所有 loss 求平均
参数更新更新 query encoder,动量更新 key encoder,更新 key 队列

是的,你理解得很准确!我们来具体梳理一下你提到的这两点:


✅ 问题 1:“在整个 key 队列中,k+_feat 是只有一个的吗?”

答案:是的,对于每一个 query 来说,它的 k+_feat(正样本)在整个 key 队列中是唯一的。

更准确地说:

  • 对于某个 query(比如 q1),它的正样本是 k1_feat
  • k1_feat 是刚刚用 key encoder 编出来的,还没有被加入到 key 队列(queue)中
  • 所以此时队列中的所有向量都是负样本(k−_feat
  • k1_feat 是当前 batch 中唯一能和 q1 成为正例的向量,它是从“外部”来的,不属于队列;

所以:

在你对 q1 做 InfoNCE loss 计算的时候,整个 key 队列中没有它的正样本; 你是把 k1_feat 拿出来,单独作为 q1 的正样本来用。


✅ 问题 2:“旧的 k_feat 会被踢出队列吗?”

答案:是的,MoCo 使用的是一个先进先出(FIFO)的“循环队列”机制。

具体机制:

  • 每处理完一个 batch(比如 64 个样本),你会得到 64 个新的 k+_feat 向量;
  • 这 64 个向量被加入队列尾部
  • 同时,队列头部的最老的 64 个向量会被移除(踢出队列);
  • 这样队列的总长度保持固定,比如 65536;

这就是为什么我们说它是一个动态的负样本字典——不断引入新的特征,淘汰最旧的样本。


🔄 图解版(简化口头图)

假设当前队列中有很多旧向量:

1
[ k_old_1, k_old_2, ..., k_old_65536 ] ← 旧 key 特征

当前 batch 处理后,会加入:

1
+ [ k1_feat, k2_feat, ..., k64_feat ] ← 当前正样本(将来会成为别人的负样本)

然后踢出 64 个最老的:

1
→ 移除 [ k_old_1, ..., k_old_64 ]

🔚 总结一下你的两个问题

问题回答
key 队列中是否只有一个 k+_feat是的,每个 query 的正样本不在队列中,单独用;队列中全部是负样本
旧的 k_feat 会被踢出吗?会!每次处理完 batch,就从队列头部踢出等量旧样本

完全正确!你已经接近抓住核心了,我们再深入一点点来彻底讲清楚:


✅ 结论先说:

是的,如果 key encoder 的特征变化太快,就会导致 queue 中的特征(旧的负样本)不再可靠, 而这会严重干扰 query encoder 的学习目标,使得对比学习“失效”或“学不到一致性”。


🔍 为什么会这样?我们一步步讲清楚。


🧠 MoCo 的目标是什么?

让 query encoder 学会输出的特征 q_feat,能靠近正样本 k+_feat,远离负样本(key 队列里的旧特征)。

所以:query encoder 的训练,严重依赖于 key encoder 提供的正负样本特征是否稳定


❌ 如果 key encoder 更新太快,会发生什么?

场景模拟:

你训练第 100 个 batch,存了 64 个 key 特征进队列(k_old_1~k_old_64)

在第 110 个 batch,query encoder 想拿这些旧 key 特征来作为负样本进行对比:

它希望这些负样本特征是 “稳住的”、表达清晰的、统一标准的,这样才能学会把自己的输出 q_feat 拉远它们。

但问题是:

  • 如果 key encoder 在这 10 个 batch 中更新太快,它的表示方式变了;
  • 比如从“看纹理”变成了“看边缘”;
  • 那么:
    • 老的 key 特征是基于旧表示方法生成的;
    • 当前的 query encoder 输出是想靠近新的 key;
    • 这两者之间的相似度就变得不再有一致意义了

于是你训练的 query encoder 就会变得很迷茫:

“你到底要我靠近哪个?远离哪个?你的标准一直变啊!” → Loss 不稳定 → 学不到有效的表征


💡 所以我们需要 key encoder 的特征保持稳定

为了解决这个问题,MoCo 不让 key encoder 每个 batch 都反向传播,而是使用:

动量更新机制

1
θ_k ← m * θ_k + (1 - m) * θ_q   (m ≈ 0.999)

这样就能保证:

  • key encoder 参数变化非常平缓;
  • 它生成的 key 特征在多个 batch 中保持一致性;
  • 队列中的旧样本不会“失效”;
  • query encoder 的训练目标就清晰明确且持续一致

✅ 总结一句话:

如果 key encoder 的特征变化太快,会让 query encoder 学不到稳定的相似性判断目标,导致整个对比学习的训练失败。 使用“动量更新”就是为了解决这个问题,保持字典特征空间稳定,让 query encoder 有可靠的“参照物”去学习。


🧠 背景一:MoCo 是 Memory Bank 机制(被采用)

在 MoCo 中:

  • 使用了一个显式的 memory bank(也叫字典或 queue) 存储大量的旧样本特征;
  • 它的好处是可以利用历史的负样本,数量大,不需要大 batch;
  • 缺点是这个过程是 非 end-to-end 的(队列中的旧特征没有梯度、不能同步更新);
  • 所以引入了“动量机制”来保持稳定性。

🔁 而另一种选择:end-to-end 对比学习

比如 SimCLR、BYOL,它们有这些特点:

  • 不使用 memory bank;
  • 所有正负样本都来自当前 batch;
  • 所有的特征都是参与梯度传播的;
  • 优点是训练目标同步,端到端优化;
  • 缺点是 batch 要够大,才能提供足够多的负样本(否则学不到什么)。

This post is licensed under CC BY 4.0 by the author.