查看全课大纲(第 91 / 93 节)
  1. 1.计算机视觉概述
  2. 2.计算机视觉应用场景
  3. 3.困难与挑战
  4. 4.开源库介绍
  5. 5.图像显示与存储
  6. 6.CLAHE
  7. 7.腐蚀膨胀与开闭运算
  8. 8.滤波与边缘填充
  9. 9.各种滤波
  10. 10.图像金字塔
  11. 11.傅里叶变换
  12. 12.短时傅里叶变换与小波变换
  13. 13.颜色特征与颜色直方图
  14. 14.几何特征_边缘
  15. 15.几何特征-角点与斑点
  16. 16.局部特征-SIFT特征描述子
  17. 17.局部特征-SURF特征描述子
  18. 18.局部特征-ORB特征描述子
  19. 19.其他特征描述子
  20. 20.传统图像分割技术
  21. 21.GraphCut
  22. 22.GrabCut
  23. 23.人脸检测的传统方法
  24. 24.行人检测的传统方法HOG+SVM
  25. 25.行人检测DPM
  26. 26.人工神经网络的架构
  27. 27.目标函数与梯度下降
  28. 28.误差反向传播
  29. 29.可手算的神经网络
  30. 30.改进目标函数与克服梯度消散
  31. 31.神经网络之传统神经网络与深度网络的差异
  32. 32.各种梯度下降算法
  33. 33.如何选择合适的梯度下降算法
  34. 34.避免过适应
  35. 35.CNN的架构
  36. 36.CNN的误差反向传播_卷积层
  37. 37.CNN的误差反向传播_池化层
  38. 38.分类所用的数据集
  39. 39.Alexnet详解
  40. 40.VGG详解
  41. 41.GoogLeNet_v1
  42. 42.BatchNormalization
  43. 43.GoogLeNet_v3v4
  44. 44.ResNet
  45. 45.ResNeXt
  46. 46.CNN设计准则
  47. 47.图像检索与推土机距离
  48. 48.基于颜色相似性
  49. 49.基于形状相似性
  50. 50.基于纹理相似性
  51. 51.基于局部特征相似性
  52. 52.DL的二值哈希编码
  53. 53.高维空间中检索KDTree
  54. 54.高维空间中检索LSH
  55. 55.目标识别任务综述
  56. 56.RCNN
  57. 57.SPP-Net
  58. 58.Fast R-CNN
  59. 59.Faster R-CNN
  60. 60.R-CNN系列模型回顾
  61. 61.R-FCN的设计思路
  62. 62.R-FCN的实现
  63. 63.YOLOv1
  64. 64.YOLOv2_9000
  65. 65.YOLOv3
  66. 66.图像分割应用场景与数据集
  67. 67.上采样的实现方式
  68. 68.FCN网络架构的由来
  69. 69.FCN构建细节
  70. 70.DeepLabv1
  71. 71.CRF
  72. 72.DeepLabv2
  73. 73.DeepLabv3
  74. 74.特定的医学影像分割任务
  75. 75.U-Net
  76. 76.3D U-Net
  77. 77.V-Net
  78. 78.DenseNet
  79. 79.病理切片分析任务及数据集
  80. 80.病理切片分析
  81. 81.图说任务及其传统处理方法
  82. 82.深度语言模型与RNN
  83. 83.LSTM及其变种
  84. 84.NIC模型
  85. 85.SAT模型
  86. 86.图说的性能指标
  87. 87.判别模型与生成模型
  88. 88.KL散度与JS散度
  89. 89.VAE
  90. 90.GAN的基本原理
  91. 91.DCGAN与CGAN
  92. 92.Wasserstein GAN
  93. 93.SRGAN

DCGAN与CGAN

小象实战讲义 · 基于深度学习的计算机视觉

