AI生成对抗网络(GAN)完全教程

教程简介

本教程全面讲解生成对抗网络(GAN)的核心原理与实战应用,涵盖DCGAN/WGAN/StyleGAN架构演进、条件生成、SRGAN超分辨率、Pix2Pix/CycleGAN图像翻译、视频生成GAN、3D-aware GAN、GAN vs Diffusion对比等核心内容,通过人脸生成编辑案例帮助开发者掌握GAN技术。

AI生成对抗网络(GAN)完全教程

1. GAN原理与训练机制

生成对抗网络(Generative Adversarial Network)由Ian Goodfellow于2014年提出,其核心思想源自博弈论中的二人零和博弈——生成器(Generator)和判别器(Discriminator)在对抗中共同进化。

生成器G:从随机噪声z中学习数据分布,生成逼真样本。 判别器D:区分真实数据与生成数据。

两者的目标函数构成极小极大博弈:

min_G max_D  V(D,G) = E[log D(x)] + E[log(1 - D(G(z)))]

训练流程(交替优化):

  1. 固定G,训练D:让D更好地分辨真假
  2. 固定D,训练G:让G生成更逼真的样本欺骗D
  3. 重复直到纳什均衡
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from torchvision.utils import save_image

class Generator(nn.Module):
    """基础生成器:噪声向量 → 图像"""
    def __init__(self, latent_dim=100, img_channels=1, img_size=28):
        super().__init__()
        self.img_size = img_size
        self.net = nn.Sequential(
            nn.Linear(latent_dim, 256),
            nn.BatchNorm1d(256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 512),
            nn.BatchNorm1d(512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 1024),
            nn.BatchNorm1d(1024),
            nn.LeakyReLU(0.2),
            nn.Linear(1024, img_channels * img_size * img_size),
            nn.Tanh()
        )

    def forward(self, z):
        return self.net(z).view(-1, 1, self.img_size, self.img_size)

class Discriminator(nn.Module):
    """基础判别器:图像 → 真假概率"""
    def __init__(self, img_channels=1, img_size=28):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(img_channels * img_size * img_size, 512),
            nn.LeakyReLU(0.2),
            nn.Dropout(0.3),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Dropout(0.3),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )

    def forward(self, img):
        return self.net(img.view(img.size(0), -1))

def train_gan(epochs=50, batch_size=64, latent_dim=100, lr=0.0002):
    """标准GAN训练循环"""
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    # 数据加载(以MNIST为例)
    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize([0.5], [0.5])
    ])
    dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

    G = Generator(latent_dim).to(device)
    D = Discriminator().to(device)

    criterion = nn.BCELoss()
    opt_G = optim.Adam(G.parameters(), lr=lr, betas=(0.5, 0.999))
    opt_D = optim.Adam(D.parameters(), lr=lr, betas=(0.5, 0.999))

    for epoch in range(epochs):
        for i, (real_imgs, _) in enumerate(dataloader):
            real_imgs = real_imgs.to(device)
            bs = real_imgs.size(0)
            real_label = torch.ones(bs, 1, device=device)
            fake_label = torch.zeros(bs, 1, device=device)

            # ===== 训练判别器 =====
            z = torch.randn(bs, latent_dim, device=device)
            fake_imgs = G(z)

            d_real = D(real_imgs)
            d_fake = D(fake_imgs.detach())
            d_loss = criterion(d_real, real_label) + criterion(d_fake, fake_label)

            opt_D.zero_grad()
            d_loss.backward()
            opt_D.step()

            # ===== 训练生成器 =====
            d_fake = D(fake_imgs)
            g_loss = criterion(d_fake, real_label)  # G希望D认为fake是real

            opt_G.zero_grad()
            g_loss.backward()
            opt_G.step()

        print(f"Epoch [{epoch+1}/{epochs}]  D_loss: {d_loss.item():.4f}  "
              f"G_loss: {g_loss.item():.4f}")

        # 每个epoch保存样本
        save_image(fake_imgs[:25], f'generated_epoch_{epoch+1:03d}.png',
                   nrow=5, normalize=True)

2. 经典架构演进

2.1 DCGAN(Deep Convolutional GAN)

DCGAN确立了卷积GAN的设计准则:用转置卷积替代全连接层,用BatchNorm稳定训练。

class DCDiscriminator(nn.Module):
    """DCGAN判别器"""
    def __init__(self, img_channels=3, ndf=64):
        super().__init__()
        self.net = nn.Sequential(
            # 输入: (C, 64, 64)
            nn.Conv2d(img_channels, ndf, 4, 2, 1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),
            # (ndf, 32, 32)
            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2, inplace=True),
            # (ndf*2, 16, 16)
            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2, inplace=True),
            # (ndf*4, 8, 8)
            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 8),
            nn.LeakyReLU(0.2, inplace=True),
            # (ndf*8, 4, 4)
            nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )

    def forward(self, x):
        return self.net(x).view(-1, 1)

