欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 资讯 > 【AI模型学习】Moco(下)——巧妙的队列设计

【AI模型学习】Moco(下)——巧妙的队列设计

2025/4/19 12:19:47 来源:https://blog.csdn.net/wwl412095144/article/details/147284167  浏览:    关键词:【AI模型学习】Moco(下)——巧妙的队列设计

文章目录

    • 三、模型
      • 3.1 架构
      • 3.2 双分支编码结构(Dual Encoders)
      • 3.3 动量更新的 Key Encoder
      • 3.4 字典队列(Dynamic Dictionary Queue)
      • 3.5 对比损失(InfoNCE Loss)
    • 四、技术分析
      • 4.1 动量更新
      • 4.2 双分支编码
      • 4.3 队列结构
    • 五、实验
      • 5.1 主实验
      • 5.2 消融实验

这是上一篇:【AI模型学习】Moco(上)——自监督学习和对比学习

三、模型

3.1 架构

感觉看懂代码就直接看懂Moco的流程了

for x in loader:  # load a minibatch x with N samplesx_q, x_k = aug(x), aug(x)  # apply two data augmentationq = encoder_q(x_q)  # queries: NxCwith torch.no_grad():k = encoder_k(x_k)  # keys: NxC# normalizeq = normalize(q, dim=1)k = normalize(k, dim=1)# compute logits# Einstein sum is more intuitive# positive logits: Nx1l_pos = einsum('nc,nc->n', [q, k]).unsqueeze(-1)# negative logits: NxKl_neg = einsum('nc,ck->nk', [q, queue.clone().detach()])# logits: Nx(1+K)logits = cat([l_pos, l_neg], dim=1)# apply temperaturelogits /= T# labels: positive key indicatorslabels = zeros(N, dtype=torch.long)loss = CrossEntropyLoss(logits, labels)# SGD update: query encoderloss.backward()update(encoder_q)# momentum update: key encoderupdate_momentum(encoder_k, encoder_q)# dequeue and enqueuedequeue_and_enqueue(queue, k)

一行一行详解(含 shape)

for x in loader:

  • 从数据加载器中读入一个 mini-batch 的图像。
  • x.shape = (N, 3, H, W)
    • e.g. (256, 3, 224, 224)
  • N 是 batch size,H×W 是图像分辨率(224×224)

x_q, x_k = aug(x), aug(x)

  • 对每一张图像做两次随机数据增强(crop、flip、color jitter…)

  • 生成两个视图(view),分别作为 query 和 key。

    x_q.shape = (N, 3, H, W)
    x_k.shape = (N, 3, H, W)
    

q = encoder_q(x_q)

  • 主编码器处理 query 图像

  • 输出:query 表征向量

    q.shape = (N, C)    # C 是输出特征维度,比如 128
    

with torch.no_grad(): k = encoder_k(x_k)

  • 动量编码器处理 key 图像

  • no_grad():这部分不参与反向传播

  • 输出:正样本 key 向量

    k.shape = (N, C)   # 同样是 128 维
    

q = normalize(q, dim=1)
k = normalize(k, dim=1)

  • 所有向量都做 L2 归一化,让它们分布在单位球上

  • 相当于把相似度计算变成 cosine similarity

    ||q[i]|| = ||k[i]|| = 1
    

l_pos = einsum('nc,nc->n', [q, k]).unsqueeze(-1)

  • 计算每对 (q[i], k[i]) 的正样本点积(余弦相似度)

  • einsum('nc,nc->n') 表示:

    • 对每一对向量 q[i]k[i] 做内积 → 得到长度为 N 的向量
  • unsqueeze(-1) → shape 变成 (N, 1)

    l_pos.shape = (N, 1)
    

l_neg = einsum('nc,ck->nk', [q, queue.clone().detach()])

  • queue 是 shape 为 (K, C) 的字典向量矩阵,存着旧的负样本 key(K = 65536)

  • 每个 query q[i] 要和所有 queue 中的负样本 k⁻[j] 做点积

    l_neg.shape = (N, K)
    
  • einsum('nc,ck->nk') 相当于 q @ queue.T

logits = cat([l_pos, l_neg], dim=1)

  • 把正样本 logit 和所有负样本 logit 拼在一起

    logits.shape = (N, 1 + K)
    

    每行代表一个样本和:

    • 第 0 列:正样本相似度
    • 第 1~K 列:负样本相似度

logits /= T

  • 所有 logits 除以温度参数 T(比如 0.07)
  • 控制 softmax 的 sharpness,温度越低 → 分布越尖锐

