跳转至

实验任务二:深度卷积对抗生成网络(DCGAN)

目标

  • 了解 DCGAN 与标准 GAN 的不同之处。

  • 掌握 DCGAN 的生成器(Generator)和判别器(Discriminator)设计。

  • 使用 PyTorch 搭建并训练 DCGAN 生成 MNIST 手写数字。

  • 学习如何优化 GAN 训练以获得更稳定的结果。

1. GAN 与 DCGAN 的区别

标准 GAN 主要由全连接层构成,生成器使用全连接网络从随机噪声生成数据,而判别器使用全连接网络对输入数据进行分类。GAN 存在的问题包括:

  • 训练不稳定,容易出现模式崩溃(Mode Collapse)。
  • 生成的图像质量较低,缺乏空间结构信息。

深度卷积生成对抗网络(DCGAN,Deep Convolutional Generative Adversarial Network)是生成对抗网络(GAN)的一种扩展,它通过使用卷积神经网络(CNN)来实现生成器和判别器的构建。与标准的GAN相比,DCGAN通过引入卷积层来改善图像生成质量,使得生成器能够生成更清晰、更高分辨率的图像。

DCGAN(Deep Convolutional GAN)引入卷积神经网络(CNN)来改进 GAN,使其在生成高分辨率的图像时表现更好。

DCGAN 相比普通 GAN 的改进

卷积层替代全连接层(提高图像质量)。传统的GAN使用全连接层,而DCGAN将其替换为卷积层。卷积层在处理图像时能够更好地保留图像的空间结构,从而生成更为清晰的图像。

使用 BatchNorm(稳定训练)。

什么是BatchNorm:Batch Normalization(批归一化)是一种用于加速训练和稳定梯度的技术,它的核心思想是对 mini-batch 内的特征进行归一化

为什么要用BatchNorm:BatchNorm 使数据的分布更加稳定,从而防止梯度在深层网络中消失或爆炸,提高训练的稳定性、由于 BatchNorm 归一化了激活值,使得模型对不同初始权重更加鲁棒,因此可以使用较大的学习率,加快收敛速度。

LeakyReLU 代替 ReLU(防止梯度消失)。

Tanh 作为生成器输出激活函数(适应数据范围)。

如需更加深入的学习,可参考该论文:Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks

2. DCGAN的实现

部分细节可参考实验任务一。

生成器

归一化(Batch Normalization)

  • 加速训练:归一化输入数据,让数据分布更加稳定,提高训练效率。

  • 防止梯度消失或梯度爆炸:避免模型在训练过程中出现数值不稳定的情况。

  • 引入一定的正则化效果:减少模型对特定输入模式的依赖,提高泛化能力。

反卷积(Transposed Convolution)

  • 反卷积(也叫 上采样卷积)用于增大特征图的尺寸,最终生成目标大小的图像。

  • 通过学习权重,在空间上扩展特征图,相当于卷积的逆操作。

self.conv1 = nn.Sequential(

nn.ConvTranspose2d(128, 64, 4, stride=2, padding=1), # (7,7) -> (14,14)

nn.BatchNorm2d(64),

nn.ReLU())

  • 输入通道数 128,输出通道数 64

  • kernel_size=4(4×4 卷积核)

  • stride=2(步长 2,扩大一倍)

  • padding=1(保持尺寸一致)

class Generator(nn.Module):
    def __init__(self, input_dim):
        super(Generator, self).__init__()

        # 1. 输入层:将 100 维随机噪声投影到 32x32(1024 维)
        #TODO   # 线性变换fc1,将输入噪声扩展到 1024 维

        self.br1 = nn.Sequential(
            #TODO   # 批归一化,加速训练并稳定收敛
            #TODO   # ReLU 激活函数,引入非线性
        )

        # 2. 第二层:将 1024 维数据映射到 128 * 7 * 7 的维特征
        #TODO   # 线性变换fc2,将数据变换为适合卷积层的维数大小

        self.br2 = nn.Sequential(
            #TODO   # 批归一化
            #TODO   # ReLU 激活函数
        )

        # 3. 反卷积层 1:上采样,输出 64 通道的 14×14 特征图
        self.conv1 = nn.Sequential(
            #TODO   # 反卷积:将 7x7 放大到 14x14, kernel size设置为4, stride设置为2,padding设置为1
            #TODO   # 归一化,稳定训练
            #TODO   # ReLU 激活函数
        )

        # 4. 反卷积层 2:输出 1 通道的 28×28 图像
        self.conv2 = nn.Sequential(
            #TODO   # 反卷积:将 14x14 放大到 28x28
            #TODO   # Sigmoid 激活函数,将输出归一化到 [0,1]
        )

    def forward(self, x):
        x = self.br1(self.fc1(x))  # 通过全连接层,进行 BatchNorm 和 ReLU 激活
        x = self.br2(self.fc2(x))  # 继续通过全连接层,进行 BatchNorm 和 ReLU 激活
        x = x.reshape(-1, 128, 7, 7)  # 变形为适合卷积输入的形状 (batch, 128, 7, 7)
        x = self.conv1(x)  # 反卷积:上采样到 14x14
        output = self.conv2(x)  # 反卷积:上采样到 28x28
        return output  # 返回生成的图像