class DCGenerator(nn.Module):
    """DCGAN生成器"""
    def __init__(self, latent_dim=100, img_channels=3, ngf=64):
        super().__init__()
        self.net = nn.Sequential(
            # 输入: (latent_dim, 1, 1)
            nn.ConvTranspose2d(latent_dim, ngf * 8, 4, 1, 0, bias=False),
            nn.BatchNorm2d(ngf * 8),
            nn.ReLU(True),
            # (ngf*8, 4, 4)
            nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 4),
            nn.ReLU(True),
            # (ngf*4, 8, 8)
            nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 2),
            nn.ReLU(True),
            # (ngf*2, 16, 16)
            nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf),
            nn.ReLU(True),
            # (ngf, 32, 32)
            nn.ConvTranspose2d(ngf, img_channels, 4, 2, 1, bias=False),
            nn.Tanh()
            # (C, 64, 64)
        )

    def forward(self, z):
        return self.net(z.view(z.size(0), -1, 1, 1))

2.2 WGAN(Wasserstein GAN)

WGAN用Wasserstein距离替代JS散度,解决了原始GAN训练不稳定和模式崩溃问题。

class WGANGenerator(nn.Module):
    """WGAN生成器(与DCGAN结构类似,无Sigmoid输出)"""
    def __init__(self, latent_dim=100, img_channels=3, ngf=64):
        super().__init__()
        self.net = nn.Sequential(
            nn.ConvTranspose2d(latent_dim, ngf * 8, 4, 1, 0, bias=False),
            nn.BatchNorm2d(ngf * 8), nn.ReLU(True),
            nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 4), nn.ReLU(True),
            nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 2), nn.ReLU(True),
            nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf), nn.ReLU(True),
            nn.ConvTranspose2d(ngf, img_channels, 4, 2, 1, bias=False),
            nn.Tanh()
        )

    def forward(self, z):
        return self.net(z.view(z.size(0), -1, 1, 1))

class WGANCritic(nn.Module):
    """WGAN判别器(Critic):无Sigmoid,输出无界实数"""
    def __init__(self, img_channels=3, ndf=64):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(img_channels, ndf, 4, 2, 1),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.InstanceNorm2d(ndf * 2, affine=True),  # WGAN-GP用InstanceNorm
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.InstanceNorm2d(ndf * 4, affine=True),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
            nn.InstanceNorm2d(ndf * 8, affine=True),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(ndf * 8, 1, 4, 1, 0),
            # 无Sigmoid — WGAN输出无界
        )

    def forward(self, x):
        return self.net(x).view(-1, 1)

def compute_gradient_penalty(critic, real_imgs, fake_imgs, device, lambda_gp=10):
    """WGAN-GP梯度惩罚"""
    alpha = torch.rand(real_imgs.size(0), 1, 1, 1, device=device)
    interpolated = (alpha * real_imgs + (1 - alpha) * fake_imgs).requires_grad_(True)

    d_interpolated = critic(interpolated)

    gradients = torch.autograd.grad(
        outputs=d_interpolated,
        inputs=interpolated,
        grad_outputs=torch.ones_like(d_interpolated),
        create_graph=True,
        retain_graph=True
    )[0]

    gradients = gradients.view(gradients.size(0), -1)
    penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean()
    return lambda_gp * penalty

def train_wgan_gp(epochs=100, n_critic=5, lambda_gp=10):
    """WGAN-GP训练循环"""
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    G = WGANGenerator().to(device)
    C = WGANCritic().to(device)

    opt_G = optim.Adam(G.parameters(), lr=1e-4, betas=(0.0, 0.9))
    opt_C = optim.Adam(C.parameters(), lr=1e-4, betas=(0.0, 0.9))

    for epoch in range(epochs):
        for i, (real_imgs, _) in enumerate(dataloader):
            real_imgs = real_imgs.to(device)
            bs = real_imgs.size(0)

            # ===== 训练Critic(多次) =====
            z = torch.randn(bs, 100, device=device)
            fake_imgs = G(z).detach()

            c_real = C(real_imgs).mean()
            c_fake = C(fake_imgs).mean()
            gp = compute_gradient_penalty(C, real_imgs, fake_imgs, device, lambda_gp)

            c_loss = c_fake - c_real + gp  # Wasserstein距离 + 梯度惩罚

            opt_C.zero_grad()
            c_loss.backward()
            opt_C.step()

            # 每n_critic次更新一次生成器
            if i % n_critic == 0:
                z = torch.randn(bs, 100, device=device)
                fake_imgs = G(z)
                g_loss = -C(fake_imgs).mean()

                opt_G.zero_grad()
                g_loss.backward()
                opt_G.step()

        print(f"Epoch [{epoch+1}/{epochs}]  C_loss: {c_loss.item():.4f}  "
              f"G_loss: {g_loss.item():.4f}")

2.3 StyleGAN

StyleGAN引入风格映射网络和自适应实例归一化(AdaIN),实现分层风格控制。

class MappingNetwork(nn.Module):
    """StyleGAN映射网络:z → w(中间潜在空间)"""
    def __init__(self, latent_dim=512, w_dim=512, num_layers=8):
        super().__init__()
        layers = []
        for i in range(num_layers):
            layers.extend([
                nn.Linear(latent_dim if i == 0 else w_dim, w_dim),
                nn.LeakyReLU(0.2)
            ])
        self.net = nn.Sequential(*layers)

    def forward(self, z):
        # z先做像素归一化
        z = z / (z.norm(dim=1, keepdim=True) + 1e-8)
        return self.net(z)