labels = zeros(N, dtype=torch.long)

  • 因为正样本都在第 0 位 → 所以标签都是 0
    labels.shape = (N,)
    labels = [0, 0, 0, ..., 0]
    

loss = CrossEntropyLoss(logits, labels)

  • 计算对比损失:将 logits 看作 (N, 1+K) 的分类输出
  • 每个样本目标是选出第 0 类(正样本)

loss.backward()
update(encoder_q)

  • 正常反向传播 + 优化 query encoder 参数
  • encoder_k 不参与反向传播!

update_momentum(encoder_k, encoder_q)

  • 用如下方式更新 encoder_k 的参数(动量更新):

    θ_k ← m * θ_k + (1 - m) * θ_q
    
  • m 是 momentum 值(通常取 0.999)

  • 保证 encoder_k 的输出更平滑、稳定

dequeue_and_enqueue(queue, k)

  • 把当前 batch 的 k 插入 queue 尾部

  • 从头部移除等量最旧的负样本

  • queue 是一个大小固定的 FIFO 队列

    新 queue.shape = (K, C)
    

每一轮训练 MoCo 做了什么?

  1. 从图像生成两个视图 → 编码为 qk⁺
  2. 用当前 queue 中的所有历史 key 作为负样本
  3. 计算对比损失 InfoNCE
  4. 更新 encoder_q 参数
  5. 用动量更新 encoder_k
  6. 把新生成的 k⁺ 加入 queue,移除最旧的 key

3.2 双分支编码结构(Dual Encoders)

功能目的:
将一张图像生成两个不同增强版本,分别输入两个网络编码器,用于构造对比对。

对比学习需要一对“相似”样本作为正样本,MoCo 采用两次数据增强来构造这一对。与 SimCLR 相同,但 MoCo 将两条路径分别送入不同 encoder,以便使用动量更新机制。

结构细节:

原图像 x ∈ [B, H, W, C]→ Data Augmentation₁ → x_q ∈ [B, H, W, C]→ Data Augmentation₂ → x_k ∈ [B, H, W, C]

分别送入两个 encoder:

q = f_q(x_q) ∈ [B, D]      ← query encoder(主干)
k = f_k(x_k) ∈ [B, D]      ← key encoder(动量更新)

两个 encoder 初始结构完全相同(如 ResNet-50),但参数更新方式不同。


3.3 动量更新的 Key Encoder

保持 key encoder 的稳定性,防止因梯度更新造成的特征漂移,从而保障 queue 中的 key 表征在跨 batch 时的一致性。

  • 如果 query 和 key 用同一个 encoder(如 SimCLR),那 key 会随训练波动剧烈;
  • 如果是 memory bank(如 InstDisc),则 key 会滞后于 encoder 的当前状态;
  • MoCo 折中:key encoder 不反向传播,只做动量追踪

θ k ← m ⋅ θ k + ( 1 − m ) ⋅ θ q \theta_k \leftarrow m \cdot \theta_k + (1 - m) \cdot \theta_q θkmθk+(1m)θq

  • θ q \theta_q θq:query encoder 的参数;
  • θ k \theta_k θk:key encoder 的参数;
  • m m m:动量系数(默认 0.999);
  • 更新在每个 step 后进行;
  • 不占用额外显存或梯度。

3.4 字典队列(Dynamic Dictionary Queue)

构建一个可跨 batch、随训练动态更新的负样本池。实现小 batch 训练,大字典效果。

  • SimCLR 的负样本来自当前 batch,batch 小 → 负样本少;
  • InstDisc 用 memory bank,特征滞后;
  • MoCo 提出使用 队列(queue)结构

“先进先出”维护 key 表征,使得负样本库既稳定,又动态更新。

数据流结构:

Queue ∈ [K, D]    ← 保存历史 k 的表示(如 65536 个)
每轮训练:- 当前 batch 的 k⁺ ∈ [B, D] 入队;- 最旧的 B 个样本出队。
  • 与 encoder 解耦,不直接依赖 batch;
  • 队列中的向量来自动量 encoder,具有一致性。

3.5 对比损失(InfoNCE Loss)