判别器

DCGAN 的判别器使用多个卷积层对输入图像进行特征提取,并最终输出真假概率。

DCGAN 中,判别器是一个 卷积神经网络(CNN),主要有:

  • 多个卷积层(Conv2D):提取局部特征,如边缘、纹理。

  • LeakyReLU 激活函数:相比于 ReLU,它可以防止梯度消失问题。

  • 最大池化:降采样,根据kernel_size,和stride降低特征图的尺寸。

  • 全连接层(Linear):最终映射到 [0,1] 的概率。

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()

        # 1. 第一层:输入 1 通道的 28x28 图像,输出 32 通道的特征图,然后通过MaxPool2d降采样
        self.conv1 = nn.Sequential(
            #TODO  # 5x5 卷积核,步长为1
            #TODO   # LeakyReLU,negative_slope参数设置为0.1
        )
        self.pl1 = nn.MaxPool2d(2, stride=2)

        # 2. 第二层:输入 32 通道,输出 64 通道特征
        self.conv2 = nn.Sequential(
            #TODO   # 5x5 卷积核,步长为1
            #TODO  # LeakyReLU 激活函数,negative_slope参数设置为0.1
        )
        self.pl2 = nn.MaxPool2d(2, stride=2)

        # 3. 全连接层 1:将 64x4x4 维特征图转换成 1024 维向量
        self.fc1 = nn.Sequential(
            #TODO   # 线性变换,将 64x4x4 映射到 1024 维
            #TODO   # LeakyReLU 激活函数,negative_slope参数设置为0.1
        )

        # 4. 全连接层 2:最终输出真假概率
        self.fc2 = nn.Sequential(
            #TODO   # 线性变换,将 1024 维数据映射到 1 维
            #TODO   # Sigmoid 归一化到 [0,1] 作为概率输出
        )

    def forward(self, x):
        x = self.pl1(self.conv1(x))  # 第一层卷积,降维
        x = self.pl2(self.conv2(x))  # 第二层卷积,降维
        x = x.view(x.shape[0], -1)  # 展平成向量
        x = self.fc1(x)  # 通过全连接层
        output = self.fc2(x)  # 通过最后一层全连接层,输出真假概率
        return output  # 返回判别结果

训练过程及数据保存参考实验任务一

def train_discriminator(real_images, D, G, loss_func, optim_D, batch_size, input_dim, device):
    # TODO
    return loss_D.item()

def train_generator(D, G, loss_func, optim_G, batch_size, input_dim, device):
    # TODO
    return loss_G.item()

def main():
    # 设备配置:使用 GPU(如果可用),否则使用 CPU
    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

    # 设定超参数
    input_dim = 100  # 生成器输入的随机噪声向量维度
    batch_size = 128  # 训练时的批量大小
    num_epoch = 30  # 训练的总轮数

    # 加载 MNIST 数据集
    train_dataset = datasets.MNIST(root="./data/", train=True, transform=transforms.ToTensor(), download=True)
    train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)

    # 创建生成器和判别器,并移动到 GPU(如果可用)
    # TODO
    # TODO

    # 定义优化器,优化器要求同任务一
    # TODO
    # TODO

    loss_func = nn.BCELoss()

    # 初始化 TensorBoard
    writer = SummaryWriter(log_dir='./logs/experiment_dcgan')

    # 开始训练
    for epoch in range(num_epoch):
        total_loss_D, total_loss_G = 0, 0
        for i, (real_images, _) in enumerate(train_loader):
            loss_D = train_discriminator(real_images, D, G, loss_func, optim_D, batch_size, input_dim, device)
            loss_G = train_generator(D, G, loss_func, optim_G, batch_size, input_dim, device)

            total_loss_D += loss_D
            total_loss_G += loss_G

            # 每 100 步打印一次损失
            if (i + 1) % 100 == 0 or (i + 1) == len(train_loader):
                print(f'Epoch {epoch:02d} | Step {i + 1:04d} / {len(train_loader)} | Loss_D {total_loss_D / (i + 1):.4f} | Loss_G {total_loss_G / (i + 1):.4f}')

        # 记录损失到 TensorBoard
        writer.add_scalar('DCGAN/Loss/Discriminator', total_loss_D / len(train_loader), epoch)
        writer.add_scalar('DCGAN/Loss/Generator', total_loss_G / len(train_loader), epoch)

        # 生成并保存示例图像
        with torch.no_grad():
            noise = torch.randn(64, input_dim, device=device)
            fake_images = G(noise)

            # 记录生成的图像到 TensorBoard
            img_grid = torchvision.utils.make_grid(fake_images, normalize=True)
            writer.add_image('Generated Images', img_grid, epoch)

    writer.close()

if __name__ == '__main__':
    main()

思考题

思考题1: DCGAN与传统GAN的主要区别是什么?为什么DCGAN更适合图像生成任务?

思考题2: DCGAN的生成器和判别器分别使用了哪些关键的网络结构?这些结构如何影响生成效果?

思考题3: DCGAN中为什么使用批归一化(Batch Normalization)?它对训练过程有什么影响?