class AdaIN(nn.Module):
    """自适应实例归一化"""
    def __init__(self, channels, w_dim=512):
        super().__init__()
        self.norm = nn.InstanceNorm2d(channels, affine=False)
        self.style_scale = nn.Linear(w_dim, channels)
        self.style_bias = nn.Linear(w_dim, channels)

    def forward(self, x, w):
        normalized = self.norm(x)
        scale = self.style_scale(w).view(x.size(0), -1, 1, 1)
        bias = self.style_bias(w).view(x.size(0), -1, 1, 1)
        return normalized * (1 + scale) + bias

class StyleBlock(nn.Module):
    """StyleGAN生成器的一个块"""
    def __init__(self, in_channels, out_channels, w_dim=512):
        super().__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, 3, 1, 1)
        self.adain = AdaIN(out_channels, w_dim)
        self.noise_scale = nn.Parameter(torch.zeros(1))
        self.act = nn.LeakyReLU(0.2)

    def forward(self, x, w, noise=None):
        x = self.conv(x)
        if noise is not None:
            x = x + self.noise_scale * noise
        x = self.adain(x, w)
        return self.act(x)

3. 条件生成与可控生成

3.1 条件GAN(cGAN)

class ConditionalGenerator(nn.Module):
    """条件生成器:同时接受噪声和类别标签"""
    def __init__(self, latent_dim=100, num_classes=10, embed_dim=50):
        super().__init__()
        self.label_embed = nn.Embedding(num_classes, embed_dim)

        self.net = nn.Sequential(
            nn.Linear(latent_dim + embed_dim, 256),
            nn.BatchNorm1d(256),
            nn.ReLU(),
            nn.Linear(256, 512),
            nn.BatchNorm1d(512),
            nn.ReLU(),
            nn.Linear(512, 784),
            nn.Tanh()
        )

    def forward(self, z, label):
        label_vec = self.label_embed(label)
        input_vec = torch.cat([z, label_vec], dim=1)
        return self.net(input_vec).view(-1, 1, 28, 28)

class ConditionalDiscriminator(nn.Module):
    """条件判别器:同时接受图像和类别标签"""
    def __init__(self, num_classes=10, embed_dim=50):
        super().__init__()
        self.label_embed = nn.Embedding(num_classes, embed_dim)

        self.net = nn.Sequential(
            nn.Linear(784 + embed_dim, 512),
            nn.LeakyReLU(0.2),
            nn.Dropout(0.3),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Dropout(0.3),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )

    def forward(self, img, label):
        label_vec = self.label_embed(label)
        img_flat = img.view(img.size(0), -1)
        input_vec = torch.cat([img_flat, label_vec], dim=1)
        return self.net(input_vec)

3.2 InfoGAN — 无监督可控生成

InfoGAN通过最大化潜在编码与生成结果的互信息,实现无监督的属性解耦:

class InfoGANDiscriminator(nn.Module):
    """InfoGAN判别器 + Q网络(推断潜在编码)"""
    def __init__(self, img_channels=1, num_disc_codes=10, num_cont_codes=2):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(img_channels, 64, 4, 2, 1),
            nn.LeakyReLU(0.2),
            nn.Conv2d(64, 128, 4, 2, 1, bias=False),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2),
        )
        # D head: 真假判断
        self.d_head = nn.Sequential(
            nn.Linear(128 * 7 * 7, 1),
            nn.Sigmoid()
        )
        # Q head: 推断潜在编码(用于InfoGAN的互信息最大化)
        self.q_head = nn.Sequential(
            nn.Linear(128 * 7 * 7, 128),
            nn.LeakyReLU(0.2),
        )
        self.q_disc = nn.Linear(128, num_disc_codes)  # 离散编码
        self.q_cont_mu = nn.Linear(128, num_cont_codes)  # 连续编码均值
        self.q_cont_var = nn.Linear(128, num_cont_codes)  # 连续编码方差

    def forward(self, x):
        feat = self.features(x).view(x.size(0), -1)
        d_out = self.d_head(feat)

        q_feat = self.q_head(feat)
        q_disc = self.q_disc(q_feat)
        q_mu = self.q_cont_mu(q_feat)
        q_var = torch.exp(self.q_cont_var(q_feat))

        return d_out, q_disc, q_mu, q_var

4. 图像超分辨率GAN

4.1 SRGAN

class SRResidualBlock(nn.Module):
    """SRGAN残差块"""
    def __init__(self, channels=64):
        super().__init__()
        self.block = nn.Sequential(
            nn.Conv2d(channels, channels, 3, 1, 1, bias=False),
            nn.BatchNorm2d(channels),
            nn.PReLU(),
            nn.Conv2d(channels, channels, 3, 1, 1, bias=False),
            nn.BatchNorm2d(channels),
        )

    def forward(self, x):
        return x + self.block(x)

