
简介这份资源是面向深度学习初学者与GAN爱好者的DCGAN PyTorch完整实现包围绕深度卷积生成对抗网络的原理与代码落地展开帮助读者理解生成器与判别器如何通过转置卷积和卷积层协同训练并完成从随机噪声到逼真图像的生成任务。压缩包共31个文件约17.35MB以8个py源码文件为核心涵盖生成器、判别器、数据读取与主训练脚本另附14个pyc缓存、4张png结果图、1个gif训练过程动图及3个txt日志与文件列表并配有readme.md说明文档便于快速上手与复现实验。目前已有3779人学习下载热度较高。读者可借助完整代码、数据集加载脚本与训练日志直观对比不同训练阶段生成图像的变化掌握DCGAN的架构设计、损失函数选择与交替优化流程适合作为GAN入门实践与PyTorch编程的参考案例。1. 从一份能直接跑的 DCGAN 源码说起如果你正在找一份能直接跑起来的 DCGAN 实现大概率已经翻过不少仓库有的只有模型定义没有训练脚本有的训练脚本写死了本地路径有的连数据集都要自己从头准备。这份 DCGAN-pytorch 实现带数据集和说明文件解决的就是这个最实际的问题——拿到手就能跑跑完能看到生成结果而不是先花半天配环境、找数据、改路径。它适合两类人一类是刚接触生成对抗网络、想先跑通一个完整训练流程再回头啃理论的新手另一类是想拿 DCGAN 做二次开发、比如换数据集、改网络结构、加条件标签的开发者。项目本身不复杂但麻雀虽小五脏俱全数据加载、模型定义、训练循环、结果保存这几块都齐了正好拿来当模板改。2. 项目结构与技术选型为什么是 DCGAN 加 PyTorch2.1 目录结构长什么样拿到源码包后先别急着跑花两分钟把目录看一遍后面改代码会顺很多。这类 DCGAN 项目的目录结构通常是这样组织的dcgan-pytorch/ ├── data/ # 数据集目录 │ └── faces/ # 按类别或直接平铺的图片 ├── models/ # 保存训练好的生成器和判别器权重 ├── samples/ # 训练过程中生成的样本图 ├── config.py # 超参数配置 ├── dataset.py # 数据加载与预处理 ├── model.py # 生成器与判别器定义 ├── train.py # 训练主循环 ├── utils.py # 权重初始化、图像保存等工具函数 └── README.md # 说明文件这个划分方式很典型好处是职责清晰想换数据只动dataset.py想改网络只动model.py想调训练策略只动train.py和config.py。我一般拿到陌生项目也是先看这个层次能快速判断改动成本。2.2 为什么选 DCGAN 而不是原始 GAN原始 GAN 用全连接层搭生成器和判别器训练起来非常不稳定生成 64×64 的图都费劲。DCGAN 的核心改动是把卷积引入 GAN生成器用转置卷积做上采样判别器用步长卷积做下采样同时去掉了池化层改用 BatchNorm 稳定训练。具体到这份实现生成器的典型结构是输入一个 100 维噪声向量经过一系列转置卷积逐层放大最后输出 64×64×3 的图像。判别器反过来把图像逐步卷积成一个小特征图最后接一个 Sigmoid 输出真假概率。这个结构在 PyTorch 里写起来很直观也方便你替换成其他尺寸。2.3 PyTorch 版本选择与依赖PyTorch 的版本兼容性是个老生常谈的坑。这份代码如果用的是较新的 PyTorch1.x 以上基本不需要改什么。依赖通常只有三个pip install torch torchvision numpy pillow如果你有 GPU建议装对应 CUDA 版本的 PyTorch训练速度差距很大。CPU 也能跑但 64×64 的图训练几百个 epoch 可能要几个小时这个要有心理预期。提示装 PyTorch 时先去官网查一下对应 CUDA 版本的安装命令直接pip install torch有时候装的是 CPU 版跑起来才发现用不了 GPU。3. 核心模块拆解生成器、判别器与训练循环3.1 生成器从噪声到图像的上采样链路生成器是 DCGAN 里最值得细看的部分因为它决定了生成图像的质量上限。下面是一个典型的生成器实现import torch.nn as nn class Generator(nn.Module): def __init__(self, nz100, ngf64, nc3): super(Generator, self).__init__() self.main nn.Sequential( # 输入: nz x 1 x 1 nn.ConvTranspose2d(nz, ngf * 8, 4, 1, 0, biasFalse), nn.BatchNorm2d(ngf * 8), nn.ReLU(True), # 状态: (ngf*8) x 4 x 4 nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, biasFalse), nn.BatchNorm2d(ngf * 4), nn.ReLU(True), # 状态: (ngf*4) x 8 x 8 nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, biasFalse), nn.BatchNorm2d(ngf * 2), nn.ReLU(True), # 状态: (ngf*2) x 16 x 16 nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, biasFalse), nn.BatchNorm2d(ngf), nn.ReLU(True), # 状态: ngf x 32 x 32 nn.ConvTranspose2d(ngf, nc, 4, 2, 1, biasFalse), nn.Tanh() # 输出: nc x 64 x 64 ) def forward(self, input): return self.main(input)逐段看这段代码。nz100是噪声向量的维度这是 DCGAN 论文里的默认值改大改小都行但太小会导致生成多样性不足。ngf64是生成器特征图的基础通道数控制整个网络的容量显存不够就调小到 32。每个ConvTranspose2d的参数含义是输入通道、输出通道、卷积核大小 4、步长 2、padding 1。这个组合刚好让特征图尺寸翻倍从 1×1 一路到 4×4、8×8、16×16、32×32、64×64。biasFalse是因为后面接了 BatchNorm偏置会被抵消省掉能减少参数量。最后一层用Tanh把输出压到 [-1, 1]这和后面数据预处理把图像归一化到 [-1, 1] 是对应的两边必须一致否则生成器学出来的分布和真实数据对不上。3.2 判别器步长卷积做下采样判别器结构和生成器镜像对称用步长卷积代替池化class Discriminator(nn.Module): def __init__(self, nc3, ndf64): super(Discriminator, self).__init__() self.main nn.Sequential( # 输入: nc x 64 x 64 nn.Conv2d(nc, ndf, 4, 2, 1, biasFalse), nn.LeakyReLU(0.2, inplaceTrue), # 状态: ndf x 32 x 32 nn.Conv2d(ndf, ndf * 2, 4, 2, 1, biasFalse), nn.BatchNorm2d(ndf * 2), nn.LeakyReLU(0.2, inplaceTrue), # 状态: (ndf*2) x 16 x 16 nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, biasFalse), nn.BatchNorm2d(ndf * 4), nn.LeakyReLU(0.2, inplaceTrue), # 状态: (ndf*4) x 8 x 8 nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, biasFalse), nn.BatchNorm2d(ndf * 8), nn.LeakyReLU(0.2, inplaceTrue), # 状态: (ndf*8) x 4 x 4 nn.Conv2d(ndf * 8, 1, 4, 1, 0, biasFalse), nn.Sigmoid() # 输出: 1 x 1 x 1 ) def forward(self, input): return self.main(input).view(-1, 1).squeeze(1)判别器第一层没有 BatchNorm这是 DCGAN 的一个经验做法因为输入是原始图像做归一化反而会破坏像素间的原始统计特性。激活函数用LeakyReLU(0.2)而不是 ReLU是为了避免梯度稀疏让判别器在负半轴也有梯度回传。最后一层输出经过 Sigmoid 变成 0 到 1 之间的概率值view(-1, 1).squeeze(1)是把输出整理成 batch 大小的一维向量方便后面算 BCE 损失。3.3 训练循环两个优化器交替更新训练部分是整个项目最核心的逻辑也是新手最容易看晕的地方。GAN 的训练是生成器和判别器交替更新代码结构如下import torch import torch.nn as nn import torch.optim as optim # 初始化 netG Generator().to(device) netD Discriminator().to(device) criterion nn.BCELoss() optimizerD optim.Adam(netD.parameters(), lr0.0002, betas(0.5, 0.999)) optimizerG optim.Adam(netG.parameters(), lr0.0002, betas(0.5, 0.999)) for epoch in range(num_epochs): for i, data in enumerate(dataloader): # --- 更新判别器 --- netD.zero_grad() real data[0].to(device) batch_size real.size(0) label_real torch.ones(batch_size).to(device) output_real netD(real) lossD_real criterion(output_real, label_real) noise torch.randn(batch_size, nz, 1, 1, devicedevice) fake netG(noise) label_fake torch.zeros(batch_size).to(device) output_fake netD(fake.detach()) lossD_fake criterion(output_fake, label_fake) lossD lossD_real lossD_fake lossD.backward() optimizerD.step() # --- 更新生成器 --- netG.zero_grad() label_gen torch.ones(batch_size).to(device) output_gen netD(fake) lossG criterion(output_gen, label_gen) lossG.backward() optimizerG.step()几个关键点值得展开说。betas(0.5, 0.999)是 DCGAN 论文推荐的 Adam 参数把默认的 0.9 改成 0.5能明显减少训练震荡这是血泪经验不改的话前期 loss 会跳得厉害。判别器更新时fake.detach()很重要它把生成器输出的梯度切断确保这一步只更新判别器的参数。如果忘了 detach生成器会被误更新训练直接翻车。生成器更新时标签用的是torch.ones也就是把假图当成真图来算损失。这是 GAN 对抗的核心生成器的目标就是骗过判别器所以它希望判别器对假图输出 1。注意判别器和生成器的更新顺序不能乱先更新判别器再更新生成器是标准做法。反过来会导致判别器跟不上生成器的变化训练不稳定。3.4 数据加载与预处理数据这块dataset.py通常用ImageFolder加DataLoader的组合from torchvision import datasets, transforms transform transforms.Compose([ transforms.Resize(64), transforms.CenterCrop(64), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), ]) dataset datasets.ImageFolder(rootdata/faces, transformtransform) dataloader torch.utils.data.DataLoader(dataset, batch_size64, shuffleTrue, num_workers2)Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))把像素从 [0,1] 映射到 [-1,1]和生成器最后的 Tanh 对应。num_workers在 Windows 上有时会出问题设成 0 能跑但慢设成 2 或 4 比较合适如果报错就调回 0。4. 跑起来环境配置、训练与结果查看4.1 环境准备与数据集放置先把依赖装好然后确认数据集位置。项目自带的 data 目录里应该已经放好了图片直接平铺或者按子文件夹分类都行ImageFolder会自动扫描。如果你的图片尺寸不统一Resize(64)和CenterCrop(64)会统一处理但建议提前把特别小的图筛掉否则放大后很糊影响生成质量。# 确认目录结构 ls data/faces | head # 启动训练 python train.py4.2 训练过程与日志解读训练启动后控制台通常会打印每个 epoch 的 loss。判别器 loss 和生成器 loss 会此消彼长这是正常的。如果两个 loss 都稳定在一个值附近不动可能是学习率太小或者网络容量不够。如果 loss 剧烈震荡甚至变成 NaN先检查学习率是不是太大再检查有没有忘了 detach。samples/目录下会定期保存生成器输出的图片一般每个 epoch 或每隔几个 epoch 存一张网格图。早期看到的是噪声几十个 epoch 后开始有轮廓几百个 epoch 后五官逐渐清晰。这个过程需要耐心DCGAN 不是几个 epoch 就能出好结果的。4.3 常见报错与快速定位跑不起来的情况无非几种CUDA out of memory把 batch_size 从 64 降到 32 或 16找不到数据检查data/faces路径和ImageFolder的 root 参数是否一致生成的全是灰图多半是归一化和 Tanh 没对上检查 Normalize 的均值和方差是不是 0.5。5. 避坑与排查训练 DCGAN 时最容易翻车的几个点5.1 生成器 loss 一直不降现象是训练了几十个 epoch生成器 loss 居高不下生成的图还是噪声。原因通常是判别器太强把生成器压得没有梯度。解决办法是降低判别器的学习率或者减少判别器的更新次数比如每更新两次判别器才更新一次生成器。5.2 模式崩溃生成的图全都一样现象是生成器只会生成某一类图多样性极差。这是 GAN 的经典问题DCGAN 里可以通过增加噪声维度、加 Dropout、或者用标签平滑来缓解。标签平滑就是把真实标签从 1 改成 0.9假标签从 0 改成 0.1让判别器不那么自信。5.3 显存溢出现象是训练一开始就报 CUDA out of memory。原因可能是 batch_size 太大、ngf/ndf 太大、或者图片尺寸太大。解决顺序是先把 batch_size 减半不行再把 ngf 和 ndf 从 64 降到 32还不行就把图片从 64 降到 32。5.4 生成的图片有网格状伪影现象是生成的图上有规律的方格纹路。这是转置卷积的棋盘效应原因是卷积核大小和步长不匹配。解决办法是把卷积核从 4 改成 3步长保持 2padding 改成 1或者改用最近邻上采样加普通卷积。5.5 训练到一半 loss 突然爆炸现象是前几十个 epoch 都正常突然 loss 变成 NaN。原因可能是学习率过大、梯度爆炸、或者数据里有损坏的图片。解决办法是加梯度裁剪torch.nn.utils.clip_grad_norm_(netD.parameters(), 0.5)同时检查数据集里有没有读不出来的文件。6. 二次开发换数据集、改结构、加条件的实操思路6.1 换数据集要动哪几行换数据集是最常见的二次开发需求。假设你想从人脸换成动漫头像步骤是把新图片放到data/下新建的目录改dataset.py里的 root 路径确认图片尺寸和Resize参数匹配。如果新数据集是灰度图把nc3改成nc1同时 Normalize 的通道数也要改。# 灰度图配置 transform transforms.Compose([ transforms.Resize(64), transforms.CenterCrop(64), transforms.Grayscale(num_output_channels1), transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,)), ]) # 模型初始化时 nc1 netG Generator(nc1).to(device) netD Discriminator(nc1).to(device)6.2 改网络结构的注意事项想加深网络或者改特征图尺寸要保证生成器和判别器的对称性。生成器每加一层转置卷积判别器就要对应加一层步长卷积否则输出尺寸对不上。另外生成器的最后一层输出尺寸必须和真实图片尺寸一致判别器的第一层输入也要匹配。6.3 加条件标签的改造路径如果你想让生成器按类别生成比如指定生成“微笑”或“不微笑”的人脸就需要把 DCGAN 改成条件 GAN。核心改动是在生成器输入里拼接标签的 embedding在判别器输入里也拼接同样的标签信息。这一步改动量不小但思路清晰标签先过 Embedding 层变成向量再和噪声或特征图拼接。6.4 验证改造是否有效的习惯每次改完代码我习惯先跑一个极小的实验把数据集缩到几十张图epoch 设成 5batch_size 设成 8看能不能跑通、loss 有没有正常变化、samples 里有没有图生成。这个流程能快速暴露维度不匹配、路径错误、显存溢出这些问题比直接上全量数据跑几个小时再发现报错要划算得多。从那以后我每次改 GAN 相关代码都强制先跑一遍这个小实验确认链路通了再放大规模。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取