在掌握了GAN的基本原理后,我们自然会思考如何让它生成更高质量的图像,以及如何控制生成的内容。本节将介绍两个重要的GAN变体:DCGAN和CGAN。DCGAN通过引入深度卷积结构,极大地提升了GAN训练的稳定性和生成图像的质量;而CGAN则通过引入条件信息,实现了对生成内容的可控性。学完本节,你将能够理解如何构建更稳定、更可控的图像生成模型。

💡 核心导读

  • DCGAN的核心贡献:几乎全部用卷积层替换全连接层,并提出一系列稳定训练的工程技巧,是GAN在工程实践上的重要里程碑。
  • DCGAN的架构特点:生成器采用反卷积(分数步长卷积)进行上采样,判别器采用标准卷积,两者形成对称结构,并广泛使用批量归一化(Batch Normalization)。
  • 潜在向量的语义特性:DCGAN发现输入噪声向量z在潜在空间中具有可计算的语义属性,如“戴眼镜的男士”减去“男士”加上“女士”可以得到“戴眼镜的女士”。
  • CGAN的基本思想:在GAN的生成器和判别器的输入中,额外加入一个条件向量y(如类别标签),从而引导模型生成指定类别的图像。
  • CGAN的实现方式:通过将条件信息与噪声(或真实图像)进行拼接(concatenate),并输入到网络中,实现条件生成。

DCGAN:深度卷积生成对抗网络

原始的GAN网络结构相对自由,生成器和判别器可以使用全连接层。然而,这种结构在训练中极不稳定,容易出现模式崩塌(mode collapse)等训练失败。DCGAN(Deep Convolutional GAN)的提出,正是为了解决这一问题。它并非理论上的突破,而是一系列工程技巧的胜利,为GAN的训练提供了一个稳定且高效的卷积网络架构。

网络架构:对称的卷积与反卷积

DCGAN的核心是几乎完全用卷积层取代全连接层——只在生成器的输入端和判别器的输出端保留全连接层。其生成器(G)和判别器(D)的架构几乎是对称的。

  • 判别器(D):是一个标准的卷积神经网络(CNN)。它接收一张图像(真实或生成),经过一系列卷积层(通常伴随步长stride>1的下采样)后,最终通过一个全连接层输出一个标量,代表图像为真的概率。
  • 生成器(G):其操作与判别器正好相反。它从一个随机噪声向量z开始,首先通过一个全连接层将其重塑(reshape)为一个小的、通道数多的特征图(例如 4x4x1024)。然后,通过一系列反卷积层(或称为转置卷积层、分数步长卷积层)进行上采样。在这个过程中,特征图的空间尺寸逐渐增大,而通道数逐渐减少,直到最后生成一张具有目标尺寸(如 64x64x3)的RGB图像。
import numpy as np
import cv2

# 模拟DCGAN生成器前向传播中的一个关键步骤:反卷积(上采样)
# 我们使用一个简单的双线性插值来模拟上采样的效果,实际DCGAN中使用的是反卷积层。
np.random.seed(42)
# 假设来自上一层的小特征图 (2x2x3 通道)
small_feature = np.random.randn(2, 2, 3).astype(np.float32)
print("输入小特征图 (2x2x3):")
print(small_feature[:,:,0]) # 打印第一个通道
print("\n...(其他两个通道类似)")

# 使用双线性插值上采样到4x4(模拟反卷积的尺寸放大效果)
upsampled_feature = cv2.resize(small_feature, (4,4), interpolation=cv2.INTER_LINEAR)
print("\n上采样后特征图 (4x4x3) 的第一个通道:")
print(upsampled_feature[:,:,0])
输入小特征图 (2x2x3):
[[0.49671414 1.5230298 ]
 [1.5792128  0.54256004]]

...(其他两个通道类似)

上采样后特征图 (4x4x3) 的第一个通道:
[[0.49671414 0.75329304 1.2664509  1.5230298 ]
 [0.7673388  0.89498216 1.1502689  1.2779124 ]
 [1.3085881  1.1783605  0.9179051  0.78767747]
 [1.5792128  1.3200495  0.8017232  0.54256004]]

稳定训练的工程技巧

