ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GAN到DCGAN/ACGAN:生成对抗网络理论与PyTorch实战全解析

GAN到DCGAN/ACGAN:生成对抗网络理论与PyTorch实战全解析 自己折腾了半个月的GAN从最开始连损失函数都看不懂到后来能熟练调参训练出稳定出图的DCGAN和ACGAN这中间踩的坑实在太多了。今天把整套学习笔记整理出来主要围绕生成对抗网络、DCGAN和ACGAN这三个核心模型展开从理论推导到PyTorch代码实现再到训练技巧和排坑经验一次讲清楚。这份笔记适合有一定深度学习基础、了解卷积神经网络但还没系统学过生成模型的读者。如果你是刚接触深度学习的小白建议先补齐BP反向传播和CNN的基础知识再来阅读。我们会从最基本的对抗思想讲起一步步推导到ACGAN这种带条件控制的生成模型全程附带可以运行的代码片段和我在实际训练中记录的损失曲线、图像效果变化。1. 核心思想与整体设计思路1.1 为什么需要生成模型它解决什么问题传统监督学习解决的是“从输入到输出”的映射问题比如给一张猫的图片判断它是猫还是狗。但生活中大量问题属于“没有标准答案”的生成问题——给一段文字生成配图、给模糊图片补全高清细节、给黑白照片上色、生成新的游戏角色立绘。这些问题你没法拿标注好的数据集去监督学习因为“正确答案”本身就不唯一。生成模型的终极目标是学会训练数据背后的真实概率分布然后从这个分布中采样出全新样本。生成对抗网络GAN就是这类模型中最具代表性的一个。它的核心思路非常反直觉不用去显式建模概率密度函数而是通过两个网络相互博弈让生成器在“骗过判别器”的过程中隐式地逼近真实数据分布。在我第一次看到这个思想的时候脑子里蹦出来的类比是一群造假币的犯人和一个验钞员。造假者不断改进工艺验钞员不断提升鉴别能力两者互相逼迫。等这场博弈收敛时造假者造出来的假币已经能以假乱真换句话说生成器学到了真实货币的“分布”。GAN的数学本质确实就是这个过程两个网络玩一个二人零和博弈最后达到纳什均衡。1.2 GAN的架构拆解生成器、判别器与博弈目标GAN由两个神经网络组成。生成器G接收一个从简单分布通常是标准正态分布采样的随机噪声向量z输出一张图像G(z)。判别器D接收一张图像输出一个0到1之间的标量表示这张图是真实数据的概率。训练时真实图像标记为1生成图像标记为0D的目标是尽量区分两者G的目标是让D把生成图像判断为1。数学上整个博弈过程被写成这样一个minimax目标函数[ \min_G \max_D V(D,G) \mathbb{E}{x \sim p{data}}[\log D(x)] \mathbb{E}_{z \sim p_z}[\log(1 - D(G(z)))] ]这个公式初看有点抽象拆开看其实很简单。第一项是判别器对真实图片输出logD(x)的期望判别器想让这个值尽量大也就是把真实图片判成1。第二项是判别器对生成图片输出log(1-D(G(z)))的期望判别器想让1-D(G(z))尽量大等价于让D(G(z))尽量小也就是把假图判成0。生成器正好相反它希望D(G(z))尽量大也就是让判别器把假图错判成真图。实际训练时我们不是直接求解这个minimax问题而是交替优化。先固定G训练D若干步再固定D训练G一步。这个交替训练的策略非常关键——如果G更新太快D还没学会鉴别整个训练就崩了。我在训练第一个GAN时犯过一个典型错误直接让G迭代太多轮结果生成图像全是毫无意义的噪声纹理。后来才意识到博弈训练的节奏比普通监督学习敏感得多两个网络的能力必须大致匹配。就像下棋两个旗鼓相当的对手才能越下越精彩一方碾压另一方的话棋局很快就结束了谁都没进步。2. 从理论到实践GAN的训练过程与损失函数细节2.1 交替训练的完整流程与代码骨架GAN的训练是一个交替迭代过程。下面这个PyTorch伪代码是我调试过很多版本后留在笔记里的骨架每一步都有对应的数学含义# 伪代码骨架实际训练需配合DataLoader for epoch in range(epochs): for i, (real_images, _) in enumerate(dataloader): batch_size real_images.size(0) # 1. 构造真实和伪造标签 real_labels torch.ones(batch_size, 1) fake_labels torch.zeros(batch_size, 1) # 2. 训练判别器真实图像判为1生成图像判为0 optimizer_D.zero_grad() # 真实图像损失 real_output discriminator(real_images) loss_D_real criterion(real_output, real_labels) # 用当前生成器产生假图 z torch.randn(batch_size, latent_dim, 1, 1) fake_images generator(z) fake_output discriminator(fake_images.detach()) loss_D_fake criterion(fake_output, fake_labels) loss_D loss_D_real loss_D_fake loss_D.backward() optimizer_D.step() # 3. 训练生成器让判别器把假图判为真 optimizer_G.zero_grad() fake_output discriminator(fake_images) loss_G criterion(fake_output, real_labels) # 注意标签是1 loss_G.backward() optimizer_G.step()注意生成器训练时我们使用了真实标签全1数组来骗判别器。这就是所谓的“非饱和损失”设计来自Goodfellow等人2014年论文的改进版本。早先版本用的是minimax损失也就是最小化log(1-D(G(z)))但这个函数在D训练得太好时梯度极小生成器几乎学不到任何东西。改成最小化-log(D(G(z)))之后当生成图像很差、D(G(z))接近0时梯度反而很大更有利于生成器进步。这个小改动是整个GAN能训练起来的基石之一。2.2 原始GAN的局限为什么训练这么难最初版本的GAN在MNIST等简单数据集上能生成看得过去的数字但换到稍微复杂的数据集就问题百出。我统计了自己训练原始GAN时遇到的几个典型现象生成图像多样性极差全部集中在一两个模式上这就是著名的模式崩塌。判别器损失迅速降到接近0之后生成器梯度爆炸或消失训练彻底停滞。生成的图像带有明显噪点边缘和真实图像分布有明显差异。模式崩塌的本质原因从数学上看是生成器在最小化Jensen-Shannon散度时倾向于找出几个最容易骗过判别器的“捷径”。特别是当真实分布是多峰分布时GAN的minimax目标和JS散度只关注重叠部分的拟合容易漏掉分布在支撑集外的模式。从实践角度看解决模式崩塌最粗暴的方法是增大生成器的容量、降低判别器的能力让判别器不要学得太快。但这样治标不治本于是研究者们在原始GAN基础上做了两个重要改进一是用卷积替代全连接让生成器有空间结构先验这就是DCGAN二是引入类别信息让生成器必须学会每一类的特征分布这就是ACGAN。3. 深度卷积生成对抗网络DCGAN的核心演进3.1 DCGAN架构设计中的四把火DCGAN是Radford等人2015年提出的架构改进全称Deep Convolutional Generative Adversarial Networks核心思想是把生成器和判别器的全连接层全部替换成卷积与转置卷积。但DCGAN的真正价值不只在换了网络结构而是总结出了一套让GAN训练稳定下来的架构准则。第一用带步幅的卷积替换池化层。生成器用转置卷积做上采样判别器用步幅为2的普通卷积做下采样。规则是生成器的每一层转置卷积将图像长宽翻倍判别器的每一层步幅卷积将长宽减半。这样整个网络是全卷积结构参数数量大幅减少同时空间信息保留得更好。第二除生成器输出层使用Tanh激活函数外生成器其余层统一使用ReLU。判别器除输出层使用Sigmoid外其余层统一使用LeakyReLU斜率参数一般设为0.2。这里有一个很容易踩的坑生成器输出层不能用ReLU因为ReLU会把输出截断到非负区间而图像像素归一化后范围在[-1,1]负值无法表达。第三几乎每一层都使用BatchNorm。BN层的核心作用是统一各层输入的分布防止梯度在深层网络中消失或爆炸。在使用BN时要特别注意判别器的输入层和生成器的输出层不能加BN否则会让训练不稳定。第四生成器的输入从全连接层改为一维噪声向量直接reshape成四维张量然后通过转置卷积逐层放大空间尺寸。比如输入z的维度是100x1x1经过5层转置卷积后逐步变为4x4、8x8、16x16、32x32、64x64的特征图。下面这个表格是我在实际项目中常用的DCGAN生成器配置以输出64x64的RGB图像为例层输入尺寸输出尺寸卷积核步幅激活函数转置卷积1100x1x1512x4x44x41ReLUBN转置卷积2512x4x4256x8x84x42ReLUBN转置卷积3256x8x8128x16x164x42ReLUBN转置卷积4128x16x1664x32x324x42ReLUBN转置卷积564x32x323x64x644x42Tanh3.2 为什么卷积结构更适合图像生成全连接神经网络在处理图像时会破坏空间局部性。一个全连接层会把整张图像的所有像素展开成一维向量每个神经元都和所有像素相连这意味着它无法内建“相邻像素相关性强”这一先验知识。CNN则通过局部感受野和权重共享两大特性天然适合处理网格结构数据。转置卷积可以理解为卷积的“逆操作”它通过插值配合卷积核将小特征图放大。从数学上看转置卷积和普通卷积的梯度计算是完全对偶的因此它能够端到端学习上采样过程而不是像传统方法那样依赖固定的双线性插值或最近邻插值。我做过一个对比实验在相同数据和参数下把DCGAN的转置卷积换成双线性插值加普通卷积生成图像清晰度明显下降尤其边缘存在模糊现象。这说明可学习的上采样远比固定插值效果好因为它能根据数据自动学会如何重建高频细节。3.3 DCGAN的训练配置经验DCGAN的论文给出了一套经过大量调参验证的默认配置我自己实验后确认这套配置对多数数据集都适用。优化器选择Adam学习率统一设为0.0002beta1设为0.5beta2设为0.999。这个beta10.5和常规Adam默认的0.9很不一样原因在于GAN训练的早期阶段梯度波动很大如果使用0.9会导致动量累计过快训练震荡严重。把beta1降到0.5后动量衰减更快训练稳定性明显提升。学习率0.0002也远低于常规分类任务的默认值这是为了让G和D的更新步伐保持同步。批大小一般取64或128但我实测在GPU显存足够的情况下把batch size从64提到128会带来更稳定的梯度估计代价是单轮训练时间变长。潜在空间维度latent_dim通常取100这是从原始GAN论文沿用下来的值。增大这个值可以提升生成样本的多样性但也会增加生成器学习难度。数据集方面图像需要预处理到[-1,1]范围和生成器输出层的Tanh激活函数范围一致。这一点看似简单但很多人会忽略直接用0-1范围的数据训练导致生成器一直无法收敛——因为判别器看到的真实分布和生成器能产生的分布天然错位。我在第一次训练DCGAN时就踩了这个坑图像一直灰蒙蒙的排查了很久才发现是数据归一化范围错了。4. 带有条件控制的ACGAN详解4.1 ACGAN的动机让生成可控DCGAN虽然能生成质量不错的高清图像但无法控制生成什么内容。你输入一个随机噪声只能被动接受生成器输出的结果可能是猫可能是狗也可能是沙发。这在很多场景下是不可接受的。比如做游戏素材生成我希望指定要“一个穿红衣服的弓箭手角色”而不是随机生成一个不知名的角色。ACGANAuxiliary Classifier GAN辅助分类器GAN由Odena等人在2017年提出核心思路是在生成器和判别器中同时引入类别标签让生成器不仅接收随机噪声z还接收类别向量c输出指定类别的图像。判别器则同时输出两个结果一个标量表示图像真伪一个类别概率分布表示图像属于哪个类别。ACGAN最大的创新点在于判别器除了做二分类真假判别还要额外完成一个多分类的辅助任务——判断输入图像的具体类别。这个辅助分类器的梯度流会让判别器学习到更丰富的类别特征表达这些特征通过对抗损失反向传播给生成器迫使生成器针对每一类都生成足够逼真的图像。原本类别特征混杂在一个隐空间里难以分离现在被显式地逼迫到不同条件子空间中。4.2 ACGAN的损失函数变化与代码实现ACGAN的损失函数由两部分组成。判别器总损失等于真假分类损失加辅助分类损失。假图像的辅助分类损失是基于生成器指定的类别计算的真实图像的辅助分类损失基于真实标签计算。生成器总损失则只有两部分让判别器把生成图像判为真的对抗损失以及让判别器正确识别生成图像类别的辅助分类损失。用PyTorch实现ACGAN时代码结构和DCGAN类似区别在于判别器输出从单个标量变为两个独立输出生成器的输入需要拼接类别向量和噪声向量。类别向量通常先过一个Embedding层将离散类别映射为连续的高维向量再和噪声向量拼接。下面是我自己实验后整理的一个简化版ACGAN实现要点# 生成器输入构造 z torch.randn(batch_size, latent_dim, 1, 1) c torch.randint(0, num_classes, (batch_size, 1)) # 类别标签经过Embedding层映射为类嵌入向量 c_emb embedding(c).permute(0, 3, 1, 2) # 调整为与z一致的尺寸 # 沿特征通道拼接 gen_input torch.cat([z, c_emb], dim1) # 判别器输出构造用同一个主干网络提取特征 feature feature_extractor(x) validity fc_adv(feature) # 输出标量真实/伪造 label fc_aux(feature) # 输出类别概率分布关于Embedding类的嵌入维度类别数量少时用32或64即可类别多时适当增大到128。我实验发现嵌入维度太小比如8会导致生成图像类别风格趋同太大比如256则容易过拟合训练集中的噪声特征。4.3 类别条件如何影响生成质量ACGAN的辅助分类器设计对生成质量有三重影响。第一重它让生成器必须学会每类的区分性特征否则辅助分类器无法正确分类梯度会惩罚它。第二重它让判别器不再只关注“像不像真实图像”这个整体特征而是同时关注“是不是某类可识别的对象”这促使判别器学习到更有判别力的中间特征表达。第三重从梯度流角度看辅助分类器的梯度信号比单纯对抗信号更平滑因为它是一个有明确正确答案的分类任务可以为生成器提供更稳定的学习方向。我用ACGAN在CIFAR-10数据集上做过实验生成效果和同配置的DCGAN相比单类图像的清晰度和类间可分性都有明显提升。特别是容易混淆的类别比如猫和狗DCGAN生成的图像经常介于两者之间的模糊状态ACGAN则基本能稳定生成清晰的某一类。话说回来ACGAN也有一个需要警惕的副作用如果类别数很多且数据量不足辅助分类器会变得过于强大导致生成器陷入类别过拟合生成的图像缺乏类内多样性。这个概念类似于过拟合判别器把所有注意力放在类别区分上忽略了对类内细节的建模。5. 从零训练一个DCGAN/ACGAN的完整实操5.1 环境配置与数据准备你的硬件如果只有CPU训练64x64以上分辨率的GAN会非常痛苦。我最初一台老笔记本训练MNIST还可以接受换到CIFAR-10就明显力不从心。有条件的话建议使用单张显存不低于8GB的GPU。CPU训练虽然可行但迭代速度会成为最大的瓶颈。数据准备阶段需要关注几个点。第一图像统一缩放并中心裁剪为目标尺寸我建议用64x64作为起步分辨率训练速度快且效果直观。第二数据归一化必须缩放到[-1,1]和生成器输出层的Tanh匹配。第三数据增强尽量少用或者不用因为GAN训练需要真实数据分布保持原本状态过度增强可能让生成器学到增强痕迹。我最常用的数据加载模板是transforms.Compose([ transforms.Resize(image_size), transforms.CenterCrop(image_size), transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ])注意Normalize的mean和std设为0.5公式是(x-0.5)/0.5正好把0-1范围的图像映射到-1到1。5.2 训练流程、超参数监控与图像效果调试正式训练ACGAN时我在日志里记录五条曲线判别器真实损失、判别器生成损失、生成器对抗损失、生成器分类损失、判别器分类准确率。每过一定epoch保存一次生成器参数并固定随机种子生成一组预览图持续跟踪视觉效果。训练的初始阶段epoch 1-10是最难熬的。生成器还没学会任何结构输出的完全是随机噪点。判别器则学得很快很快就能把真图和假图完全区分开。此时判别器损失会比生成器损失低很多这是正常现象不要慌。大约训练20-30个epoch之后能开始看到图像的粗略轮廓。此时如果生成图像还是纯噪声就要检查是不是学习率太高、判别器太强或者生成器的容量不足。如果生成图像只在若干类之间切换观察判别器辅助分类的准确率——如果准确率持续接近100%说明判别器分类分支过于强大可以考虑降低判别器的学习率。我在实际调参中总结出了一个比较有效的节奏训练初期让判别器稍微弱一点学习率低一点中期保证两者能力均衡后期可以适当让判别器更强来迫使生成器提升细节质量。整体训练过程大约需要100-300个epoch才能获得比较满意的效果具体取决于数据集复杂度和硬件性能。5.3 一次MNIST手写数字生成的小型实践记录下面记录一次我在MNIST上用ACGAN从零训练的实验过程。MNIST图像是28x28灰度图为了保持结构稳定我把它padding到32x32输入。潜在空间维度设为100类别数10batch size 128学习率0.0002beta10.5。训练50个epoch后生成图像已经能清晰看到数字轮廓。此时分类分支的测试准确率约80%真假判别准确率约90%。到150个epoch时生成图像在视觉效果上已经和真实图像难以区分。我通过固定相同的随机噪声向量z、切换不同类别标签c来观察生成效果发现同一套噪声在不同类别下的数字风格具有一定一致性说明生成器确实学会了分离“形状风格”和“类别特征”这两类信息。有一个值得记录的现象分类准确率并不是稳定上升的中间会出现明显的波动尤其在40到60个epoch时判别器分类准确率骤降了将近15%。这说明生成器在某些类别上突然学会了一种能迷惑判别器的生成模式但很快又被判别器识破。这种对抗波动是正常现象只要整体趋势在改善就无需干预。6. 常见问题与排查技巧实录6.1 模式崩塌生成图像多样性不足模式崩算是GAN家族最顽固的问题。典型表现是生成图像看似高质量但变化很少比如CIFAR-10中只产生马和汽车的图像其他类别几乎不出现。我的排查顺序是这样的。先怀疑类别信息是否被正确传递到生成器检查Embedding层是否参与训练、类别标签是否和输入做了正确拼接。再怀疑判别器是否过于强大可以通过大幅降低判别器学习率比如从0.0002降到0.00005或给判别器增加Dropout来削弱它。还怀疑潜在噪声向量z的维度太小导致生成器的表达能力受限尝试增大latent_dim到256。如果上述手段都无效最后试试给真实标签添加噪声——也就是标签平滑技术label smoothing把真实标签的1替换成0.9左右防止判别器对真实图像过于自信这能在一定程度缓解模式崩塌。6.2 训练不收敛损失函数震荡严重GAN训练的损失曲线不像分类任务那样平稳下降它天然就是震荡的。但如果震荡幅度大到生成图像质量时好时坏就需要干预。常见原因之一是学习率太高。可以把学习率调低一个数量级或使用学习率衰减。另一个常见原因是batch size太小导致梯度估计噪声大我建议batch size至少64。还有一个容易被忽略的原因是BN层在小batch size下统计量不稳定这时可以考虑使用 spectral normalization 替代部分BN层。一个实用的判断标准是如果两边损失都长期维持在一个高水平且无任何下降趋势说明两个网络可能都在原地打转。这时可以试试重新初始化模型而不是继续硬着头皮训练。我在多次实验中确认GAN训练对初始化特别敏感同样的参数换一套随机种子结果差距可能非常大——这倒逼我们在实验时固定随机种子保证对比实验的公平性。6.3 生成图像模糊或带噪点这类问题通常指向生成器分辨率不足或者模型容量不够。分辨率方面确认图像是否被正确归一化到[-1,1]检查生成器输出层的Tanh是否正常工作。容量方面尝试增加生成器每层的通道数比如从64翻倍到128或增加网络的层数。带噪点的问题有时候和反规范化操作有关。在PyTorch中如果模型内部使用了BatchNorm且训练和推理模式切换不正确生成结果会带有明显的分布偏移。生成器输出后可以试着加上一次手动标准化看看噪点是否来自BN层的统计量问题。此外可以用adam的lr衰减或余弦退火学习率调度帮助训练后期精细收敛。6.4 排查技巧速查表现象可能原因优先排查手段生成图像全为噪声学习率过高生成器容量不足降低学习率增加通道数生成图像多样性差模式崩塌标签平滑削弱判别器增大z维度训练震荡剧烈学习率过高batch size过小调低学习率增大batch size图像模糊不清晰数据未归一化到[-1,1]分辨率受限检查预处理归一化增加模型容量判别器损失为0判别器过强降低D学习率增加Dropout生成图像带网格伪影BN在推理模式出错卷积核尺寸不匹配确保使用train模式检查卷积参数7. 进一步优化的方向与实际应用感想经过这一轮系统的学习和实验我对GAN的理解已经从“玄学炼丹”上升到了“有章可循的博弈优化”。但这里要坦白说GAN训练至今仍是一门经验性很强的学科很多结论在论文里看起来简洁优美落到实际数据集上往往要反复调试。我遇到的很多问题并不是因为代码bug而是因为超参数组合没有满足特定数据集的特性。对于想真正掌握GAN的读者我的建议是先手动实现一遍DCGAN把每一步的维度变化都算清楚再用ACGAN做条件生成实验感受类别信息带来的改变。跑通之后可以尝试用WGAN-GP替换原始损失函数观察对训练稳定性的提升。如果想进一步提升图像质量后续我会单独写一期关于StyleGAN和扩散模型的内容。最后分享一个实操中的小技巧训练过程时可以固定一个噪声向量集合每次保存模型时都用这个固定向量生成一组预览图。这样做有一个特别好的好处你可以非常直观地看到同一个输入在不同训练阶段的演化过程——从纯噪声逐渐变成清晰图像这个视觉反馈比任何损失曲线都更能告诉你模型学到哪一步了。我自己靠这个技巧排查过无数次问题强烈推荐你试试。
返回列表