[LAB 07 · MoCo:历史 key、队列与两种更新](/course/notebooks/0d1b8f6fbf6a476c93ffcc169f2ec1f4.html) # 为什么队列需要缓慢变化的键编码器? [独立阅读](/course/notebook-blocks/180ef4ed3ff34c0eaafec7bde6f86a8f.html) 按问题继续 真实FIFO键、EMA、梯度更新及低于随机基线的保留结果 ## 为什么队列需要缓慢变化的键编码器? 批内方法的键都由当前参数生成。FIFO 让字典容量与当前批大小分开,但旧键来自较早的参数;若表示空间每步剧烈旋转,今天的查询与昨天的键就难以比较。MoCo 用 $\theta_k\leftarrow m\theta_k+(1-m)\theta_q$ 减缓键编码器变化。下面的教学实现 m=0.95,队列容量240、批大小60;query 和 key 初始权重相同。每步先对 key 做 EMA,再在 `no_grad` 中编码第二视图;查询梯度只更新 query;当次键以 detach 状态入队,最旧的键出队。第一次队列为空,采用当前批 detached keys 热身;后续正例放第0列,队列是负例列。 为避免一个源图跨 epoch 再出现时被当作自己的负例,队列同时保存源图 ID,并屏蔽同源旧键。**同类别的不同源图仍可能是假负例**;这正是实例目标与语义类别不同的地方。此实现没有BatchNorm,因此没有多设备BN信息泄漏处理;论文工程细节不能据此宣称已全部实现。 主讲按原论文Algorithm 1展示先计算本批key、更新query、再EMA的流程;下面代码在本批key前做EMA。二者时序应分别按各自实现追踪,详见 [把一次学习走通,也读清实现顺序](/course/blocks/moco-one-step-paper-and-code.html)。这不是把旧队列重新编码。