想象你走进“智汇小馆”,主厨老张师傅是这条街上出了名的“人脸识别机”。他记性极好,凡是来过一次的客人,下次推门进来他都能立刻认出。可老张从不问客人姓名——他只看特征:发型、口音、点菜习惯、坐哪个位置。今天店里来了位新客,老张一边打量,一边翻开手边那本厚厚的“食客档案簿”:簿子上画着最近见过的每位客人的速写。他拿眼前这位的速写,和档案簿里的每一页逐一比对,最像的,就是熟客。这,就是对比学习的直觉。 MoCo(Momentum Contrast,动量对比学习)就是把这套“认人功夫”教给深度学习模型的一套心法。它有三样绝活:第一,用“对比”代替“标注”——不需要人告诉模型“这是猫”,只问“这两张图像不像”;第二,让一个“动量编码器”负责画速写,它更新极慢,确保档案簿里所有记录都用同一套画风;第三,把海量“档案速写”存进一个先进先出的队列,让模型每次比对都有成百上千个“陌生人”可参考。这篇文章,我们就跟着老张师傅的日常,把这套心法一层层拆开。
对AI从业者而言,MoCo是自监督学习的必修课:它用“动量编码器+负样本队列”的组合拳,让你在无标签数据上练出通用特征,大幅降低下游任务的标注成本,这是大数据时代降本增效的核心技能之一。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
对比学习基础
每天下午三点,“智汇小馆”都会搬出一筐刚洗好的蔬菜。老张师傅的徒弟小刘问:“师父,这些菜没贴标签,您怎么教新来的帮工认菜?”老张笑了笑:“不用标签,拿两棵菜,问他像不像、是哪一种就行——认对了就夸,认错了就罚。”这正是对比学习的起点:数据没有人工标注,但我们可以自己制造“考题”——把同一张图片裁剪出两个不同视角,它们就是一对“正样本”(像两棵同品种的菜);再把这张图和图库里随便一张别的图凑一对,它们就是“负样本”(完全不同的菜)。模型的任务,是学会把正样本在特征空间里拉近,把负样本推开。
这个“制造考题”的过程,在自监督学习里叫“代理任务”。MoCo用的代理任务是“实例判别”:给定一张查询图(query),让它从一大堆候选图(key)里找出“唯一的那张同源图”。猫的两张不同角度照片得像,猫和狗的照片必须判然有别。模型在拼命踩这个跷跷板的过程中,被迫学会真正通用的视觉特征——这些特征后来可以直接迁移到分类、检测、分割等下游任务。
用一张图来概括对比学习的核心目标,就是让正样本的相似度永远高于任何负样本的相似度:
回到小馆里,老张师傅就是这样训练帮工的:他要求帮工对每一位熟客的评分,都必须高于对档案簿里所有陌生人的评分。条件越苛刻,帮工观察细节的本事就越强。对比学习的第一步,就是立下这条“正高负低”的铁律。
- 相似度函数这里的 \(s\) 是衡量两个特征向量像不像的“尺子”,不同任务可以用点积、余弦相似度或欧氏距离的负数。
- 正样本键\(k^{+}\) 是与查询样本 \(q\) 同源的另一个视角,比如同一张猫图的翻转图,它应当是高分对象。
- 负样本键\(k^{-}\) 是随机挑选的其他样本,比如猫图配一张狗图,它应当得分低。
- 全称量词\(\forall k^{-} \in \mathcal{N}\) 表示“对负样本集合中的每一个负样本”,这个不等式都必须成立,模型才算真正会认人了。
动量编码器机制
老张师傅有个怪癖:他每天收工后会尝一道新菜,但绝不因此立刻改掉自己的拿手菜配方。他常说:“一道菜没试够一个月,不算数。”徒弟们私下笑他固执,但正是这份固执,让档案簿里三百页速写都保持了同一种画风——如果今天跟这个师傅学画、明天跟那个师傅学画,档案簿早就乱了套。
MoCo里有两位画师:一位是“实习生小刘”(query编码器),学得飞快,每看一批新样本就大幅调整自己的画笔;另一位是“老张师傅”(key编码器),负责给档案簿画速写。问题是,老张不能完全不变——否则跟不上新菜品的风格。于是MoCo采用“动量更新”:老张的画笔参数,是“自己的老参数”和“小刘的新参数”按 99.9% 和 0.1% 的比例调和出来的。老参数占绝对主导,所以老张每一笔都只是微调,画风始终保持稳定。
数学上,这个调和过程用一个简洁的递推式表达。设 \(\theta_q\) 是query编码器的参数,\(\theta_k\) 是key编码器的参数,\(m\) 是动量系数:
如果 \(m\) 太接近1(比如0.9999),老张几乎听不进新意见,画风僵化;如果 \(m\) 太小(比如0.9),老张的画风跟着小刘剧烈抖动,档案簿前后页的画风就对不上了。0.999这个数字,是MoCo团队在ImageNet上反复试出来的黄金平衡点——老张既跟上了时代,又没有丢掉自己的笔触。
- 两个参数主角\(\theta_k\) 是key编码器的参数,它“持有”档案簿的画风;\(\theta_q\) 是query编码器的参数,它负责快速吸收新知识。
- 动量系数\(m\) 是介于0和1之间的超参数,MoCo论文取0.999,意思是“每一次更新,老张只采纳0.1%的新意见”。
- 加权求和新的 \(\theta_k\) 由旧的 \(\theta_k\) 占 99.9%、当前的 \(\theta_q\) 占 0.1% 相加而成。
- 箭头语义\(\gets\) 表示“用右边的计算结果覆盖左边的变量”,这是每训练一步都会执行一次的赋值操作。
负样本队列维护
老张的档案簿是一本活页册,册子上固定保留最近五千位客人的速写。每当新客人进门,他就翻到最后一页,把新客人的速写贴上去;同时,最前面那页画得最久的速写被撕下来丢掉。簿子永远是“最近五千人”,不会越攒越多,也不会因为客人太少而没东西可比。这本活页册,就是MoCo里的“负样本队列”。
队列的每一个格子,都存着一位“客人”的特征向量——它是key编码器(老张)在某次前向计算时画出的速写。在训练中,模型拿到一个batch的查询图,会算出一批新的key特征,然后执行“先进先出”(FIFO)操作:从队头弹出最旧的batch个特征,把新算出的batch个特征压入队尾。队列长度始终保持不变,就像一个匀速滑动的传送带。关键在于,MoCo的队列容量可以做得非常大——论文里默认 65536,而batch通常只有 256。这意味着负样本数量不再受显存限制,模型每次对比都有几万个“陌生人”可供区分。
队列和动量编码器是天生一对。因为key编码器更新极慢,队列里几万个特征虽然在不同的历史时刻生成,但它们共享着几乎相同的“画风基准”。如果key编码器每天都在剧烈变化,队列里的老特征和新特征就变成了不同画风的画,拿它们当参照系必然一塌糊涂。所以MoCo的完整故事是:动量编码器负责让队列“旧而不乱”,队列负责让对比学习“多而不贵”。用集合的语言描述队列的更新:
夜深了,小刘看着师傅撕下最旧一页、贴上最新一页,若有所思:“师傅,您这档案簿永远满着,又永远在换血,难怪不管来多少客人都不会认错。”老张点点头:“记性好,不如方法好。该忘的忘,该记的记,才是真本事。”
- 当前队列\(\mathcal{Q}_t\) 表示第 \(t\) 步训练时的队列特征集合,它是一个长度固定的字典。
- 差集删除\(\mathcal{Q}_{t-1} \setminus \{k_{\text{old}}\}\) 表示从上一时刻的队列中,去掉一组最旧的样本特征。
- 并集加入\(\cup \{k_{\text{new}}\}\) 表示把本batch新算出的key特征并进队列,它们是“刚进门的新客人速写”。
- 容量守恒左边是更新后的队列,右边删掉 \(b\) 个、加上 \(b\) 个,一进一出,队列总长度始终不变。
InfoNCE损失
现在到了最紧张的时刻:老张手里攥着新客人的速写,要在档案簿六百页里找出“最像的那一页”。他如何给每一页打分?又如何衡量自己认人的水平?打分的方法,是计算速写特征与每一页特征的点积——点积越大,说明两个向量方向越一致,人就越像。认人水平的衡量,则用一个叫 InfoNCE 的损失函数,它是对比学习的核心引擎。
InfoNCE的直觉是“揪出内鬼”:在正样本和一大堆负样本中,模型要用 softmax 的方式判断“谁是正样本”。如果正样本的得分在所有候选里脱颖而出,损失就低;如果模型瞎了眼把某个负样本当成了正样本,损失就高。公式里还有一个温度系数 \(\tau\),它控制打分分布的“脾气”:\(\tau\) 越小,softmax 越偏向“唯我独尊”,对错误零容忍;\(\tau\) 越大,softmax 越“温吞水”,错得再多也罚得轻。MoCo 常取 \(\tau = 0.07\),相当于一位要求严苛的主考官。
完整形式如下,其中 \(q\) 是查询特征,\(k_+\) 是正样本特征,\(k_i\) 是队列里的负样本特征:
老张每月底会看自己的“认人考核表”:认对的概率越高,损失分数越低,奖金越厚。InfoNCE就是那张考核表——它不关心老张对每个陌生人的具体评分,只关心“正确的人有没有排到最前面”。正是这张考核表,逼着模型把特征空间里的每个家族都安排得明明白白,同类扎堆、异类分家。
- 查询与正键\(q\) 是查询样本的特征向量,\(k_{+}\) 是与它同源的正样本键,二者点积应尽量大。
- 温度缩放把点积结果除以 \(\tau\),相当于调整评分尺度的粗细,\(\tau=0.07\) 会把分数放大几十倍。
- 分子指数化\(\exp(\cdot)\) 把正样本分数放大为非负数,便于和分母里的其他项比较所占比例。
- 分母总和分母是“正样本得分 + 所有负样本得分”的总和,负样本数量 \(N\) 就是队列长度。
- 负对数姿态\(-\log(\cdot)\) 把“正样本概率”转化为损失,概率越接近1,损失越接近0。
与SimCLR对比
隔壁街新开了一家“现代小馆”,主厨小刘跳槽过去后发明了新招:他不记档案簿,只认“当天到店的客人”。每天午市客人多,他就靠这批客人的相互对比练眼力;如果午市冷清、只有两三桌客人,他就彻底抓瞎——因为他的所有负样本都来自“当天这拨人”内部,店里人越少,参考对象就越少。这家店,就是SimCLR。
SimCLR的负样本完全依赖batch内互斥:假设batch大小是 \(N\),每张图生成两个增强视角,总共 \(2N\) 个样本,负样本数量是 \(2N-1\)。想让负样本多,就得把batch堆得巨大——SimCLR论文用了 4096 的batch,这在当时的显存条件下是烧钱行为。MoCo的做法则完全不同:负样本存进队列,batch 只需要 256,负样本却有 65536。前者是“今天客人必须够多”,后者是“过去很长时间的客人都算数”,二者在数学上都是InfoNCE的不同实例,但工程代价天差地别。
SimCLR的损失公式可以写成:
老张听了小刘的新店故事,连连摇头:“你把全部希望押在当天客流量上,不累吗?”MoCo的答案很明确:与其烧钱堆batch,不如建一座长效的档案库。队列与batch解耦,让显存压力骤降,也让“负样本超市”永远货架充盈——这是工程上的巧劲,更是思想上的破局。
- 正样本对\(z_i\) 和 \(z_j\) 是同一张图的两次增强视角的特征,它们的相似度分数构成分子。
- 分母遍历全batch分母求和范围是 \(1\) 到 \(2N\),即遍历当前batch的所有增强样本,一个不落。
- 排除自身\(\mathbb{1}_{[k \neq i]}\) 是指示函数,\(k = i\) 时不参与求和,防止模型把“自己”当成负样本。
- 负样本来源分母中还隐含着“负样本=当前batch内其他样本”,当batch不够大时,负样本就贫瘠。
与BYOL对比
又过了半年,小刘第三家店开张了,主打“师徒制”:他不再翻档案簿,而是让一个学徒网络整天盯着自己的判断标准学——师傅说“这是熟客”,学徒就记下“原来长这样”,完全不用拿陌生人来对比。这家店有个新名字:BYOL(Bootstrap Your Own Latent)。BYOL最激进的地方在于,它把负样本整个扔掉了。
MoCo坚持“必须拿正样本和负样本比一比,模型才知道边界在哪”;BYOL却认为,只要让“学生网络”不断追赶“教师网络”的输出,模型自己就能悟出好特征。BYOL的结构是:在线网络(student)对同一张图的两个增强视角分别预测,目标是逼近目标网络(teacher)的输出,而目标网络同样采用动量式更新——这一点和老张的“缓慢微调”异曲同工。但BYOL没有队列、没有负样本,它靠一个额外的“预测头”和“停止梯度”操作来防止模型崩溃。所谓崩溃,就是网络偷懒:把所有图片都映射成同一个特征,损失照样很低,等于什么都没学到。BYOL用工程诀窍避开了崩溃,MoCo则用显式对比天然免疫崩溃——这是两条截然不同的生存哲学。
BYOL的损失是两个特征向量之间的均方误差:
老张这样评价两个流派:“我翻档案簿,是跟陌生人比,清清楚楚地知道'不一样'长什么样;小蒋(BYOL的研究者)只跟师傅学,虽然看起来走捷径,但也悟出了一套防止偷懒的心法。”在自监督学习的食谱上,MoCo是色香味俱全的正统大餐,BYOL是剑走偏锋的分子料理——二者都端得上台面,只是对“什么是必要的对比”给出了不同的答案。
- 在线表示\(z_\theta\) 是学生网络从第一个视角提取的特征,\(\bar{q}_\theta\) 表示再经过一个预测头做一次非线性变换。
- 目标表示\(\bar{z}_{\xi'}\) 是教师网络从第二个视角提取的特征,并且这个特征的梯度被切断(stop-gradient)。
- L2范数平方\(\left\| \cdot \right\|_2^2\) 把两个向量逐元素相减后求平方和,衡量它们的“距离”。
- 无负样本的代价这个损失里只有“拉近”没有“推远”,因此模型必须依赖预测头和stop-gradient防止所有特征搅成一团。
效果与实现
老张的“智汇小馆”终于评上了星级。在计算机视觉的“米其林榜单”ImageNet上,MoCo的表现同样亮眼:仅用自监督预训练,不借助任何人工标签,MoCo v1在ImageNet线性分类协议上达到了60.6%的Top-1准确率,超越当时许多需要标签的方法。这个数字说明:模型从几百万张无标注图片里学到的通用特征,已经不输给拿着标签死记硬背的同行。
MoCo的训练配置里藏着几个关键数字。动量系数 \(m = 0.999\),温度系数 \(\tau = 0.07\),队列长度 \(K = 65536\),batch size = 256,优化器用SGD(初始学习率0.03,配合cosine学习率衰减),数据增强包括随机裁剪、颜色抖动、随机灰度化等。这些超参数像老张的调味配方:少了哪个,菜都不对味。特别要强调队列与batch的悬殊对比:
实现MoCo的代码其实不复杂:定义两个编码器、维护一个队列、计算InfoNCE损失、用动量公式更新key编码器。真正考验功力的,是对“一致性”的理解——队列里的老特征和新特征必须处于同一坐标系,这正是动量编码器存在的唯一理由。老张合上档案簿,笑着对徒弟说:“方法不在多,在于每一步都踩在点子上。稳定地积累,远比急功近利地堆砌更接近成功。”
- 队列容量\(K_{\text{queue}}\) 是负样本队列的最大长度,它决定模型每次对比能看到多少个“陌生人”。
- 训练批大小\(\text{BatchSize}\) 是每步训练用的样本数,它决定队列每步新增多少个特征。
- 远大于符号\(\gg\) 表示左边的数量级远大于右边,队列容量是batch的256倍,负样本的“货源”极为充足。
- 工程意义正因为队列与batch解耦,研究者才能在有限显存下享受“超大规模负样本”的红利。
AI 应用场景
电商推荐系统的视觉预训练
阶段:特征工程与预训练
解决的问题:电商平台上大量新上架的商品没有历史交互数据,传统推荐系统面对“冷启动”几乎无计可施;人工标注商品品类成本高、粒度粗。MoCo能在大规模无标签商品图上做自监督预训练,学出“不看销量也能懂商品”的视觉表征。
具体案例:某电商团队用千万张无标注商品主图训练MoCo,得到通用视觉编码器;下游只需少量标注商品图文数据,微调后即可支撑“以图搜图”和相似商品推荐,冷启动点击率提升约18%。
医学影像的低标注诊断
阶段:模型微调与迁移
解决的问题:医疗影像数据缺标注、隐私严、获取难,靠人工标注训练模型费时费力。MoCo先在大规模无标注影像(如胸部X光、CT切片)上预训练,再以少量标注数据微调,能有效缓解“标注稀缺”痛点。
具体案例:某医疗AI团队在100万张无标注眼底照片上做MoCo预训练,随后仅用5000张专家标注图微调用于糖尿病视网膜病变分级,准确率接近使用5万张标注图的全监督模型。
大规模视频行为理解
阶段:多模态表征学习
解决的问题:视频数据量巨大但语义稀疏,人工标注行为类别几乎不可能穷尽。MoCo可以对视频帧或短视频片段做实例判别预训练,让模型学到“哪些画面在讲同一件事”,从而为下游动作识别提供强初始化。
具体案例:某安防厂商利用海量监控录像(无标注)训练MoCo视频编码器,下游微调后用于异常行为检测,在公开数据集UCF101上取得超越传统光流方法的自监督精度。
Python 演示
import numpy as np def info_nce(query, pos_key, neg_keys, tau=0.1): """InfoNCE: 最大化正样本概率, 最小化负样本概率""" pos_sim = np.sum(query * pos_key, axis=-1) / tau neg_sim = np.matmul(query, neg_keys.T) / tau logits = np.concatenate([pos_sim[:, None], neg_sim], axis=-1) logits -= np.max(logits, axis=-1, keepdims=True) exp_logits = np.exp(logits) probs = exp_logits / np.sum(exp_logits, axis=-1, keepdims=True) return -np.mean(np.log(probs[:, 0] + 1e-9)) class MiniMoCo: def __init__(self, dim=64, queue_size=128, momentum=0.999, tau=0.07): self.dim = dim self.queue_size = queue_size self.momentum = momentum self.tau = tau self.query_encoder = np.random.randn(dim, dim) * 0.02 self.key_encoder = np.random.randn(dim, dim) * 0.02 self.queue = np.random.randn(queue_size, dim) self.queue /= np.linalg.norm(self.queue, axis=-1, keepdims=True) + 1e-9 def _normalize(self, x): return x / (np.linalg.norm(x, axis=-1, keepdims=True) + 1e-9) def encode(self, x, encoder): return self._normalize(x @ encoder.T) def info_nce_loss(self, x_q, x_k): q = self.encode(x_q, self.query_encoder) k = self.encode(x_k, self.key_encoder) return info_nce(q, k, self.queue, self.tau) def step(self, x_q, x_k): loss = self.info_nce_loss(x_q, x_k) # 动量更新 key 编码器: theta_k = m * theta_k + (1-m) * theta_q self.key_encoder = self.momentum * self.key_encoder + (1 - self.momentum) * self.query_encoder # FIFO: 弹出最旧 batch 个特征, 尾部压入新 key 特征 b = len(x_k) new_keys = self.encode(x_k, self.key_encoder) self.queue = np.concatenate([self.queue[b:], new_keys], axis=0) return loss if __name__ == "__main__": np.random.seed(42) model = MiniMoCo(dim=64, queue_size=128) for step in range(5): x_q = np.random.randn(32, 64) x_k = np.random.randn(32, 64) loss = model.step(x_q, x_k) print(f"step {step+1}: loss = {loss:.4f}, queue size = {model.queue.shape[0]}")
def info_nce(query, pos_key, neg_keys, tau=0.1):定义 InfoNCE 损失函数,把所有对比逻辑收敛到一个函数里。pos_sim = np.sum(query * pos_key, axis=-1) / tau计算正样本点积相似度并按温度缩放,等价于“这个熟客像不像”。neg_sim = np.matmul(query, neg_keys.T) / tau用矩阵乘法一次性算出 query 与队列所有负样本的相似度,高效且向量化。self.key_encoder = self.momentum * self.key_encoder + (1 - self.momentum) * self.query_encoder动量更新 key 编码器,让画风保持稳定。new_keys = self.encode(x_k, self.key_encoder)用当前 key 编码器给本批样本画速写,准备入队。self.queue = np.concatenate([self.queue[b:], new_keys], axis=0)FIFO 队列维护:丢掉最旧的 batch 个特征,把新的接到队尾。
常见误区
误区很多初学者以为,负样本队列既然在损失函数里出现了,那么队列里的特征也应该反向传播更新参数,于是尝试对队列里的每个特征求梯度。
正解MoCo 队列中的负样本特征来自 key 编码器的前向计算,它们只是“被读取的档案”,整个 InfoNCE 损失只对 query 编码器(和当前 batch 的正样本编码路径)反传梯度。key 编码器靠动量公式更新,它的梯度是彻底断开的,这就像老张翻档案簿时只是“看”,不会因为看了谁而改变自己的画风。
为何常见:因为 InfoNCE 公式里负样本确实参与了损失计算,初学者容易把“参与前向”和“参与反向”混为一谈。
误区新手常觉得“0.999 已经让 key 编码器很稳定了,那我设成 0.9999 或直接冻结不是更好?”
正解动量系数 \(m\) 过大会让 key 编码器更新太慢,追不上 query 编码器和数据分布的变化,队列里的特征会逐渐“过时”;过小则让 key 编码器抖动剧烈,队列前后页画风不一。0.999 是 MoCo 在 ImageNet 上验证的均衡点,不同数据集可能需要微调,但“越大越好”是错觉。
为何常见:人们直觉上认为“稳定”就是好事,却忽略了编码器也需要跟上数据分布的漂移,这是一对需要平衡的矛盾。
误区有同学认为“负样本越多越好,那把队列设成 100 万甚至无穷大,效果是不是更强?”
正解队列越长,负样本越丰富,但队列中最早的特征越是“陈年旧账”——尽管动量编码器缓解了漂移,它无法完全消除成本。特征空间随时间缓慢旋转,太旧的特征反而成为噪声。MoCo 论文选择 65536 是在“容量”和“新鲜度”之间权衡的结果,盲目增大不一定带来收益,还可能增加内存负担。
为何常见:只看收益不看代价,忽略了队列一致性这个隐性约束。
误区有人认为 MoCo 无非是把负样本从 batch 挪到了队列里,和 SimCLR 没有本质不同,谈不上思想创新。
正解队列只是表象,真正的思想创新是用动量编码器解决“队列一致性”问题——让历史的负样本特征仍然处于当前可比的坐标系中。这个“用缓慢更新换取新旧兼容”的思路,是 MoCo 区别于 SimCLR 的关键贡献,后续很多自监督方法都沿用了这一设计。
为何常见:初学者容易聚焦在显眼的“队列”上,而忽略了更隐蔽也更重要的“动量一致性”动机。
误区看到 BYOL 在某些榜单上分数更高,就认为它是 MoCo 的替代品,MoCo 只剩历史价值。
正解BYOL 去掉了负样本,但它引入了预测头和 stop-gradient 等防坍缩技巧,训练敏感性更高;MoCo 的显式对比框架更可解释、更稳定,且在迁移学习、多模态检索等领域仍被广泛用作基线。两者是“对比式”与“预测式”两条路线的代表,各有适用场景,不是简单的优劣关系。
为何常见:唯准确率论,忽略了算法的稳定性、可解释性和场景适配性。
面试题
Q1. MoCo 中的负样本主要来自哪里?
- A.当前 batch 内其它样本
- B.队列中的历史样本特征← 正确答案
- C.手工精心标注的困难样本
- D.随机生成的噪声向量
答案:B。MoCo 的负样本来自队列里存储的历史 key 特征,由动量编码器逐步生成并缓存。当前 batch 内的其它样本只是极小一部分负样本来源,而手工标注和随机噪声都不符合对比学习的负样本定义。
Q2. 动量编码器的参数更新方式是什么?
- A.每步直接赋值 query 的参数
- B.新旧参数按比例加权平均← 正确答案
- C.按损失梯度反向传播更新
- D.随机初始化后不再更新
答案:B。动量编码器用 \(\theta_k \gets m \theta_k + (1-m) \theta_q\) 更新,其中 \(m=0.999\) 让旧参数占绝对主导,key 编码器只做缓慢微调,而不是直接复制 query 参数,也不走梯度反传。
Q3. 为什么动量编码器能让队列中的特征保持一致?
- A.特征全部存在 GPU 显存中
- B.它更新缓慢让新旧特征同坐标系← 正确答案
- C.它完全不使用任何数据增强
- D.它每步都同步复制 query 参数
答案:B。队列里的特征是不同时刻由 key 编码器生成的,只有 key 编码器的参数变化足够缓慢,这些特征才可能位于同一特征空间。动量更新正是为了压制这种漂移,让新旧“档案”在同一套画风下可比。
Q4. MoCo 的负样本队列采用什么更新策略?
- A.后进先出的栈式淘汰
- B.先进先出的队列淘汰← 正确答案
- C.随机抽取负样本进行替换
- D.按损失大小淘汰负样本
答案:B。MoCo 使用 FIFO 先进先出:每步把最旧的 batch 个特征弹出队首,新算出的 key 特征压入队尾,队列长度恒定。这种策略简单高效,保证了负样本不断更新又不会突然清零。
Q5. 与 SimCLR 相比,MoCo 的核心优势是什么?
- A.可以完全不需要数据增强
- B.负样本与 batch 大小解耦← 正确答案
- C.损失函数天然更加稳定
- D.训练速度一定大幅提升
答案:B。SimCLR 的负样本数量取决于 batch 大小,不得不使用 4096 的大 batch;MoCo 把负样本存进队列,batch 可小至 256 而负样本仍达 65536,彻底解耦了二者。
Q6. 关于 InfoNCE 损失,下列说法哪些是正确的?
- A.温度系数越小分布越尖锐← 正确答案
- B.温度系数越小对比越宽松
- C.分子是正样本的相似度← 正确答案
- D.分母包含全部正负样本
答案:AC。InfoNCE 的分子是正样本相似度的指数,分母是正样本加所有负样本的指数和。温度系数 \(\tau\) 越小,指数缩放越大,softmax 分布越尖锐,对比要求越严苛,因此 B 说法相反。
Q7. BYOL 与 MoCo 最本质的差别是什么?
- A.BYOL 不用卷积神经网络
- B.BYOL 没有显式负样本← 正确答案
- C.MoCo 没有任何损失函数
- D.MoCo 只能处理图像分类
答案:B。BYOL 完全去掉了负样本,只靠学生网络逼近教师网络输出来学习表示,用预测头和 stop-gradient 防止特征坍塌。MoCo 则始终依赖显式的正负样本对比。
Q8. 关于 MoCo 训练机制,下列说法正确的是?
- A.key 编码器不计算梯度回传← 正确答案
- B.队列中的样本都来自同一批
- C.最旧的样本会被弹出队列← 正确答案
- D.query 参数也按动量方式更新
答案:AC。key 编码器只用动量公式更新,不参与反传;队列里的样本来自不同历史时刻,而非同一 batch;FIFO 策略每步弹旧入新;query 编码器走正常梯度更新而非动量更新。因此 A、C 正确。
Q9. 如果将动量系数 m 从 0.999 改为 0.5,最可能发生什么?
- A.队列特征一致性变差且训练不稳← 正确答案
- B.训练加速且效果明显提升
- C.队列容量会自动翻倍增长
- D.损失函数会被自动改变
答案:A。m=0.5 意味着 key 编码器每步有一半参数来自 query,画风剧烈抖动,队列里新旧特征不再同一坐标系,负样本信号噪声大,训练容易崩溃。这不改变损失函数或队列容量。
Q10. MoCo 预训练得到的编码器主要用在哪里?
- A.仅用于图像分类任务
- B.迁移到下游任务作初始化← 正确答案
- C.替代优化器和损失函数
- D.替代全部数据增强策略
答案:B。MoCo 的产物是一个通用视觉编码器,预训练完成后可以丢弃动量编码器和队列,把 query 编码器用作下游分类、检测、分割等任务的初始化权重,这是自监督预训练的标准用法。