生成对抗网络 GAN
GAN 由生成器与判别器交替对抗,采样快但训练可能不稳定。
学习目标
- detach 区分两次反向的责任
- 冻结 D 参数为什么仍能训练 G?
- 说明:对抗损失和一次梯度方向不能证明生成质量;训练可能震荡或模式塌缩。
为什么需要这个概念
生成对抗网络由生成器 G 和判别器 D 组成。D 学习区分真实与生成样本,G 学习让生成样本被判为真实,二者目标与更新路径不同。
学习前需要掌握
背景与问题
若很难为高维图像直接指定精确概率密度,可以让另一个网络评价样本是否像真实数据。GAN 通过生成器与判别器的交替训练形成这种反馈。
概念定义
生成对抗网络由生成器 G 和判别器 D 组成。D 学习区分真实与生成样本,G 学习让生成样本被判为真实,二者目标与更新路径不同。
直观理解
生成器不断交作品,判别器不断学会挑问题;两者同时变化,训练不像最小化一个固定评分表那样简单。
核心原理
判别器常输出一个原始 logit,用 BCEWithLogitsLoss 分别匹配真实标签 1 与生成标签 0。更新 D 时,应对生成样本 detach,避免把这一目标反传到 G。
更新 G 时需要重新让未 detach 的生成样本通过 D,并把目标设为真实;常用非饱和损失 -log D(G(z)),与页面经典 minimax 式中的生成器形式有所不同。可以暂时冻结 D 参数,但不能用 no_grad 包住其前向,否则连到 G 的路径也会断。
D 与 G 的损失会随对手变化,不能要求都单调下降。模式塌缩可能使样本看似清晰却缺少多样性;应固定采样噪声并结合更广泛评价,不能只看判别正确率或精选图片。
数学表达
核心公式
生成对抗网络由生成器 G 和判别器 D 组成。D 学习区分真实与生成样本,G 学习让生成样本被判为真实,二者目标与更新路径不同。
变量含义
Nbatch sizeD/H特征或隐藏维度
计算步骤
- 1D 使用 fake.detach,反向后 G 没有梯度。
- 2清理 D 梯度并冻结其参数。
- 3G 使用未 detach 的 fake,按真实目标反向得到梯度。
代码对应位置
示例 1detach 区分两次反向的责任的最小验证:使用完整构造输入核对detach 区分两次反向的责任,输出只表示本例计算。
变量解释
| 变量 | 含义 |
|---|---|
N | batch size |
D/H | 特征或隐藏维度 |
完整数值示例
detach 区分两次反向的责任
已知条件
- 标量 G(z)=w_g z,D(x)=w_d x,两个权重均为 1
- 噪声 z=1,真实值为 2
计算步骤
- D 使用 fake.detach,反向后 G 没有梯度。
- 清理 D 梯度并冻结其参数。
- G 使用未 detach 的 fake,按真实目标反向得到梯度。
计算结果
D 阶段生成器梯度为空;G 阶段生成器梯度约为 -0.268941。
处理前后对比
- 对抗损失和一次梯度方向不能证明生成质量;训练可能震荡或模式塌缩。
detach 区分两次反向的责任
负梯度意味着 SGD 会增大生成器权重,使当前生成样本的判别 logit 更大。这只是一个固定判别器下的局部方向,不表示真实数据分布已经学会。
示例没有实际交替训练,只验证最容易接错的图路径。真实训练还需要独立优化器、清理梯度、合理更新频率和数据范围一致性。
计算与实现步骤
- D 使用 fake.detach,反向后 G 没有梯度。
- 清理 D 梯度并冻结其参数。
- G 使用未 detach 的 fake,按真实目标反向得到梯度。
代码实现
示例 1
detach 区分两次反向的责任的最小验证
example_01.py用途:使用完整构造输入核对detach 区分两次反向的责任,输出只表示本例计算。
import torch
from torch import nn
g, d = nn.Linear(1, 1, bias=False), nn.Linear(1, 1, bias=False)
with torch.no_grad():
g.weight.fill_(1.)
d.weight.fill_(1.)
z, real = torch.tensor([[1.]]), torch.tensor([[2.]])
bce = nn.BCEWithLogitsLoss()
fake = g(z)
loss_d = bce(d(real), torch.ones(1, 1)) + bce(d(fake.detach()), torch.zeros(1, 1))
loss_d.backward()
print("G gradient absent in D phase:", g.weight.grad is None)
d.zero_grad(set_to_none=True)
for p in d.parameters():
p.requires_grad_(False)
loss_g = bce(d(g(z)), torch.ones(1, 1))
loss_g.backward()
print(f"D_loss={loss_d.item():.6f} G_loss={loss_g.item():.6f} G_grad={g.weight.grad.item():.6f}")代码解析
解析始终位于完整代码下方,并按实际代码段逐项对应。
输入数据与任务
使用完整构造输入核对detach 区分两次反向的责任,输出只表示本例计算。
gradient- 损失对参数的局部变化率。
Step 1 · 1–18 行
导入当前步骤需要的数值计算、预处理、模型或评价工具。依赖集中写在代码开头,便于复现。
import torch
from torch import nn
g, d = nn.Linear(1, 1, bias=False), nn.Linear(1, 1, bias=False)
with torch.no_grad():
g.weight.fill_(1.)
d.weight.fill_(1.)
z, real = torch.tensor([[1.]]), torch.tensor([[2.]])
bce = nn.BCEWithLogitsLoss()
fake = g(z)
loss_d = bce(d(real), torch.ones(1, 1)) + bce(d(fake.detach()), torch.zeros(1, 1))
loss_d.backward()
print("G gradient absent in D phase:", g.weight.grad is None)
d.zero_grad(set_to_none=True)
for p in d.parameters():
p.requires_grad_(False)
loss_g = bce(d(g(z)), torch.ones(1, 1))
loss_g.backward()
print(f"D_loss={loss_d.item():.6f} G_loss={loss_g.item():.6f} G_grad={g.weight.grad.item():.6f}")代码与数学原理
参数更新由当前梯度与学习率共同决定。
平方误差代码对应均方损失。
预期输出或运行结果
G gradient absent in D phase: True D_loss=1.440190 G_loss=0.313262 G_grad=-0.268941
常见错误 · 4 条
- D 更新忘 detach
- G 更新错误 detach
- 只看 loss
- 模式崩溃
实际应用
- 无显式密度的样本生成
- 检查交替训练中的梯度隔离
常见错误
- D 更新忘 detach
- G 更新错误 detach
- 只看 loss
- 模式崩溃
常见问题
冻结 D 参数为什么仍能训练 G?
D 的运算仍可对输入求导,只是不为 D 的参数累积梯度;若把整个 D 前向放进 no_grad 才会切断到 G 的路径。
这个示例能说明什么边界?
对抗损失和一次梯度方向不能证明生成质量;训练可能震荡或模式塌缩。
推荐学习资料
参考库不会生成虚假资源或无效外部链接。