很多讲生成对抗网络的文章都喜欢用“把马变成斑马”作为开场例子。它看起来就像给照片套了一层滤镜,但真正动手训练过才会发现,这是个比想象麻烦得多的问题——训练数据里只有一堆马的照片和一堆斑马的照片,没有任何一张“同一匹马长了斑马条纹”的配对样本。模型必须自己搞清楚,马和斑马之间哪些共同点要保留,哪些差异要被替换。这正是**无监督条件生成(Unsupervised Conditional Generation)**要解决的核心问题。
我在整理深度学习公开课笔记时,对着第76讲琢磨了很久,又亲手把循环一致性生成对抗网络从零跑了一遍,才慢慢理解所谓“无监督条件生成”到底难在哪、CycleGAN那种朴素的循环一致性思路为什么有效,以及它在真实项目里又会踩到哪些坑。这篇文章就把整个来龙去脉讲清楚,适合两类读者:一是刚开始接触GAN、知道对抗生成但还不太理解无监督设定的学习者;二是打算把图像转换模型用在真实项目里,却被训练不稳定、输出崩坏反复折磨的实践者。
下面按我从课程里梳理出来的逻辑顺序讲,先谈任务本身的困难,再说直接方案为何必败,然后拆解CycleGAN的机制,最后分享实测经验。
1. 没有配对样本的图像转换任务,卡点到底在哪
1.1 先分清“监督式条件生成”和“无监督条件生成”
“条件生成”这个词在GAN里已经有明确含义:给模型一个输入条件(一句描述、一个类别标签、一张参考图),再让模型输出符合该条件的图像。比如给一张素描生成对应的彩色照片,或者给一句“一朵红色的花”生成一张花的照片。训练时,监督式条件生成依赖成对数据,每个输入x都有一个对应的目标y。损失函数可以直接比较生成结果和目标之间的差距,模型学的是“输入x应该对应哪个y”。
无监督条件生成的任务设定则完全不同。还是以马和斑马为例,我们只有两个独立的图像集,没有任何一一对应关系,没有人告诉你哪匹马对应哪匹斑马。你甚至不知道哪些信息应当保留,模型的唯一任务只剩下:输入任意一张马的照片,输出的图要满足两个条件——看起来像斑马,同时保留输入那张马的关键内容。
我把两种设定的差异整理成了下面的表格,方便对照:
| 对比维度 | 监督式条件生成 | 无监督条件生成 |
|---|---|---|
| 训练数据 | 成对数据(x, y) | 两组独立图像集,无对应关系 |
| 核心损失 | 生成结果与真实目标直接比较 | 只能依赖对抗损失加各种间接约束 |
| 内容保持 | 天然由配对数据保证 | 需要额外设计机制 |
| 典型方法 | cGAN、pix2pix系列 | CycleGAN、共享隐空间方法 |
如果拿翻译来类比:监督式条件生成就像有一本双语词典和大量对照例句,可以一句一句对着检查译文。无监督条件生成则像把一堆中文文章和一堆英文文章直接扔给你,没有词典也没有对照,要求你学会把中文翻成英文,还要保证译文忠实原文。显然,这是两种难度完全不同的任务。
1.2 内容保持为什么是核心难点
如果把一张图像拆开看,它其实包含两类信息:语义内容(物体轮廓、空间结构、姿态)和风格属性(颜色、纹理、光照)。图像转换的本质就是换风格、留内容。难点在于,没有配对样本的情况下,模型怎么知道哪些属于内容、哪些属于风格?
这本质上是个归纳偏置问题。从统计上看,马群和斑马群最明显的差异是颜色和条纹,而背景、草原、光照分布基本一致。模型可以通过统计数据学到“把两个域差异较小的部分保留下来,把差异较大的部分替换掉”。但统计规律不等于逐样本约定,训练的不稳定性和数据偏差都会让内容的定义发生漂移。
举个例子,一个域里如果大部分马都站在草地上,模型可能会把“草地”误当成马的内容特征。训练集里所有斑马恰好都是右侧视角,模型又可能把“视角”当作斑马的域特征。这类偏差积累起来,生成结果就会在内容保真和风格迁移之间来回摇摆。课程里一开始就强调:无监督条件生成最大的困难不是生成质量,而是约束的缺失——没有约束,模型会趋向于选择最简单的捷径。这条主线贯穿了后面所有解法。
2. 直接用GAN做转换,为什么必然塌方
2.1 判别器只看“像不像”,根本不管“对不对”
最直观的方案是这样的:训练一个生成器G,输入一张马的照片,输出一张“看起来像斑马”的图片;再训练一个判别器D,判断生成图片有没有斑马的样子。这个思路在结构上完全可行,但它漏掉了最关键的一点——判别器只能判断生成图片是否属于目标域,也就是“像不像斑马”,它完全没有能力判断生成图片是否和输入的马保留了对应关系。
想明白这一点,你就能预判训练中会发生什么。G要让D满意,只需要把输出变成某种“平均斑马”就行。比如斑马训练集里大部分都是站在草原上的侧面视角,G完全可以把任何姿势、任何背景的马都映射到同一张“标准斑马图”上。D会认为这张图非常真实,但转换任务彻底失败。训练中你会看到生成图片多样性急剧下降,所有输出长得越来越像,这就是GAN里常见的模式坍塌。
我最早跑这类模型时,头几十个epoch看起来一切正常,损失曲线还在下降,但到某个节点突然所有输出都变成同一张图。如果你也遇到这种情况,大概率就是走了这条老路。
2.2 更隐蔽的情况:局部作弊与信息丢失
模式坍塌是彻底崩盘的情况,实际训练里更常见的是“局部作弊”。比如马的照片里背景有树、有栅栏、有骑手,生成器发现D对“带马鞍的斑马”判假,于是学会把马鞍抹掉;发现D对“草原背景中的斑马”判真,于是把背景全部改成草原。结果生成图看起来确实是斑马,但内容已经被改得面目全非——马没了,马鞍也没了,只剩一张“斑马皮”。
为什么G会这样?因为对抗损失只约束了生成分布和目标分布的整体相似性,没有任何一项在约定逐样本的映射规则。课程里反反复复强调这一点:把GAN当作监督学习来用,本质上是在拿分布匹配错误地替代映射学习。换到翻译场景会更直观:老师只看译文是否像目标语言,完全不检查是否忠实原文,那学生当然会琢磨出各种花式作弊法——不管原文是什么,统统回一句“今天天气不错”,老师还觉得他的英文很地道。要打破这种局面,必须给损失函数加一个“忠实原文”的检查项。下一章的循环一致性,就是在做这件事。
3. CycleGAN:用“翻译回来”这个笨办法,解决无配对核心难题
3.1 结构拆解:两个生成器、两个判别器、一个闭环
CycleGAN的思路朴素得近乎笨拙:如果要把马变成斑马,那就先变过去,再变回来。如果来回一趟图片还能还原成原来的马,说明“变过去”这个过程确实保留了足够多的内容信息。
具体来说,CycleGAN同时训练两个生成器。G负责把马域A变成斑马域B,F负责把斑马域B变回马域A。同时还有两个判别器,D_B判断G的输出是否像真实斑马,D_A判断F的输出是否像真实马。整个流程可以写成这样:
输入一张马的照片x,经过G变成伪斑马G(x)。把G(x)再交给F,得到重建的马F(G(x)),希望它尽量接近最初的x。反向同理,输入一张斑马y,F把它变成伪马F(y),再交给G得到G(F(y)),希望它接近y。这两条“往返路径”构成循环一致性约束。
这套结构里,真正聪明的地方在于:没有配对样本,但通过“往返一致”制造了一个间接的监督信号。正像翻译里说的“回译检查”——把中文翻成英文,再从英文翻回中文,如果得到的还是原来那句话,说明第一次翻译基本没有丢掉语义。CycleGAN把同样的逻辑搬到了图像上。
3.2 循环一致性损失:为什么用L1而不是L2
循环一致性损失可以写成下面的形式:
L_cyc(G,F) = E_x [ ||F(G(x)) - x||_1 ] + E_y [ ||G(F(y)) - y||_1 ]这里x来自马域,y来自斑马域,||·||_1表示L1范数,也就是绝对误差之和。
很多新手会问:为什么距离度量用L1而不用L2?这个问题值得说清楚。L2范数对较大误差施加平方惩罚,优化时会倾向于把误差分散到每个像素上,结果就是图像变得模糊,所有边缘细节都被抹平。L1范数对孤立的大误差更宽容,能保留图像锐利的高频细节。CycleGAN在实际训练中普遍使用L1,这是无数实验总结出来的经验,不是拍脑袋决定。
总损失函数可以写成:
L_total = L_GAN(G, D_B) + L_GAN(F, D_A) + λ * L_cyc(G, F) + λ_identity * L_identity(G, F)GAN损失可以采用最小二乘形式,训练比标准交叉熵更稳定,不容易出现梯度消失。λ一般取10,identity loss权重通常取λ/2左右。为什么λ要拉这么高?因为对抗损失本身就是“欺骗游戏”,对梯度的干扰很剧烈,如果不把循环一致性的权重抬起来,内容约束很容易被对抗项淹没,生成器又会回到偷懒的老路上去。
3.3 训练配置里的几个关键约定
CycleGAN的推荐训练参数经过了很多人的验证:Adam优化器、学习率2e-4、beta1=0.5、batch size设为1。生成器常用ResNet-9blocks结构,判别器用70×70的PatchGAN。学习率可以前100个epoch保持恒定,再线性衰减到0,这个“线性衰减”步骤对后期稳定非常关键。
两个设计细节值得展开。
第一,PatchGAN选择只看局部。它的感受野大约70×70像素,不需要看完整张图,只需要判断每个局部区域的纹理是否真实。这种设计迫使生成器在每个局部都认真工作,而不是把整体色调调对就蒙混过关。第二种,InstanceNorm对每个样本独立做归一化。当batch size=1时,BatchNorm其实会自动退化成InstanceNorm,等价于对单张图片做规范化,这样能更好保留内容和风格的细节。
还有一个经常被忽略的细节:Identity Loss。它要求G(y)仍然接近y,F(x)仍然接近x。换句话说,如果你的输入本身已经是一张斑马,用G去转换,输出应该还是斑马,而不是被强行改成了马。这个约束能防止不必要的色调漂移。我在实际训练中观察到,当马域和斑马域亮度差异比较大时,去掉identity loss,生成结果往往整体发灰发暗,颜色完全偏离原图;加上之后颜色明显更忠实。
4. 从CycleGAN延伸出去:另外几条无监督条件生成路线
4.1 特征空间的闭环:把内容约束放到语义层
CycleGAN在像素空间要求往返重建,这个约束在内容差异很小的任务是可靠的,比如马到斑马、照片到油画。但一旦涉及姿态或视角变化,比如正面脸转换到侧面脸,像素级的循环一致反而成为障碍——因为正面脸和侧面脸在像素上本来就不可能一一对应,强制循环一致只会让模型不敢做真正的几何变形。
后续的很多工作把循环一致性的思想从像素空间搬到了特征空间:先用编码器把图像压缩成语义特征,在特征层面对齐和重建,再通过解码器生成图像。这样既能保留高层的内容信息,又允许合理的几何形变,处理的任务范围比像素级CycleGAN宽得多。如果你在课程学完后想做更复杂的转换,这个是第一个值得深入的方向。
4.2 离散数据上的无监督条件生成:难点完全不同
如果想把CycleGAN的思路搬到文本或语音上,会立刻发现逐字重建这个约束很难定义。文本的语义是离散的、符号化的,两个语义相同的句子可能字面完全不同,不能用逐像素L1来比较。在无监督机器翻译、语音转换这类任务里,回译约束依然有用,但需要配合序列模型的最大似然目标或者对比学习机制才能跑起来。
这里想强调一个常见误区:不要以为CycleGAN是个通用无监督转换框架,就随便把它套到别的领域。图像能这么做,是因为图像的像素空间天然连续,L1距离可以比较;离散符号空间根本没有这种几何结构,方法必须重新设计。课程里一般点到即止,但理解这个边界,能帮你少走很多弯路。
4.3 半监督混合:少量配对数据能带来多大收益
在实际项目里,完全无监督通常不是最优选择。如果任务里能拿到少量配对样本,比如100对同景别的照片和素描,可以先做有监督预训练,再让CycleGAN式的无监督损失在大量无配对数据上继续微调。这种混合方式有两个好处:一是预训练让生成器一开始就学到相对合理的对应关系,不容易走进模式坍塌的捷径;二是无监督部分能覆盖有监督样本覆盖不到的多样性,让模型面对新场景时依然有迁移能力。我自己做模拟项目X时,先手工标注了一百多对样本,把整个训练流程从“赌运气”变成了“可预期”。
5. 实操复盘:跑一个CycleGAN,我踩过的坑和排障清单
5.1 数据清洗的影响比模型更大
在模拟项目X里,我先后跑过两个任务:马到斑马转换,以及风景照片到印象派油画风格转换。第一次直接用抓取到的原始图片集训练,结果发现生成的斑马轮廓里出现了人形,仔细排查才知道,马的照片里很多带有骑手,模型把“骑手”当作马的组成部分保留了下来。去掉这些人形样本后,输出才变得干净。
另一个典型问题是水印。网络图片如果带文字水印,模型会把水印当成内容的一部分学进去,生成画面里就会出现一串奇怪文字伪影。清洗数据时,至少要去掉带水印、主体过小、画面里有多只动物、有明显遮挡的样本。数据量方面,个人经验是每个域300到1000张起步比较稳。如果数据太少,生成器很快就会把目标域的几张记忆图硬编码到所有输出里。
5.2 训练过程常见的三个翻车现象
第一个是损失曲线永远剧烈震荡。这很正常,GAN训练本身就是博弈过程,重要的是看长期趋势而不是某个step的数值。如果发现循环一致性损失在快速下降,但判别器损失持续上升,不必立刻调参,多半是生成器正在“追赶”判别器的判断标准。
第二个是生成图发灰发暗。我在自己的数据集上观察到,循环一致性权重过高会让模型宁愿什么都不改,也不敢冒险换风格,结果输出非常接近输入。适度降低λ,比如从10降到5,同时把对抗损失的权重拉起来,情况会明显改善。
第三个是输出崩成噪声或纯色。这通常是判别器太强、生成器被碾压导致的。降低学习率、或者减小判别器容量,都可以缓解。
我把常见现象对应成了一张排障表,方便你直接查阅:
| 现象 | 常见原因 | 调整方向 |
|---|---|---|
| 输出模糊 | L2损失、batch过大 | 换L1、batch=1 |
| 生成图色调漂移 | 缺少identity loss | 增加identity loss并提高权重 |
| 内容丢失或模式坍塌 | 循环一致性权重太低 | 提高λ |
| 风格迁移不足 | 循环一致性权重太高 | 降低λ |
| 训练后期不稳定 | 学习率恒定不衰减 | 学习率线性衰减到0 |
| 局部纹理粗糙 | 判别器感受野太大 | 使用PatchGAN结构 |
5.3 没有配对数据,怎么评估转换效果
没有配对数据,你没法直接算PSNR或SSIM,这是无监督任务里最让人头疼的问题之一。我在项目里常用的土办法有三类。
第一是人眼抽检。随机挑一批马变斑马的结果,看姿态背景是否被保留、纹理是否真实,这是最朴素也最可靠的评估方式,尤其适合快速判断生成器有没有出现明显的局部作弊。
第二是预训练特征距离。把原图和生成图送进通用图像分类器,提取中间层特征算L2距离,距离越小表示内容保持得越好。这里需要注意,要用预训练模型而不是自己训练的模型,因为通用特征更能反映语义层面的相似度。
第三是往返一致性。直接看F(G(x))和x的差异,如果循环重建都模糊不清,基本可以断定主生成器在偷懒。这些指标都不完美,但足以帮你快速判断训练状态,比盲目盯着loss曲线强得多。
6. 这门课之外:往多域、高分辨率与可控生成走的方向
6.1 多域转换:一个生成器代替上百对生成器
CycleGAN的缺点在任务一多时就非常明显。想实现10种风格互转,理论上要维护90对生成器、判别器,训练和管理的成本都很高。后来的StarGAN用一个条件向量控制同一个生成器,指定“当前要去的目标域”。这样一套网络就能处理多域转换,模型容量利用效率高很多,也更容易推广到大规模数据集。如果课程学完想继续深入无监督条件生成,StarGAN是一个非常自然的下一步。
6.2 高分辨率与可控性:从像素循环到语义控制
CycleGAN在高分辨率图像上训练很吃力,因为判别器对全局纹理过度敏感,循环一致性又限制了生成器的自由度。实际工程里可以先在低分辨率(比如128×128)跑稳定,再做渐进式上采样。或者引入分割图、深度图这类辅助条件,有了语义图,内容保持就不再完全依赖循环一致性兜底,模型可以用更明确的监督信号约束结构,风格迁移交给对抗损失。这种“语义图和生成器配合”的路线,在很多图像编辑落地项目里比纯无监督更实用。
6.3 个人学习路线建议
如果想把这一讲的内容彻底吃透,我的建议是别只看理论,动手做一遍消融实验。先把循环一致性损失拿掉,只看对抗损失训练,模型很快就露出模式坍塌的真面目。再把identity loss拿掉,颜色漂移会让你直观感受每个损失项到底在保护什么。这种体感远比你背十遍损失函数公式有用。很多初学者怕训练崩了浪费时间,但GAN这类模型,崩过一次你就真正理解了它的博弈结构。
最后分享一个我自己的小习惯:CycleGAN这类模型刚跑通时,输出往往偏软、细节不足。我会在训练的最后阶段把生成器冻结,只微调判别器几十个epoch,让判别器更严格地扫描真实目标域的细节,然后再解冻生成器训练。这个技巧不一定每次都能带来质变,但至少能帮你确认瓶颈到底在生成器还是判别器。折腾这类模型,最重要的不是找到万能参数,而是学会判断每个损失项在干什么——这一点,也是那门公开课第76讲最想传达的东西。