资讯动态

PyTorch实战:在CelebA上训练DCGAN的人脸生成全流程

发布时间:2026/10/5 4:52:01 来源:尧图企业网站定制
简介一套基于Pytorch在CelebA人脸数据集上训练DCGAN的完整项目源码面向深度学习初学者及希望提升生成模型实战能力的中级开发者。项目覆盖DCGAN模型搭建、CelebA数据预处理、损失函数与优化器配置、训练过程监控以及生成结果分析等完整流程可帮助读者系统掌握生成对抗网络的落地实现。压缩包共12个文件含4个Python脚本模型定义、训练、图像生成与工具模块、5张训练过程及生成效果图谱、1份模型权重文件、1个动态生成演示GIF及1份README说明文档整体大小102.4MB。目前已有399人学习下载。通过实操训练读者不仅能理解DCGAN的网络结构与对抗训练机制还能直接加载训练好的模型权重生成人脸图像并将相关方法迁移到其他图像生成任务中。1. 用 Pytorch 在 CelebA 上训练 DCGAN先搞清楚它到底能给你什么把 DCGAN 第一次在 CelebA 上跑通很多人以为难点在于“生成对抗网络”这几个字很吓人实际上真正卡住你的往往是数据没对齐、归一化范围写错、训练循环里那个 detach 用错了位置以及看着 loss 一点点降下去、生成的图像却始终像一幅抽象画。DCGAN 的价值在于它用一套相对规整的卷积架构把 GAN 训练从“完全玄学”拉到了“按参数复现”的层面生成器把 100 维噪声映射成 64x64 人脸图判别器负责区分真图和假图两者对抗着收敛。这篇文章要做的就是把这套东西在 CelebA 上的完整链路讲清楚从数据准备、网络搭建、训练循环写到踩坑排查。适合的人有两个一是刚学完 Pytorch 基础、想找一个完整实战项目的入门者二是已经跑过一些分类模型、第一次接触生成模型的算法工程师想看看 DCGAN 到底有哪些必须亲手踩一遍才知道的细节。2. 准备 CelebA 数据对齐裁剪和归一化决定了训练能不能起步2.1 下载与目录落盘先别急着写代码把文件结构和数量核对清楚CelebA 数据集最常用的版本是 img_align_celeba它已经做过了人脸对齐和中心裁剪每张图是 178x218 的 RGB 图片。很多人直接在 torchvision.datasets.CelebA 里传入 downloadTrue但实际下载过程会因为源站连接问题经常中断等你跑到一半再去折腾网络反而浪费时间。我一般建议手动把数据集下载好放到本地目录然后用自定义 Dataset 去读。这个思路在后续换数据集、加数据过滤时也更灵活。下载完之后至少核对三样东西图片目录里有 202599 张 jpglist_attr_celeba.txt 里有对应的属性标注list_eval_partition.txt 里有训练集、验证集、测试集划分。这个数量级意味着单个 epoch 在普通 GPU 上也就是几分钟的事但如果你忘了核对文件数量训练到一半发现 dataloader 报了“找到 0 张图片”再回头排查就非常浪费时间。目录结构建议这样安排Dataset 代码里直接引用路径即可data/ img_align_celeba/ 000001.jpg 000002.jpg ... list_attr_celeba.txt list_eval_partition.txt这段目录结构本身不是必须的但它会让后续调试省心很多。比如你后期想只看“戴眼镜”的人脸list_attr_celeba.txt 里的属性列就能直接用来做过滤条件不用重新下载数据。2.2 自定义 Dataset把对齐图裁剪成 64x64 并归一化到 [-1, 1]DCGAN 原文用的是 64x64 输入而 CelebA 对齐图是 178x218所以要做一次中心裁剪再到缩放。这里有个常见误区很多人直接用 transforms.Resize((64, 64))结果人脸被纵向压扁生成器学到的脸型都是畸形的。正确做法是先 CenterCrop 到正方形再 Resize。CenterCrop 的尺寸我习惯用 160这样裁掉的是四周背景人脸主体保留得更完整。归一化这里必须特别注意。DCGAN 生成器最后一层用 Tanh输出范围是 [-1, 1]所以输入图像也必须归一化到 [-1, 1]。对应的 transform 是from torchvision import transforms transform transforms.Compose([ transforms.CenterCrop(160), transforms.Resize((64, 64)), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), ])这里的 ToTensor 会把像素值从 [0, 255] 缩放到 [0, 1]再用 mean0.5、std0.5 做归一化最终每个像素落在 [-1, 1]。标准差的取值直接决定了后续训练稳定性。很多人在 DCGAN 里把归一化写成 ImageNet 的 mean/std图像输入范围变成了 [-1.5, 2.5] 这种不对称区间虽然也能训但收敛速度和最终质量都会受影响。自定义 Dataset 的写法比较简单核心是把图片路径和 transform 绑定起来import os from PIL import Image from torch.utils.data import Dataset class CelebADataset(Dataset): def __init__(self, img_dir, transformNone): self.img_dir img_dir self.img_names sorted([ name for name in os.listdir(img_dir) if name.endswith(.jpg) ]) self.transform transform def __len__(self): return len(self.img_names) def __getitem__(self, idx): img_path os.path.join(self.img_dir, self.img_names[idx]) img Image.open(img_path).convert(RGB) if self.transform: img self.transform(img) return img, 0这里的返回标签 0 是占位用的DCGAN 训练时不需要真实属性标签但保留 label 的位置可以方便后续切换成 cGAN 或者 ACGAN。sorted() 排序保证每次迭代顺序一致这在复现实验结果时很重要。如果你发现个别图片损坏导致训练中断可以在getitem里加 try/except 跳过坏图这个后面避坑章节会专门讲。2.3 DataLoader 参数batch_size、num_workers 和 pin_memory 怎么配合CelebA 有 20 万张图图片本身不大但如果不设置 num_workers数据加载会成为训练瓶颈GPU 使用率上不去。常见的配置是 num_workers4 或 8pin_memoryTrue。pin_memory 的作用是把数据锁定在页锁定内存里向 GPU 拷贝时会快一些但这个收益在数据量小时不明显只有训练循环足够快才能体现出来。from torch.utils.data import DataLoader dataset CelebADataset(img_dirdata/img_align_celeba, transformtransform) dataloader DataLoader( dataset, batch_size64, shuffleTrue, num_workers4, pin_memoryTrue, drop_lastTrue, )drop_lastTrue 是很多新手会忽略的配置。如果最后一个 batch 不足 batch_size在判别器里会造成真图和假图数量不对等虽然不影响反向传播但 loss 曲线会产生周期性的小尖刺排查时会误判为训练不稳定。batch_size 的取值在 DCGAN 里建议 64 起步显存不够就降到 32但不要低于 16。太小的 batch 会让判别器对单个样本过拟合生成器学到的分布非常窄。2.4 训练前先做一次可视化验证把数据管道的坑提前暴露数据代码写完不要直接开训先用一个简单的循环把一批图像拼成网格保存下来确认三个细节图像不是全黑或全白、人脸方向一致、没有出现“绿头”这种通道错位。这些检查只要 5 分钟能省下后面一整天的排查时间。import torch import torchvision.utils as vutils batch next(iter(dataloader)) img_grid vutils.make_grid(batch[0][:16], nrow4, normalizeTrue, value_range(-1, 1)) vutils.save_image(img_grid, data_check.png)make_grid 里的 normalizeTrue 和 value_range(-1, 1) 是关键参数。因为图像取值范围是 [-1, 1]如果不显式指定 value_range它会自动按批次里的最小最大值做归一化导致颜色看起来很奇怪你以为是数据坏了实际只是可视化参数没设对。看到保存的 data_check.png 里人脸清晰、颜色正常再进入下一步。3. 搭建 DCGAN 网络生成器、判别器与权重初始化缺一不可3.1 DCGAN 对原始 GAN 的四个结构改动理解之后再动手抄代码DCGAN 能成为 GAN 入门的标准架构不是因为它是第一个把卷积用进 GAN 的模型而是它把训练不稳定的原因归到了结构设计上并给出了四个明确改动第一去掉池化层用带 stride 的卷积和转置卷积来做采样第二除生成器输出层和判别器输入层外网络内部都加 BatchNorm第三生成器内部用 ReLU输出层用 Tanh判别器内部用 LeakyReLU第四去掉全连接层让网络全卷积化。这四个改动有一个共同的逻辑让梯度在深层网络里传得更顺畅减少判别器太容易碾压生成器的情况。BatchNorm 的引入对 DCGAN 至关重要。生成器里每一层转置卷积的输出经过 BN 后特征的尺度被稳定下来不会因为某一层输出过大直接把 Tanh 推到饱和区。判别器里的 BN 则让真假样本的特征分布都被拉到一个可控的范围内避免判别器快速收敛到 100% 准确率后梯度消失。如果你在训练中发现 loss 很奇怪先回到结构上检查有没有漏掉 BN而不是先调学习率。3.2 生成器实现从 100 维噪声到 64x64 图像的转置卷积生成器输入是形状为 (batch_size, 100, 1, 1) 的噪声张量输出是 (batch_size, 3, 64, 64) 的图像。中间的尺寸变化从 4x4 开始每经过一个转置卷积翻倍最终到 64x64。这个“从 4 到 64”的路径需要 4 层转置卷积。下面是完整的生成器代码每一层的输出通道和尺寸变化我都写在注释里方便你核对import torch.nn as nn class Generator(nn.Module): def __init__(self, z_dim100, feat_map64): super().__init__() self.main nn.Sequential( # 输入: z_dim x 1 x 1 nn.ConvTranspose2d(z_dim, feat_map * 8, 4, 1, 0, biasFalse), nn.BatchNorm2d(feat_map * 8), nn.ReLU(True), # 输出: (feat_map*8) x 4 x 4 nn.ConvTranspose2d(feat_map * 8, feat_map * 4, 4, 2, 1, biasFalse), nn.BatchNorm2d(feat_map * 4), nn.ReLU(True), # 输出: (feat_map*4) x 8 x 8 nn.ConvTranspose2d(feat_map * 4, feat_map * 2, 4, 2, 1, biasFalse), nn.BatchNorm2d(feat_map * 2), nn.ReLU(True), # 输出: (feat_map*2) x 16 x 16 nn.ConvTranspose2d(feat_map * 2, feat_map, 4, 2, 1, biasFalse), nn.BatchNorm2d(feat_map), nn.ReLU(True), # 输出: feat_map x 32 x 32 nn.ConvTranspose2d(feat_map, 3, 4, 2, 1, biasFalse), nn.Tanh() # 输出: 3 x 64 x 64 ) def forward(self, z): return self.main(z)这里有两个参数需要解释。feat_map64 是基础通道数控制网络的宽度显存比较紧张时改成 32 可以大幅减少参数量但生成图像的细节表达能力也会下降。ConvTranspose2d 里 kernel_size4、stride2、padding1 是 DCGAN 里最经典的组合它能让特征图尺寸精确翻倍。第一层 kernel4、stride1、padding0 的作用是把 1x1 的噪声“展开”成 4x4 的特征图这个形状是后续所有尺寸计算的基础。3.3 判别器实现用带步长卷积把图像压成真/假分数判别器是生成器的镜像结构但方向相反输入 3x64x64 的 RGB 图像输出一个标量。DCGAN 原文判别器最后一层接 Sigmoid输出 (0, 1) 之间的概率。但在实际工程中我推荐去掉 Sigmoid直接输出 logit配合 BCEWithLogitsLoss 使用。原因很简单Sigmoid 在输入很大时会产生梯度消失而 BCEWithLogitsLoss 内部把 Sigmoid 和交叉熵融合在一起数值上更稳定。class Discriminator(nn.Module): def __init__(self, feat_map64): super().__init__() self.main nn.Sequential( # 输入: 3 x 64 x 64 nn.Conv2d(3, feat_map, 4, 2, 1, biasFalse), nn.LeakyReLU(0.2, inplaceTrue), # 输出: feat_map x 32 x 32 nn.Conv2d(feat_map, feat_map * 2, 4, 2, 1, biasFalse), nn.BatchNorm2d(feat_map * 2), nn.LeakyReLU(0.2, inplaceTrue), # 输出: (feat_map*2) x 16 x 16 nn.Conv2d(feat_map * 2, feat_map * 4, 4, 2, 1, biasFalse), nn.BatchNorm2d(feat_map * 4), nn.LeakyReLU(0.2, inplaceTrue), # 输出: (feat_map*4) x 8 x 8 nn.Conv2d(feat_map * 4, feat_map * 8, 4, 2, 1, biasFalse), nn.BatchNorm2d(feat_map * 8), nn.LeakyReLU(0.2, inplaceTrue), # 输出: (feat_map*8) x 4 x 4 nn.Conv2d(feat_map * 8, 1, 4, 1, 0, biasFalse), # 输出: 1 x 1 x 1 ) def forward(self, x): return self.main(x).view(-1)判别器第一层不加 BatchNorm这是 DCGAN 原文明确的设置。原因在于判别器输入是原始像素BN 会强制把输入分布拉向零均值单位方差丢失掉真实图像和生成图像之间最基础的统计差异反而让判别器变弱。LeakyReLU 的 slope 取 0.2这个值在 DCGAN 原文里就是这么定的一般不用调。最后一层把输出 view(-1) 展平成 (batch_size,) 的形状方便后续和标签张量直接做 loss 计算。3.4 权重初始化为什么 DCGAN 必须用均值为 0 标准差 0.02 的正态分布DCGAN 原文里有一个细节很多复现版本会漏掉所有可训练参数按均值为 0、标准差为 0.02 的正态分布初始化。这个设置不是随便拍的。如果初始化权重过大转置卷积的输出在早期就会让 Tanh 处于饱和区梯度直接消失如果全部置零BN 之前的层输出全为常数网络根本学不动。0.02 这个量级确保了网络初始输出在一个敏感的动态范围内。def weights_init(m): classname m.__class__.__name__ if classname.find(Conv) ! -1: nn.init.normal_(m.weight.data, 0.0, 0.02) elif classname.find(BatchNorm) ! -1: nn.init.normal_(m.weight.data, 1.0, 0.02) nn.init.constant_(m.bias.data, 0) netG Generator() netD Discriminator() netG.apply(weights_init) netD.apply(weights_init)注意 BatchNorm 的初始化跟卷积不一样weight 初始化为 1.0bias 初始化为 0。因为 BN 的 weight 是缩放因子初始为 1 意味着不做额外缩放让网络自己学习是否需要对特征做缩放。如果你用的 Pytorch 版本较新有些层默认初始化其实已经能工作但复现 DCGAN 时我还是建议显式调用 weights_init确保生成器和判别器在同一起点。3.5 把生成器输出尺寸验证一遍前向传播是唯一的检验标准结构代码写完后不要直接进训练循环先用固定形状的随机张量做一次前向传播确认输出 shape 符合预期。这一步能拦住 80% 的维度错误z torch.randn(4, 100, 1, 1) fake_img netG(z) print(fake_img.shape) # torch.Size([4, 3, 64, 64]) real_img torch.randn(4, 3, 64, 64) d_out netD(real_img) print(d_out.shape) # torch.Size([4])如果你在这步发现输出尺寸不对优先检查每层卷积的 kernel_size、stride、padding 三个参数是否正确。ConvTranspose2d 的输出尺寸计算公式是 (H-1)stride - 2padding kernel_size对照注释里的每一层尺寸手动推一遍很快就能定位是哪一层出了问题。4. 训练循环与损失在 Pytorch 里把对抗训练跑通4.1 训练超参数配置Adam 的 beta1 为什么必须设成 0.5DCGAN 原文的优化器配置是 Adam学习率 0.0002beta10.5。很多人直接用 Pytorch 默认的 Adam 参数beta10.9结果训练震荡非常剧烈。原因在于 beta1 控制梯度一阶矩的指数衰减速度0.9 意味着梯度历史会被保留很久而 GAN 的对抗过程本身是动态博弈梯度方向变化极快保留太多历史梯度会让更新方向滞后判别器和生成器互相追不上。beta10.5 让优化器“记性变短”更及时地响应对方策略的变化。batch_size、学习率、beta1 这三个参数在 DCGAN 里最好不要单独大改。它们之间存在耦合batch_size 调小后每个 batch 的梯度噪声变大需要一定的历史平滑如果此时把 beta1 调得更小训练会变得很脆。我实际调参的习惯是先固定 lr0.0002、beta10.5如果 loss 震荡优先调判别器的更新频率而不是动优化器参数。4.2 训练循环代码判别器和生成器的交替更新顺序训练循环是 DCGAN 最容易写错的地方。核心规则是判别器和生成器交替更新但每次更新时只计算对应网络的梯度另一个网络必须用 detach() 断开梯度传播。下面是完整的训练循环我按实际工程习惯做了注释import torch import torch.nn as nn criterion nn.BCEWithLogitsLoss() lr 0.0002 beta1 0.5 optimizerD torch.optim.Adam(netD.parameters(), lrlr, betas(beta1, 0.999)) optimizerG torch.optim.Adam(netG.parameters(), lrlr, betas(beta1, 0.999)) fixed_noise torch.randn(64, 100, 1, 1) for epoch in range(num_epochs): for i, (real_img, _) in enumerate(dataloader): real_img real_img.to(device) current_batch_size real_img.size(0) # ---------- 训练判别器 ---------- netD.zero_grad() real_label torch.full((current_batch_size,), 1.0, devicedevice) fake_label torch.full((current_batch_size,), 0.0, devicedevice) # 先用真实图像计算判别器损失 real_out netD(real_img) lossD_real criterion(real_out, real_label) # 生成假图注意这里要 detach 掉生成器的梯度 noise torch.randn(current_batch_size, 100, 1, 1, devicedevice) fake_img netG(noise).detach() fake_out netD(fake_img) lossD_fake criterion(fake_out, fake_label) lossD lossD_real lossD_fake lossD.backward() optimizerD.step() # ---------- 训练生成器 ---------- netG.zero_grad() noise torch.randn(current_batch_size, 100, 1, 1, devicedevice) fake_img netG(noise) fake_out netD(fake_img) lossG criterion(fake_out, real_label) lossG.backward() optimizerG.step() if i % 100 0: print(fEpoch [{epoch}/{num_epochs}] Batch [{i}/{len(dataloader)}] fLossD: {lossD.item():.4f} LossG: {lossG.item():.4f})这段代码里有三个容易写错的位置。第一判别器损失是 real 和 fake 两部分之和backward 只需要调用一次第二生成器训练阶段传入判别器的 fake_img 不能 detach否则生成器拿不到梯度第三训练生成器时用的标签是 real_label因为生成器的目标就是让判别器认为假图是真的。如果你把标签搞反了生成器会学习到把图像生成得更假loss 直接反向走。4.3 BN 在训练与推断之间的切换一个被忽略的显式步骤训练完成后保存模型、做推理或生成 GIF 时必须调用 netG.eval()否则生成器的 BatchNorm 层会继续使用训练时的 batch 统计量推理结果受输入 batch 影响出现“这次生成的是人脸、下次生成的全是噪点”这种诡异情况。反过来如果你在训练过程中对生成器做可视化比如每个 epoch 保存一次固定噪声的生成图也需要在生成前切换到 eval()生成完再切回 train()。def save_fake_images(netG, fixed_noise, epoch, output_diroutput): netG.eval() with torch.no_grad(): fake netG(fixed_noise) grid vutils.make_grid(fake, nrow8, normalizeTrue, value_range(-1, 1)) vutils.save_image(grid, f{output_dir}/epoch_{epoch:03d}.png) netG.train()这段代码在训练循环里每个 epoch 结束时调用一次。使用 fixed_noise 而不是重新生成随机噪声是有意为之每次都用同一个噪声向量你能直观地看到同一批“种子”随训练推进逐渐从噪点变成人脸这是判断训练进程最直观的手段。4.4 checkpoint 保存除了网络权重优化器状态也必须存训练到一半机器死机或者你想换个时间段继续训练如果只保存了 model.state_dict()优化器的动量状态会丢失恢复训练后 loss 会出现明显跳变。正确做法是把生成器、判别器、两个优化器、当前 epoch 打包成字典保存torch.save({ epoch: epoch, netG_state_dict: netG.state_dict(), netD_state_dict: netD.state_dict(), optimizerG_state_dict: optimizerG.state_dict(), optimizerD_state_dict: optimizerD.state_dict(), }, fcheckpoints/gan_epoch_{epoch:03d}.pth)恢复时用 load_state_dict 分别加载然后调用 optimizer 的 state 更新。如果你只想用训练好的生成器出图不恢复优化器也可以但如果要断点续训优化器状态漏掉会让后续训练的质量明显下降损失曲线也会出现一个台阶式的扰动。5. DCGAN 训练避坑常见问题与排查清单5.1 判别器先崩了D loss 恒定在 0.69 附近不降现象训练一开始或经过几个 epoch 后判别器的损失稳定在 0.69 左右生成器损失也在同一水平生成的图像全是灰蒙蒙的噪声无论怎么调学习率都不动。原因0.69 是 log(2) 的值说明判别器对真假样本的输出概率都是 0.5也就是完全分不清了。这种情况通常有两种来源一是判别器模型太强直接把生成器压制到梯度消失二是生成器太弱输出全是噪声判别器很快学到了“凡是这种纹理都是假的”导致生成器反向传播的梯度失去指导意义。解决先降低判别器的学习率比如从 0.0002 降到 0.00005让生成器有更多机会追上或者减少判别器更新频率改成每更新两次生成器才更新一次判别器。另一个有效手段是标签平滑把真实标签从 1.0 改为 0.9让判别器不那么自信留给生成器一点梯度空间。如果你用的是 BCEWithLogitsLoss直接传 soft label 就行不需要改网络结构。5.2 图像颜色正常但五官位置是乱的数据对齐问题现象生成的图像里肤色、背景色都对了但眼睛、嘴巴的位置分布错乱有的脸有两双眼睛有的脸五官挤在左下角。原因这是数据对齐出了问题。如果用了未对齐的原始 CelebA 图片或者自己采集的数据集人脸在图像中的位置不居中生成器只能学到“平均脸”位置导致五官结构错位。解决回到数据准备阶段对人脸做一次检测并对齐。简单做法是先用 OpenCV 的级联检测器或 MTCNN 检测人脸框按人脸框中心做裁剪再缩放。如果你用的就是 img_align_celeba 这类对齐好的数据出现这个现象则要检查 transforms 里是否做了一步 CenterCrop 再 Resize否则原图在缩放后人脸依旧在中心问题不大。此外还要确认你训练时没有把图像旋转或翻转CelebA 的人脸方向本身是统一的额外加 RandomHorizontalFlip 可能造成左右脸特征混淆。5.3 训练中期 loss 变成 NaN数值溢出而不是网络写错现象训练进行到几百个 iteration 后loss 突然打印出 nan然后所有指标都变成 nan。原因绝大多数情况是 logits 数值过大导致经过 Sigmoid 时溢出或者学习率太大导致权重更新幅度过大参数直接发散。使用 BCEWithLogitsLoss 会比“Sigmoid BCELoss”更稳定但 logits 仍然可能在极端情况下跑到几千造成 loss 计算时无限值。解决给判别器的输出加一个梯度裁剪但不要直接裁剪 loss而是在反向传播之前对梯度做 cliplossD.backward() torch.nn.utils.clip_grad_norm_(netD.parameters(), max_norm1.0) optimizerD.step()同时把判别器里的 BatchNorm 换成 SpectralNorm 也可以缓解但这会偏离 DCGAN 原本结构属于进阶改动。如果你想保持原版 DCGAN先试梯度裁剪和降低学习率NaN 出现概率会显著下降。另外检查输入图像里有没有损坏的图片损坏像素读到 NaN 也会顺着网络传播这种情况数据管道的责任更大。5.4 训练速度慢到无法接受瓶颈往往在数据加载而非模型计算现象GPU 利用率只有 20-30%一个 epoch 要跑很久但模型本身并不大。原因最常见的坑是 num_workers0。Pytorch 默认不启用多进程数据加载所有图像读取、裁剪、归一化都在主进程里排队GPU 等 CPU 干活。另一个原因是每次训练循环里都把 dataloader 重新创建或者在循环内部做了太多 Python 级别的张量拼接。解决确认 DataLoader 设置了 num_workers 大于等于 4pin_memoryTrue确认训练循环外面只创建一次 dataloader。如果数据是机械硬盘可以把数据集放到 SSD 或者内存盘上CelebA 解压后约 1.5GB 的 jpg 图片放到内存里读取速度会有明显提升。还有一个细节用的 torchvision 版本较旧时CenterCrop 和 Resize 的 PIL 实现较慢升级 torchvision 或把 Resize 改成调用 OpenCV 的 cv2.resize 也能提速不少。5.5 复现效果不稳定换了机器结果差异很大现象同样的代码在 A 机器上能生成清晰人脸在 B 机器上却一直模糊或者同一台机器跑两次一次效果好一次效果差。原因GAN 训练本身随机性较大生成器初始化的种子和 DataLoader 的 shuffle 顺序都会影响最终结果。此外不同 Pytorch 版本对 BatchNorm 和卷积的实现细节有改动也会造成训练轨迹偏移。解决在所有涉及随机的地方设置固定种子包括 Pytorch、Python random、NumPy 三个库。设置 CUDA 的确定性模式也会让结果更可复现但会牺牲一点速度。我的习惯是实验阶段必须固定种子否则后面的改结构、调参对比全部没有意义上生产或者出最终结果时再解除固定跑多次取效果最好的。固定种子的代码很简单但很多人只设了 torch.manual_seed忘了 random.seed 和 np.random.seedDataLoader 的 shuffle 仍然随机。6. 验证生成质量从损失曲线到潜在空间插值损失曲线在 GAN 里只能告诉你“判别器和生成器是否还在博弈”不能告诉你“图像质量如何”。我自己的经验是记录 loss 辅助判断但真正决定训练是否继续的是固定噪声生成图的视觉变化。用得最多的验证手段是潜在空间插值用代码实现就是取两个随机噪声向量在它们之间做线性插值然后把插值结果依次送进生成器import torch z1 torch.randn(1, 100, 1, 1) z2 torch.randn(1, 100, 1, 1) steps 10 interpolated [] for i in range(steps 1): alpha i / steps z z1 * (1 - alpha) z2 * alpha with torch.no_grad(): img netG(z) interpolated.append(img) final_grid torch.cat(interpolated, dim0) vutils.save_image(final_grid, interpolation.png, nrowsteps 1, normalizeTrue, value_range(-1, 1))如果插值图像从一张脸平滑过渡到另一张脸中间没有突变或破碎说明生成器学到了连续的人脸分布如果中间帧出现明显撕裂、五官消失说明潜在空间的流形还没收敛好通常需要继续训练或调大生成器容量。这个验证方法非常直观比盯着 loss 数字判断靠谱得多。训练收敛后我还习惯观察判别器对真假样本的输出分布。具体做法是把一批真实图像和一批生成图像分别送入判别器打印输出的均值和标准差。质量好的 GAN判别器对两者的输出都应该在 0.5 附近且分布有重叠这说明生成分布和真实分布已经比较接近如果输出完全分开生成质量无论视觉上多好都说明判别器仍然能轻松区分继续训练可能让生成器过拟合到判别器的“盲区”而不是真实分布。最后提一个我自己踩过的坑训练早期看看已有梯度下降趋势就急着把生成的图拿去给人评价。对 DCGAN 来说前几个 epoch 的生成图大概率是一团模糊色块但这时候网络可能已经学到正确的特征方向。要给训练足够的时间等固定噪声图的轮廓稳定形成后再开始调结构细节。我现在的习惯是每次训练都保存固定噪声每个 epoch 的快照序列翻车时把序列从头看到尾基本能定位是哪个阶段出了问题。希望你也能用这个方式早点摸清 DCGAN 的训练节奏。希望这篇帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