class SRGenerator(nn.Module):
    """SRGAN生成器:低分辨率 → 高分辨率(4x放大)"""
    def __init__(self, scale_factor=4, num_blocks=16):
        super().__init__()
        self.pre_layer = nn.Sequential(
            nn.Conv2d(3, 64, 9, 1, 4),
            nn.PReLU()
        )

        blocks = [SRResidualBlock(64) for _ in range(num_blocks)]
        blocks.extend([
            nn.Conv2d(64, 64, 3, 1, 1, bias=False),
            nn.BatchNorm2d(64),
        ])
        self.residuals = nn.Sequential(*blocks)

        # 上采样层
        upsample = []
        for _ in range(scale_factor // 2):
            upsample.extend([
                nn.Conv2d(64, 256, 3, 1, 1),
                nn.PixelShuffle(2),  # 亚像素卷积上采样
                nn.PReLU(),
            ])
        self.upsample = nn.Sequential(*upsample)

        self.final = nn.Conv2d(64, 3, 9, 1, 4)

    def forward(self, x):
        pre = self.pre_layer(x)
        res = self.residuals(pre)
        up = self.upsample(pre + res)
        return torch.tanh(self.final(up))

class SRDiscriminator(nn.Module):
    """SRGAN判别器"""
    def __init__(self):
        super().__init__()
        def block(in_c, out_c, stride):
            return nn.Sequential(
                nn.Conv2d(in_c, out_c, 3, stride, 1, bias=False),
                nn.BatchNorm2d(out_c),
                nn.LeakyReLU(0.2, inplace=True)
            )

        self.net = nn.Sequential(
            nn.Conv2d(3, 64, 3, 1, 1),
            nn.LeakyReLU(0.2),
            block(64, 64, 2),
            block(64, 128, 1),
            block(128, 128, 2),
            block(128, 256, 1),
            block(256, 256, 2),
            block(256, 512, 1),
            block(512, 512, 2),
            nn.AdaptiveAvgPool2d(1),
            nn.Flatten(),
            nn.Linear(512, 1024),
            nn.LeakyReLU(0.2),
            nn.Linear(1024, 1),
            nn.Sigmoid()
        )

    def forward(self, x):
        return self.net(x)

class PerceptualLoss(nn.Module):
    """感知损失:基于预训练VGG的特征匹配"""
    def __init__(self):
        super().__init__()
        vgg = models.vgg19(pretrained=True).features[:36]
        self.vgg = nn.Sequential(*list(vgg.children())).eval()
        for param in self.vgg.parameters():
            param.requires_grad = False
        self.mse = nn.MSELoss()

    def forward(self, sr_img, hr_img):
        sr_features = self.vgg(sr_img)
        hr_features = self.vgg(hr_img)
        return self.mse(sr_features, hr_features)

5. 图像翻译

5.1 Pix2Pix(配对图像翻译)

class UNetDown(nn.Module):
    def __init__(self, in_c, out_c, normalize=True):
        super().__init__()
        layers = [nn.Conv2d(in_c, out_c, 4, 2, 1, bias=False)]
        if normalize:
            layers.append(nn.BatchNorm2d(out_c))
        layers.append(nn.LeakyReLU(0.2))
        self.model = nn.Sequential(*layers)

    def forward(self, x):
        return self.model(x)

class UNetUp(nn.Module):
    def __init__(self, in_c, out_c, dropout=False):
        super().__init__()
        layers = [
            nn.ConvTranspose2d(in_c, out_c, 4, 2, 1, bias=False),
            nn.BatchNorm2d(out_c),
            nn.ReLU()
        ]
        if dropout:
            layers.append(nn.Dropout(0.5))
        self.model = nn.Sequential(*layers)

    def forward(self, x, skip):
        x = self.model(x)
        return torch.cat([x, skip], dim=1)

class Pix2PixGenerator(nn.Module):
    """U-Net结构的Pix2Pix生成器"""
    def __init__(self, in_channels=3, out_channels=3):
        super().__init__()
        # Encoder
        self.down1 = UNetDown(in_channels, 64, normalize=False)
        self.down2 = UNetDown(64, 128)
        self.down3 = UNetDown(128, 256)
        self.down4 = UNetDown(256, 512)
        self.down5 = UNetDown(512, 512)
        self.down6 = UNetDown(512, 512)
        self.down7 = UNetDown(512, 512)
        self.down8 = UNetDown(512, 512, normalize=False)

        # Decoder(带skip connection)
        self.up1 = UNetUp(512, 512, dropout=True)
        self.up2 = UNetUp(1024, 512, dropout=True)
        self.up3 = UNetUp(1024, 512, dropout=True)
        self.up4 = UNetUp(1024, 512)
        self.up5 = UNetUp(1024, 256)
        self.up6 = UNetUp(512, 128)
        self.up7 = UNetUp(256, 64)

        self.final = nn.Sequential(
            nn.ConvTranspose2d(128, out_channels, 4, 2, 1),
            nn.Tanh()
        )

    def forward(self, x):
        d1 = self.down1(x)
        d2 = self.down2(d1)
        d3 = self.down3(d2)
        d4 = self.down4(d3)
        d5 = self.down5(d4)
        d6 = self.down6(d5)
        d7 = self.down7(d6)
        d8 = self.down8(d7)

        u1 = self.up1(d8, d7)
        u2 = self.up2(u1, d6)
        u3 = self.up3(u2, d5)
        u4 = self.up4(u3, d4)
        u5 = self.up5(u4, d3)
        u6 = self.up6(u5, d2)
        u7 = self.up7(u6, d1)

        return self.final(u7)

class PatchDiscriminator(nn.Module):
    """PatchGAN判别器:对图像局部区域判别"""
    def __init__(self, in_channels=6):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(in_channels, 64, 4, 2, 1),
            nn.LeakyReLU(0.2),
            nn.Conv2d(64, 128, 4, 2, 1, bias=False),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2),
            nn.Conv2d(128, 256, 4, 2, 1, bias=False),
            nn.BatchNorm2d(256),
            nn.LeakyReLU(0.2),
            nn.Conv2d(256, 512, 4, 1, 1, bias=False),
            nn.BatchNorm2d(512),
            nn.LeakyReLU(0.2),
            nn.Conv2d(512, 1, 4, 1, 1),
        )

    def forward(self, x):
        return self.net(x)

