资讯动态

PyTorch实现DCGAN:从零训练生成二次元头像

发布时间:2026/8/31 23:22:47 来源:尧图企业网站定制
简介本资源是一个基于PyTorch实现的DCGAN二次元头像生成项目面向深度学习初学者与PyTorch实践者旨在通过完整可运行的生成对抗网络案例帮助用户掌握卷积生成模型的核心原理、训练流程与工程部署。压缩包共3478个文件包含3464张高质量二次元头像JPG训练数据、6个.pkl模型权重文件含生成器与判别器的中间及最终版本、5张PNG结果图、2个核心Python脚本DCGAN.py用于训练DCGAN_test.py用于推理生成、1份README说明文档以及gen_imgs、res、gen_imgs_256等结果目录整体大小为386.11MB。已有1171人下载学习资源提供从数据加载、模型定义、损失计算、训练循环到图像生成的全流程代码附带预训练权重与多阶段生成样图开箱即用便于调试、复现与二次开发。 做深度学习练手项目我踩过不少坑。跑分类网络觉得太简单transformer 又觉得环境太重直到我把目标锁定在 DCGAN 上——用 PyTorch 从零搭建输入一堆随机噪声让它自己学着生成二次元头像。整个过程既有直观的视觉反馈又覆盖了生成模型最经典的知识点代码量还不大特别适合作为 PyTorch 学习练手项目。这个项目我完整跑通了源码、训练测试代码、训练数据和训练权重最终能生成一批看起来很二次元的头像图。很多朋友一上来就奔着训练数据、训练权重去但没理解 DCGAN 的核心机制导致换自己的数据集时直接翻车。这篇文章会把你从环境搭建带到训练收敛再讲到怎么用保存下来的权重做生成顺便把训练过程中那些反直觉的坑都讲清楚。1. 项目整体设计与思路拆解1.1 从 GAN 到 DCGAN生成器与判别器的博弈DCGAN 全称是 Deep Convolutional Generative Adversarial Network也就是深度卷积生成对抗网络。它底层还是 GAN 那套对抗思想一个生成器、一个判别器两个网络互相较劲。生成器的任务是把随机噪声变成一张“很像真实头像”的图片。判别器的任务则是判断拿到的图片到底是来自真实数据集还是生成器伪造的。训练过程就像做假币的和验钞机互相升级生成器努力造出以假乱真的图片判别器努力把伪造图挑出来。两者对抗到最后生成器造出来的头像就能骗过判别器这时候我们就得到了一个能出图的生成模型。那 DCGAN 和原始 GAN 的区别在哪原始 GAN 早期多用全连接层堆叠生成图像容易模糊、结构不稳定。DCGAN 的核心改进就是引入卷积神经网络把生成器里的全连接层替换成转置卷积把判别器里的池化层替换成带步长的卷积再搭配批归一化BatchNorm和特定激活函数。这个改动看起来不大但稳定性提升非常明显图像空间结构也能保留得更好。1.2 为什么拿二次元头像当练手项目选择二次元头像作为数据集不是随便拍的。我练过几个生成模型项目对比下来二次元头像有几个很明显的优势。第一图像结构相对规整。头像基本都是正脸或半侧脸五官位置比较集中背景也相对干净生成器更容易学到“鼻子大致在脸中间”这类内在规则收敛速度比生成复杂场景快得多。第二公开数据集好找。网上有大量二次元头像图包像丹尼尔Danbooru这类图库虽然不适合直接作为训练集下载但有不少经过筛选的头像数据集比如 Anime Face Dataset一张图基本都是 64x64 或 128x128 的头像预处理工作量小。第三视觉反馈强。训练分类模型时你可能盯了很久 loss 也不知道模型学成什么样。但训练 DCGAN 时每个 epoch 结束都可以把生成器输出的图片贴出来看谁都能一眼判断“这像不像二次元头像”。这种即时的正反馈对坚持刷完训练循环非常重要。1.3 源码结构规划写代码之前我建议先把项目目录规划好不然后面调试的时候会非常混乱。我的目录结构大概是这样的dcgan-anime/ ├── data/ │ └── anime_faces/ # 原始图片 ├── checkpoints/ # 训练权重保存目录 ├── outputs/ # 生成结果保存目录 ├── dataset.py # 数据集加载与预处理 ├── model.py # 生成器与判别器定义 ├── train.py # 训练入口 ├── test.py # 测试与生成入口 └── config.py # 超参数配置我把数据集加载、模型定义、训练循环拆成独立文件。这样改动超参数时不需要翻整个训练脚本换数据集时也只需要改 dataset.py训练逻辑完全不用动。这个规划习惯来自之前跑 YOLO 训练自己数据集时的经验——数据、模型、训练三个部分解耦后面复现代码能省下大量时间。2. PyTorch 环境搭建与二次元头像数据集准备2.1 Anaconda 配置环境与 CUDA 版本匹配环境搭建是劝退新手的第一道坎尤其是 GPU 版本 PyTorch 的安装很多人在这里卡了一整天。我的建议是第一步先确认显卡驱动支持的最高 CUDA 版本。你可以打开命令提示符输入nvidia-smi右上角会显示 “CUDA Version” 字样那个数字就是驱动支持的上限。然后用 Anaconda 创建一个独立环境别把项目依赖直接装到 base 环境里不然之后跑别的项目会因为版本冲突搞到怀疑人生。创建环境并安装 GPU 版 PyTorch 的命令如下conda create -n dcgan python3.9 conda activate dcgan pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118这里用cu118表示 CUDA 11.8 版本。需要说明的是PyTorch 安装包内部已经自带了对应的 CUDA 运行时不需要额外全局安装一套完整 CUDA Toolkit只要显卡驱动满足要求PyTorch 就能调用到 GPU。这也是很多新手最容易误解的地方——以为必须先装 CUDA Toolkit 才能装 PyTorch一旦装错版本环境直接崩掉。装完之后务必执行一条验证命令import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果打印True说明 PyTorch 已经可以调用 GPU。如果输出False大概率是 PyTorch 装成了 CPU 版本或者 CUDA 版本和驱动不匹配。我遇到过很多次这种情况排除思路很简单先确认是否安装了 CPU 版再确认驱动版本是否过低。2.2 训练数据的收集与处理关于训练数据网上可以直接下载整理好的二次元头像数据集也可以自己抓图。我更推荐从现成数据集开始因为项目目标是练手 DCGAN而不是研究数据清洗。拿到图片后需要统一做预处理。DCGAN 对输入图片尺寸有固定要求生成器的输入是 100 维随机噪声输出一般是 64x64x3 的图片所以训练集的图片也要缩放到 64x64 大小。不要觉得 64x64 太小二次元头像本身细节不算特别丰富64x64 已经能把五官轮廓表达清楚而且这个分辨率训练速度极快一张消费级显卡几分钟就能跑一个 epoch。我用的预处理流程是这样的from torch.utils.data import Dataset from PIL import Image import os import torchvision.transforms as transforms class AnimeFaceDataset(Dataset): def __init__(self, root_dir): self.image_paths [] for root, _, files in os.walk(root_dir): for f in files: if f.lower().endswith((.png, .jpg, .jpeg)): self.image_paths.append(os.path.join(root, f)) self.transform transforms.Compose([ transforms.Resize((64, 64)), transforms.CenterCrop((64, 64)), transforms.ToTensor(), transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) ]) def __len__(self): return len(self.image_paths) def __getitem__(self, idx): img Image.open(self.image_paths[idx]).convert(RGB) return self.transform(img)这里有个关键细节Normalize我用了mean0.5、std0.5这样会把图像像素值从(0, 1)范围映射到(-1, 1)范围。为什么这么做因为生成器的最后一层通常用Tanh激活函数它的输出范围正好是(-1, 1)让生成器输出的分布和真实图片分布对齐训练会更稳定。这是 DCGAN 原文里的一个经典设置别改成默认的归一化参数。2.3 数据增强要不要加我的实践结论做分类任务时数据增强几乎是无脑加但生成任务不一样。我试过在二次元头像数据集上加随机水平翻转和随机旋转结果训练出来的图片偶尔会出现五官错位的情况。我的结论是对于这种结构相对固定的头像数据可以加一个水平翻转但旋转角度别超过 10 度。因为二次元头像本身数据集规模通常不小生成模型需要的是真实的分布细节过度增强反而会让生成器学到扭曲的特征。如果用torchvision.transforms可以这样加transforms.RandomHorizontalFlip(p0.5),如果数据集只有几千张图加翻转能起到扩充作用如果已经有两万张以上不加也没问题。3. 生成器与判别器核心代码逐段拆解3.1 生成器从 100 维噪声到 64x64 图片生成器的工作就是把 100 维的随机噪声一步步上采样直到变成 64x64x3 的图像。DCGAN 使用转置卷积实现上采样。我的生成器实现如下import torch.nn as nn class Generator(nn.Module): def __init__(self, latent_dim100, ngf64): super(Generator, self).__init__() self.model nn.Sequential( nn.ConvTranspose2d(latent_dim, ngf * 8, 4, 1, 0, biasFalse), nn.BatchNorm2d(ngf * 8), nn.ReLU(True), nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, biasFalse), nn.BatchNorm2d(ngf * 4), nn.ReLU(True), nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, biasFalse), nn.BatchNorm2d(ngf * 2), nn.ReLU(True), nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, biasFalse), nn.BatchNorm2d(ngf), nn.ReLU(True), nn.ConvTranspose2d(ngf, 3, 4, 2, 1, biasFalse), nn.Tanh() ) def forward(self, x): return self.model(x)你可以看到噪声输入x的形状是(batch_size, 100, 1, 1)相当于把 100 维向量“摆放”成一个 1x1 的特征图。第一层转置卷积的步长是 1输出尺寸变为 4x4后面四层步长为 2尺寸依次变成 8x8、16x16、32x32、64x64。设计生成器时有几个细节一定要记住。卷积层都设置了biasFalse因为后面接了 BatchNorm 层BatchNorm 有自己的可学习偏移参数如果卷积层再带 bias这两个参数功能重复反而影响收敛。此外除了最后一层用Tanh前面所有激活函数都用ReLU这是 DCGAN 原文的要求印象里当时作者专门对比过不同激活函数ReLU 在生成器上的表现最稳。这里顺便回答一个很多新手总会问的点为什么说“参数就是模型从训练数据里学到的‘内在规则’被压缩成的数字集合”拿生成器来说它里面那些卷积核权重和 BatchNorm 的缩放系数本质上就是一套不断更新的数字。刚开始这些数字是随机的生成器画出来的东西就是噪声。训练时每次看到一张二次元头像损失函数都会告诉它“你画得不像”于是这些数字就一点点朝“能画得像”的方向调整。训练结束后几百万个数字被固化成.pth文件以后加载这个文件模型就“会”画二次元头像了。这些权重数字之间没有直观语义但它们整体编码了五官比例、发丝走向、颜色偏好这些内在规则。3.2 判别器真伪图片的鉴别器判别器是个二分类网络输入一张 3x64x64 的图片输出一个标量表示这张图片有多大概率是真实图片。DCGAN 中的判别器使用带步长的卷积替代池化结构如下class Discriminator(nn.Module): def __init__(self, ndf64): super(Discriminator, self).__init__() self.model nn.Sequential( nn.Conv2d(3, ndf, 4, 2, 1, biasFalse), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(ndf, ndf * 2, 4, 2, 1, biasFalse), nn.BatchNorm2d(ndf * 2), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, biasFalse), nn.BatchNorm2d(ndf * 4), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, biasFalse), nn.BatchNorm2d(ndf * 8), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(ndf * 8, 1, 4, 1, 0, biasFalse), nn.Sigmoid() ) def forward(self, x): return self.model(x)输入 64x64 图片经过四层步长为 2 的卷积后特征图尺寸依次变为 32x32、16x16、8x8、4x4最后一层卷积核大小为 4输出变成 1x1 的数值再用Sigmoid压缩到 0 到 1 之间表示“判别器认为这图有多真”。跟生成器不同判别器里所有中间层激活函数都是LeakyReLU负斜率设置为 0.2。这么做是为了避免梯度消失因为 ReLU 在输入为负数时梯度直接变成 0判别器学不到信息。LeakyReLU 允许很小的负梯度通过能维持判别器稳定的学习能力。3.3 损失函数与优化器配置细节决定成败DCGAN 的损失函数用的是二元交叉熵在 PyTorch 里可以直接用nn.BCELoss。但这里有一个非常关键的技巧判别器的真实图片标签不是 1而是一个接近 1 的数比如 0.9生成图片标签不是 0而是 0.1。这个技巧叫标签平滑Label Smoothing能避免判别器过于自信缓解训练不稳定。我的训练循环里的损失计算是这样的criterion nn.BCELoss() # 训练判别器 real_label 0.9 fake_label 0.1 real_pred discriminator(real_images) d_real_loss criterion(real_pred, torch.full_like(real_pred, real_label)) fake_images generator(noise) fake_pred discriminator(fake_images.detach()) d_fake_loss criterion(fake_pred, torch.full_like(fake_pred, fake_label)) d_loss d_real_loss d_fake_loss这里fake_images.detach()非常重要。如果不 detach生成器输出的梯度会反传到生成器网络里导致在更新判别器参数时顺带更新了生成器的参数。而我们希望在更新判别器时生成器固定不动所以要截断梯度。优化器配置这块从 DCGAN 原文到现在的各种复现实验最经典的配置就是用 Adam 优化器学习率lr0.0002betas(0.5, 0.999)。注意这里的 beta1 用了 0.5而不是 PyTorch 默认的 0.9。原因是 GAN 训练非常敏感beta1 太大会让历史梯度对当前更新影响过大导致训练震荡。这个细节网上不少源码都没给解释我第一次跑的时候直接用默认 betas结果训练到第十来个 epoch 就明显不稳定损失大幅波动后来才发现是这个参数在起作用。3.4 训练循环两个网络的轮番更新DCGAN 的训练循环可以概括为一个迭代更新过程。每个 batch 里的数据先更新判别器再更新生成器两个网络交替前进。完整的单轮更新代码for epoch in range(num_epochs): for i, real_images in enumerate(dataloader): real_images real_images.to(device) current_batch real_images.size(0) # 更新判别器 discriminator.zero_grad() noise torch.randn(current_batch, latent_dim, 1, 1, devicedevice) real_pred discriminator(real_images) d_real_loss criterion(real_pred, torch.full_like(real_pred, real_label)) fake_images generator(noise) fake_pred discriminator(fake_images.detach()) d_fake_loss criterion(fake_pred, torch.full_like(fake_pred, fake_label)) d_loss d_real_loss d_fake_loss d_loss.backward() optimizer_d.step() # 更新生成器 generator.zero_grad() noise torch.randn(current_batch, latent_dim, 1, 1, devicedevice) fake_images generator(noise) fake_pred discriminator(fake_images) g_loss criterion(fake_pred, torch.full_like(fake_pred, real_label)) g_loss.backward() optimizer_g.step()注意更新生成器时判别器对伪造图片的预测目标是real_label也就是希望生成器学会“骗过”判别器——让判别器看到伪造图也输出接近 1。这里生成器的噪声需要重新采样虽然用同一个noise变量也行但重新采样可以减少噪声分布的相关性这点在调试模糊问题时会有意义。3.5 每个 epoch 结束后的固定噪声可视化训练过程中一定要把生成结果可视化出来而不是只盯着 loss。我习惯在训练前生成一组固定的随机噪声每次 epoch 结束时把这组噪声喂给生成器把输出图片拼接保存下来。这样可以直观看到模型从纯噪声逐渐“学会”画头像的整个过程训练出了问题也能尽早发现。fixed_noise torch.randn(64, latent_dim, 1, 1, devicedevice) # 每个 epoch 结束后: with torch.no_grad(): fake generator(fixed_noise).detach().cpu() # 保存拼接图用固定噪声的意义在于每个 epoch 输入完全相同才能对比同一个潜在向量在不同训练阶段生成效果的变化。如果每次都用随机噪声你根本分不清是模型进步了还是只是这次抽到的噪声比较顺眼。4. 训练过程要点与权重保存策略4.1 超参数选择直接抄作业的参数表训练 DCGAN 的超参数没有一个绝对标准答案不同数据集可能需要微调。但二次元头像这类相对规整的数据集下面这组参数在大多数情况下表现都很稳你完全可以先照抄再慢慢调参数取值说明latent_dim100随机噪声维度过低生成多样性差过高训练变慢batch_size128显存允许范围内越大越稳lr0.0002Adam 学习率过大震荡过小收敛慢beta10.5Adam 一阶矩衰减系数beta20.999Adam 二阶矩衰减系数num_epochs100数据集不大通常够用image_size64生成图分辨率关于 batch_size我多说一句。如果是 8G 显存64x64 分辨率的图batch_size 设 128 基本没问题。如果显存不足导致 OOM优先降低 batch_size而不是降低图片分辨率。分辨率一旦降成 32x32生成结果会丢失大量细节对二次元头像这种本来结构就比较简单的图来说观感会差很多。4.2 观察损失曲线别被数字骗了训练 DCGAN 时最让新手困惑的就是损失曲线和直觉判断不一致。判别器的 loss 不是越低越好生成器的 loss 也不是越低越好这两个数字是在互相拉扯关键是看它们能否维持一个动态平衡。我的经验判断标准是如果判别器 loss 快速降到接近 0说明判别器太强生成器生成的图片一眼就能被识破这时候训练已经失效。如果生成器 loss 快速降到接近 0而判别器 loss 一直很高说明生成器可能找到了判别器的某个漏洞生成的结果可能全是同一张图也就是模型坍塌。比较理想的状态是两个 loss 都在 1 附近震荡幅度不要太剧烈同时固定噪声可视化结果在逐渐变清晰。一个很常见但不该做的操作是看着 loss 不降就加大学习率。GAN 的训练和普通分类网络不一样普通网络 loss 不降是学习率太小或者模型容量不够GAN 训练不稳定时调整学习率反而会加剧振荡。要调的优先顺序应该是先检查网络结构里是否有细节错误再看数据预处理是否正确最后才考虑调整学习率。4.3 权重保存训练到一半崩了也不怕训练 DCGAN 动辄跑几十个 epoch中途断电或者显存溢出是家常便饭所以权重保存一定要设计好。只保留最后一个 epoch 的权重远远不够我习惯每个 epoch 保存一次并且只保留最新的几个检查点。torch.save({ generator_state_dict: generator.state_dict(), discriminator_state_dict: discriminator.state_dict(), optimizer_g_state_dict: optimizer_g.state_dict(), optimizer_d_state_dict: optimizer_d.state_dict(), epoch: epoch, g_loss: g_loss.item(), d_loss: d_loss.item(), }, fcheckpoints/checkpoint_epoch_{epoch}.pth)保存完整字典而不是只保存模型权重是为了能在崩溃后恢复训练。断点续训时要同时加载生成器、判别器和两个优化器的状态并且把 epoch 数也恢复过来checkpoint torch.load(checkpoints/checkpoint_epoch_epoch.pth) generator.load_state_dict(checkpoint[generator_state_dict]) discriminator.load_state_dict(checkpoint[discriminator_state_dict]) optimizer_g.load_state_dict(checkpoint[optimizer_g_state_dict]) optimizer_d.load_state_dict(checkpoint[optimizer_d_state_dict]) start_epoch checkpoint[epoch] 1这里要提醒一点如果用 GPU 训练而加载权重时用的设备不同torch.load可能报 device 不一致的错。简单处理是在加载后调用.to(device)把模型迁移到当前设备同时训练和测试脚本里的device要保持一致。同时我建议每个 epoch 保存后顺带判断一下当前 epoch 的固定噪声生成图片是否比之前的清晰如果更清晰就把这一版单独复制为best_generator.pth。这样即便训练后期出现模式坍缩你手里还有一个质量不错的备份权重。5. 测试与推理用训练好的权重生成二次元头像5.1 加载权重快速生成训练结束后最爽的一步就是加载权重生成头像。测试代码比训练代码简单得多核心流程就是加载生成器、把模型切到评估模式、生成固定噪声、输出图片。import torch from torchvision.utils import save_image device torch.device(cuda if torch.cuda.is_available() else cpu) # 加载模型 generator Generator().to(device) checkpoint torch.load(checkpoints/best_generator.pth, map_locationdevice) generator.load_state_dict(checkpoint if generator_state_dict not in checkpoint else checkpoint[generator_state_dict]) generator.eval() num_images 64 noise torch.randn(num_images, 100, 1, 1, devicedevice) with torch.no_grad(): fake_images generator(noise) # 因为生成器输出范围是 -1 到 1需要映射回 0 到 1 再保存 fake_images (fake_images 1) / 2 save_image(fake_images, outputs/generated_anime.png, nrow8, padding2)测试阶段的with torch.no_grad()一定要加一方面省显存另一方面避免 BatchNorm 和 Dropout 这类在训练和推理时行为不同的层产生额外计算。生成器里的 BatchNorm 在训练时统计的是当前 batch 的均值和方差推理时会用训练阶段累计的全局统计量省去梯度计算后得到的输出才是稳定的。把fake_images映射回(0, 1)范围再保存是因为save_image默认按(0, 1)范围处理。如果不做这个映射保存出来的图片会整体偏暗或偏亮看起来很脏。我在第一次测试时就漏掉了这一步生成图片颜色很怪排查半天才发现不是模型问题而是视觉单位的问题。5.2 潜在空间插值DCGAN 最好玩的玩法训练完毕的生成器其实学了一个从 100 维噪声空间到图片空间的映射。这个空间里隐藏着很多有趣的规律最简单的玩法就是潜在空间插值——在噪声空间里取两个点然后沿着直线逐步过渡观察生成图片的变化。z1 torch.randn(1, 100, 1, 1, devicedevice) z2 torch.randn(1, 100, 1, 1, devicedevice) interpolated [] for alpha in torch.linspace(0, 1, 10): z z1 * (1 - alpha) z2 * alpha with torch.no_grad(): img generator(z) interpolated.append((img 1) / 2) output torch.cat(interpolated, dim0) save_image(output, outputs/interpolation.png, nrow10)第一次跑这个插值时你会发现头像之间能平滑过渡从一个人物的脸型逐渐变成另一个人物的脸型。这说明生成器学到的不只是死记硬背的训练样本而是真的理解了头像的抽象特征。如果插值结果出现突然跳变或者中途变成噪声说明训练还不充分或者模型已经过拟合了训练样本。5.3 生成结果评估不只看 loss要看图和统计很多人问怎么评价生成结果好不好。我觉得实用方法有两个维度。第一是主观视觉。随机生成 64 张头像检查有没有明显的五官扭曲、颜色异常、背景杂乱。合格的模型大多数图片都应该符合二次元头像的基本特征。第二是统计指标。可以计算生成图片和真实图片在特征分布上的差异比如 FID 分数。不过 FID 需要用到 InceptionV3 的特征提取器和数据分布统计对练手项目来说有点重。我更推荐一个轻量方法把生成图片的亮度、饱和度统计值和真实图片做对比如果偏差特别大往往说明模型在色彩分布上没有学好。6. 训练中常见问题与排查技巧实录6.1 问题速查表我在调这个项目时踩了不少坑也帮别人排查过不少问题。下面这些是最常见的整理成表方便你对照排查现象可能原因解决办法生成图片全是模糊一片生成器容量不足或训练不充分增大 ngf 参数量延长训练 epoch生成图片都长一个样模式坍缩判别器太弱生成器赢了对抗降低学习率增大判别器深度检查标签平滑设置判别器 loss 直接掉到接近 0判别器太强减弱判别器比如减少通道数或调高生成器学习率整体画面偏暗/偏亮保存图片时忘记反归一化保存前把(-1, 1)映射到(0, 1)早期训练就出现 NaN学习率过大或初始化问题降低学习率检查输入是否有 NaN训练很慢GPU 利用率低DataLoader 并发数不够设置num_workers4或更高换数据集后效果很差没有调整图片归一化参数确认数据的预处理和生成器输出范围一致6.2 模式坍缩的处理思路模式坍缩是 GAN 训练里最经典也最头疼的问题表现就是生成器找到了一个“安全答案”——只要生成某一类看起来比较像的图就能骗过判别器于是它就不再去学多样的头像了。我之前训练到第 60 个 epoch 时突然发现生成结果全是同一个发型的角色变都没变。复盘之后我的处理顺序是先减小学习率到原来的四分之一让更新更保守。再把判别器的卷积通道数从 64 降到 32削弱判别器能力。把标签平滑值从 0.9/0.1 调得更极端一点比如 1.0/0.0让生成器有更大空间去变换花样。这三步里最有效的是削弱判别器。原理其实很好理解如果判别器能力太强生成器稍微有点偏差就会被抓住干脆躲在几个固定模式里不出来把判别器削弱之后生成器尝试新风格的代价变低了模式坍缩的情况就会缓解。6.3 OOM显存溢出的排查方法显存溢出在训练 DCGAN 时很常见尤其是在生成器输出分辨率调大之后。第一次遇到 OOM 时我下意识把 batch_size 从 128 降到 32但问题还是出现。后来排查才发现问题出在可视化的过程中——每个 epoch 结束后为了把固定噪声生成图保存下来我直接把拼接图保存在显存里没有及时释放随着 epoch 增加显存里的临时张量越积越多。正确做法是把生成结果用detach().cpu()拷回内存再释放 GPU 上的张量。保存完后可以顺手调用torch.cuda.empty_cache()清一下缓存。这里的经验是拿到 OOM 报错先看自己的代码里有没有累积张量的操作而不是无脑降低 batch_size。很多情况下 OOM 不是显存不够而是代码写得不严谨。6.4 源码调试时的几个实用小技巧调试 DCGAN 时我最推荐的方法是把网络每一层的输出尺寸打印出来。你可以在forward内部直接打印张量尺寸确认每层输出的height和width是否符合预期。很多训练时“loss 下降但生成图不对”的问题根源都是网络结构尺寸算错了导致图像信息在中间层丢失。另一个技巧是先用很小的数据集、很少的 epoch 做冒烟测试。比如只放 32 张图跑 2 个 epoch如果代码能跑通再换完整数据。别一上来就用全部数据跑 100 个 epoch那样一旦训练到一半报错大概率已经浪费了几个小时。7. 扩展思考从二次元头像到“训练自己的数据集”7.1 换数据集时真正要改的是什么很多朋友跑通这个项目后下一步就是把 DCGAN 用到自己的图片数据集上比如生成自己的 logo 素材、生成动漫场景甚至生成抽象艺术画。这里我想把话挑明换数据集最容易翻车的地方不在代码而在数据准备和超参数调整。先看数据准备。DCGAN 对图像尺寸非常敏感如果你拿来训练的图片是各种尺寸的混合比如横屏风景图、竖屏人像图混在一起那必须先统一裁剪成正方形再缩放。不统一裁剪就直接 Resize会严重破坏图像的比例结构生成器学到的就是一堆变形的特征。做二次元头像时因为这个图形本身接近正方形这个问题不明显换成生活照就暴露出来了。再看超参数。之前说过 0.0002 的学习率对二次元头像很稳但换到复杂数据集上这个学习率可能就不合适。我的建议是先保持原参数跑一次观察固定噪声的变化情况。如果图片一直模糊没什么纹理就降低学习率到 0.0001 并适当增加 epoch。如果训练振荡明显就把批大小加倍看看批大小加大后梯度估计更准确有助于稳定训练。7.2 与目标检测那些“训练自己的数据集”思路的对比说到训练自己的数据集很多人会联想到 YOLO、Faster R-CNN 这类目标检测模型热词里也经常看到“yolov8 训练自己的数据集”。它们和 DCGAN 这类生成模型在一个底层逻辑上是相通的都要准备数据、都要定义网络、都要训练出权重、都要把权重当成“从数据里学到的内在规则的压缩集合”。区别在于目标检测通常在给定图片中找物体的位置和类别需要的标注文件是框和类别而 DCGAN 不需要标注图片本身就是唯一的信息来源。我同时跑过 YOLO 和 DCGAN 的本地训练最大的体会是目标检测模型的上限更多取决于标注质量标注错了loss 降到一定程度就降不下去了而生成模型的上限更多取决于数据分布的丰富程度如果你的二次元头像数据集里只有固定几种画风生成器再厉害也无法凭空创造新风格。所以训练前花时间把数据集弄干净、弄丰富永远比调参重要。7.3 可选的进阶方向当你把基础的 DCGAN 跑通后会觉得它能力有限生成图像分辨率低、训练不稳定、多样性控制能力弱。这时候可以往几个方向延伸。第一个方向是换更强的生成模型比如 StyleGAN 系列。StyleGAN 在生成高质量人脸和头像上效果极好但它需要的算力和调参复杂度也远远高于 DCGAN不太适合新手第一个上手。第二个方向是给 DCGAN 加上条件控制变成 Conditional GAN。比如训练时把“发色”或者“眼睛颜色”作为条件喂给生成器之后就可以指定条件生成特定风格的图片。这个改动不算大把噪声向量和条件向量拼接在一起就可以了但对理解生成模型的拓展非常有帮助。第三个方向是继续用 PyTorch 原生实现更经典的结构比如 VAE、Diffusion Model。Diffusion 模型在图像生成上的效果确实更好但也更难理解其中的数学推导。如果你把 DCGAN 的对抗训练逻辑吃透了再去看 Diffusion 里前向加噪声、反向去噪的流程会轻松不少。我个人在实际操作中的体会是DCGAN 虽然不是现在效果最强的生成模型但它作为 PyTorch 练手项目的价值极高。数据加载、模型定义、训练循环、权重保存、推理测试这些在任何一个深度学习项目里都要用到的环节它全都覆盖到了而且每一步都有直观的反馈。训练时把固定噪声的生成图从头看到尾那种从一团噪声逐渐变成清晰头像的成就感是跑分类网络给不了的。最后再分享一个小技巧如果训练发现自己在某个 epoch 之后生成结果明显变好别急着停先把当前权重复制一份存好再继续跑。因为 GAN 训练不是永远往上走的后期随时可能崩回去手里有好的 checkpoint后面再怎么折腾都不慌。代码和权重都在手边跑通一遍之后多改几个超参数玩几次你对 PyTorch 和生成模型的理解会提升得比你预想快得多。本文还有配套的精品资源点击获取

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

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

免费获取报价