1. 项目概述为什么要把 Conditional GAN 单独拎出来讲最近系统回顾了一轮 2020 年的机器学习与深度学习公开课看到第 75 讲这个标题有点感慨Generative Adversarial Network 单独讲已经够复杂了还要加上 Conditional GAN 作为重点这一讲的信息量其实非常大。但恰恰是这种安排点破了一个很关键的事实——普通 GAN 只是让模型“会生成”Conditional GAN 才是让模型“按指令生成”而后者才是大部分实际业务场景真正需要的。如果你是第一次接触 GAN别被“对抗”“博弈”这些词吓住。简单说生成对抗网络就是让两个神经网络互相较劲一个负责造假一个负责打假打着打着造假水平就被逼上来了。Conditional GAN 则是在这个框架里额外塞进一个“条件”让造假者必须按照你给定的类别、文字、画面之类的要求来生成而不是漫无目的地瞎编。这个“条件”的设计直接决定了 GAN 能不能从玩具变成工具。这门公开课适合谁看我也多说一句既能帮初学者打通 GAN 的整体认知框架也能给已经做过一些生成模型的开发者提供结构层面的反思——比如条件到底该加在生成器输入上还是该加在判别器输入端为什么两者都要加才稳定。下面我会把这一讲的核心内容拆开揉细再结合我自己实操中的经验把 Conditional GAN 的原理、实现和调参思路完整过一遍。2. GAN 的核心原理造假与打假的博弈2.1 生成器与判别器的分工普通 GAN 由两个网络组成生成器 G 和判别器 D。生成器的任务是从一个随机噪声向量 z通常用高斯分布或均匀分布采样得到映射到一张图像、一段音频或任意目标域数据本质上是学习真实数据分布。判别器的任务则简单粗暴给一张图判断它是来自真实数据集还是由生成器伪造。这里有一个关键问题判别器给的不是绝对的“真假标签”而是一个概率。一开始生成器完全不会画出的是纯噪声判别器很容易识别。但随着训练推进生成器会不断根据判别器的反馈调整自己的参数生成的样本越来越像真的判别器也不得不变得更敏锐。这个此消彼涨的过程就是所谓的对抗训练。我见过不少初学者在这里犯迷糊以为 GAN 的目标是把判别器训练到“永远正确”。恰恰相反理想的平衡点是判别器无法区分真假也就是输出概率稳定在 0.5 附近。如果判别器太强生成器拿不到有效的梯度训练就直接僵死如果判别器太弱生成器又会敷衍了事。这种“势均力敌”的平衡是整个 GAN 训练中真正难把握的地方。2.2 目标函数与训练循环的内在逻辑普通 GAN 的目标函数可以写成极小极大博弈min_G max_D V(D,G) E[log D(x)] E[log(1 - D(G(z)))]其中 x 来自真实数据分布z 是随机噪声。判别器想最大化这个目标——也就是尽量把真实样本判成 1、把伪造样本判成 0生成器想最小化它——也就是尽量让伪造样本也被判别器判成 1。两个网络在同一个目标函数里对抗这就是“极小极大”的含义。训练时交替更新固定生成器更新判别器再固定判别器更新生成器。实际操作中还有一个很经典的细节更新生成器时目标函数通常改写成最大化 log D(G(z))而不是最小化 log(1 - D(G(z)))。原因在于当生成器效果还很差时D(G(z)) 接近 0log(1 - D(G(z))) 的梯度非常平缓生成器学不到东西而 log D(G(z)) 的梯度更大能提供更强的学习信号。这个细节在课程里被明确点过我在自己训练时也验证过——用原始形式确实很容易在早期就卡住。2.3 从“能生成”到“按条件生成”普通 GAN 有一个天然缺陷你没有办法控制生成内容。输入一个随机噪声输出可能是数字 3也可能是数字 7、8完全随机。这在很多场景下是致命的你想生成一张猫的图像结果模型给了你一堆狗想做文字转图像输入“一只红色的鸟”输出却完全与描述无关。Conditional GAN 的解决方案非常直观在生成器的输入中额外拼接一个条件向量 y。y 可以是一组 one-hot 编码的类别标签、一段文本嵌入向量甚至是一张低分辨率图像。生成器需要同时根据 z 和 y 来生成样本判别器在判断真假时也要把 y 作为参考信息从而约束生成结果必须与条件一致。这样一来生成器学到的就不再是简单的“真实数据分布”而是“在条件 y 下的真实数据分布”。这个转变看起来只是输入维度上多拼了一段向量但背后的设计哲学完全不同它把无监督的生成问题变成了带约束的生成问题。3. Conditional GAN 的技术拆解条件到底该怎么注入3.1 生成器侧的条件注入方式条件注入的第一个层面是生成器。绝大多数实现里生成器输入是噪声 z 和条件 y 的拼接向量。以 MNIST 手写数字生成为例如果噪声 z 是 100 维条件 y 是 10 维的 one-hot 向量拼接后就是 110 维送入全连接层或者通过 reshape 送入卷积网络。但直接把 y 拼在输入层只是最简单的做法。在一些更复杂的生成器结构里条件会以“条件归一化”的方式注入到每一层。比如条件批归一化Conditional Batch Normalization它的做法是让每一层的缩放和偏移参数由条件 y 通过一个小的映射网络生成而不是用固定的可学习参数。这样做的好处是条件信息能够在不同尺度、不同深度上影响特征图生成结果对条件的响应更精细。还有一个不那么容易被注意到的点条件的表达方式会直接影响生成质量。用 one-hot 类别编码比较直接但如果条件是文本直接拼原始词向量往往效果不佳一般会把文本通过预训练编码器比如某些语言模型转成语义向量再接进来。我个人的经验是条件嵌入的质量比生成器结构更影响最终效果很多“条件不起作用”的问题根源其实出在条件特征本身没有代表性。3.2 判别器侧的条件注入方式判别器侧的条件注入同样关键甚至比生成器侧更容易被忽略。无条件 GAN 的判别器只需要区分“真实”和“伪造”Conditional GAN 的判别器需要区分的是“真实且匹配条件”和“伪造或不匹配条件”。这个设计有什么意义举个例子生成器输入条件 y“猫”结果画了一只狗。如果判别器不看条件它只能从图像质量上判断这幅图是不是真实的——狗的图像本身可以是真实的于是判别器会给出高分生成器误以为“按条件生成猫”已经做对了其实完全跑偏。只有在判别器接收 (x, y) 组合作为输入时它才能同时检查“图像是否真实”和“图像是否与条件匹配”两方面的压力才能共同约束生成器。具体实现上最常见的方式是把条件 y 与图像 x 在通道维度或特征维度上进行拼接。对于图像类条件比如要给超分辨率模型注入低分辨率图通常把低分图像和待判别图像在通道维度拼接起来。对于类别条件则在判别器的某个特征层把 one-hot 向量扩展成二维平面后拼到特征图后面。条件注入的层级也很有讲究注入到靠近输出的高层特征会让判别器更关注条件与整体内容的匹配注入到低层特征则更容易影响细节质感。3.3 为什么“两边都要有条件”才是标准做法这一讲里有一个很重要的结论值得反复体会生成器和判别器两侧都要注入条件不是冗余设计而是让训练稳定收敛的保障。如果只给生成器加条件判别器无法判断伪造图像是否与条件匹配上面说的“画狗却判定为真”的问题就无法被约束如果只给判别器加条件生成器虽然能从判别器反馈中感受到“条件不对会被惩罚”但训练信号需要绕一圈才能传导效率低得多。当然如果你手头有足够强的预训练条件判别模型作为额外监督也可以只改生成器一侧但这是另一种思路了。对于从零开始训练的标准 Conditional GAN两侧同时注入条件是最稳妥、最不容易踩坑的方案。我画一个简单的对照方便理解无条件和有条件在输入输出上的差别项目无条件 GANConditional GAN生成器输入随机噪声 z噪声 z 条件 y判别器输入样本 x样本 x 条件 y目标函数仅判断 x 是否真实判断 x 是否真实且匹配 y输出可控性不可控按条件生成指定内容典型应用随机人脸生成、风格探索文字转图像、图像翻译、可控人像编辑4. 动手实现一个 Conditional GAN从 MNIST 到实战4.1 数据准备与预处理作为入门实现MNIST 是最好用的数据集图像小、类别清晰、便于快速验证“条件是否真的生效”。我把整个流程走一遍给出可以直接参考的配置。数据集方面使用标准的 0 到 9 手写数字集图像尺寸 28x28单通道。预处理只需要把像素归一化到 [-1, 1] 区间——这一点很重要因为生成器输出层一般用 tanh 激活函数输出范围正好是 [-1, 1]与数据分布对齐才能让判别器有效工作。如果数据范围是 [0, 1] 而生成器输出是 [-1, 1]训练一开始就存在系统性偏差。条件标签处理成 one-hot 向量维度为 10。为了增强泛化能力我通常会加一点有监督信号的技巧在训练过程中给标签做一点平滑比如把 one-hot 中的 1 替换成 0.9把 0 替换成 0.1 的均值这样可以在一定程度上防止判别器过度自信导致生成器梯度消失。后面还会细说。4.2 模型结构配置与参数量考量生成器我采用了一个简单的三层全连接结构输入是 110 维噪声 100 维 条件 10 维通过全连接层逐步扩大最后输出 784 维即 28x28 展开reshape 成图像。隐藏层激活函数用 ReLU输出层用 tanh。这个结构很小但在 MNIST 上已经足以生成像样的数字。判别器结构与生成器镜像输入是 784 维的图像展开向量与 10 维条件向量拼接中间层用 Leaky ReLU输出层用 Sigmoid 得到真假概率。使用 Leaky ReLU 而不是普通 ReLU 是一个细节ReLU 在负区间梯度为 0容易让判别器的某些神经元死亡Leaky ReLU 保留了负区间的梯度流动对稳定对抗训练有帮助。有条件图像数据增强的做法我建议在训练时对图像做轻微的随机偏移或旋转范围控制在 5 度以内、2 像素以内。这样能给判别器增加一点难度不至于让它记住几个模板直接背答案。4.3 训练配置与超参数选择优化器方面我使用了 Adam两个网络都设学习率为 0.0002beta1 设为 0.5。这里 beta1 的取值很讲究默认的 0.9 会保留较大的历史梯度动量在 GAN 这类非平稳优化问题中容易导致震荡0.5 更激进能让参数更快适应当前对抗形势。这是很多人训练不稳的隐藏原因之一。批次大小设为 128训练轮次设定为 50 轮。每一轮内先固定生成器更新判别器再固定判别器更新生成器交替进行。具体流程如下从真实数据集中采样一批图像 x 和对应标签 y从高斯分布中采样一批噪声 z用当前生成器生成伪造图像 x_fake G(z, y)判别器分别对真实样本 (x, y) 和伪造样本 (x_fake, y) 打分计算二元交叉熵损失更新判别器参数让真实样本得分趋近 1、伪造样本得分趋近 0再次采样噪声 z 和条件 y生成伪造图像固定判别器让生成器的目标是让伪造图像被判别器识别为真实借此计算损失并更新生成器参数重复以上过程直到训练完成。训练过程中我建议每 5 个 epoch 保存一次生成图像定时检查质量。如果只看损失曲线很容易被骗——因为两个网络在对抗损失并不像普通分类任务那样单调下降上下波动是常态。4.4 训练过程中的现象观察与判断我实际训练时观察到几个很有代表性的现象。前 5 轮生成图像基本是灰色模糊团块只能隐约看到笔画的轮廓现象最早在 5 到 10 轮之间出现——关键特征是某些数字类别开始出现雏形比如条件标签为“1”时输出确实是一条竖线而不是歪七扭八的团块。这说明生成器已经开始利用条件信息了。判别器的损失在第 3 轮左右通常会有一个快速上涨再回落的过程这是因为生成器突然学到了一个粗略的笔画模式判别器短暂不适应的表现。这种波动是正常的不用急着调参先让它继续跑。如果第 15 轮之后仍然看不到类别差异我一般会直接检查条件注入是否出了问题比如是否有拼错维度、条件向量是否被当作普通噪声一起随机采样了、或者学习率是不是设得太低。这些排查手段后面会专门讲。5. 常见问题与排查技巧实录5.1 生成器崩溃模式坍塌与梯度消失模式坍塌是 GAN 训练中最经典的问题生成器找到某个“容易骗过判别器”的输出模式于是无论输入什么噪声、什么条件都只输出这一种图像。在 Conditional GAN 中模式坍塌的表现是不同条件对应的输出逐渐趋同——所有类别的数字都长得差不多。排查时我最先做的是降低判别器能力把判别器网络减小、增加 Dropout、或者降低判别器的学习率。因为模式坍塌的根源通常是判别器太好提供给生成器的梯度几乎全部指向那几个“安全区域”生成器就没有动力探索其他模式。同时把噪声 z 的维度调大一些也有帮助更高的噪声维度能给生成器更多的独立随机变量让它更难把所有输入都映射到同一个输出。另一个直接有效的技巧是标签平滑。把判别器的目标从 1真实和 0伪造改成 0.9 和 0.1可以在一定程度上防止判别器过于自信反向给生成器留出更大的梯度空间。我在自己的实现里加入标签平滑后模式坍塌出现的频率明显降低。5.2 条件信息被忽略生成了但与条件无关条件信息被忽略是 Conditional GAN 特有的问题生成图像质量看起来不错但类别完全不匹配。这通常是因为条件 y 在生成器的输入拼接中被噪声 z 的信息量淹没了。噪声 z 通常是 100 维条件 y 可能只有 10 维网络很容易学着直接忽略低维的 y。解决思路有几个层次。第一增大条件在模型中的遍及程度不仅是输入端拼接在中间层也把条件拼接进去。第二在判别器侧强化条件约束让不匹配条件的样本获得更低的真实性分数。第三也是最容易被忽略的就是条件标签本身的质量——如果使用 fine-grained 的属性描述比如“红色头发的女性”用随机初始化的 one-hot 效果会打折扣需要更丰富的条件编码。5.3 训练震荡与损失异常GAN 的损失曲线不像分类那样稳步下降这一点新手要提前接受。但是如果判别器损失和生成器损失出现极端交替——比如判别器损失一路跌到接近 0生成器损失飙升——这往往意味着判别器完全碾压了生成器。此时生成器拿不到有效梯度后面大概率会进入死循环。我常用的恢复手段是先调低判别器学习率或者让判别器每更新一次、生成器更新两到三次给生成器更多追赶机会。此外检查一下是否使用了 Adam 默认的 beta10.9如果用了就改为 0.5。很多不明原因的震荡改完 beta1 就缓和不少。训练稳定的一个直观参考标准是真实样本的判别得分稳定在 0.9 左右伪造样本得分稳定在 0.1 到 0.3 之间且两个分数都不出现断崖式变化。如果伪造样本得分长期来回在 0.5 附近摆动说明生成器可能已经找到了一种以假乱真的局部最优需要引入下面要说的评估手段来判断生成质量到底是好是坏。5.4 生成质量怎么评估在分类任务里准确率就是硬指标。但 GAN 生成质量没有标准答案传统做法是肉眼观察。肉眼评估在 MNIST 这种小图上还算可行但放到人脸生成或高清场景就完全不靠谱了。目前业内用得比较多的两个指标是 Inception ScoreIS和 FIDFréchet Inception Distance。IS 衡量的逻辑是生成的图像类别分布应该既多样又清晰也就是说把生成图像送入一个预训练分类器输出的类别概率向量应该接近 one-hot清晰且所有类别的概率分布应该均匀多样。FID 则更简单直接比较真实图像和生成图像在某个特征空间中的分布距离距离越小越好。在 Conditional GAN 的场景里我还会额外看一个条件匹配精度——把生成的图像用预训练分类器识别一下统计“生成类别等于指定类别”的比例这个指标直观且好算能快速发现“条件被忽略”的问题。6. 从这一讲延伸出去Conditional GAN 的典型应用6.1 文本生成图像条件从类别升级为语义描述MNIST 里条件是 10 个类别标签真实场景中条件往往是一句话。文本生成图像就是把这句话嵌入成语义向量再用类似 Conditional GAN 的结构约束生成过程。这类任务比类别条件困难得多因为文本语义和视觉像素之间存在巨大的语义鸿沟。通常的做法是分阶段生成先生成低分辨率的整体轮廓再逐步细化到高清。低分辨率阶段条件信息的权重应该更高让整体布局贴合文本描述高分辨率阶段则更多地依赖图像本身的纹理感知。我实际做这类任务时还有一个心得文本编码器一定要用大规模预训练模型来提取相比随机初始化的嵌入层预训练语义向量能极大缩短训练时间而且生成结果和文本的匹配度会高很多。6.2 图像翻译与超分辨率条件本身就是一张图除了类别和文本条件也可以是图像。图像翻译任务比如把黑白图上色、把草图变成真实照片的常见做法就是直接把输入图像作为条件喂给生成器同时把输入图像和输出图像拼接后送给判别器。这类模型的代表性结构本质上是 Conditional GAN 的图像版扩展条件不是向量而是与输入图片同尺寸的图像。超分辨率同理低分辨率图作为条件生成器输出高清图判别器需要同时判断“高清图是否真实”和“高清图是否符合低分图的内容”。在条件为图像时条件注入一般直接采用通道维度拼接因为通道拼接保留了空间对应关系能让判别器逐位置检查条件匹配度。这也是“条件注入方式由条件形态决定”的典型体现。6.3 更进一步条件解耦与可控生成如果把条件从类别扩展到更连续的空间比如人脸属性肤色、发型、年龄、风格向量甚至图像中某些语义区域Conditional GAN 就演进成了可控生成模型。这类模型的核心不是把条件拼进去就完事而是要让条件在特征空间中“解耦”——也就是每个条件维度都独立控制一个视觉属性互不干扰。想要解耦只在输入端加条件是不够的前面提到的条件归一化、在多个中间层注入条件、以及对抗性的正则化手段都要配合使用。这也是从课程中的 Conditional GAN 走向工业级应用时避不开的方向。坦白说解耦问题至今没有完全解决但这不妨碍 Conditional GAN 框架已经成为图像生成、视频合成、数据增强等很多方向的基础底座。7. 一些个人的实操体会回顾这一讲和后续做的多个生成项目我最大的感受是Conditional GAN 的门槛不在“把代码跑起来”而在“对条件的理解是否足够深”。条件加在哪里、用什么形式表达、如何让判别器充分利用条件这些设计决策直接决定模型的上限。跑通一个 MNIST 很简单但如果你能把前面说的模式坍塌、条件忽略、训练震荡这几个问题都靠调试解决一遍你对 GAN 的理解会上一个台阶。最后分享一个我自己常用的排查习惯训练开头就把条件响应当作首要检查目标而不是先盯着画质。如果前几轮模型完全没体现出条件差异越早发现问题越好调拖到几十轮之后再回头排查往往已经积累了一堆噪声中还混杂着有效信息的参数调整代价大很多。另外从这样的课程出发做自己的扩展实验时一定要做好实验记录——记录每个 epoch 的生成样本、损失值、关键超参数后面比对效果时会省非常多的时间。