你在搜索引擎里敲下“GAN”三个字母,大概率会看到两种完全不同的东西:一种讲的是氮化镓功率器件,另一种讲的是能生成人脸、画作、街景的深度学习模型。这篇文章只聊后者,即生成对抗网络(Generative Adversarial Network),以及它最出名的三个变种:pix2pix、CycleGAN 和 pix2pixHD。
这四个术语经常出现在论文、GitHub 仓库和各类影像处理工具中,但很多人对它们的关系一知半解:既然有了 GAN,为什么还要搞出 pix2pix?CycleGAN 和 pix2pix 到底差在哪一步?pix2pixHD 又“HD”在哪里?这篇文章用一条主线把这四个模型串起来,从原理、结构再到实战踩坑,尽量讲得直白,但在关键地方又会给出足够的深度。适合刚入门生成模型的同学,也适合已经在跑代码但对设计逻辑还有些模糊的工程师。
1. 先搞清楚 GAN 在解决什么问题
1.1 生成器与判别器:造假者和鉴定师
GAN 的基本思想其实非常朴素,可以用一个比喻说清楚:生成器(Generator)就像一个造假者,负责伪造画作;判别器(Discriminator)就像一个鉴定师,负责分辨哪一幅是真迹、哪一幅是赝品。
造假者一开始手艺很烂,伪造出来的画作一眼就能被鉴定师识破。但每次被识破之后,造假者会根据被识破的原因改进工艺,画出更逼真的假画。鉴定师也在同步进化,不断寻找更细的破绽。两个人就这么互相“卷”下去,最后达到一种状态:造假者画出来的图已经能以假乱真,鉴定师完全分不清真假。
这个博弈过程在数学上对应一个极小极大(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)))] ]
这个公式看起来吓人,实际含义不复杂。( D(x) ) 表示判别器认为一幅图是真实数据的概率,目标是把真实图的概率推向 1,把生成图的概率推向 0。生成器 ( G ) 的目标恰好相反,它想让判别器把生成图也识别为真实图,也就是让 ( D(G(z)) ) 尽量接近 1,从而让 ( \log(1-D(G(z))) ) 尽可能小。两者在这一个式子里面互相拉扯,最后达到纳什均衡。
1.2 训练的本质:交替优化的猫鼠游戏
在具体实现时,GAN 的训练并不是把两个网络放在一起联合梯度下降,而是交替更新。每个训练 step 大概分为两步:
- 固定生成器参数,更新判别器。从真实数据中取一批图像,再从噪声中取样一批生成图,让判别器学会把真实图和生成图分开。
- 固定判别器参数,更新生成器。生成一批新的假图,让生成器的梯度方向是尽量让判别器把假图判成真图。
这个交替过程在实际操作中非常容易翻车。最常见的情况是判别器太强,生成器怎么骗都骗不过去,梯度消失;或者生成器太强,判别器被瞬间击穿,无法提供有效梯度。所以很多人会在训练中加入标签平滑(label smoothing)、梯度惩罚(gradient penalty)等技巧,本质上都是为了让这场猫鼠游戏不至于一边倒。
1.3 原始 GAN 的两个致命问题
原始 GAN 能生成图片,但有两个问题特别突出。
第一个是模式坍塌(mode collapse)。简单说,就是生成器找到了一个“万能的答案”——不管输入什么噪声,都输出同一张能骗过判别器的图。比如训练 MNIST 数字生成时,生成器可能只学会了写数字“1”,因为数字“1”最容易骗过当前状态的判别器。判别器还没来得及学会识破,生成器就已经收敛到这条偷懒路径上了。
第二个是生成结果不可控。原始 GAN 的输入是随机噪声 ( z ),你没法指定生成出来的图像内容。想让模型生成一张“晚上有灯光照射的街景”,直接改噪声向量是做不到的。噪声的维度空间太大,每个维度和图像语义特征之间没有可解释的映射关系。
这两个问题就是后面几个模型演化的核心推动力。pix2pix 解决的是可控性问题,CycleGAN 解决的是数据配对问题,pix2pixHD 解决的是分辨率与细节质量问题。
2. 可控生成的第一步:条件 GAN 的出现
思路其实很直接:既然随机噪声不可控,那就在输入里加入“条件”。条件可以是一个类别标签、一段文字,也可以是一张图像。加入条件之后,生成器不再是“从纯噪声映射到图像”,而是“从噪声加条件映射到图像”。
pix2pix 用的正是条件 GAN(conditional GAN)这个框架。它的核心公式可以写成:
[ \mathcal{L}{cGAN}(G,D) = \mathbb{E}{x,y}[\log D(x,y)] + \mathbb{E}_{x,z}[\log(1 - D(x, G(x,z)))] ]
这里 ( x ) 是输入条件图,( y ) 是目标图。判别器收到的是“条件图 + 待判别的图”这个组合,而不是单独的一张图。为什么改成这样?因为如果把条件图也喂给判别器,判别器就能学习“条件图和目标图是否匹配”这个关系,而不只是“目标图是不是真实的”。
举个例子,假设任务是把线稿变成彩色图。输入条件 ( x ) 是线稿,目标 ( y ) 是成品彩图。判别器看到的是一对图:左边线稿、右边彩色图。它要判断右边这张彩图是不是“真的”并且“和左边线稿匹配”。如果生成器输出了一张质量很高但跟线稿内容完全无关的彩图,判别器照样能看出问题。这就强制了生成结果必须与输入条件保持一致。
3. pix2pix:成对数据下的图像翻译机
3.1 任务定义与整体架构
pix2pix 把大量图像生成问题统一成了一个“图像翻译”问题:输入一张图(如线稿、语义分割图、灰度图),输出另一张图(如彩色图、真实街景、彩色照片)。训练要求是成对数据——同一场景同时存在输入图和目标图。
模型整体是编码器-解码器结构。编码器把输入图压缩成特征向量,解码器再从特征向量还原出目标图。但这里有个问题:图像翻译任务要求输入和输出之间保留大量空间细节。比如从语义分割图还原街景时,分割图里的每个边缘、每个物体的轮廓都必须原样保留,只是把“语义类别”变成“真实纹理”。如果经过压缩再还原,这些细节很容易丢失。
解决办法是引入 U-Net 结构,即在编码器和解码器对称层之间加跳跃连接(skip connection)。跳跃连接的意义是:解码器第 ( i ) 层向上采样时,直接把编码器第 ( i ) 层保存的高分辨率特征拼接到当前特征上。这样低层细节信息就不需要全部塞进压缩后的特征向量里,而是走快捷通道传给解码器。这个改动在图像翻译任务里几乎是决定性的,实测下来没有跳跃连接的生成结果会糊成一团。
3.2 PatchGAN 的巧妙设计:判别器只看局部
pix2pix 的判别器设计也很有意思,用的是 PatchGAN。传统 GAN 的判别器最后输出一个标量,表示整张图是真还是假。PatchGAN 输出的是一张 ( N \times N ) 的特征图,每一个位置代表原图的一个局部 patch 的真假。在原始实现中,patch 大小一般是 ( 70 \times 70 ) 像素。
为什么只看局部就够了?原因是图像翻译任务中,生成器最容易忽略的是高频细节(边缘、纹理),而这些刚好是局部特征。全局结构往往已经被 L1 损失约束住了,不需要判别器再来监督。PatchGAN 只关注局部纹理是否真实,既能提供更清晰的梯度信号,又比判别整张图更省显存、更稳定。
pix2pix 还有一个很关键的细节:损失函数并不是只用 GAN 损失,而是 GAN 损失加上 L1 距离损失。L1 损失的作用是拉近生成图和目标图的整体结构。
[ \mathcal{L}{L1}(G) = \mathbb{E}{x,y,z}[\lVert y - G(x,z) \rVert_1] ]
总损失为:
[ G^* = \arg\min_G \max_D \mathcal{L}{cGAN}(G,D) + \lambda \mathcal{L}{L1}(G) ]
( \lambda ) 默认取 100。为什么 L1 比 L2 更好?L2 损失会惩罚大的误差,导致生成器在不确定的地方倾向于取平均值,结果就是图像模糊。L1 对大误差的惩罚是线性增长的,生成器会更愿意做出“锐利的选择”。这是图像生成里一个非常实用的经验。
3.3 跑通 pix2pix 的实操配置建议
如果你第一次在 PyTorch 里复现 pix2pix,有几组参数需要格外留心。
输入图像通常会归一化到 [-1, 1] 区间,目标图像也一样。生成器和判别器都使用 Adam 优化器,学习率设 0.0002,( \beta_1=0.5 )。注意这个 ( \beta_1 ) 跟默认的 0.9 不一样,更小的 ( \beta_1 ) 能降低动量,避免训练震荡。batch size 一般是 1 或 4,配合实例归一化(InstanceNorm)而不是批归一化(BatchNorm)。
实例归一化核心思想是对单张图的每个通道单独做归一化,不依赖 batch 内的数据统计。这个在图像生成任务里很重要,因为图像翻译的每个样本风格差异可能很大,如果用 batch 统计量,会引入样本间的干扰。
4. CycleGAN:没有成对数据,就造一个循环
4.1 从监督到无监督的跨越
pix2pix 依赖成对数据,这个条件在真实场景中往往非常苛刻。把白天的街景变成夜晚的街景,要获得成对数据就得在同一机位从白天拍到晚上;把马变成斑马,自然界根本没有对应的成对照片。
CycleGAN 解决了这个问题:输入只有两个图像集,一个是马的图片集,一个是斑马的图片集,不需要任何配对。它能学会把马匹照片转换成斑马风格,同时保留姿态、背景和光照等关键信息。
难点在于:没有配对监督,怎么保证生成图在语义上与原图一致?假设把一张马的照片转换成斑马,模型可能学到的是“把任何图都输出成一张固定的斑马图”,因为从判别器角度看,只要是看起来像斑马的图都是合格的。这就是标准的无监督翻译中的模式坍塌问题。
4.2 循环一致性损失:唯一的关键约束
CycleGAN 的突破在于提出循环一致性损失(cycle consistency loss)。它同时学习两个映射:( G: X \rightarrow Y ) 和 ( F: Y \rightarrow X )。把一张马的照片 ( x ) 通过 ( G ) 变成斑马 ( G(x) ),然后再把这张生成的斑马图通过 ( F ) 变回马 ( F(G(x)) ),要求 ( F(G(x)) \approx x )。反过来也一样:
[ \mathcal{L}{cyc}(G,F) = \mathbb{E}{x \sim p_{data}(X)}[\lVert F(G(x)) - x \rVert_1] + \mathbb{E}{y \sim p{data}(Y)}[\lVert G(F(y)) - y \rVert_1] ]
这个约束的意思是:如果你把一个物体翻译到另一个域,再翻译回来,必须回到原点。它通过“环形路径重构”间接强制了翻译过程保留内容信息。为什么这么有效?因为如果 ( G ) 把马的位置、姿态等结构信息丢失了,那么 ( F ) 再怎么努力也无法还原出原图。循环损失产生的梯度信号在结构保持上比单纯对抗损失强得多。
整体损失函数是:
[ \mathcal{L}(G,F,D_X,D_Y) = \mathcal{L}{GAN}(G,D_Y) + \mathcal{L}{GAN}(F,D_X) + \lambda \mathcal{L}_{cyc}(G,F) ]
( \lambda ) 默认为 10。( D_X ) 是负责判别 X 域真假的判别器,( D_Y ) 负责判别 Y 域。两个判别器各管一摊。
4.3 身份映射损失:防止颜色与纹理的意外漂移
CycleGAN 原始论文里还有一个可选的损失项——身份映射损失(identity loss):
[ \mathcal{L}{identity}(G,F) = \mathbb{E}{y \sim p_{data}(Y)}[\lVert G(y) - y \rVert_1] + \mathbb{E}{x \sim p{data}(X)}[\lVert F(x) - x \rVert_1] ]
意思是:如果输入本来就是 Y 域的图,用 ( G ) 翻译还是应该输出 Y 域的图,不应该发生改变。这个损失看起来多余,实际作用很大。不加身份损失时,模型经常会出现“过度翻译”——比如把一张已经画好斑马纹理的图案再叠一层痕迹,或者改变背景色调。加了这个损失,能约束模型只在非目标域的图像上动刀,色彩偏移问题明显缓解。
4.4 训练配置与生成器架构
CycleGAN 的生成器采用 ResNet 残差块结构。先下采样提取高层语义,然后用 9 个或 6 个残差块处理,再上采样恢复分辨率。残差块内部通过跳跃连接维持梯度稳定,让网络在加深时不退化。
训练时如果用 256x256 分辨率,学习率前 100 个 epoch 保持 0.0002,后 100 个 epoch 线性衰减到 0。为什么要线性衰减而不是指数衰减?因为生成对抗训练后期已经接近收敛,大步长的更新容易跳出最优区域。线性衰减更平滑,稳定性更好。
我在实际训练中遇到过一个问题:生成器反复震荡,loss 始终不下降。查了很久发现是数据没有做随机裁剪和水平翻转增强。CycleGAN 的样本量通常不大,不增强的话模型很容易过拟合到少量样本上,导致生成结果在训练集上很惊艳,在测试集上崩得一塌糊涂。
5. pix2pixHD:高分辨率多语义的工程化升级
5.1 为什么要一个新的模型
pix2pix 在低分辨率(256x256)上效果不错,但直接放大到 1024x1024 或 2048x2048 时,生成结果会出现两种劣化:全局结构崩坏、局部纹理模糊。另外 pix2pix 每次只能处理“一张完整图像到一张完整图像”的翻译,当输入是高度结构化的语义标签图(比如城市街景的逐像素类别标注)时,它无法有效利用实例级别的信息。
这两个问题催生了 pix2pixHD,论文全称《High-Resolution Image Synthesis and Semantic Manipulation with Conditional GANs》,本质上是 pix2pix 在分辨率和可控性上的全面升级。
5.2 粗到细生成器:两级网络协同工作
pix2pixHD 把生成器拆成了两个阶段:
- G1(全局生成网络):处理低分辨率图,负责抓全局结构。
- G2(局部增强网络):以 G1 的输出和原始分辨率特征作为输入,负责补细节。
具体流程是:输入的先下采样到较低分辨率送入 G1,G1 输出“全局草图”后,再与编码器的多尺度特征拼接送入 G2。G2 在 G1 的结果上做残差细化,最终输出高分辨率图。
这种粗到细(coarse-to-fine)设计的好处在于,把全局结构和高频细节两个目标拆解到两个网络,每个网络的任务更单纯。G1 不必在低层特征上浪费容量,G2 也不必担心全局布局,显著降低了高分辨率生成的难度。
5.3 多尺度判别器:从不同视野检视生成结果
pix2pixHD 用了三个判别器,分别工作在不同尺度上。这种设计称为多尺度判别器(multi-scale discriminator)。
三个判别器的输入分别是原图、原图下采样 2 倍、原图下采样 4 倍。最深的判别器视野最大,整体上看全局结构和语义布局是否合理;最浅的判别器视野最小,聚焦纹理和边缘细节。损失函数取三个判别器输出损失的平均:
[ \min_G \max_{D_1,D_2,D_3} \sum_{k=1}^3 \mathcal{L}_{cGAN}(G, D_k) ]
实测中多尺度判别器对稳定训练有很大帮助。三个判别器相当于三个不同经验的评审,各自关注不同层面的质量,综合意见后生成器不容易在某个尺度上“作弊”。
5.4 感知损失:让生成结果在人的感知上更接近目标
除了 GAN 损失和多尺度判别器,pix2pixHD 还引入了感知损失(perceptual loss)。做法是把生成图和目标图都送入预训练的 VGG16 网络,取若干中间层(如 relu1_2、relu2_2、relu3_2、relu4_2)的特征图,计算这些特征之间的 L1 距离。
为什么直接用像素 L1 不够,还要在 VGG 特征空间算距离?像素级损失只衡量每个像素点的数值差异,但人眼感知图像质量更关注的是“特征级别的差异”——比如纹理是否相似、结构是否一致。VGG 特征距离更能反映人眼感知到的差异,称为感知距离。
5.5 实例边界图带来的可控性提升
pix2pixHD 的一个重要创新是支持输入实例边界图(instance boundary map)。也就是说,除了语义分割标签(哪些区域属于道路、哪些属于建筑、哪些属于树木),还能额外输入每个物体的边界轮廓。
实例边界图让模型能够区分“同一语义类别中的不同个体”。比如一片建筑区域里有两栋相邻的楼,语义分割图把两栋楼标成一类,实例边界图可以额外告诉模型“这两栋楼中间有一条边界”。这让模型能生成更自然的物体边界和阴影关系,而不是把整个建筑区域糊成一大块。
5.6 显存爆炸的处理思路
pix2pixHD 在 2048x1024 分辨率下训练需要大约 8GB 以上的显存,这还是在生成器足够精简的前提下。如果你只有一块消费级显卡,有几个降显存的策略:
- 减小 batch size 到 1,配合实例归一化使用。
- 把输入分辨率降到 1024x512,多数语义生成任务在这个分辨率下视觉效果差别不大。
- 使用梯度累积(gradient accumulation),每步只算 1/4 的 batch 梯度,累积 4 步再更新参数。
- 关闭判别器不需要的层或减少判别器数量,只用两个尺度判别器。
不过要注意的是:分辨率降低后,VGG 感知损失的权重、判别器的感受野都会变化,参数可能需要重新调整,不能指望同一个配置完全换分辨率直接用。
6. 四个模型的选型矩阵与踩坑实录
6.1 什么场景选哪个模型
| 模型 | 数据要求 | 核心能力 | 典型应用 | 输出分辨率 |
|---|---|---|---|---|
| GAN | 无需配对,单域数据 | 从噪声生成图像 | 生成人脸、图像的分布建模 | 通常较低 |
| pix2pix | 成对数据 | 条件图像翻译 | 线稿上色、分割图转街景、灰度图着色 | 256~512 |
| CycleGAN | 两个域的独立数据集 | 无配对风格迁移 | 马变斑马、照片转油画、白天转黑夜 | 256 |
| pix2pixHD | 成对数据 + 可选实例边界 | 高分辨率、多语义可控图像合成 | 城市街景合成、语义布局转照片 | 1024+ |
一句话总结:有配对数据用 pix2pix 系列,没有配对数据用 CycleGAN;需要高清输出优先考虑 pix2pixHD;只是想生成全新图像、没有任何约束条件的用原始 GAN 或改进版 GAN。
6.2 训练不稳定的标准排查路径
我见过不少朋友训练这些模型时 loss 反复横跳,第一反应是调学习率或者换损失函数权重。我建议按照下面这个优先级排查:
第一,确认两个网络的更新频率是否均衡。GAN 训练时如果判别器 loss 降到接近 0,说明判别器已经碾压生成器。此时可以降低判别器学习率,或者给判别器加谱归一化(spectral normalization)。反过来,如果生成器 loss 几乎不变,判别器 loss 也飘忽不定,说明判别器能力太弱,反而要给判别器加容量。
第二,检查数据和归一化。确认图像预处理已经归一化到 [-1, 1] 还是保持 [0, 255]。不同实现要求不一样,弄错的话 loss 永远降不下去。
第三,检查生成器输出是否过饱和。如果生成的图像对比度极高、颜色过于鲜艳,通常是判别器对真实数据和生成数据的分布区分过于容易,可以调大 L1 损失的 lambda 权重,让生成器更多地依赖重建损失来引导训练。
第四,如果训练初期就出现 NaN,先检查学习率是否过大。Adam 的默认学习率 0.001 对 GAN 来说偏大,降到 0.0002 能解决绝大多数 NaN 问题。
6.3 数据准备阶段最容易忽略的细节
数据质量直接决定模型质量,这个道理在 GAN 领域尤其明显。原图有重复样本,CycleGAN 训练时模型会把重复样本当重点样本盲目学习,导致生成结果偏向某些特定姿态。曝光不均的数据会让生成器把曝光异常也当成域特征学进去。
对于 pix2pix 类任务,尤其要注意配对图的对齐精度。输入图和目标图如果差了一个像素,L1 损失就会给边缘区域模型带来极大干扰,因为模型永远无法同时满足空间偏移的目标图。实际上这种情况下模型会倾向于生成模糊结果来“平均”掉偏移误差。
预处理管线建议保持固定顺序:先统一缩放尺寸,再做中心裁剪或随机裁剪,最后做翻转增强。色彩空间保持一致,RGB 三通道顺序不要搞混。
6.4 提升效果的三条经验
第一,CycleGAN 任务可以适当增强循环一致性损失权重。默认的 lambda=10 是作者在大量实验下的选择,但在颜色风格差异特别大的域之间翻译时(比如照片转油画),可以适当增加到 20。因为颜色风格变化大的任务更需要保留结构,而循环一致性结构起着关键保护作用。
第二,pix2pix 家族更新参数时,生成器和判别器的 loss 权重不要固定不变。训练中期(比如前 1/3 轮次结束后),如果判别器能力已经到位,可以把 GAN 损失权重调低、L1 损失权重调高,让生成器做更精细的重建微调。
第三,测试时把 dropout 关掉。很多实现中生成器网络里有 dropout 层,训练时用于增强泛化性,但推理时必须切换到 eval 模式。很多人测试结果不稳定,就是这个原因。
7. 这几个模型的后续演变可以怎么追
掌握了这四个模型的原理,再看目前各种生成模型论文会容易很多。比如 pix2pix 的后续工作 pix2pix3D 把图像翻译扩展到了三维场景;CycleGAN 的循环一致性思想也被引入了视频域风格迁移;pix2pixHD 的粗到细结构在超分辨率领域的很多 SOTA 模型里也能看到影子。
还有一个值得关注的方向是把 GAN 和扩散模型结合。扩散模型近几年在图像生成质量上表现很好,但迭代生成的速度还赶不上 GAN。有些工作尝试用 GAN 的结构加速扩散采样,或者反过来用扩散模型的生成能力辅助 GAN 判别器的特征提取。这个交叉地带还有不少可以挖的空间。
我个人的体会是:GNN 系列模型的底子其实非常经典,它把“生成”这个看似玄学的任务拆成了两个互相博弈、交替优化的子网络,这种思想本身比任何具体实现都重要。被后续各种模型取代的是具体的损失函数和网络结构,但“对抗博弈”这个大框架至今仍然是很多生成任务绕不开的出发点。如果你能把这篇里的原理吃透,再动手跑至少两个模型的代码,后续看新论文的路会顺很多。