利用正负样本对,通过 softmax 分类方式进行判别性训练,让 query 特征学到语义空间。

  • 标准的对比损失使用:
    • 一个正样本( q q q, k + k^+ k+
    • 多个负样本( k − k^- k);
  • 将“是否为同一个图像增强”看成一个二分类任务(在一堆中找唯一正样本)。

数学定义:

L q = − log ⁡ exp ⁡ ( q ⋅ k + / τ ) exp ⁡ ( q ⋅ k + / τ ) + ∑ k − exp ⁡ ( q ⋅ k − / τ ) \mathcal{L}_q = -\log \frac{\exp(q \cdot k^+ / \tau)}{\exp(q \cdot k^+ / \tau) + \sum_{k^-} \exp(q \cdot k^- / \tau)} Lq=logexp(qk+/τ)+kexp(qk/τ)exp(qk+/τ)

  • q q q:query 表征;
  • k + k^+ k+:对应 key;
  • { k − } \{k^-\} {k}:从 queue 中取出;
  • τ \tau τ:温度参数,调控分布陡峭度;
  • 所有向量做 L2 normalize。

最终结构图示意(模块级):

          ┌────────────┐│  Image x   │└────┬───────┘Aug1   │   Aug2│      ▼       ▼┌──────┐        ┌──────┐│ x_q  │        │ x_k  │└──┬───┘        └──┬───┘▼                ▼┌───────┐        ┌────────┐│ f_q   │        │  f_k   │ ← momentum updated└──┬────┘        └──┬─────┘▼                 ▼q ∈ [B,D]        k⁺ ∈ [B,D]│                 │└───┐        ┌────┘▼        ▼InfoNCE Loss   ◄──── Queue: k⁻ ∈ [K,D]↑└──── enqueue(k⁺), dequeue(oldest)

四、技术分析

4.1 动量更新

Memory Bank 的根本问题:向量更新不一致

在早期的对比学习方法中(如 InstDisc),Memory Bank 是一种常见策略:

  • 为每个训练样本维护一个特征向量;
  • 每轮训练后,用当前 encoder 输出更新这条向量;
  • 用于为下一个 batch 构建负样本集合。

表面看起来:

  • 每个样本都有一个最新表示;
  • 不依赖 batch 同步,资源效率高;
  • 易于实现大规模负样本对比。

但真正的问题在于 —— 特征表示更新是“异步”的

每一轮训练中:

  • 当前 batch 中的样本,其 memory 中的表示会被立即更新;
  • 而其他所有样本的向量保持不变(也就是说,是由旧 encoder 提取的);
  • 当 encoder 发生剧烈更新时,这些“老旧的表示”可能与当前 encoder 的语义空间不一致

于是,字典中的特征不再处于同一“表征空间”

这很严重

对比学习的核心目标是:

在一个统一的特征空间中,拉近正样本、推远负样本。

但如果:

  • 正样本是由最新 encoder 提取的;
  • 而负样本来自几轮之前的 encoder;
  • 那么我们实际上是在对齐两个不同时期、不同语义空间的向量

这会导致:

  • 训练目标不准确
  • 对比学习失效或训练极不稳定
  • 特别在 early training 阶段,encoder 学习变化剧烈时,影响尤为显著。

MoCo 的解决方案:

MoCo 使用 动量更新 encoder 取代了 Memory Bank 中的“样本表征”:

  • 所有 key 表征由 同一个 encoder(key encoder)生成;
  • 且该 encoder 是通过滑动平均方式更新(即动量);
  • 所有的 key 来自 encoder 的同一“表征空间”或其缓慢演化版本;
  • 因此可以保证:
    • 语义空间连续;
    • 不会有“key 表征落后 N 轮”的问题。
问题维度Memory BankMoCo Queue
表征来源不同轮次 encoder同一动量 encoder
特征表示一致性不一致连贯、稳定
表征更新方式按样本、非同步更新batch 更新 queue,encoder 一致
对比空间稳定性不同特征“时间错位”所有负样本在同一 encoder 空间中
导致的问题训练不稳定,损失函数扭曲可稳定训练,支持小 batch

4.2 双分支编码

MoCo 将对比学习中的两张图像增强( x q x_q xq, x k x_k xk)分别输入两个结构相同但更新策略不同的 encoder

编码器名称参数更新方式用途
f_q(x_q)Query Encoder正常反向传播(BP)提取 query 表征 q q q
f_k(x_k)Key Encoder动量更新(Momentum)提取 key 表征 k k k

这两个 encoder 是两个相互协作、功能分离的网络模块。

为什么要两个 encoder?
核心动机:解决 End-to-End 对比学习的不稳定性

对比:End-to-End 单 encoder(如 SimCLR)

特点描述
结构所有图像(正/负)都输入同一个 encoder
正负样本构造来自 batch 的 2N 个图像,两两组合构造对比对
特征一致性encoder 每个 step 都在变动,特征可能不稳定
表征空间每个 batch 的表示都在“漂移”
训练稳定性依赖于极大的 batch size(保证负样本质量)

当 encoder 参数不断更新时,正样本和负样本的语义空间都在变,softmax 计算不准确,训练容易崩。

双 encoder

  1. 保持 key 表征稳定一致
  2. 支持队列机制(Queue-based Dictionary)
  3. 解耦表示学习与字典维护
  4. 小 batch 下性能依然强劲

MoCo 双分支结构 vs SimCLR 单分支结构:对比表

方面SimCLR(单 encoder)MoCo(双 encoder)
编码器结构同一个 encoderQuery encoder + Key encoder
更新方式每轮梯度下降同步更新Query 正常更新,Key 动量追踪
特征空间一致性每轮变化剧烈表征平稳过渡
负样本构造当前 batch 内历史 queue + 当前 batch
训练稳定性依赖大 batch小 batch 可用,收敛稳定
表征解耦编码器要兼顾 query 和 keyf_q 负责学习,f_k 负责对比

双 encoder = 表征稳定性 + 训练灵活性 + 模块解耦 + 可扩展性

它是 MoCo 架构的第一性原理层级的设计突破,解决了 SimCLR 面临的核心结构性瓶颈。


4.3 队列结构

MoCo 中的 queue(字典)是用来存放历史 key 编码器生成的特征向量( k k k,用于构建负样本池。

每个 query 的正样本来自当前 batch
每个 query 的负样本来自字典队列(由历史 batch 累积构成)

MoCo 中,队列在每个训练 step 中都会更新一次:

  1. 用 key encoder 计算当前 batch 的 key 表征 k + k^+ k+

    k^+ ∈ [B, D]
    
  2. 将这 B 个 key 入队(enqueue):

    queue.append(k⁺)
    
  3. 将最旧的 B 个 key 出队(dequeue):

    queue.pop(B)
    

保证队列长度始终为固定的 K K K,控制内存与负样本数量平衡。

MoCo 队列机制的本质意义

MoCo 的字典不是“为每个样本建一个空间”,而是“持续构造一个可控、高质量、大规模的负样本集合”。

它结合了:

  • Memory Bank 的“跨 batch 表征能力”
  • SimCLR 的“新鲜样本”机制
  • Momentum Encoder 的“语义一致性”

是 MoCo 成为小 batch 对比学习之王的基础。


五、实验

5.1 主实验

任务实验设置方法比较评价指标结果表现论文位置
ImageNet 线性评估MoCo 预训练 + Linear probe比较有监督、MoCo、InstDisc、SimCLRTop-1 Acc (%)MoCo ResNet-50 达到 60.6%(比 SimCLR 好)Sec. 4.1 / Table 1
小 batch 能力batch size = 256(SimCLR 无法运行)MoCo vs SimCLRTop-1 Acc (%)MoCo 不依赖大 batch,性能优异Sec. 4.1
Downstream 迁移(目标检测)用 MoCo 表征初始化 Faster-RCNN与有监督预训练比较box AP / mask APMoCo 略优于有监督(如 38.5 vs 38.2)Sec. 4.3 / Table 3
小数据场景(VOC / COCO)微调 MoCo 表征在小数据集与有监督比较mAPMoCo 表征迁移能力更强Sec. 4.3

5.2 消融实验

实验因素设置对比Top-1 Accuracy / 结果趋势实验结论论文位置 / 图表
是否使用动量 encoder使用动量更新 vs 不使用(共享 encoder 参数)不用动量:性能严重退化动量更新极大提升表征一致性,是 MoCo 成功的核心结构Fig. 5(a), Sec 4.2
动量系数 m m m 的大小 m = 0.5 m = 0.5 m=0.5, 0.9 0.9 0.9, 0.99 0.99 0.99, 0.999 0.999 0.999(默认) m = 0.999 m = 0.999 m=0.999 时性能最优较大的动量使得 encoder 更新更平滑,特征更稳定Fig. 5(b)
Queue 队列长度4K, 16K, 65K(默认)越大性能越好,65K 最优更大的 queue 能提供更多负样本,有效增强判别能力Fig. 5©
是否使用 queue使用 queue vs 只用当前 batch不使用 queue 性能急剧下降queue 是实现跨 batch 对比的核心机制Fig. 5(a), 隐含讨论
Batch size 大小变化256(MoCo 默认) vs SimCLR 4096MoCo 在小 batch 下依然表现良好MoCo 不依赖大 batch,工程更友好Sec. 4.1
Linear probe vs FinetuneDownstream 任务中对 MoCo 表征进行线性分类或微调两者都表现优异MoCo 表征迁移性强,支持各种下游任务Sec. 4.3 / Table 3
预训练 vs 随机初始化用 MoCo 表征初始化目标检测模型 vs 随机权重明显优于随机初始化MoCo 表征泛化性强,具备替代有监督 pretrain 的潜力Sec. 4.3

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词