5.2 CycleGAN(无配对图像翻译)

CycleGAN引入循环一致性损失,无需配对数据即可实现域间翻译:

class CycleGAN:
    """CycleGAN训练框架"""

    def __init__(self, G_A2B, G_B2A, D_A, D_B, lambda_cycle=10):
        self.G_A2B = G_A2B  # A域 → B域
        self.G_B2A = G_B2A  # B域 → A域
        self.D_A = D_A      # A域判别器
        self.D_B = D_B      # B域判别器
        self.lambda_cycle = lambda_cycle

    def compute_cycle_loss(self, real_a, real_b):
        """循环一致性损失:A → B → A ≈ A"""
        fake_b = self.G_A2B(real_a)
        recovered_a = self.G_B2A(fake_b)

        fake_a = self.G_B2A(real_b)
        recovered_b = self.G_A2B(fake_a)

        cycle_loss_a = nn.functional.l1_loss(recovered_a, real_a)
        cycle_loss_b = nn.functional.l1_loss(recovered_b, real_b)

        return cycle_loss_a + cycle_loss_b, fake_a, fake_b

    def compute_identity_loss(self, real_a, real_b):
        """身份损失:保持域内样本不变"""
        same_b = self.G_A2B(real_b)
        same_a = self.G_B2A(real_a)
        identity_loss = (
            nn.functional.l1_loss(same_b, real_b) +
            nn.functional.l1_loss(same_a, real_a)
        )
        return identity_loss

6. 视频生成GAN

视频生成GAN在图像生成基础上增加时序一致性约束。

class TemporalDiscriminator(nn.Module):
    """时序判别器:检测帧间时序连贯性"""
    def __init__(self, in_channels=3, num_frames=16):
        super().__init__()
        self.num_frames = num_frames

        # 3D卷积处理时空特征
        self.net = nn.Sequential(
            nn.Conv3d(in_channels, 64, (3, 4, 4), (1, 2, 2), (1, 1, 1)),
            nn.LeakyReLU(0.2),
            nn.Conv3d(64, 128, (3, 4, 4), (1, 2, 2), (1, 1, 1), bias=False),
            nn.BatchNorm3d(128),
            nn.LeakyReLU(0.2),
            nn.Conv3d(128, 256, (3, 4, 4), (1, 2, 2), (1, 1, 1), bias=False),
            nn.BatchNorm3d(256),
            nn.LeakyReLU(0.2),
            nn.Conv3d(256, 1, (3, 4, 4), (1, 2, 2), (0, 0, 0)),
        )

    def forward(self, video):
        """video: (B, C, T, H, W)"""
        return self.net(video).mean()

class VideoGenerator(nn.Module):
    """视频生成器:噪声 + 随机帧 → 视频序列"""
    def __init__(self, latent_dim=512, num_frames=16, img_size=64):
        super().__init__()
        self.num_frames = num_frames

        # 空间生成(每帧)
        self.spatial_gen = DCGenerator(latent_dim, 3, 64)

        # 时序运动网络
        self.motion_net = nn.Sequential(
            nn.Linear(latent_dim, 256),
            nn.ReLU(),
            nn.Linear(256, num_frames * 64),  # 每帧的运动编码
        )

        # 光流预测
        self.flow_predictor = nn.Sequential(
            nn.Conv2d(64 + 3, 64, 3, 1, 1),
            nn.ReLU(),
            nn.Conv2d(64, 2, 3, 1, 1),  # 2通道:x,y方向光流
        )

    def forward(self, z):
        B = z.size(0)
        # 生成关键帧
        key_frame = self.spatial_gen(z)

        # 生成运动序列
        motion_codes = self.motion_net(z).view(B, self.num_frames, 64)

        frames = [key_frame]
        for t in range(1, self.num_frames):
            # 基于运动编码预测光流
            motion = motion_codes[:, t].view(B, 64, 1, 1).expand(-1, -1, 64, 64)
            flow_input = torch.cat([key_frame, motion], dim=1)
            flow = self.flow_predictor(flow_input)

            # 光流变形生成新帧
            warped = self._apply_flow(frames[-1], flow)
            frames.append(warped)

        return torch.stack(frames, dim=2)  # (B, C, T, H, W)

    def _apply_flow(self, img, flow):
        """用光流对图像进行变形"""
        B, C, H, W = img.shape
        grid_y, grid_x = torch.meshgrid(
            torch.linspace(-1, 1, H, device=img.device),
            torch.linspace(-1, 1, W, device=img.device),
            indexing='ij'
        )
        grid = torch.stack([grid_x, grid_y], dim=0).unsqueeze(0).expand(B, -1, -1, -1)
        grid = grid + flow * 0.1  # 缩放光流
        return nn.functional.grid_sample(img, grid.permute(0, 2, 3, 1),
                                          align_corners=True)

