一句话
sg[·](stop-gradient):算数时它不存在,传梯度时它是路障——数值原样透传,指令到此为止。
学习 = 每个参数收到一条「往哪挪」的指令
训练时发生的事:
- 模型算出一个损失(loss):一个数字,衡量「这次干得多差」
- 系统计算每个参数的梯度:「你往这边挪一点点,损失会降低一点点」
- 每个参数按自己的梯度挪一小步
就这么循环。梯度 = 挪动指令。一个参数如果拿不到指令,它就原地不动、永远学不到东西。
指令沿着「计算路线」倒着送回
参数不是直接连着损失的,中间隔着一串计算:
梯度传递像追责:损失说「重建得不好」,责任沿计算路线一站一站往回追——追到 decoder、追到 、最后追到 encoder 的参数。每一站收到自己的「往哪挪」。
关键推论
追责路线在哪一断了,断点之前的所有参数都收不到指令,全部原地不动。
sg[·] 就是「追责到此为止」的牌子
- 前向算数时: 就等于 ,数值一模一样
- 反向追责时:追到 sg 就停,括号里的变量不背锅、不收指令、不动
一行代码就是 .detach():不改任何数值,只切断指令传递路线。
用数字看差别
设 encoder 输出 ,码字 ,损失 ,目标:两者靠拢。
不加 sg——两边都收到指令:
- :「变大一点,去接近 1.0」
- :「变小一点,去接近 0.8」
- 结果:两边互相走,在中间 碰头—— 没到 1.0, 也没留在原地,两头各走一半
加 sg——损失写成 :
- 前向数值不变,还是
- 追责时 被 sg 罩住 → 不收指令,钉死在 1.0
- 只有 收到指令,一路追到 附近
一句话
不加 sg 是两块磁铁互吸;加 sg 是单方面追。
回到 VQ-VAE 里那句话
为 stop-gradient,「这一项里把我当常数、别给我传梯度」
逐字翻译:
- 这一项里:sg 只影响它所在的这一个损失项; 在别的损失项里照样正常收指令
- 把我当常数:「我」= sg 括号里的变量,追责时当它是写死的数字
- 别给我传梯度:不许把挪动指令送到我这,这项损失训不到我
VQ-VAE 有三个学习者(encoder、decoder、码本),设计者要求每个损失项只训指定的一方:
| 损失项 | sg 括住谁 | 谁被冻结 | 实际训谁 |
|---|---|---|---|
| 码本项 | encoder | 只把码字拉向 encoder 输出 | |
| commitment 项 | 码本 | 只把 encoder 输出拉向码字 |
没有 sg,两项都是「两头互拉、中点会合」,encoder 和码本互相迁就,谁也到不了该在的位置——即笔记里的「同一根弹簧两头互拉」。sg 把弹簧一端焊死,让它只朝一个方向收缩。
追问:后两项能不能合并成一项不带 sg 的?
观察很敏锐:sg 不改数值,所以前向上
而且两个 sg 项在一次反向传播里都生效——码本项推 、commitment 项推 ,一次传播两个都在动。那和不带 sg 直接写 有什么区别?
前向数值等价 ≠ 梯度等价
学习由每个变量分到的梯度份额决定,这才是差别所在。
设 、,一步更新量 :
写成一项不带 sg——两边对称各拿 1 份:
- :+0.1 → 0.9,:−0.1 → 0.9,各走一半、中点会合
- encoder 的输出被码本拖着走了一半
拆成两个 sg 项():
- 从码本项拿 1 份:+0.1 → 0.9,全速追
- 从 commitment 项只拿 份:−0.025 → 0.975,几乎没动
同样一次反向传播、两个变量都动,但速率和角色不同:一个全速追,一个只让四分之一步。
不对称是刻意设计的主从关系:encoder 输出的方向由重建损失主导(怎么编码图像还原得最好),码本只是全速追赶 encoder 输出的聚类中心。若对称互拉:
- encoder 输出被码本拉偏一半,主任务被污染
- 更新力度锁死 1:1,没有调节旋钮
拆成两个 sg 项后 是独立旋钮——码本追多快(系数 1)与 encoder 被拴多紧(系数 )解耦。极端情况 :commitment 消失,剩下「argmin 定划分 + 码字追质心」,恰好退化成在线版 K-means——对称单项永远做不到这个形态。commitment 只是防止 encoder 输出在胞腔边界来回跳的稳定器,不是学习主力。