GAN 的账本用二元交叉熵(BCE)记录:判别器的损失是真样本打分与 1 的 BCE 加假样本打分与 0 的 BCE;生成器的原始损失是 log(1-D(G(z))),实践中改用非饱和形式 -log D(G(z)),以换取开局阶段可用的梯度。 本节把两本账逐笔核算,并用数值演算展示"饱和"到底卡在哪。
审讯室第二案:账本。2.1 节的 V 函数是"进账",深度学习框架习惯记"损失"(要最小化),两者只差一个负号。本节先核鉴定师的账,再核造假者的账——后者的原始记账方式有个致命伤,非饱和改写是 GAN 工程史上最重要的一笔修改。
鉴定师的每一批训练数据一半真一半假,损失相应两笔:
L_D = -E[log D(x)] - E[log(1 - D(G(z)))]
第一笔惩罚"把真品打假",第二笔惩罚"把赝品放行"。D 的输出经 sigmoid 落在 0 到 1 之间,解释为"真品概率"。把不同打分下的单笔损失算出来贴在墙上,账本的严厉程度一目了然:
import numpy as np for p in [0.9, 0.7, 0.5, 0.3, 0.1, 0.01]: print(f"打分 D={p:4.2f}: 认真品的账 -log(p)={-np.log(p):7.4f} | " f"识赝品的账 -log(1-p)={-np.log(1-p):7.4f}") # 输出: # 打分 D=0.90: 认真品的账 -log(p)= 0.1054 | 识赝品的账 -log(1-p)= 2.3026 # 打分 D=0.70: 认真品的账 -log(p)= 0.3567 | 识赝品的账 -log(1-p)= 1.2040 # 打分 D=0.50: 认真品的账 -log(p)= 0.6931 | 识赝品的账 -log(1-p)= 0.6931 # 打分 D=0.30: 认真品的账 -log(p)= 1.2040 | 识赝品的账 -log(1-p)= 0.3567 # 打分 D=0.10: 认真品的账 -log(p)= 2.3026 | 识赝品的账 -log(1-p)= 0.1054 # 打分 D=0.01: 认真品的账 -log(p)= 4.6052 | 识赝品的账 -log(1-p)= 0.0101
读数要点:打分 0.5 处两笔账都是 0.6931(等于 log 2)——完全没主见的代价;把真品打成 0.01 要赔 4.6052,错误越是自信,账越是指数级加重。这种"重罚自信错误"的特性正是对抗训练能逼出高判别力的原因。
工程实现里还有个常用折扣:单边标签平滑。把真样本的目标从 1 改成 0.9,等于给鉴定师留一点"永远别太自信"的余地,防止它把 logits 推到极端、掐断造假者的梯度(呼应 6.2 节)。一行代码的事,效果常常显著。
造假者的原始账本是 L_G = E[log(1 - D(G(z)))](最小化它等价于 2.1 节中最小化 V 的生成器一侧)。问题藏在开局:训练初期造假者的产品一眼假,D(G(z)) 接近 0,此时 log(1 - D) 的曲线几乎躺平——梯度微弱,造假者不知从何改起。
把账本换成 L_G' = -E[log D(G(z))],最小化的方向不变(都想让 D(G(z)) 变大),但梯度曲线完全不同。用数值演算对比(对 sigmoid 输出求导,梯度幅值记到 logit 上):
for d in [0.01, 0.1, 0.3, 0.5]: sat = d # 原始账本 d/d(logit)[-log(1-D)] 的幅值 = D non = 1 - d # 改写账本 d/d(logit)[-log(D)] 的幅值 = 1 - D print(f"D(G(z))={d:4.2f}: 原始账本梯度幅值={sat:.3f} | 非饱和账本梯度幅值={non:.3f}") # 输出: # D(G(z))=0.01: 原始账本梯度幅值=0.010 | 非饱和账本梯度幅值=0.990 # D(G(z))=0.10: 原始账本梯度幅值=0.100 | 非饱和账本梯度幅值=0.900 # D(G(z))=0.30: 原始账本梯度幅值=0.300 | 非饱和账本梯度幅值=0.700 # D(G(z))=0.50: 原始账本梯度幅值=0.500 | 非饱和账本梯度幅值=0.500
差距最悬殊处正是最需要它的地方:D(G(z))=0.01 时,原始账本只给 0.010 的梯度(接近断供),非饱和账本给 0.990(满血)。两条曲线在 0.5 处交汇,此后优劣互换——训练后期赝品已经很像时,非饱和形式的梯度反而收窄,恰好起到自动减速的作用。
💡 关键直觉:非饱和改写没有改变"想要什么"(让 D 打高分),只改变了"多急切地想要"。开局急、收尾缓,这个不对称恰好匹配训练各阶段的需要——一次被引用上万次的"简单改法",本质是对梯度供给曲线的重新设计。
| 账本 | 表达式 | 谁最小化 | 梯度流向 | 工程备注 |
|---|---|---|---|---|
| 鉴定师 | -log D(x) - log(1-D(G(z))) | D | 只更新 D 参数 | 真目标可平滑到 0.9 |
| 造假者原始 | log(1 - D(G(z))) | G | 经 D 回传到 G | 开局梯度断供,弃用 |
| 造假者改写 | -log D(G(z)) | G | 经 D 回传到 G | 标准做法;后期梯度自动收窄 |
注意造假者两本账的梯度都要先经过 D 再回到 G——反向传播穿过判别器到达生成器,这是"知识经由鉴定师中介"在计算图上的体现。训练循环里那句 detach(截断梯度)因此成为高频出错点:训 D 时必须截断赝品批对 G 的梯度,否则"训练判别器"会顺手改掉生成器。
| 场景 | 用哪个式子 | 一句话记忆 |
|---|---|---|
| 判别器训练 | 负的 log D(x) 加负的 log(1 减 D(G(z))) | 真打 1 假打 0,自信错重罚 |
| 生成器训练 | 负的 log D(G(z)) | 只管骗分,开局要满血梯度 |
| 真目标平滑 | 真样本目标从 1 改 0.9 | 给判别器留刹车 |
| 判别器步内 | 赝品批 detach | 别让训 D 顺手改 G |
两个网络的学习率要一样吗? 不必,而且常不一样。判别器学得快是常态(任务更简单:二分类),把它学习率设为生成器一半是常见起手式。第 4.1 节的清单把它列为优先级最高的旋钮之一。
标签平滑只平滑真目标吗? 常见做法只平滑真样本目标(1 改 0.9),假样本目标保持 0——这叫单边平滑。双边平滑会让判别器"两头都犹豫",通常没必要。
BCE 里的对数底重要吗? 只差一个常数倍(换底乘常数),对梯度方向无影响。框架里统一用自然对数,手算时底数随意,但别在同一张表里混用。