7. 3D-aware GAN

3D-aware GAN(如pi-GAN、EG3D)从2D图像学习3D结构,实现多视角一致的生成。

class NeuralRadianceField(nn.Module):
    """简化的NeRF渲染器 — 用于3D-aware GAN"""
    def __init__(self, latent_dim=512, hidden_dim=256):
        super().__init__()
        # 位置编码
        self.pos_encoding_dim = 63  # L=10 for position
        self.dir_encoding_dim = 27  # L=4 for direction

        # MLP网络
        input_dim = self.pos_encoding_dim + latent_dim
        self.sigma_net = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim + 1),  # hidden + sigma
        )

        self.color_net = nn.Sequential(
            nn.Linear(hidden_dim + self.dir_encoding_dim + latent_dim, 128),
            nn.ReLU(),
            nn.Linear(128, 3),
            nn.Sigmoid()
        )

    def positional_encoding(self, x, L):
        """位置编码:将低维坐标映射到高维"""
        encodings = [x]
        for i in range(L):
            encodings.append(torch.sin(2 ** i * x))
            encodings.append(torch.cos(2 ** i * x))
        return torch.cat(encodings, dim=-1)

    def forward(self, positions, directions, w):
        """
        positions: (B, N, 3) 采样点3D坐标
        directions: (B, N, 3) 观察方向
        w: (B, latent_dim) StyleGAN的w向量
        """
        pos_enc = self.positional_encoding(positions, 10)
        dir_enc = self.positional_encoding(directions, 4)

        B, N, _ = positions.shape
        w_expand = w.unsqueeze(1).expand(-1, N, -1)

        # 密度预测
        h_sigma = torch.cat([pos_enc, w_expand], dim=-1)
        h = self.sigma_net(h_sigma)
        sigma = torch.relu(h[..., -1:])
        feature = h[..., :-1]

        # 颜色预测
        h_color = torch.cat([feature, dir_enc, w_expand], dim=-1)
        color = self.color_net(h_color)

        return color, sigma

8. GAN训练技巧与稳定性

class GANTrainer:
    """GAN训练实用技巧集合"""

    @staticmethod
    def label_smoothing(real_label=0.9, fake_label=0.1):
        """标签平滑:防止判别器过于自信"""
        return real_label, fake_label

    @staticmethod
    def instance_noise(images, epoch, max_epochs, noise_std=0.1):
        """实例噪声:训练初期添加噪声帮助D学习,逐步衰减"""
        decay = 1 - epoch / max_epochs
        noise = torch.randn_like(images) * noise_std * decay
        return images + noise

    @staticmethod
    def spectral_norm_layers(model):
        """谱归一化:稳定判别器训练"""
        for name, module in model.named_modules():
            if isinstance(module, (nn.Conv2d, nn.Linear)):
                nn.utils.spectral_norm(module)
        return model

    @staticmethod
    def two_timescale_update_rule(G, D, g_lr=1e-4, d_lr=4e-4):
        """双时间尺度更新规则:D学习率高于G"""
        opt_G = optim.Adam(G.parameters(), lr=g_lr, betas=(0.0, 0.999))
        opt_D = optim.Adam(D.parameters(), lr=d_lr, betas=(0.0, 0.999))
        return opt_G, opt_D

    @staticmethod
    def exponential_moving_average(model, ema_model, decay=0.999):
        """EMA:平滑生成器参数,提升生成质量"""
        with torch.no_grad():
            for param, ema_param in zip(model.parameters(),
                                         ema_model.parameters()):
                ema_param.data.mul_(decay).add_(param.data, alpha=1 - decay)

    @staticmethod
    def gradient_penalty_check(d_loss, threshold=1000):
        """梯度监控:检测训练崩溃"""
        if d_loss > threshold:
            print("[WARNING] 判别器损失异常高,可能存在训练不稳定")
            return True
        return False

常见训练问题与对策:

问题 现象 解决方案
模式崩溃 G只生成少数几种样本 使用WGAN-GP、增加多样性正则
训练震荡 D_loss和G_loss剧烈波动 降低学习率、使用TTUR
梯度消失 G_loss下降后D_loss趋近0 使用Wasserstein距离、谱归一化
生成质量差 图像模糊或有伪影 增加模型容量、使用渐进式训练

9. GAN vs Diffusion对比

维度 GAN Diffusion Model
生成速度 快(单次前向传播) 慢(需多步去噪)
训练稳定性 较差(对抗训练难收敛) 较好(简单的去噪损失)
模式多样性 易模式崩溃 天然多样性好
图像质量 高分辨率已成熟 当前SOTA质量
可控性 通过latent space控制 通过条件引导控制
理论基础 博弈论(纳什均衡) 扩散过程(随机微分方程)
典型应用 实时生成、视频、编辑 文生图、高保真合成
# GAN生成 — 一次前向传播
with torch.no_grad():
    z = torch.randn(1, 512, device=device)
    image = gan_generator(z)  # ~10ms

