
简介这份资源是基于Python实现的生成对抗网络GAN学习资料包面向具备一定神经网络基础、希望动手理解GAN原理的开发者与学习者。内容围绕判别模型与生成模型两条主线展开判别网络输入图像、输出真假概率生成网络则以随机噪声Z为输入生成图像帮助读者从代码层面厘清对抗训练的完整流程。压缩包共11个文件约630KB包含1个Python源码文件gan.py、1个docx说明文档、1个README.md、1个LICENSE及若干png、jpg训练效果图另有.gitignore等辅助文件源码与文档配合可快速复现实验。其中迭代500次与1000次的生成效果图直观呈现了训练过程的变化便于对照理解模型收敛情况。目前已有359人学习适合作为GAN入门实践与课程作业的参考素材。1. 从一份 GAN 压缩包说起Python 环境、网络结构与训练闭环很多人第一次拿到「基于Python的生成对抗网络GAN.zip」这类压缩包时解压完盯着满屏的.py文件发懵model.py、train.py、utils.py到底谁先跑python train.py一执行就报ModuleNotFoundError或者干脆卡在Downloading MNIST...不动。这个标题背后其实是一条完整的落地链路——用 Python 把生成器Generator和判别器Discriminator搭起来让两者在对抗中互相提升最终生成以假乱真的图像。它适合两类人一类是刚学完 Python 基础语法、想找个能跑通的深度学习项目练手的新手另一类是已经会调库、但没亲手写过 GAN 训练循环、想搞清楚损失函数和梯度更新细节的熟手。这一章先把「这是什么、能解决什么」讲清楚后面几章再拆环境、网络结构、训练循环和踩坑。GAN 的核心思想不复杂生成器负责把随机噪声变成假图判别器负责判断一张图是真是假。两者目标相反训练过程就像造假者和鉴定师互相博弈。原始 GAN 的损失函数里那个交叉熵为什么没有负号是很多人卡住的第一个点——因为判别器要最大化「判对」的概率而生成器要最小化「被判对」的概率写进代码时通过BCEWithLogitsLoss和梯度方向控制负号被吸收进了优化器的更新逻辑里。压缩包里通常包含数据集加载、模型定义、训练脚本和生成结果保存四部分跑通之后你能看到output/目录下每隔几个 epoch 冒出一批越来越像样的手写数字或人脸小图。这一章不贴代码先把整条链路的地图铺开下一章从 Python 环境配置开始动手。2. 把 Python 环境配到能跑 GAN从安装到依赖锁定2.1 为什么 GAN 项目对 Python 版本和 CUDA 特别敏感GAN 训练比普通分类任务更吃显存和算力因为它每步要同时更新两个网络。如果你用 CPU 跑一个简单的 MNIST GAN 跑 50 个 epoch 可能要几小时而 GPU 上几分钟就出结果。所以环境配置的第一原则是先确认显卡和 CUDA 驱动再选 PyTorch 或 TensorFlow 版本最后才装其他依赖。常见做法是打开命令行执行nvidia-smi看右上角CUDA Version那一栏。比如显示12.1那你可以装支持 CUDA 12.1 的 PyTorch 2.x。如果显示11.8就装对应 11.8 的轮子。很多人翻车是因为直接pip install torch装了 CPU 版训练时torch.cuda.is_available()返回False然后对着代码怀疑人生。Python 版本建议 3.8 到 3.10太新的 3.12 有时第三方库还没跟上。安装 Python 时记得勾选Add Python to PATH否则后面在 VSCode 或 PyCharm 里配解释器会找不到。国内源可以加速下载比如清华源https://pypi.tuna.tsinghua.edu.cn/simple在 pip 命令后加-i参数即可。虚拟环境强烈建议用venv或conda隔离避免和系统里其他项目的包版本打架。2.2 用 conda 建环境并锁定 GAN 所需依赖下面这套命令是我在 Linux 和 Windows 上都验证过的流程先建环境再装框架最后补可视化库。# 创建名为 gan_env 的虚拟环境指定 Python 3.9 conda create -n gan_env python3.9 -y # 激活环境 conda activate gan_env # 安装 PyTorch以 CUDA 11.8 为例具体版本按 nvidia-smi 结果调整 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装常用辅助库numpy 做数值计算matplotlib 画损失曲线tqdm 显示进度条 pip install numpy matplotlib tqdm pillow # 验证 GPU 是否可用 python -c import torch; print(torch.cuda.is_available())逻辑说明conda create负责隔离环境python3.9是兼容性较好的版本。PyTorch 安装命令里的--index-url指向官方 CUDA 11.8 轮子仓库如果你的是 CUDA 12.1把cu118换成cu121。最后一行验证命令如果输出True说明 GPU 环境通了输出False就回到nvidia-smi检查驱动或者确认你装的是不是 CPU 版。numpy和matplotlib用来画生成器与判别器的损失曲线tqdm给训练循环加进度条pillow用于保存生成的图像。2.3 VSCode 和 PyCharm 里怎么选对解释器装完环境后在 VSCode 里按CtrlShiftP输入Python: Select Interpreter找到gan_env对应的python.exe路径。PyCharm 则在File Settings Project Python Interpreter里添加conda环境。这一步不做的话编辑器会一直提示cannot be resolved against python helper roots代码补全和跳转全废。选好解释器后在项目根目录新建.vscode/settings.json写入python.defaultInterpreterPath: 你的环境路径团队协作时别人克隆下来也能快速对齐。提示如果pip install卡在Solving environment换成pip直接装而不是conda installconda 的依赖求解在装 PyTorch 时经常慢得离谱。3. 生成器与判别器的网络结构从全连接到卷积怎么选3.1 原始 GAN 的 MLP 结构为什么在图像上翻车最早的 GAN 用多层感知机MLP做生成器和判别器输入是 100 维噪声输出是 784 维的 MNIST 图像。这种结构在低分辨率灰度图上勉强能跑但一换到彩色人脸或 64x64 以上的图生成结果就糊成一团。原因是全连接层把图像展平后丢失了空间结构生成器不知道相邻像素之间的关系判别器也学不到局部纹理特征。血泪经验是只要你的目标图像超过 32x32就别用 MLP直接上卷积。3.2 DCGAN 的四个结构约束与代码实现DCGANDeep Convolutional GAN给了一套可复现的卷积结构规则判别器用步长卷积代替池化生成器用转置卷积上采样两者都加 BatchNorm生成器输出层和判别器输入层除外激活函数生成器用 ReLU、最后一层用 Tanh判别器统一用 LeakyReLU。下面是一个生成 64x64 彩色图像的生成器定义。import torch import torch.nn as nn class Generator(nn.Module): def __init__(self, z_dim100, img_channels3, feature_dim64): super(Generator, self).__init__() self.net nn.Sequential( # 输入 z: (batch, 100, 1, 1) - (batch, 512, 4, 4) nn.ConvTranspose2d(z_dim, feature_dim * 8, 4, 1, 0, biasFalse), nn.BatchNorm2d(feature_dim * 8), nn.ReLU(True), # (batch, 512, 4, 4) - (batch, 256, 8, 8) nn.ConvTranspose2d(feature_dim * 8, feature_dim * 4, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_dim * 4), nn.ReLU(True), # (batch, 256, 8, 8) - (batch, 128, 16, 16) nn.ConvTranspose2d(feature_dim * 4, feature_dim * 2, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_dim * 2), nn.ReLU(True), # (batch, 128, 16, 16) - (batch, 64, 32, 32) nn.ConvTranspose2d(feature_dim * 2, feature_dim, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_dim), nn.ReLU(True), # 输出层: (batch, 64, 32, 32) - (batch, 3, 64, 64)Tanh 压到 [-1,1] nn.ConvTranspose2d(feature_dim, img_channels, 4, 2, 1, biasFalse), nn.Tanh() ) def forward(self, z): return self.net(z)逻辑说明z_dim100是噪声向量维度feature_dim64控制每层通道基数。每个ConvTranspose2d的kernel_size4, stride2, padding1组合让特征图尺寸翻倍从 4x4 一路到 64x64。biasFalse是因为后面接了 BatchNorm偏置会被归一化抵消省掉能减少参数量。最后一层用Tanh把像素值压到[-1,1]和训练时对真实图像的归一化范围保持一致。判别器则是生成器的镜像把转置卷积换成普通卷积最后输出一个标量概率。class Discriminator(nn.Module): def __init__(self, img_channels3, feature_dim64): super(Discriminator, self).__init__() self.net nn.Sequential( # 输入 (batch, 3, 64, 64) - (batch, 64, 32, 32) nn.Conv2d(img_channels, feature_dim, 4, 2, 1, biasFalse), nn.LeakyReLU(0.2, inplaceTrue), # - (batch, 128, 16, 16) nn.Conv2d(feature_dim, feature_dim * 2, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_dim * 2), nn.LeakyReLU(0.2, inplaceTrue), # - (batch, 256, 8, 8) nn.Conv2d(feature_dim * 2, feature_dim * 4, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_dim * 4), nn.LeakyReLU(0.2, inplaceTrue), # - (batch, 512, 4, 4) nn.Conv2d(feature_dim * 4, feature_dim * 8, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_dim * 8), nn.LeakyReLU(0.2, inplaceTrue), # 输出 (batch, 1, 1, 1)后面接 Sigmoid 或 BCEWithLogitsLoss nn.Conv2d(feature_dim * 8, 1, 4, 1, 0, biasFalse), ) def forward(self, x): return self.net(x).view(-1, 1)参数说明LeakyReLU(0.2)的负斜率 0.2 是 DCGAN 论文推荐值让负区间有梯度避免神经元死亡。判别器第一层不加 BatchNorm因为输入是原始像素归一化会破坏颜色分布。最后view(-1, 1)把输出压成(batch, 1)方便和标签计算损失。3.3 条件生成对抗网络cGAN的标签嵌入怎么加如果你想让生成器按类别出图比如指定生成「数字 7」而不是随机数字就需要条件生成对抗网络。做法是在生成器输入和判别器输入里都拼接类别标签的嵌入向量。生成器把标签嵌入后和噪声在通道维拼接判别器则把标签嵌入扩展成空间维度后和图像拼接。这样训练后你可以用z加label7生成指定数字。条件 GAN 的损失函数和原始 GAN 一样只是判别器的输入多了一个条件信息代码改动量不大但数据加载时要同时返回图像和标签。4. 训练循环与损失函数把对抗过程写成可调试的代码4.1 原始 GAN 公式里的负号到底去哪了原始 GAN 的极小极大博弈公式写的是判别器最大化log D(x) log(1 - D(G(z)))生成器最小化log(1 - D(G(z)))。落到 PyTorch 代码里判别器的损失是BCEWithLogitsLoss对真实样本和假样本分别算再相加生成器的损失是把假样本标签设成 1 再算交叉熵。负号没有消失而是被「最小化生成器损失」这个动作吸收了——你写loss_G criterion(D(fake), ones)优化器执行loss_G.backward()时梯度方向自动指向让判别器判对的方向等价于原始公式里的最小化。很多人搜「原始gan公式的交叉熵为什么没有负号」答案就在这代码层面用标签翻转代替了显式负号。4.2 一个可跑通的训练循环模板下面这段代码把数据加载、模型初始化、损失函数和优化器串起来每个 epoch 先更新判别器再更新生成器。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transforms from tqdm import tqdm # 超参数 z_dim 100 batch_size 64 lr 0.0002 beta1 0.5 epochs 50 device torch.device(cuda if torch.cuda.is_available() else cpu) # 数据加载归一化到 [-1,1]和生成器 Tanh 输出对齐 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(data/your_dataset, transformtransform) dataloader DataLoader(dataset, batch_sizebatch_size, shuffleTrue, num_workers2) # 初始化模型、损失、优化器 G Generator(z_dim).to(device) D Discriminator().to(device) criterion nn.BCEWithLogitsLoss() optimizer_G optim.Adam(G.parameters(), lrlr, betas(beta1, 0.999)) optimizer_D optim.Adam(D.parameters(), lrlr, betas(beta1, 0.999)) for epoch in range(epochs): for i, (real_imgs, _) in enumerate(tqdm(dataloader)): real_imgs real_imgs.to(device) cur_batch real_imgs.size(0) # --------------------- # 训练判别器真图标签为 1假图标签为 0 # --------------------- optimizer_D.zero_grad() # 真图损失 pred_real D(real_imgs) loss_D_real criterion(pred_real, torch.ones_like(pred_real)) # 假图损失生成器不更新用 detach 切断梯度 z torch.randn(cur_batch, z_dim, 1, 1).to(device) fake_imgs G(z) pred_fake D(fake_imgs.detach()) loss_D_fake criterion(pred_fake, torch.zeros_like(pred_fake)) loss_D (loss_D_real loss_D_fake) / 2 loss_D.backward() optimizer_D.step() # --------------------- # 训练生成器希望判别器把假图判成真标签设为 1 # --------------------- optimizer_G.zero_grad() pred_fake_for_G D(fake_imgs) loss_G criterion(pred_fake_for_G, torch.ones_like(pred_fake_for_G)) loss_G.backward() optimizer_G.step() print(fEpoch {epoch} | loss_D: {loss_D.item():.4f} | loss_G: {loss_G.item():.4f}) # 每个 epoch 保存一批生成图方便肉眼观察质量变化 if epoch % 5 0: from torchvision.utils import save_image save_image(fake_imgs, foutput/epoch_{epoch}.png, normalizeTrue)逻辑说明判别器训练时fake_imgs.detach()是关键它阻止梯度回传到生成器确保这一步只更新判别器参数。生成器训练时复用同一个fake_imgs但此时梯度会穿过判别器回传到生成器。beta10.5是 DCGAN 论文推荐值比默认的 0.9 更稳定能减少训练震荡。save_image的normalizeTrue会把[-1,1]的像素重新映射到[0,1]再保存否则图片看起来全黑。4.3 损失曲线怎么读判别器太强或太弱的表现训练时盯着loss_D和loss_G两条曲线。理想状态是两者都在 0.5 到 1.0 之间波动生成器损失缓慢下降。如果loss_D迅速掉到 0.1 以下说明判别器太强生成器梯度消失生成的图会一直很糊。解决办法是降低判别器学习率或者给判别器加 dropout或者减少判别器每步更新次数。反过来如果loss_D一直在 0.7 以上判别器太弱生成器会输出重复样本骗过判别器这叫模式崩溃。常见做法是调整lr让两者学习率不同比如判别器用 0.0001生成器用 0.0002。5. 避坑与排查GAN 训练中最容易翻车的五个地方5.1 生成图片全黑或全白现象output/里的图要么纯黑要么纯白看不出任何数字或人脸轮廓。原因通常是归一化范围不匹配——生成器最后一层用Tanh输出[-1,1]但保存时没加normalizeTrue或者数据加载时用了[0,1]归一化而生成器学的是[-1,1]。解决检查transforms.Normalize的均值和方差是否为(0.5,0.5,0.5)保存图片时加normalizeTrue。5.2 训练几轮后 loss 变成 NaN现象loss_D或loss_G突然变成nan后续所有输出都是噪声。原因可能是学习率太大、BatchNorm 在 batch size 太小时不稳定、或者数据里有损坏图像。解决把学习率降到 0.0001 或更低batch size 至少 16检查数据集里有没有全黑或尺寸异常的图。另外在BCEWithLogitsLoss前不要手动加 Sigmoid否则数值不稳定。5.3 模式崩溃生成器只输出同一张图现象不管输入什么噪声生成器都输出几乎一样的图。原因是判别器太弱或生成器学得太快生成器发现某一个样本能稳定骗过判别器就不再探索其他模式。解决降低生成器学习率给判别器加标签平滑把真实标签从 1 改成 0.9或者引入 minibatch discrimination。常见做法是每训练生成器一次就训练判别器两次保持判别器略强。5.4 CUDA out of memory现象训练到一半报RuntimeError: CUDA out of memory。原因是 batch size 太大或模型通道数太多。解决把 batch size 减半或者把feature_dim从 64 降到 32。另外在验证阶段用torch.no_grad()包住避免保存计算图。如果还是不够用torch.cuda.empty_cache()手动清理缓存。5.5 数据加载卡住不动现象tqdm进度条一直停在 0%或者报BrokenPipeError。原因是num_workers设太大导致多进程通信问题或者数据集路径写错。解决把num_workers设为 0 先跑通确认路径无误后再逐步加到 2 或 4。Windows 上num_workers大于 0 有时需要把训练代码放在if __name__ __main__:里面。6. 进阶技巧用特征匹配和 TTUR 把生成质量再提一档跑通基础版之后如果你想让生成的人脸或物体更清晰可以试两个改动量小但效果明显的技巧。第一个是特征匹配Feature Matching出自 Salimans 等人的 Improved Techniques 论文。做法是不直接让生成器骗过判别器输出而是让生成器生成的假图在判别器中间层的特征和真实图特征尽量一致。代码上把生成器损失从BCEWithLogitsLoss换成中间层特征的 L2 损失。# 假设判别器返回中间层特征和最终输出 def forward(self, x): features [] for layer in self.net: x layer(x) features.append(x) return x.view(-1, 1), features # 生成器损失改为特征匹配 pred_fake, feat_fake D(fake_imgs) _, feat_real D(real_imgs) loss_G sum( nn.functional.mse_loss(f_fake, f_real.detach()) for f_fake, f_real in zip(feat_fake, feat_real) )逻辑说明feat_real.detach()确保真实图特征不参与梯度更新只作为目标。这样生成器不再追求「骗过判别器」而是追求「在判别器眼里和真图长得像」训练更稳定模式崩溃概率降低。第二个技巧是 TTURTwo Time-scale Update Rule出自同一篇论文。核心思想是让判别器和生成器用不同的学习率通常判别器学习率是生成器的 2 到 4 倍。比如optimizer_D用lr0.0004optimizer_G用lr0.0001。这样判别器先学好特征生成器再慢慢跟上避免生成器过早收敛到局部最优。验证改进是否有效不能只看损失曲线要定期用固定噪声生成同一批图对比。我习惯每 5 个 epoch 用torch.manual_seed(42)固定一组z保存成网格图训练结束后按时间顺序排开肉眼判断清晰度和多样性是否在提升。如果特征匹配跑完 50 个 epoch 后生成的人脸仍然模糊检查判别器中间层特征有没有做归一化未归一化的特征 L2 损失会被大数值主导。我自己踩过最深的坑是过早调大模型通道数以为feature_dim128能出更清晰的图结果显存爆了不说训练时间翻倍生成质量反而因为判别器过强而下降。后来固定用feature_dim64加 TTUR在 64x64 人脸数据集上 30 个 epoch 就能看到五官轮廓。希望帮到你。本文还有配套的精品资源点击获取