DCGAN论文中总结了一系列让GAN训练保持稳定的技巧,这些技巧后来被广泛采纳:

  1. 用卷积层替代全连接层:判别器和生成器中均使用卷积层,去除全连接层(除生成器输入和判别器输出外)。
  2. 去除池化层:使用带步长的卷积(Strided Convolution) 代替判别器中的池化层进行下采样;使用分数步长卷积(Fractional-Strided Convolution,即反卷积) 代替生成器中的上采样层。
  3. 使用批量归一化(Batch Normalization):在生成器和判别器的中间层都应用BN层,有助于稳定训练、加速收敛。但注意,生成器的输出层和判别器的输入层一般不使用BN。
  4. 选择合适的激活函数:
    • 生成器:中间层使用ReLU,输出层使用Tanh(将像素值约束到[-1, 1])。
    • 判别器:中间层使用Leaky ReLU(缓解梯度消失,对负值输入给予一个小的斜率,如0.2)。
  5. 使用Adam优化器:论文发现使用Adam优化器,并设置一个较小的学习率(如 0.0002)效果很好。
  6. 标签平滑(一种后续常用的技巧,DCGAN原论文未强调):在训练判别器时,对真实样本的标签使用略小于1的值(如0.9),生成样本的标签保持0,即单边标签平滑,可以防止判别器过于自信。注意不要顺手把生成样本的标签也平滑成0.1:提出该技巧的 Salimans 等人(2016)明确推荐只做单边平滑,并指出平滑负标签会让最优判别器中多出一项与生成分布成正比的成分,使“远离真实数据的错误样本”失去向数据靠拢的动力,反而强化生成器当前的坏行为。

潜在空间(Z向量)的语义特性

DCGAN一个令人惊奇的发现是,生成器的输入噪声向量z所在的潜在空间(Latent Space)具有良好的插值特性和语义运算能力。

  • 向量运算:例如,在训练了人脸图像的DCGAN后,我们可以找到代表“戴眼镜的男士”的向量z1、代表“男士”的向量z2和代表“女士”的向量z3。那么,运算 z4 = z1 - z2 + z3 所得到的向量z4输入生成器后,很可能会生成一张“戴眼镜的女士”的图像。这表明潜在空间编码了可分离的语义属性。
  • 平滑插值:在两个不同的噪声向量z_a和z_b之间进行线性插值,z = α * z_a + (1-α) * z_b,并将插值结果输入生成器,可以得到两张人脸图像之间平滑的 morphing 过渡效果,例如从“向左看”渐变到“向右看”。不过实际效果中有些脸变形比较明显,这是网络本身能力有限所致。

效果与局限性

DCGAN在LSUN卧室数据集等大型数据集上生成了质量显著提升的图像,证明了其架构的有效性。然而,它也存在局限:

  1. 分辨率限制:受当时算力所限,主要生成较低分辨率(如 64×64)的图像。
  2. 结构理解不足:生成的图像“远看很像,近看经不起推敲”。模型有时会混淆物体的结构和空间关系(例如将床和墙的特征混合在一起),说明其并未真正理解场景的几何和语义结构。

CGAN:条件生成对抗网络

原始的GAN输入是随机噪声,生成的内容不可控。CGAN(Conditional GAN)的核心思想是为生成过程增加条件约束,从而实现对生成内容的控制。需要先说明的是,CGAN 与 DCGAN 之间没有继承关系:DCGAN 改的是网络结构,CGAN 改的是输入的信息,两者可以独立使用,也可以组合成一个条件卷积 GAN。

基本思路

CGAN在原始GAN的基础上,为生成器(G)和判别器(D)都额外输入一个条件信息y。这个条件y可以是类别标签(如数字“7”)、文本描述、甚至是另一张图像(如图像到图像的翻译)。最直观的例子是数字字体生成:想让模型写出几,就把“几”的编码作为条件输入,输出就是对应的数字图像。

  • 生成器:输入从 (z) 变为 (z, y)。目标是生成既逼真又符合条件y的图像G(z|y)。
  • 判别器:输入从 (x) 变为 (x, y)。任务不仅是判断图像x的真假,还要判断图像x与条件y是否匹配。它需要拒绝两种样本:1) 真实的图像但配错了条件;2) 生成的图像(即使看起来逼真)。

