生成对抗网络与图像合成技术:理论、演进与应用
目录
摘要:生成对抗网络(Generative Adversarial Networks, GANs)作为深度学习领域最具开创性的框架之一,彻底改变了图像合成技术的发展轨迹。本文系统性地论述了GAN的核心理论基础、训练动力学及其面临的关键挑战(如模式崩溃、训练不稳定性)。文章重点剖析了为解决这些挑战而诞生的一系列里程碑式变体,如DCGAN、WGAN、StyleGAN等,并深入探讨了其在图像生成、风格迁移、图像超分辨率、图像修复等核心计算机视觉任务中的应用原理与性能表现。最后,本文对GAN未来的研究方向,包括训练稳定性、可控生成、与大模型的融合以及伦理问题进行了展望。本研究旨在为读者提供一个全面而深入的技术视角,理解GAN如何推动图像合成技术迈向逼真化、可控化和实用化的新阶段。
关键词:生成对抗网络;图像合成;深度学习;计算机视觉;模式崩溃;风格迁移;超分辨率
第一章 引言
1.1 研究背景
在GAN出现之前,图像合成领域主要由变分自编码器(VAEs)和自回归模型(如PixelRNN)主导。这些模型虽然能够生成数据,但往往存在生成图像模糊、生成速度慢或似然函数难以计算等问题。2014年,Ian Goodfellow等人提出了生成对抗网络(GANs)[1],其通过一个生成器(Generator)和一个判别器(Discriminator)的极小极大博弈(Minimax Game) 进行训练,提供了一种无需显式定义似然函数即可学习数据分布的新范式。该框架能够生成极其逼真、细节丰富的图像,迅速成为人工智能领域的研究热点。
1.2 研究意义
GAN的研究意义深远且多维:
-
理论价值:GAN提供了一种全新的无监督和半监督学习范式,推动了概率生成模型和深度学习理论的发展。
-
技术价值:它极大地提升了图像合成的质量与多样性,在多个视觉任务中达到了 state-of-the-art 的性能。
-
应用价值:GAN技术在影视娱乐(特效制作)、电子商务(虚拟试衣)、医学影像(数据增强)、自动驾驶(场景生成)等领域展现出巨大的应用潜力。
1.3 本文主要贡献与结构
本文的主要贡献在于对GAN的技术演进脉络进行了系统性的梳理,并对其在不同图像合成任务中的应用机制进行了深入的对比分析。全文结构如下:第二章将介绍GAN的基础理论与训练挑战;第三章将详细论述GAN的各种变体及其改进原理;第四章将聚焦于GAN在风格迁移、超分辨率等具体任务中的应用;第五章将讨论当前面临的开放性挑战与未来研究方向;第六章为总结。
第二章 GAN:基础理论与核心挑战
2.1 基本框架与训练动力学
GAN的核心思想来源于博弈论中的纳什均衡。它由两个神经网络构成:
-
生成器(G, Generator):其输入通常是随机噪声向量
z(从先验分布p_z(z)中采样),目标是将z映射到数据空间,生成足以“以假乱真”的样本G(z),试图欺骗判别器。 -
判别器(D, Discriminator):其输入是真实数据样本
x或生成样本G(z),目标是正确区分输入的真伪,输出一个标量,代表输入为真实数据的概率。
二者的价值函数V(G, D)表示为:
minGmaxDV(D,G)=Ex∼pdata(x)[logD(x)]+Ez∼pz(z)[log(1−D(G(z)))]minGmaxDV(D,G)=Ex∼pdata(x)[logD(x)]+Ez∼pz(z)[log(1−D(G(z)))]
生成器G试图最小化该函数,而判别器D试图最大化它,形成一个动态的博弈过程。
2.2 关键挑战与局限性
尽管思想巧妙,原始GAN面临着几个公认的难题:
-
训练不稳定性(Training Instability):生成器和判别器的训练需要保持精妙的平衡。任何一方的过强都会导致训练无法继续(例如,判别器太强会导致生成器梯度消失)。
-
模式崩溃(Mode Collapse):生成器倾向于只生成少数几种甚至单一模式的样本,无法覆盖真实数据的所有多样性。
-
评估指标缺乏(Lack of Evaluation Metrics):如何客观、定量地评估生成图像的质量和多样性是一个长期挑战。常用的指标包括初始分数(IS)[2]和弗雷歇初始距离(FID)[3],后者因其与人类感知的一致性更受青睐。
第三章 GAN的核心演进与变体
为解决上述挑战,研究人员提出了大量改进方案。
3.1 架构创新:DCGAN
Radford等人提出的深度卷积生成对抗网络(DCGAN)[4]是首个将CNN成功引入GAN的工作。其核心贡献包括:
-
使用转置卷积(Transposed Convolution) 进行上采样。
-
在生成器和判别器中使用批归一化(Batch Normalization)。
-
移除全连接层,使用深度卷积架构。
-
生成器使用ReLU激活,输出层使用Tanh;判别器使用LeakyReLU。
DCGAN提供了稳定训练CNN架构的蓝图,并证明了GAN可以学习到有意义的潜在空间表示。
3.2 损失函数革新:WGAN与LSGAN
原始GAN的JS散度在训练中容易导致梯度问题。Wasserstein GAN (WGAN) [5]引入了Wasserstein距离(Earth-Mover距离) 来衡量分布差异。其价值函数为:
minGmaxD∈1−LipschitzEx∼pdata[D(x)]−Ez∼pz[D(G(z))]minGmaxD∈1−LipschitzEx∼pdata[D(x)]−Ez∼pz[D(G(z))]
通过权重裁剪或梯度惩罚(WGAN-GP)[6]来强制判别器满足1-Lipschitz条件,WGAN极大改善了训练稳定性,并基本解决了模式崩溃问题。
最小二乘GAN(LSGAN)[7]则将判别器的损失函数替换为最小二乘损失,为生成样本提供更平滑的梯度,也能有效提升训练稳定性。
3.3 精细化控制:StyleGAN系列
NVIDIA提出的StyleGAN[8]及其后续版本StyleGAN2[9]、StyleGAN3[10]将图像生成质量推向了新的高度。其核心创新在于:
-
风格迁移思想:重新设计了生成器架构,通过自适应实例归一化(AdaIN) 将潜在编码
z映射到一个中间潜在空间(W空间),该空间解耦性更好,允许对生成图像的风格(如发型、肤色、姿态)进行精细、分离的控制。 -
解耦学习:实现了高水平的风格混合(Style Mixing)和随机变化(Stochastic Variation)。
-
消除 artifacts:StyleGAN2分析了并消除了StyleGAN中常见的“液滴”artifacts。
-
等变性改善:StyleGAN3进一步改善了模型对平移、旋转等变换的等变性,生成更加自然的内容。
第四章 GAN在图像合成任务中的应用
4.1 图像生成与编辑
这是GAN最直接的应用。从生成手写数字(MNIST)、人脸(CelebA)到复杂场景(ImageNet),GAN证明了其生成高保真图像的能力。基于潜在空间的可解释性,用户可以通过编辑潜在向量z来实现语义图像编辑,如添加微笑、改变年龄等(如InterFaceGAN)。
4.2 风格迁移(Style Transfer)
虽然最初由Gatys等人基于优化提出,但GAN提供了更快的前向风格迁移方案。CycleGAN[11]和Pix2Pix[12]是该领域的代表性工作。
-
Pix2Pix:使用条件GAN(cGAN) 和U-Net架构作为生成器,在成对数据上学习图像到图像的映射(如语义分割图→照片)。
-
CycleGAN:引入了循环一致性损失(Cycle-Consistency Loss),无需成对数据即可实现风格迁移(如马→斑马,照片→莫奈画风)。
4.3 图像超分辨率(Super-Resolution)
SRGAN[13]首次将GAN引入超分辨率任务,其核心贡献在于提出了感知损失(Perceptual Loss),该损失结合了内容损失(基于VGG网络的特征重建) 和对抗损失,而非传统的像素级MSE损失。这使得模型能够重建出感知上更逼真、细节更丰富的高分辨率图像,尽管PSNR/SSIM指标可能不高。
4.4 图像修复与补全(Image Inpainting)
图像修复旨在填充图像中的缺失或损坏区域。Context Encoder[14]和DeepFill[15]等基于GAN的模型,通过结合对抗损失和重建损失,能够根据周围上下文信息,生成视觉上合理且语义一致的内容来填充空白区域。
第五章 挑战与未来展望
尽管取得了巨大成功,GAN仍面临诸多挑战:
-
训练稳定性与模式崩溃:虽经改善,但未根本解决。寻求理论上有保证的、稳定的训练算法仍是核心问题。
-
可控制性与解耦表示:如何更好地理解和控制潜在空间,实现高度解耦的、符合人类直觉的属性编辑是未来重点。
-
与扩散模型等新兴技术的融合:近期,扩散模型(Diffusion Models) 在图像生成质量上已展现出超越GAN的潜力。未来研究可能是GAN与扩散模型优势的融合,例如利用GAN的快速生成能力。
-
计算资源与效率:训练高分辨率GAN需要巨大的计算资源和时间,开发更高效的架构和训练方法是实用化的关键。
-
伦理与社会影响:GAN生成的“深度伪造(Deepfakes)”技术带来了巨大的伦理和社会挑战。开发有效的伪造检测技术和制定相关法规至关重要。
第六章 结论
生成对抗网络无疑是一场技术革命,它重新定义了图像合成的边界。从最初不稳定的概念验证,到如今能够生成以假乱真的高分辨率图像,GAN的发展历程充满了架构和理论上的创新。通过不断改进损失函数(如WGAN)、设计新型网络结构(如StyleGAN)、并成功应用于风格迁移、超分辨率等一系列任务,GAN证明了其强大的能力和灵活性。尽管面临着稳定性、可控性和伦理等方面的持续挑战,但其作为一项 foundational technology,将继续推动人工智能和计算机视觉领域向前发展。未来的研究将在追求更高生成质量的同时,更加注重模型的稳定性、可控性、效率及其负责任的应用。
参考文献
[1] Goodfellow, I., et al. (2014). Generative adversarial nets. Advances in neural information processing systems.
[2] Salimans, T., et al. (2016). Improved techniques for training gans. Advances in neural information processing systems.
[3] Heusel, M., et al. (2017). Gans trained by a two time-scale update rule converge to a local nash equilibrium. Advances in neural information processing systems.
[4] Radford, A., Metz, L., & Chintala, S. (2015). Unsupervised representation learning with deep convolutional generative adversarial networks. arXiv preprint arXiv:1511.06434.
[5] Arjovsky, M., Chintala, S., & Bottou, L. (2017). Wasserstein generative adversarial networks. International conference on machine learning.
[6] Gulrajani, I., et al. (2017). Improved training of wasserstein gans. Advances in neural information processing systems.
[7] Mao, X., et al. (2017). Least squares generative adversarial networks. Proceedings of the IEEE international conference on computer vision.
[8] Karras, T., Laine, S., & Aila, T. (2019). A style-based generator architecture for generative adversarial networks. Proceedings of the IEEE/CVF conference on computer vision and pattern recognition.
[9] Karras, T., et al. (2020). Analyzing and improving the image quality of stylegan. Proceedings of the IEEE/CVF conference on computer vision and pattern recognition.
[10] Karras, T., et al. (2021). Alias-free generative adversarial networks. Advances in Neural Information Processing Systems.
[11] Zhu, J. Y., et al. (2017). Unpaired image-to-image translation using cycle-consistent adversarial networks. Proceedings of the IEEE international conference on computer vision.
[12] Isola, P., et al. (2017). Image-to-image translation with conditional adversarial networks. Proceedings of the IEEE conference on computer vision and pattern recognition.
[13] Ledig, C., et al. (2017). Photo-realistic single image super-resolution using a generative adversarial network. Proceedings of the IEEE conference on computer vision and pattern recognition.
[14] Pathak, D., et al. (2016). Context encoders: Feature learning by inpainting. Proceedings of the IEEE conference on computer vision and pattern recognition.
[15] Yu, J., et al. (2018). Generative image inpainting with contextual attention. Proceedings of the IEEE conference on computer vision and pattern recognition.
好的,我将为您提供一系列GAN及其变体的详细代码实现,包含完整的模型架构、损失函数、训练循环和评估指标。
## 1. 基础GAN实现
```python
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.utils import make_grid
import matplotlib.pyplot as plt
import numpy as np
import os
# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
# 生成器模型
class Generator(nn.Module):
def __init__(self, latent_dim=100, img_channels=1, feature_map_size=64):
super(Generator, self).__init__()
self.latent_dim = latent_dim
self.main = nn.Sequential(
# 输入: (latent_dim, 1, 1)
nn.ConvTranspose2d(latent_dim, feature_map_size * 8, 4, 1, 0, bias=False),
nn.BatchNorm2d(feature_map_size * 8),
nn.ReLU(True),
# 输出: (feature_map_size*8, 4, 4)
nn.ConvTranspose2d(feature_map_size * 8, feature_map_size * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 4),
nn.ReLU(True),
# 输出: (feature_map_size*4, 8, 8)
nn.ConvTranspose2d(feature_map_size * 4, feature_map_size * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 2),
nn.ReLU(True),
# 输出: (feature_map_size*2, 16, 16)
nn.ConvTranspose2d(feature_map_size * 2, feature_map_size, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size),
nn.ReLU(True),
# 输出: (feature_map_size, 32, 32)
nn.ConvTranspose2d(feature_map_size, img_channels, 4, 2, 1, bias=False),
nn.Tanh() # 输出范围: [-1, 1]
# 最终输出: (img_channels, 64, 64)
)
def forward(self, x):
x = x.view(-1, self.latent_dim, 1, 1)
return self.main(x)
# 判别器模型
class Discriminator(nn.Module):
def __init__(self, img_channels=1, feature_map_size=64):
super(Discriminator, self).__init__()
self.main = nn.Sequential(
# 输入: (img_channels, 64, 64)
nn.Conv2d(img_channels, feature_map_size, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
# 输出: (feature_map_size, 32, 32)
nn.Conv2d(feature_map_size, feature_map_size * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 2),
nn.LeakyReLU(0.2, inplace=True),
# 输出: (feature_map_size*2, 16, 16)
nn.Conv2d(feature_map_size * 2, feature_map_size * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 4),
nn.LeakyReLU(0.2, inplace=True),
# 输出: (feature_map_size*4, 8, 8)
nn.Conv2d(feature_map_size * 4, feature_map_size * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 8),
nn.LeakyReLU(0.2, inplace=True),
# 输出: (feature_map_size*8, 4, 4)
nn.Conv2d(feature_map_size * 8, 1, 4, 1, 0, bias=False),
nn.Sigmoid() # 输出真实概率
)
def forward(self, x):
return self.main(x).view(-1, 1)
# 初始化模型
latent_dim = 100
img_channels = 1
img_size = 64
generator = Generator(latent_dim, img_channels).to(device)
discriminator = Discriminator(img_channels).to(device)
# 定义损失函数和优化器
criterion = nn.BCELoss()
lr = 0.0002
beta1 = 0.5
optimizer_G = optim.Adam(generator.parameters(), lr=lr, betas=(beta1, 0.999))
optimizer_D = optim.Adam(discriminator.parameters(), lr=lr, betas=(beta1, 0.999))
# 数据加载和预处理
transform = transforms.Compose([
transforms.Resize(img_size),
transforms.CenterCrop(img_size),
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,)) # 将范围从[0,1]归一化到[-1,1]
])
dataset = torchvision.datasets.MNIST(root='./data', train=True,
download=True, transform=transform)
dataloader = DataLoader(dataset, batch_size=128, shuffle=True, num_workers=2)
# 训练函数
def train_gan(num_epochs=50):
fixed_noise = torch.randn(64, latent_dim, device=device)
real_label = 1.0
fake_label = 0.0
for epoch in range(num_epochs):
for i, (real_imgs, _) in enumerate(dataloader):
batch_size = real_imgs.size(0)
real_imgs = real_imgs.to(device)
# 训练判别器:最大化 log(D(x)) + log(1 - D(G(z)))
discriminator.zero_grad()
# 真实图像
label = torch.full((batch_size, 1), real_label, device=device)
output = discriminator(real_imgs)
errD_real = criterion(output, label)
errD_real.backward()
D_x = output.mean().item()
# 生成假图像
noise = torch.randn(batch_size, latent_dim, device=device)
fake_imgs = generator(noise)
label.fill_(fake_label)
output = discriminator(fake_imgs.detach())
errD_fake = criterion(output, label)
errD_fake.backward()
D_G_z1 = output.mean().item()
errD = errD_real + errD_fake
optimizer_D.step()
# 训练生成器:最大化 log(D(G(z)))
generator.zero_grad()
label.fill_(real_label)
output = discriminator(fake_imgs)
errG = criterion(output, label)
errG.backward()
D_G_z2 = output.mean().item()
optimizer_G.step()
if i % 100 == 0:
print(f'[{epoch}/{num_epochs}][{i}/{len(dataloader)}] '
f'Loss_D: {errD.item():.4f} Loss_G: {errG.item():.4f} '
f'D(x): {D_x:.4f} D(G(z)): {D_G_z1:.4f}/{D_G_z2:.4f}')
# 每个epoch保存生成的图像
with torch.no_grad():
fake = generator(fixed_noise).detach().cpu()
grid = make_grid(fake, nrow=8, normalize=True)
plt.figure(figsize=(8, 8))
plt.imshow(np.transpose(grid, (1, 2, 0)))
plt.axis('off')
plt.savefig(f'gan_samples_epoch_{epoch}.png')
plt.close()
# 保存模型
torch.save(generator.state_dict(), 'generator.pth')
torch.save(discriminator.state_dict(), 'discriminator.pth')
# 开始训练
train_gan()
```
## 2. DCGAN实现
```python
class DCGAN_Generator(nn.Module):
"""DCGAN生成器"""
def __init__(self, latent_dim=100, img_channels=3, feature_map_size=64):
super(DCGAN_Generator, self).__init__()
self.latent_dim = latent_dim
self.main = nn.Sequential(
# 输入是Z,进入全连接层
nn.ConvTranspose2d(latent_dim, feature_map_size * 8, 4, 1, 0, bias=False),
nn.BatchNorm2d(feature_map_size * 8),
nn.ReLU(True),
# 输出尺寸: (feature_map_size*8, 4, 4)
nn.ConvTranspose2d(feature_map_size * 8, feature_map_size * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 4),
nn.ReLU(True),
# 输出尺寸: (feature_map_size*4, 8, 8)
nn.ConvTranspose2d(feature_map_size * 4, feature_map_size * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 2),
nn.ReLU(True),
# 输出尺寸: (feature_map_size*2, 16, 16)
nn.ConvTranspose2d(feature_map_size * 2, feature_map_size, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size),
nn.ReLU(True),
# 输出尺寸: (feature_map_size, 32, 32)
nn.ConvTranspose2d(feature_map_size, img_channels, 4, 2, 1, bias=False),
nn.Tanh()
# 输出尺寸: (img_channels, 64, 64)
)
def forward(self, x):
x = x.view(-1, self.latent_dim, 1, 1)
return self.main(x)
class DCGAN_Discriminator(nn.Module):
"""DCGAN判别器"""
def __init__(self, img_channels=3, feature_map_size=64):
super(DCGAN_Discriminator, self).__init__()
self.main = nn.Sequential(
# 输入: (img_channels, 64, 64)
nn.Conv2d(img_channels, feature_map_size, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
# 输出: (feature_map_size, 32, 32)
nn.Conv2d(feature_map_size, feature_map_size * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 2),
nn.LeakyReLU(0.2, inplace=True),
# 输出: (feature_map_size*2, 16, 16)
nn.Conv2d(feature_map_size * 2, feature_map_size * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 4),
nn.LeakyReLU(0.2, inplace=True),
# 输出: (feature_map_size*4, 8, 8)
nn.Conv2d(feature_map_size * 4, feature_map_size * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 8),
nn.LeakyReLU(0.2, inplace=True),
# 输出: (feature_map_size*8, 4, 4)
nn.Conv2d(feature_map_size * 8, 1, 4, 1, 0, bias=False),
nn.Sigmoid()
)
def forward(self, x):
return self.main(x).view(-1, 1)
```
## 3. WGAN-GP实现
```python
class WGAN_GP_Generator(nn.Module):
"""WGAN-GP生成器"""
def __init__(self, latent_dim=100, img_channels=3, feature_map_size=64):
super(WGAN_GP_Generator, self).__init__()
self.latent_dim = latent_dim
self.main = nn.Sequential(
nn.ConvTranspose2d(latent_dim, feature_map_size * 8, 4, 1, 0, bias=False),
nn.BatchNorm2d(feature_map_size * 8),
nn.ReLU(True),
nn.ConvTranspose2d(feature_map_size * 8, feature_map_size * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 4),
nn.ReLU(True),
nn.ConvTranspose2d(feature_map_size * 4, feature_map_size * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size * 2),
nn.ReLU(True),
nn.ConvTranspose2d(feature_map_size * 2, feature_map_size, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_map_size),
nn.ReLU(True),
nn.ConvTranspose2d(feature_map_size, img_channels, 4, 2, 1, bias=False),
nn.Tanh()
)
def forward(self, x):
x = x.view(-1, self.latent_dim, 1, 1)
return self.main(x)
class WGAN_GP_Discriminator(nn.Module):
"""WGAN-GP判别器(Critic)"""
def __init__(self, img_channels=3, feature_map_size=64):
super(WGAN_GP_Discriminator, self).__init__()
self.main = nn.Sequential(
nn.Conv2d(img_channels, feature_map_size, 4, 2, 1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(feature_map_size, feature_map_size * 2, 4, 2, 1, bias=False),
nn.InstanceNorm2d(feature_map_size * 2),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(feature_map_size * 2, feature_map_size * 4, 4, 2, 1, bias=False),
nn.InstanceNorm2d(feature_map_size * 4),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(feature_map_size * 4, feature_map_size * 8, 4, 2, 1, bias=False),
nn.InstanceNorm2d(feature_map_size * 8),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(feature_map_size * 8, 1, 4, 1, 0, bias=False)
# 注意:没有Sigmoid激活函数
)
def forward(self, x):
return self.main(x).view(-1)
def compute_gradient_penalty(D, real_samples, fake_samples):
"""计算梯度惩罚"""
alpha = torch.rand(real_samples.size(0), 1, 1, 1, device=real_samples.device)
interpolates = (alpha * real_samples + (1 - alpha) * fake_samples).requires_grad_(True)
d_interpolates = D(interpolates)
fake = torch.ones(real_samples.size(0), device=real_samples.device)
gradients = torch.autograd.grad(
outputs=d_interpolates,
inputs=interpolates,
grad_outputs=fake,
create_graph=True,
retain_graph=True,
only_inputs=True,
)[0]
gradients = gradients.view(gradients.size(0), -1)
gradient_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean()
return gradient_penalty
def train_wgan_gp(num_epochs=50, n_critic=5, lambda_gp=10):
"""训练WGAN-GP"""
generator = WGAN_GP_Generator(latent_dim).to(device)
discriminator = WGAN_GP_Discriminator().to(device)
optimizer_G = optim.Adam(generator.parameters(), lr=0.0001, betas=(0.5, 0.9))
optimizer_D = optim.Adam(discriminator.parameters(), lr=0.0001, betas=(0.5, 0.9))
for epoch in range(num_epochs):
for i, (real_imgs, _) in enumerate(dataloader):
real_imgs = real_imgs.to(device)
batch_size = real_imgs.size(0)
# 训练判别器(Critic)
for _ in range(n_critic):
optimizer_D.zero_grad()
# 真实图像
real_validity = discriminator(real_imgs)
# 生成假图像
z = torch.randn(batch_size, latent_dim, device=device)
fake_imgs = generator(z)
fake_validity = discriminator(fake_imgs.detach())
# 计算梯度惩罚
gradient_penalty = compute_gradient_penalty(
discriminator, real_imgs.data, fake_imgs.data
)
# WGAN损失加上梯度惩罚
errD = -torch.mean(real_validity) + torch.mean(fake_validity) + lambda_gp * gradient_penalty
errD.backward()
optimizer_D.step()
# 训练生成器
optimizer_G.zero_grad()
fake_imgs = generator(z)
fake_validity = discriminator(fake_imgs)
errG = -torch.mean(fake_validity)
errG.backward()
optimizer_G.step()
if i % 100 == 0:
print(f'[{epoch}/{num_epochs}][{i}/{len(dataloader)}] '
f'Loss_D: {errD.item():.4f} Loss_G: {errG.item():.4f}')
```
## 4. CycleGAN实现
```python
class ResidualBlock(nn.Module):
"""残差块"""
def __init__(self, in_features):
super(ResidualBlock, self).__init__()
self.block = nn.Sequential(
nn.ReflectionPad2d(1),
nn.Conv2d(in_features, in_features, 3),
nn.InstanceNorm2d(in_features),
nn.ReLU(inplace=True),
nn.ReflectionPad2d(1),
nn.Conv2d(in_features, in_features, 3),
nn.InstanceNorm2d(in_features),
)
def forward(self, x):
return x + self.block(x)
class GeneratorResNet(nn.Module):
"""CycleGAN生成器"""
def __init__(self, input_channels=3, output_channels=3, n_residual_blocks=9):
super(GeneratorResNet, self).__init__()
# 初始卷积块
model = [
nn.ReflectionPad2d(3),
nn.Conv2d(input_channels, 64, 7),
nn.InstanceNorm2d(64),
nn.ReLU(inplace=True),
]
# 下采样
in_features = 64
out_features = in_features * 2
for _ in range(2):
model += [
nn.Conv2d(in_features, out_features, 3, stride=2, padding=1),
nn.InstanceNorm2d(out_features),
nn.ReLU(inplace=True),
]
in_features = out_features
out_features = in_features * 2
# 残差块
for _ in range(n_residual_blocks):
model += [ResidualBlock(in_features)]
# 上采样
out_features = in_features // 2
for _ in range(2):
model += [
nn.ConvTranspose2d(in_features, out_features, 3, stride=2, padding=1, output_padding=1),
nn.InstanceNorm2d(out_features),
nn.ReLU(inplace=True),
]
in_features = out_features
out_features = in_features // 2
# 输出层
model += [
nn.ReflectionPad2d(3),
nn.Conv2d(64, output_channels, 7),
nn.Tanh(),
]
self.model = nn.Sequential(*model)
def forward(self, x):
return self.model(x)
class Discriminator(nn.Module):
"""CycleGAN判别器"""
def __init__(self, input_channels=3):
super(Discriminator, self).__init__()
def discriminator_block(in_filters, out_filters, normalize=True):
layers = [nn.Conv2d(in_filters, out_filters, 4, stride=2, padding=1)]
if normalize:
layers.append(nn.InstanceNorm2d(out_filters))
layers.append(nn.LeakyReLU(0.2, inplace=True))
return layers
self.model = nn.Sequential(
*discriminator_block(input_channels, 64, normalize=False),
*discriminator_block(64, 128),
*discriminator_block(128, 256),
*discriminator_block(256, 512),
nn.ZeroPad2d((1, 0, 1, 0)),
nn.Conv2d(512, 1, 4, padding=1)
)
def forward(self, x):
return self.model(x)
class CycleGAN:
"""CycleGAN训练框架"""
def __init__(self):
self.G_AB = GeneratorResNet().to(device) # 从A域到B域的生成器
self.G_BA = GeneratorResNet().to(device) # 从B域到A域的生成器
self.D_A = Discriminator().to(device) # A域的判别器
self.D_B = Discriminator().to(device) # B域的判别器
self.optimizer_G = optim.Adam(
list(self.G_AB.parameters()) + list(self.G_BA.parameters()),
lr=0.0002, betas=(0.5, 0.999)
)
self.optimizer_D_A = optim.Adam(self.D_A.parameters(), lr=0.0002, betas=(0.5, 0.999))
self.optimizer_D_B = optim.Adam(self.D_B.parameters(), lr=0.0002, betas=(0.5, 0.999))
self.criterion_GAN = nn.MSELoss()
self.criterion_cycle = nn.L1Loss()
self.criterion_identity = nn.L1Loss()
def train(self, real_A, real_B):
# 身份损失
same_B = self.G_AB(real_B)
loss_identity_B = self.criterion_identity(same_B, real_B) * 5.0
same_A = self.G_BA(real_A)
loss_identity_A = self.criterion_identity(same_A, real_A) * 5.0
# GAN损失
fake_B = self.G_AB(real_A)
pred_fake = self.D_B(fake_B)
loss_GAN_AB = self.criterion_GAN(pred_fake, torch.ones_like(pred_fake))
fake_A = self.G_BA(real_B)
pred_fake = self.D_A(fake_A)
loss_GAN_BA = self.criterion_GAN(pred_fake, torch.ones_like(pred_fake))
# 循环一致性损失
recovered_A = self.G_BA(fake_B)
loss_cycle_ABA = self.criterion_cycle(recovered_A, real_A) * 10.0
recovered_B = self.G_AB(fake_A)
loss_cycle_BAB = self.criterion_cycle(recovered_B, real_B) * 10.0
# 总生成器损失
loss_G = (loss_GAN_AB + loss_GAN_BA +
loss_cycle_ABA + loss_cycle_BAB +
loss_identity_A + loss_identity_B)
self.optimizer_G.zero_grad()
loss_G.backward()
self.optimizer_G.step()
# 判别器A损失
pred_real = self.D_A(real_A)
loss_D_real = self.criterion_GAN(pred_real, torch.ones_like(pred_real))
pred_fake = self.D_A(fake_A.detach())
loss_D_fake = self.criterion_GAN(pred_fake, torch.zeros_like(pred_fake))
loss_D_A = (loss_D_real + loss_D_fake) * 0.5
self.optimizer_D_A.zero_grad()
loss_D_A.backward()
self.optimizer_D_A.step()
# 判别器B损失
pred_real = self.D_B(real_B)
loss_D_real = self.criterion_GAN(pred_real, torch.ones_like(pred_real))
pred_fake = self.D_B(fake_B.detach())
loss_D_fake = self.criterion_GAN(pred_fake, torch.zeros_like(pred_fake))
loss_D_B = (loss_D_real + loss_D_fake) * 0.5
self.optimizer_D_B.zero_grad()
loss_D_B.backward()
self.optimizer_D_B.step()
return loss_G.item(), loss_D_A.item(), loss_D_B.item()
```
## 5. SRGAN实现
```python
class ResidualBlock(nn.Module):
"""SRGAN残差块"""
def __init__(self, channels):
super(ResidualBlock, self).__init__()
self.conv1 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(channels)
self.prelu = nn.PReLU()
self.conv2 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(channels)
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.prelu(out)
out = self.conv2(out)
out = self.bn2(out)
out += residual
return out
class SRGAN_Generator(nn.Module):
"""SRGAN生成器"""
def __init__(self, scale_factor=4):
super(SRGAN_Generator, self).__init__()
self.scale_factor = scale_factor
# 初始卷积层
self.conv1 = nn.Conv2d(3, 64, kernel_size=9, padding=4)
self.prelu = nn.PReLU()
# 残差块
self.residual_blocks = nn.Sequential(*[ResidualBlock(64) for _ in range(16)])
# 后续卷积层
self.conv2 = nn.Conv2d(64, 64, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(64)
# 上采样块
upsample_blocks = []
for _ in range(scale_factor // 2):
upsample_blocks.append(
nn.Conv2d(64, 256, kernel_size=3, padding=1)
)
upsample_blocks.append(nn.PixelShuffle(2))
upsample_blocks.append(nn.PReLU())
self.upsample = nn.Sequential(*upsample_blocks)
# 最终卷积层
self.conv3 = nn.Conv2d(64, 3, kernel_size=9, padding=4)
def forward(self, x):
initial = self.prelu(self.conv1(x))
x = self.residual_blocks(initial)
x = self.bn2(self.conv2(x))
x += initial # 跳跃连接
x = self.upsample(x)
x = self.conv3(x)
return torch.tanh(x)
class SRGAN_Discriminator(nn.Module):
"""SRGAN判别器"""
def __init__(self):
super(SRGAN_Discriminator, self).__init__()
self.features = nn.Sequential(
# 输入: (3, 96, 96)
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(64),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(128),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(128, 128, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(128),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(256),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(256, 256, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(256),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(512),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(512, 512, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(512),
nn.LeakyReLU(0.2, inplace=True),
)
self.classifier = nn.Sequential(
nn.AdaptiveAvgPool2d(1),
nn.Conv2d(512, 1024, kernel_size=1),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(1024, 1, kernel_size=1),
nn.Sigmoid()
)
def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x.view(-1, 1)
class VGGFeatureExtractor(nn.Module):
"""VGG特征提取器(用于感知损失)"""
def __init__(self):
super(VGGFeatureExtractor, self).__init__()
vgg19 = torchvision.models.vgg19(pretrained=True)
self.feature_extractor = nn.Sequential(*list(vgg19.features.children())[:35])
for param in self.feature_extractor.parameters():
param.requires_grad = False
def forward(self, x):
return self.feature_extractor(x)
def train_srgan(generator, discriminator, feature_extractor,
dataloader, num_epochs=100):
"""训练SRGAN"""
criterion_GAN = nn.BCELoss()
criterion_content = nn.L1Loss()
criterion_perceptual = nn.L1Loss()
optimizer_G = optim.Adam(generator.parameters(), lr=1e-4)
optimizer_D = optim.Adam(discriminator.parameters(), lr=1e-4)
for epoch in range(num_epochs):
for i, (lr_imgs, hr_imgs) in enumerate(dataloader):
lr_imgs, hr_imgs = lr_imgs.to(device), hr_imgs.to(device)
# 训练判别器
optimizer_D.zero_grad()
# 真实图像
real_labels = torch.ones(hr_imgs.size(0), 1, device=device)
real_output = discriminator(hr_imgs)
loss_D_real = criterion_GAN(real_output, real_labels)
# 生成图像
fake_imgs = generator(lr_imgs)
fake_labels = torch.zeros(hr_imgs.size(0), 1, device=device)
fake_output = discriminator(fake_imgs.detach())
loss_D_fake = criterion_GAN(fake_output, fake_labels)
loss_D = loss_D_real + loss_D_fake
loss_D.backward()
optimizer_D.step()
# 训练生成器
optimizer_G.zero_grad()
# GAN损失
gen_output = discriminator(fake_imgs)
loss_GAN = criterion_GAN(gen_output, real_labels)
# 内容损失(像素级)
loss_content = criterion_content(fake_imgs, hr_imgs)
# 感知损失(特征级)
real_features = feature_extractor(hr_imgs)
fake_features = feature_extractor(fake_imgs)
loss_perceptual = criterion_perceptual(fake_features, real_features.detach())
# 总损失
loss_G = loss_content + 1e-3 * loss_GAN + 0.006 * loss_perceptual
loss_G.backward()
optimizer_G.step()
if i % 100 == 0:
print(f'Epoch [{epoch}/{num_epochs}], Step [{i}/{len(dataloader)}], '
f'Loss_D: {loss_D.item():.4f}, Loss_G: {loss_G.item():.4f}, '
f'Loss_content: {loss_content.item():.4f}, '
f'Loss_perceptual: {loss_perceptual.item():.4f}')
```
## 6. 评估指标实现
```python
def calculate_fid(real_imgs, fake_imgs, batch_size=50):
"""计算Fréchet Inception Distance (FID)"""
inception_model = torchvision.models.inception_v3(pretrained=True,
transform_input=False).to(device)
inception_model.eval()
def get_activations(images):
activations = []
for i in range(0, len(images), batch_size):
batch = images[i:i+batch_size].to(device)
with torch.no_grad():
pred = inception_model(batch)
activations.append(pred.cpu())
return torch.cat(activations, 0)
real_acts = get_activations(real_imgs)
fake_acts = get_activations(fake_imgs)
mu_real, sigma_real = real_acts.mean(0), torch.cov(real_acts.t())
mu_fake, sigma_fake = fake_acts.mean(0), torch.cov(fake_acts.t())
diff = mu_real - mu_fake
covmean = torch.sqrt(sigma_real @ sigma_fake)
if torch.isnan(covmean).any():
covmean = sigma_real @ sigma_fake
covmean = torch.sqrt(covmean + torch.eye(covmean.size(0)) * 1e-6)
fid = diff.dot(diff) + torch.trace(sigma_real + sigma_fake - 2 * covmean)
return fid.item()
def calculate_is(images, batch_size=32, splits=10):
"""计算Inception Score (IS)"""
inception_model = torchvision.models.inception_v3(pretrained=True,
transform_input=False).to(device)
inception_model.eval()
preds = []
for i in range(0, len(images), batch_size):
batch = images[i:i+batch_size].to(device)
with torch.no_grad():
pred = torch.nn.functional.softmax(inception_model(batch), dim=1)
preds.append(pred.cpu())
preds = torch.cat(preds, 0)
scores = []
for i in range(splits):
part = preds[i * preds.size(0) // splits: (i + 1) * preds.size(0) // splits, :]
kl = part * (torch.log(part) - torch.log(torch.mean(part, 0, keepdim=True)))
kl = torch.mean(torch.sum(kl, 1))
scores.append(torch.exp(kl))
return torch.mean(torch.stack(scores)), torch.std(torch.stack(scores))
# 使用示例
def evaluate_model(generator, test_loader):
"""评估生成模型"""
generator.eval()
all_fake_imgs = []
all_real_imgs = []
with torch.no_grad():
for real_imgs, _ in test_loader:
noise = torch.randn(real_imgs.size(0), latent_dim, device=device)
fake_imgs = generator(noise)
all_fake_imgs.append(fake_imgs.cpu())
all_real_imgs.append(real_imgs.cpu())
fake_imgs = torch.cat(all_fake_imgs, 0)
real_imgs = torch.cat(all_real_imgs, 0)
# 计算评估指标
fid_score = calculate_fid(real_imgs, fake_imgs)
is_mean, is_std = calculate_is(fake_imgs)
print(f'FID: {fid_score:.4f}')
print(f'IS: {is_mean:.4f} ± {is_std:.4f}')
return fid_score, is_mean
```
这些代码提供了从基础GAN到先进变体的完整实现,包含了:
1. **基础GAN**:完整的训练框架
2. **DCGAN**:使用卷积结构的改进版本
3. **WGAN-GP**:带有梯度惩罚的Wasserstein GAN
4. **CycleGAN**:无配对图像转换
5. **SRGAN**:超分辨率生成对抗网络
6. **评估指标**:FID和IS计算
每个实现都包含了详细的技术细节和训练逻辑,可以直接用于研究和实验。