# Diffusion生成 — 多步迭代去噪
x = torch.randn(1, 3, 256, 256, device=device)
for t in range(1000, 0, -1):  # 1000步去噪
    t_tensor = torch.tensor([t], device=device)
    noise_pred = unet(x, t_tensor)
    x = scheduler.step(noise_pred, t_tensor, x)  # ~10s

GAN在需要实时生成的场景(游戏、视频流)仍有不可替代的优势;Diffusion在质量和多样性上更胜一筹。两者融合(如GAN蒸馏加速Diffusion)是当前趋势。


10. 实战案例:人脸生成与编辑

import torch
from torchvision import transforms

class FaceGANPipeline:
    """完整的人脸生成与编辑流水线"""

    def __init__(self, stylegan_weights, device='cuda'):
        self.device = torch.device(device)
        self.G = self._load_stylegan(stylegan_weights)
        self.G.eval()

        # 平均w向量(用于风格混合)
        self.w_mean = self._compute_w_mean(num_samples=10000)

    def _load_stylegan(self, weights_path):
        """加载预训练的StyleGAN"""
        G = StyleGANGenerator(z_dim=512, w_dim=512, img_resolution=1024)
        G.load_state_dict(torch.load(weights_path, map_location=self.device))
        return G.to(self.device).eval()

    def _compute_w_mean(self, num_samples=10000):
        """计算W空间的平均向量(用于编辑基准)"""
        w_list = []
        with torch.no_grad():
            for _ in range(num_samples // 64):
                z = torch.randn(64, 512, device=self.device)
                w = self.G.mapping(z)
                w_list.append(w[:, 0, :])  # 取第一层的w
        return torch.cat(w_list, dim=0).mean(dim=0, keepdim=True)

    @torch.no_grad()
    def generate_random(self, num_images=1, truncation_psi=0.7):
        """随机人脸生成(truncation控制质量-多样性权衡)"""
        z = torch.randn(num_images, 512, device=self.device)
        w = self.G.mapping(z)

        # 截断技巧:向均值靠拢,提升质量但降低多样性
        w_truncated = self.w_mean + truncation_psi * (w - self.w_mean)

        return self.G.synthesis(w_truncated)

    @torch.no_grad()
    def edit_attribute(self, source_z, direction, alpha=1.0):
        """属性编辑:沿语义方向移动"""
        w = self.G.mapping(source_z)

        # direction: 预训练的属性方向向量(如"微笑"、"年龄")
        # 通常通过在latent space中训练线性分类器获得
        w_edited = w + alpha * direction.view(1, 1, -1)

        return self.G.synthesis(w_edited)

    @torch.no_grad()
    def style_mix(self, z1, z2, crossover_point=7):
        """风格混合:前半部分用z1的风格,后半部分用z2"""
        w1 = self.G.mapping(z1)
        w2 = self.G.mapping(z2)

        # StyleGAN有18层,前8层控制粗糙特征(姿态、脸型)
        # 后10层控制细节(肤色、纹理)
        w_mixed = torch.cat([
            w1[:, :crossover_point, :],
            w2[:, crossover_point:, :]
        ], dim=1)

        return self.G.synthesis(w_mixed)

    @torch.no_grad()
    def image_inversion(self, target_image, num_steps=1000, lr=0.01):
        """图像反演:找到真实图像对应的潜在编码"""
        z = torch.randn(1, 512, device=self.device, requires_grad=True)
        optimizer = torch.optim.Adam([z], lr=lr)

        target = transforms.Resize((1024, 1024))(target_image).unsqueeze(0)
        target = target.to(self.device)

        for step in range(num_steps):
            w = self.G.mapping(z)
            generated = self.G.synthesis(w)

            # 多尺度重建损失
            loss = nn.functional.mse_loss(generated, target)
            loss += nn.functional.mse_loss(
                nn.functional.avg_pool2d(generated, 2),
                nn.functional.avg_pool2d(target, 2)
            )

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            if step % 100 == 0:
                print(f"Step {step}, Loss: {loss.item():.6f}")

        return self.G.mapping(z.detach())

# === 使用示例 ===
# pipeline = FaceGANPipeline('stylegan2_ffhq_1024.pt')
#
# 生成随机人脸
# face = pipeline.generate_random(num_images=1, truncation_psi=0.7)
#
# 添加微笑(假设已有微笑方向向量)
# smile_face = pipeline.edit_attribute(z, smile_direction, alpha=2.0)
#
# 风格混合
# mixed = pipeline.style_mix(z1, z2, crossover_point=7)

11. 评估指标

11.1 FID(Fréchet Inception Distance)

FID衡量生成图像分布与真实图像分布的距离,值越低越好。

import numpy as np
from scipy import linalg
from torchvision.models import inception_v3
import torch

def calculate_fid(real_features, gen_features):
    """计算Fréchet Inception Distance"""
    # 假设特征已通过InceptionV3提取

    mu1, sigma1 = real_features.mean(0), np.cov(real_features, rowvar=False)
    mu2, sigma2 = gen_features.mean(0), np.cov(gen_features, rowvar=False)

    diff = mu1 - mu2

    # 计算矩阵平方根
    covmean, _ = linalg.sqrtm(sigma1 @ sigma2, disp=False)

    if np.iscomplexobj(covmean):
        covmean = covmean.real

    fid = diff @ diff + np.trace(sigma1 + sigma2 - 2 * covmean)
    return float(fid)

def extract_inception_features(images, batch_size=64, device='cuda'):
    """使用InceptionV3提取特征"""
    inception = inception_v3(pretrained=True, transform_input=False)
    inception.fc = nn.Identity()  # 移除分类头,输出2048维特征
    inception = inception.to(device).eval()

    features = []
    with torch.no_grad():
        for i in range(0, len(images), batch_size):
            batch = images[i:i+batch_size].to(device)
            # InceptionV3需要299x299输入
            batch = nn.functional.interpolate(batch, size=(299, 299),
                                               mode='bilinear')
            feat = inception(batch)
            features.append(feat.cpu().numpy())

    return np.concatenate(features, axis=0)

# FID评估流程
# real_features = extract_inception_features(real_images)
# gen_features = extract_inception_features(generated_images)
# fid_score = calculate_fid(real_features, gen_features)
# print(f"FID Score: {fid_score:.2f}")  # 越低越好,通常 < 10 为优秀

11.2 IS(Inception Score)

def calculate_inception_score(generated_images, num_splits=10,
                                batch_size=64, device='cuda'):
    """计算Inception Score"""
    inception = inception_v3(pretrained=True).to(device).eval()

    preds = []
    with torch.no_grad():
        for i in range(0, len(generated_images), batch_size):
            batch = generated_images[i:i+batch_size].to(device)
            batch = nn.functional.interpolate(batch, size=(299, 299),
                                               mode='bilinear')
            pred = torch.softmax(inception(batch), dim=1)
            preds.append(pred.cpu().numpy())

    preds = np.concatenate(preds, axis=0)

    # IS = exp(E[KL(p(y|x) || p(y))])
    scores = []
    for i in range(num_splits):
        part = preds[i * len(preds) // num_splits:
                     (i + 1) * len(preds) // num_splits]

        py = part.mean(axis=0)  # p(y) 边际分布
        kl = part * (np.log(part + 1e-10) - np.log(py + 1e-10))
        kl_mean = kl.sum(axis=1).mean()
        scores.append(np.exp(kl_mean))

    return np.mean(scores), np.std(scores)

# IS越高越好,表示生成图像质量高且多样
# IS的局限:只关注类别分布,不与真实数据比较

11.3 LPIPS(Learned Perceptual Image Patch Similarity)

class LPIPS(nn.Module):
    """简化版LPIPS — 基于VGG特征的感知距离"""

    def __init__(self):
        super().__init__()
        vgg = models.vgg16(pretrained=True).features
        # 提取多个层的特征
        self.slice1 = nn.Sequential(*list(vgg[:4]))   # conv1_2
        self.slice2 = nn.Sequential(*list(vgg[4:9]))   # conv2_2
        self.slice3 = nn.Sequential(*list(vgg[9:16]))  # conv3_3
        self.slice4 = nn.Sequential(*list(vgg[16:23])) # conv4_3

        # 各层权重(可学习)
        self.weights = nn.Parameter(torch.tensor([1.0, 1.0, 1.0, 1.0]))

        for param in self.parameters():
            param.requires_grad = False

    def forward(self, x, y):
        """计算两幅图像间的感知距离"""
        x_features = self._extract_features(x)
        y_features = self._extract_features(y)

        distance = 0
        for i, (xf, yf) in enumerate(zip(x_features, y_features)):
            # L2归一化后计算距离
            xf = xf / (xf.norm(dim=1, keepdim=True) + 1e-10)
            yf = yf / (yf.norm(dim=1, keepdim=True) + 1e-10)
            distance += self.weights[i] * ((xf - yf) ** 2).mean(dim=(2, 3))

        return distance

    def _extract_features(self, x):
        h1 = self.slice1(x)
        h2 = self.slice2(h1)
        h3 = self.slice3(h2)
        h4 = self.slice4(h3)
        return [h1, h2, h3, h4]

# 使用
# lpips = LPIPS()
# perceptual_distance = lpips(image_a, image_b)
# 值越小表示两幅图像在感知上越相似

指标选择指南

指标 衡量什么 适用场景 局限性
FID 分布相似度 整体质量评估 需要大量样本
IS 质量+多样性 类条件生成 不与真实数据对比
LPIPS 感知相似度 图像翻译/重建评估 不衡量生成质量
PSNR/SSIM 像素级相似度 超分辨率评估 与人类感知不完全一致

总结

从2014年的原始GAN到如今的3D-aware GAN和视频生成,GAN技术经历了爆发式发展。虽然Diffusion Model在文生图领域取得了突破,GAN凭借其高速生成能力和成熟的架构设计,在实时应用、视频处理和图像编辑等领域依然不可替代。

核心要点:

  • 理解对抗训练的博弈本质是掌握GAN的基础
  • WGAN-GP解决了训练稳定性问题,是实践中的可靠选择
  • StyleGAN系列在人脸生成领域达到了照片级真实感
  • CycleGAN证明了无配对数据也能实现高质量域间翻译
  • 实际项目中,FID是最常用的评估指标,配合LPIPS做感知质量评估
  • GAN与Diffusion的融合是未来方向,取长补短方为上策

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录