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

文章目录

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

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

三、模型

3.1 架构

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

python 复制代码
for x in loader:  # load a minibatch x with N samples
    x_q, x_k = aug(x), aug(x)  # apply two data augmentation
    q = encoder_q(x_q)  # queries: NxC
    with torch.no_grad():
        k = encoder_k(x_k)  # keys: NxC

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

    # compute logits
    # Einstein sum is more intuitive
    # positive logits: Nx1
    l_pos = einsum('nc,nc->n', [q, k]).unsqueeze(-1)
    # negative logits: NxK
    l_neg = einsum('nc,ck->nk', [q, queue.clone().detach()])

    # logits: Nx(1+K)
    logits = cat([l_pos, l_neg], dim=1)

    # apply temperature
    logits /= T

    # labels: positive key indicators
    labels = zeros(N, dtype=torch.long)
    loss = CrossEntropyLoss(logits, labels)

    # SGD update: query encoder
    loss.backward()
    update(encoder_q)

    # momentum update: key encoder
    update_momentum(encoder_k, encoder_q)

    # dequeue and enqueue
    dequeue_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。

    text 复制代码
    x_q.shape = (N, 3, H, W)
    x_k.shape = (N, 3, H, W)

q = encoder_q(x_q)

  • 主编码器处理 query 图像

  • 输出:query 表征向量

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

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

  • 动量编码器处理 key 图像

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

  • 输出:正样本 key 向量

    text 复制代码
    k.shape = (N, C)   # 同样是 128 维

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

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

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

    text 复制代码
    ||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)

    text 复制代码
    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] 做点积

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

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

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

    text 复制代码
    logits.shape = (N, 1 + K)

    每行代表一个样本和:

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

logits /= T

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

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

  • 因为正样本都在第 0 位 → 所以标签都是 0

    text 复制代码
    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 的参数(动量更新):

    python 复制代码
    θ_k ← m * θ_k + (1 - m) * θ_q
  • m 是 momentum 值(通常取 0.999)

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

dequeue_and_enqueue(queue, k)

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

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

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

    text 复制代码
    新 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,以便使用动量更新机制。

结构细节:

text 复制代码
原图像 x ∈ [B, H, W, C]
 → Data Augmentation₁ → x_q ∈ [B, H, W, C]
 → Data Augmentation₂ → x_k ∈ [B, H, W, C]

分别送入两个 encoder:

text 复制代码
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 θk←m⋅θk+(1−m)⋅θ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 表征,使得负样本库既稳定,又动态更新。

数据流结构:

text 复制代码
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(q⋅k+/τ)+∑k−exp(q⋅k−/τ)exp(q⋅k+/τ)

  • 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 Bank MoCo 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)
编码器结构 同一个 encoder Query encoder + Key encoder
更新方式 每轮梯度下降同步更新 Query 正常更新,Key 动量追踪
特征空间一致性 每轮变化剧烈 表征平稳过渡
负样本构造 当前 batch 内 历史 queue + 当前 batch
训练稳定性 依赖大 batch 小 batch 可用,收敛稳定
表征解耦 编码器要兼顾 query 和 key f_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+:

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

    text 复制代码
    queue.append(k⁺)
  3. 将最旧的 B 个 key 出队(dequeue):

    text 复制代码
    queue.pop(B)

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

MoCo 队列机制的本质意义

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

它结合了:

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

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


五、实验

5.1 主实验

任务 实验设置 方法比较 评价指标 结果表现 论文位置
ImageNet 线性评估 MoCo 预训练 + Linear probe 比较有监督、MoCo、InstDisc、SimCLR Top-1 Acc (%) MoCo ResNet-50 达到 60.6%(比 SimCLR 好) Sec. 4.1 / Table 1
小 batch 能力 batch size = 256(SimCLR 无法运行) MoCo vs SimCLR Top-1 Acc (%) MoCo 不依赖大 batch,性能优异 Sec. 4.1
Downstream 迁移(目标检测) 用 MoCo 表征初始化 Faster-RCNN 与有监督预训练比较 box AP / mask AP MoCo 略优于有监督(如 38.5 vs 38.2) Sec. 4.3 / Table 3
小数据场景(VOC / COCO) 微调 MoCo 表征在小数据集 与有监督比较 mAP MoCo 表征迁移能力更强 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 4096 MoCo 在小 batch 下依然表现良好 MoCo 不依赖大 batch,工程更友好 Sec. 4.1
Linear probe vs Finetune Downstream 任务中对 MoCo 表征进行线性分类或微调 两者都表现优异 MoCo 表征迁移性强,支持各种下游任务 Sec. 4.3 / Table 3
预训练 vs 随机初始化 用 MoCo 表征初始化目标检测模型 vs 随机权重 明显优于随机初始化 MoCo 表征泛化性强,具备替代有监督 pretrain 的潜力 Sec. 4.3
相关推荐
莱茵菜苗4 分钟前
Python打卡训练营day46——2025.06.06
开发语言·python
爱学习的小道长6 分钟前
Python 构建法律DeepSeek RAG
开发语言·python
dudly8 分钟前
大语言模型评测体系全解析(下篇):工具链、学术前沿与实战策略
人工智能·语言模型
px不是xp10 分钟前
山东大学算法设计与分析复习笔记
笔记·算法·贪心算法·动态规划·图搜索算法
zzlyx9917 分钟前
AI大数据模型如何与thingsboard物联网结合
人工智能·物联网
luojiaao33 分钟前
【Python工具开发】k3q_arxml 简单但是非常好用的arxml编辑器,可以称为arxml杀手包
开发语言·python·编辑器
说私域42 分钟前
定制开发开源AI智能名片驱动下的海报工厂S2B2C商城小程序运营策略——基于社群口碑传播与子市场细分的实证研究
人工智能·小程序·开源·零售
英英_43 分钟前
视频爬虫的Python库
开发语言·python·音视频
Chef_Chen1 小时前
从0开始学习R语言--Day18--分类变量关联性检验
学习