实现方式

实现CGAN最常见的方式是特征拼接(Concatenation)。

  • 在生成器中,将条件向量y(例如one-hot编码的类别向量)与噪声向量z在某个维度上拼接起来,作为新的输入。
  • 在判别器中,条件同样要与输入一起送入:可以把条件向量y与展平后的图像向量拼接,也可以把y投影成与图像同尺寸的特征图后按通道拼接。

其价值函数也相应地变为条件形式: min⁡Gmax⁡DV(D,G)=Ex∼pdata(x)[log⁡D(x∣y)]+Ez∼pz(z)[log⁡(1−D(G(z∣y)∣y))]\min_G \max_D V(D, G) = \mathbb{E}_{x \sim p_{data}(x)}[\log D(x|y)] + \mathbb{E}_{z \sim p_z(z)}[\log(1 - D(G(z|y)|y))]

import numpy as np

# 模拟CGAN中生成器的输入拼接过程
np.random.seed(2024)
# 条件:假设生成数字“7”,使用one-hot编码 (10维)
y = np.zeros(10)
y[7] = 1.0  # 索引7代表数字7
print("条件向量 y (one-hot for '7'):")
print(y)

# 噪声向量 z (100维)
z = np.random.randn(100)
print("\n噪声向量 z (前5个值):")
print(z[:5])

# 拼接操作:将条件y和噪声z拼接起来作为生成器输入
generator_input = np.concatenate([y, z])
print(f"\n拼接后的生成器输入向量维度: {generator_input.shape}")
print("前10个值(即条件部分):", generator_input[:10])
print("第11-15个值(即噪声部分开头):", generator_input[10:15])
条件向量 y (one-hot for '7'):
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]

噪声向量 z (前5个值):
[ 1.66804732  0.73734773 -0.20153776 -0.15091195  0.91605181]

拼接后的生成器输入向量维度: (110,)
前10个值(即条件部分): [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
第11-15个值(即噪声部分开头): [ 1.66804732  0.73734773 -0.20153776 -0.15091195  0.91605181]

应用示例

一个经典的应用是基于草图的图像生成。例如,输入一张建筑轮廓的草图(作为条件y),CGAN可以想象出一张具有真实纹理和细节的建筑图像。课件中的对比是三栏并排:左边是输入的草图,中间是模型生成的楼房,右边是真实场景。两相比较,大多数位置生成得比较合理,但某些位置的细节仍然混乱,没有完全想清楚;课上分析,这可能与训练不够充分、或提供给模型的数据样本不够丰富有关。

# 伪代码示意CGAN训练流程(TensorFlow 1.x 风格写法,仅示意逻辑)
# 注意:以下为逻辑示意,不可直接运行。

# 定义条件(如图片标签)
y_placeholder = tf.placeholder(tf.float32, [None, num_classes])

# 定义噪声
z_placeholder = tf.placeholder(tf.float32, [None, noise_dim])

# 生成器:拼接噪声z和条件y
gen_input = tf.concat([z_placeholder, y_placeholder], axis=1)
fake_images = generator(gen_input)  # generator是生成器网络

# 判别器对真实图像和条件的判断
real_input_with_cond = tf.concat([real_images, y_placeholder], axis=1) # 可能需要调整维度
d_real_logits = discriminator(real_input_with_cond)

# 判别器对生成图像和相同条件的判断
fake_input_with_cond = tf.concat([fake_images, y_placeholder], axis=1)
d_fake_logits = discriminator(fake_input_with_cond)

# 损失函数:判别器要最大化对真实图像和正确条件的判断,最小化对生成图像的判断
# 生成器要最小化判别器对其生成图像的判断(即骗过判别器)
d_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
    logits=d_real_logits, labels=tf.ones_like(d_real_logits) * 0.9)) # 标签平滑
d_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
    logits=d_fake_logits, labels=tf.zeros_like(d_fake_logits))) # 生成样本保持0(单边平滑)
d_loss = d_loss_real + d_loss_fake

g_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
    logits=d_fake_logits, labels=tf.ones_like(d_fake_logits)))

# 分别优化生成器和判别器
d_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'discriminator')
g_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, 'generator')
d_train_opt = tf.train.AdamOptimizer(0.0002).minimize(d_loss, var_list=d_vars)
g_train_opt = tf.train.AdamOptimizer(0.0002).minimize(g_loss, var_list=g_vars)

📝 动手练一练

  1. 概念辨析:DCGAN和CGAN名称中都有一个“C”,但它们代表的含义和解决的问题有何本质不同?
👉 点击查看参考答案
  • DCGAN中的“C”:代表“Convolutional”(卷积的)。它主要解决的是GAN训练不稳定和生成质量低的问题,其贡献在于提出了一套基于深度卷积网络的稳定架构和工程技巧。
  • CGAN中的“C”:代表“Conditional”(条件的)。它主要解决的是GAN生成内容不可控的问题,其贡献在于通过引入额外的条件信息,实现了对生成图像类别或属性的定向控制。
  • 关系:两者可以结合。例如,可以构建一个Conditional DCGAN,即使用DCGAN的卷积架构,同时加入条件输入,从而得到一个既稳定又可控制的生成模型。
  1. 代码思考:在CGAN的模拟代码中,我们将100维的噪声向量z和10维的one-hot条件向量y进行了拼接。如果现在我们想将条件y改为一个128维的文本嵌入向量(例如来自一个句子),生成器的输入维度会变成多少?这种改变对网络设计可能带来什么挑战?
👉 点击查看参考答案
  • 新的输入维度:噪声向量z (100维) + 文本嵌入向量 (128维) = 228维。
  • 可能带来的挑战:
    1. 维度匹配:生成器的第一层(全连接层)的输入维度需要从110调整为228。
    2. 信息融合:文本嵌入是稠密、高维的语义信息,噪声是低维的随机信息。如何让网络有效地融合这两种异构信息是一个关键。简单的拼接可能不够,更高级的架构会使用注意力机制(Attention)或跨模态融合模块。
    3. 训练难度:条件信息变得更复杂,可能要求判别器具备更强的语义理解能力(判断图像与文本描述是否匹配),这可能会增加训练难度和收敛时间。

本章小结

本节深入探讨了GAN家族中两个奠基性的改进模型:DCGAN和CGAN。DCGAN从工程实践角度,通过引入深度卷积对称结构、批量归一化、Leaky ReLU等一系列技巧,显著提升了原始GAN的训练稳定性和生成质量,并揭示了潜在空间的语义特性。CGAN则从控制逻辑角度,通过在生成器和判别器中引入条件信息,实现了对生成内容的定向控制,打开了可控图像生成的大门。

从时间上看,CGAN(2014)与 DCGAN(2015)都属于 GAN 早期的奠基工作:它们的卷积生成器架构与条件拼接思路,后来分别被 StyleGAN 等 GAN 模型和条件扩散模型沿用;但在今天的高分辨率、强可控生成任务中,主流方案已经转向扩散模型(如 2022 年的 Stable Diffusion)与 Transformer 主干(DiT)。

📋 行动清单

  • 理解DCGAN的对称架构:在纸上画出DCGAN生成器与判别器的数据流图,明确反卷积上采样和卷积下采样的对应关系。
  • 复现CGAN的拼接操作:尝试用NumPy编写一个函数,模拟对不同形式条件(如类别标签、短向量)与噪声向量的拼接过程。
  • 思考组合可能性:设想一个实际应用场景(如生成指定风格的动漫头像),并分析如何结合DCGAN和CGAN的思想来构建模型。

—— 小象教研组