Advertisement

GANs 2.0: How to Train Your Own GANs from Scratch?

阅读量:

作者:禅与计算机程序设计艺术

1.简介

在当今时代中已逐渐成为炙手可热的话题

本文旨在全面阐述Generative Adversarial Networks(GANs)的基本概念及其相关知识体系,在涵盖生成器与判别器的工作原理以及相关的数学模型基础上,并非局限于表面的技术细节。随后将深入探讨自定义GAN模型的构建过程,并非仅仅停留在理论层面。此外,在讨论环节中将着重分析评估生成图像的质量标准以及探讨其在不同应用场景中的实际应用情况,并非单纯关注技术细节。

2.背景介绍

那么什么是GANs呢?从字面意思上说,GANs是一种通过对抗的方式训练的神经网络模型。简单来说,就是由一个生成器网络(通常用G表示)来生成看似虚假但实际是经过精心设计的数据样本(即所谓的"fake data"),而另一个判别器网络(通常用D表示)则负责判断这些生成的数据样本是否为真实数据。两者之间存在互动关系:当生成的数据样本越接近真实数据时,则判别器需要越难区分它们;反之,则更容易被识别为虚假样本。在这个过程中,两个网络的目标都是不断调整自己的参数以提高自身的性能:判别器希望更好地分辨真假数据,而生成器则致力于创造出越来越接近真实数据的东西。最终目标是实现一种平衡状态——当两个网络都无法再进一步提高正确率时,则达到了理想的对抗效果。这种基于对抗训练机制的学习过程使得GANs在各种复杂的任务中展现出强大的能力。

为了通过GANs技术生成各种数字媒体内容的原因有很多,其中最为关键的是以下几点:

Gans能够产出无数种不同类型的人像、汽车、风景以及游戏屏幕截图等。基于其强大的能力,能够创造出前所未见的图像类型

2.Gans能够从随机噪声中输出具有独特特征的图像。
即可输出与原始图片相似的样子。
而不仅仅只是简单地模仿其他图片。
这是因为由于Gans了解生成图片的基本架构。
并能从输入的随机噪声中识别潜在的独特模式。

3.GANS可以被视为一个有效的计算机视觉任务替代方案... 传统的计算机视觉任务如分类和检测等方法,在训练过程中通常需要投入大量的人为标注工作以获取高质量的数据集才能取得理想的效果... 相比之下,GANS能够通过生成海量的高质量训练图像,快速实现可靠的计算机视觉模型构建

在相同的训练数据中被GANS输出多种图像版本。该模型能够被应用于实现以下具体技术:超分辨率重建、无损图像压缩、帧插值以及运动矢量估计。同时该方法也适用于多种应用场景包括但不局限于超分辨重建、无损编码优化以及动态帧填充。

  1. Gans具备高效的图像生成能力,并不仅可以在虚拟现实领域中使用,在网页渲染、视频编辑以及创作视频等多个应用场景中也可以直接应用

Gans能够生成不同种类的音频内容,并非仅限于单一形式

对GANs而言,这是一种先进的深度学习模型,在计算机领域具有广泛的应用潜力。该技术能够有效生成多样化的图像、音频与视频内容。

3.基本概念术语说明

3.1 生成器(Generator)

该神经网络可通过特定输入条件产生所需图片的一种形式。换言之,在GAN架构中存在一种特定类型的网络结构。其核心组件包括多种卷积层、激活函数以及池化结构。其最终部分通常包含一个全连接层来完成图片的输出过程。

3.2 判别器(Discriminator)

鉴别器(discriminator)是一种用于评估输入图像真实性程度的神经网络模型。该鉴别器接收一张图片作为输入,并通过一系列复杂的计算生成一个数值结果。这个数值结果反映了该图片与真实数据集之间的相似程度。基于卷积层、激活函数和池化层等构成的结构设计,在经过多层运算后会得到一个单一数值指标作为最终判定依据。

3.3 损失函数

GANs基于对抗训练策略运作,这意味着必须设定两个网络之间的损失函数。首先设定生成器网络的损失函数这一指标用于衡量生成器输出的图像与真实图像之间的差异程度。其次设定判别器网络的损失函数这一指标用于评估判别器识别错误样本的能力。通常情况下判别器网络所采用的损失函数可被定义为"误判成本"即基于负对数似然计算得出的结果

3.4 优化算法

GANs主要运用Adam优化算法,在对抗学习中包含有针对生成器设置的梯度惩罚项(gradient penalty)。具体而言,在计算损失函数时,在判别器承担起职责的同时还需要为生成器设定相应的损失指标。因此,在更新判别器参数之前需要先为生成器损失指标计算梯度值。接着将两个损失指标按照一定权重进行加权平均作为新的综合损失函数目标值。除此之外,在完成判别器参数更新后还需要对生成器参数进行相应的调整以完成整个网络的学习过程

3.5 数据集

以上是我根据您的要求对原文进行的同义改写

4.核心算法原理和具体操作步骤以及数学公式讲解

4.1 概览

整个GANs的训练可以分为两个阶段:

在第一阶段中,鉴别器网络被用来辨别生成图像与真实图像之间的差异;在第二阶段中,生成器网络被用来致力于提高生成图像的清晰度。

4.2 生成器网络结构

生成器由多种卷积操作、激活函数以及下采样模块构成,在其后部通常连接一个全连接层以完成图像的重建任务。该段落将阐述生成器网络的基本架构

复制代码
    def generator_model():
    model = Sequential()
    
    # Layer 1 - Input layer
    model.add(Dense(input_dim=noise_dim, output_dim=hidden_dim))
    model.add(LeakyReLU(alpha=alpha))
    
    # Layers 2-n - Hidden layers with batch normalization and dropout
    for i in range(num_layers):
        model.add(Dense(units=hidden_dim*i))
        model.add(BatchNormalization())
        model.add(LeakyReLU(alpha=alpha))
        model.add(Dropout(rate=dropout_rate))
    
    # Output layer - Generating images of the same size as the input images (channels last format)
    model.add(Dense(output_shape=(image_size[0], image_size[1], num_channels)))
    model.add(Activation("tanh"))
    
    return model
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

以上代码中的变量解释如下:

  • noise_dim:噪声向量的空间维度。
    • hidden_dim:各隐藏层单元的数量。
    • num_layers:各隐藏层单元数量。
    • alpha:LeakyReLU激活函数中负斜率参数α。
    • dropout_rate:各层神经网络中的丢弃概率。
    • image_size:生成图像的空间分辨率及其宽高比参数。
    • num_channels:生成图像的颜色深度参数。

生成器网络结构的特点有以下几点:

通过LeakyReLU激活函数实现神经网络模型的训练。
批归一化(batch normalization)这一技术被成功应用于深度学习模型中。
通过应用2×2的最大池化层实现图像分辨率的缩减。
将输出图像编码为channels last的数据格式。
生成器网络生成范围在-1至1之间的图像,并且其像素值服从均值为0、方差为1的标准正态分布。

4.3 判别器网络结构

判别器网络的组织与生成器相似,在输出层仅设置一个神经元以输出概率值。该判别器网络通过一系列卷积操作、激活函数应用和下采样层对输入图像进行特征提取,并随后接入一个Sigmoid函数来计算生成的概率值

复制代码
    def discriminator_model():
    model = Sequential()
    
    # Layer 1 - Convolutional layer + Leaky ReLU activation function
    model.add(Conv2D(filters=num_filters, kernel_size=kernel_size, strides=stride, padding="same",
                     input_shape=input_shape))
    model.add(LeakyReLU(alpha=alpha))
    
    # Layers 2-n - Convolutional layers + Batch Normalization + Leaky ReLU activation function
    for i in range(num_layers-1):
        model.add(Conv2D(filters=num_filters*(i+1), kernel_size=kernel_size, strides=stride,
                         padding="same"))
        model.add(BatchNormalization())
        model.add(LeakyReLU(alpha=alpha))
    
    # Flattening and output layer - Fully connected layer with a sigmoid activation function
    model.add(Flatten())
    model.add(Dense(units=1))
    model.add(Activation('sigmoid'))
    
    return model
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

以上代码中的变量解释如下:

  • num_filters:滤波器数量。
  • kernel_size:滤波器尺寸。
  • stride:采样步长。
  • num_layers:隐藏层个数。
  • alpha:LeakyReLU负斜率系数。
  • input_shape:输入图像分辨率。

判别器网络结构的特点有以下几点:

  • 该模型采用了2×2的最大池化层(基于采样率的缩减)降低了输入图像的空间分辨率。
  • 该模型引入了BN层。
  • 每一层级均集成一个Dropout层。
  • 采用了更为复杂的特征提取架构。
  • 输出图像的数据格式为channels-first布局。
  • 判别器网络输出的概率值范围限定于[0,1]区间,并据此判断生成图像是否为真实样本。

4.4 损失函数

GANs采用了对抗训练策略,并且必须设定生成器和判别器之间的关系模型。首先必须设定生成器网络的学习目标即其目标是减少生成图像与真实图像间的差异程度;其次必须设定判别器网络的学习目标即其目标是减少错误判定的概率;通常情况下;判别模型的目标可以用'误分类成本'来描述即使用负对数似然作为基础计算公式

4.5 Adam优化算法

Adam优化算法是GANs常用的优化算法,其公式如下:

在模型中,θ代表神经网络中的参数设置,在每一轮迭代过程中涉及参数更新。其中,在每一轮迭代中需要考虑的因素包括:学习速率η为优化过程中的一个关键参数;此外,在计算过程中还需要使用到两个重要的中间变量:v_{t}为当前迭代所使用的动量变量;以及m_{t}代表该轮计算得到的梯度变量。

当进行判别器训练时,在每一个迭代过程中计算判断者网络损失函数对于其各参数的梯度,并利用Adam优化算法来更新这些参数。

  1. 对于判别器网络的参数\theta

计算生成器关于判别器损失函数θ的梯度等于从数据概率分布抽取样本x_i时计算f_θ(x_i)的梯度加上从潜在空间中按照概率分布p_Z抽取隐变量z_j后通过生成器G变换得到样本G(z_j)时计算f_θ(G(z_j))的梯度,并标记为方程(1)。

其中,p_{\text{data}}代表用于训练的数据集;生成器网络G(\cdot)被用来生成与训练数据分布相匹配的数据样本;噪声向量\boldsymbol{z}^{j}遵循均值为0、方差为1的标准正态分布。

f_\theta代表判别器网络及其参数θ;判别器网络的损失函数由L_{dis}表示。对于输入x或噪声z而言,\nabla_\theta f_\theta(\cdot)代表了该函数的梯度。

  1. 为了防止梯度消失或爆炸,加入了动量项和方差项:

\beta_1, \beta_2各自调节v_tm_t的变化速率。在达到一定阶段时将梯度的一阶与二阶动量进行累加,在此过程中可获得当前参数的新值。

  1. 更新判别器网络的参数\theta

在训练生成器时, 采用优化算法来调整其参数. 每当进行一次迭代时, 在每个迭代周期中, 计算出生成器网络损失函数对各参数的梯度值; 随后应用Adam优化算法来调整这些参数; 具体的操作步骤将在下文详细阐述.

复制代码
1. 对于生成器网络的参数$\theta$:

θ参数生成器损失函数L_gen的梯度等于从数据分布中抽取样本点z^j的所有梯度的期望值(5)。

其中概率分布p_{\mathcal{Z}}定义了噪声\boldsymbol{z}的概率特性。生成模型G(\cdot)负责将潜在空间中的样本\boldsymbol{z}^{j}映射到数据空间中。服从标准正态分布的白噪声\boldsymbol{z}^{j}遵循高斯先验分布。

其中符号f_{\theta}被定义为生成器网络,并包含参数\theta;符号L_{\text{gen}}代表了生成器网络所对应的损失函数。符号\nabla_{\theta}f_{\θ}(G(\cdot))计算的是在输入噪声变量z时的梯度。

复制代码
2. 根据式$(2),(3)$更新生成器网络的参数$\theta$:

当判别器网络的损失函数持续处于负值状态时,则表明模型正处于学习过程中,并将继续进行训练;而若判别器网络的损失函数随着时间推移逐渐降低,则表明模型已基本完成训练阶段,并随后启动生成样本并进行评估过程。

4.6 数据集

数据集是由高质量图片与其相应的标注信息组成的集合体。训练GANs依赖于获取数量充足的高质量图片;如果缺少这些真实样本的话,则难以使模型学习到多样化的特征并生成出具有创造力的内容。此外;数据集的规模对其生成效果会产生显著影响;因此合理选择数据集至关重要

5.具体代码实例和解释说明

5.1 模型构建与编译

首先,导入必要的库,设置相关的参数,然后构建生成器和判别器的模型。

复制代码
    import tensorflow as tf
    from keras import Model
    from keras.layers import Input, Dense, Conv2D, MaxPooling2D, UpSampling2D, BatchNormalization, Activation, Dropout, Flatten
    from keras.optimizers import Adam
    
    # Set hyperparameters
    img_rows, img_cols = 28, 28   # Resolution of input images
    img_chns = 1    # Number of channels (Grayscale -> 1 channel)
    latent_dim = 100     # Size of latent vector
    
    # Build Generator Network
    def build_generator():
    gen_input = Input((latent_dim,))
    x = Dense(7*7*128)(gen_input)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Reshape((7, 7, 128))(x)
    
    x = Conv2DTranspose(64, kernel_size=3, strides=2, padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    
    x = Conv2DTranspose(32, kernel_size=3, strides=2, padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    
    x = Conv2DTranspose(1, kernel_size=3, strides=2, padding='same', use_bias=False, activation='tanh')(x)
    
    gen_model = Model(inputs=[gen_input], outputs=[x])
    return gen_model
    
    # Build Discriminator Network
    def build_discriminator():
    disc_input = Input((img_rows, img_cols, img_chns))
    x = Conv2D(16, kernel_size=3, strides=2, padding='same')(disc_input)
    x = LeakyReLU(alpha=0.2)(x)
    
    x = Conv2D(32, kernel_size=3, strides=2, padding='same')(x)
    x = BatchNormalization()(x)
    x = LeakyReLU(alpha=0.2)(x)
    
    x = Conv2D(64, kernel_size=3, strides=2, padding='same')(x)
    x = BatchNormalization()(x)
    x = LeakyReLU(alpha=0.2)(x)
    
    x = Flatten()(x)
    x = Dense(1)(x)
    x = Activation('sigmoid')(x)
    
    disc_model = Model(inputs=[disc_input], outputs=[x])
    return disc_model
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

5.2 加载数据集

载入MNIST手写数字数据集。

复制代码
    mnist = tf.keras.datasets.mnist
    (_, _), (_, _) = mnist.load_data()
    X_train, y_train = X_train / 255.0, y_train.astype('float32')
    X_train = np.expand_dims(X_train, axis=-1)
    
    # Construct train and validation sets using subset of training data
    X_train, X_valid = X_train[:int(len(X_train)*0.9)], X_train[int(len(X_train)*0.9):]
    y_train, y_valid = y_train[:int(len(y_train)*0.9)], y_train[int(len(y_train)*0.9):]
    
    train_dataset = tf.data.Dataset.from_tensor_slices((X_train, y_train)).shuffle(buffer_size).batch(batch_size)
    valid_dataset = tf.data.Dataset.from_tensor_slices((X_valid, y_valid)).shuffle(buffer_size).batch(batch_size)
    
      
      
      
      
      
      
      
      
      
      
    
    代码解读

5.3 训练模型

复制代码
    def train(epochs, noise_dim):
    optimizer = Adam(lr=learning_rate, beta_1=0.5)
    
    # Define loss functions and metrics
    cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)
    def discriminator_loss(real_output, fake_output):
        real_loss = cross_entropy(tf.ones_like(real_output), real_output)
        fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
        total_loss = real_loss + fake_loss
        return total_loss
    
    def generator_loss(fake_output):
        return cross_entropy(tf.ones_like(fake_output), fake_output)
    
    def gradient_penalty(samples, generated_samples):
        gradients = tf.gradients(generated_samples, samples)[0]
        slopes = tf.sqrt(tf.reduce_sum(tf.square(gradients), axis=[1, 2, 3]))
        gp = tf.reduce_mean((slopes - 1.)**2)
        return gp
    
    @tf.function
    def train_step(images):
        valid = np.ones((images.shape[0], *disc_patch))
        fake = np.zeros((images.shape[0], *disc_patch))
    
        with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
            z = tf.random.normal([images.shape[0], noise_dim])
    
            generated_images = generator(z, training=True)
    
            real_output = discriminator(images, training=True)
            fake_output = discriminator(generated_images, training=True)
    
            gen_loss = generator_loss(fake_output)
            disc_loss = discriminator_loss(real_output, fake_output)
    
            epsilon = tf.random.uniform([], 0., 1.)
            x_hat = epsilon * images + (1. - epsilon) * generated_images
            x_hat.set_shape(images.shape)
            gp = gradient_penalty(x_hat, generated_images)
            disc_loss += lambda_gp * gp
    
        gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)
        gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)
    
        optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))
        optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))
    
        return {'d_loss': disc_loss, 'g_loss': gen_loss}
    
    generator = build_generator()
    discriminator = build_discriminator()
    
    steps = 0
    g_loss_avg = []
    d_loss_avg = []
    
    for epoch in range(epochs):
        print('Epoch {}/{}'.format(epoch+1, epochs))
    
        for images, labels in tqdm(train_dataset):
            cur_batch_size = len(images)
    
            real_images = images
    
            losses = train_step(real_images)
    
            d_loss_avg.append(losses['d_loss'])
            g_loss_avg.append(losses['g_loss'])
    
            if steps % display_interval == 0:
                clear_output(wait=True)
    
                plt.figure(figsize=(15, 5))
    
                # Display Images
                display_list = [
                    real_images[0].reshape(28, 28),
                    generate_and_save_images(generator, cur_batch_size, seed, display_dir)]
    
                title = ['Real Images', 'Fake Images']
    
                for i in range(2):
                    plt.subplot(1, 2, i+1)
                    plt.title(title[i])
                    plt.imshow(display_list[i] * 0.5 + 0.5)
                    plt.axis('off')
    
                plt.show()
    
            steps += 1
    
    save_models(generator, discriminator, save_dir)
    
    def generate_and_save_images(model, cur_batch_size, seed, folder):
    predictions = model(seed, training=False)
    
    fig = plt.figure(figsize=(4, 4))
    
    for i in range(predictions.shape[0]):
        plt.subplot(4, 4, i+1)
        plt.imshow(((predictions[i]*0.5)+0.5))
        plt.axis('off')
    
    plt.savefig(filename)
    plt.close(fig)
    
    def save_models(generator, discriminator, path):
    generator.save(os.path.join(path, 'generator.h5'))
    discriminator.save(os.path.join(path, 'discriminator.h5'))
    
    if __name__ == '__main__':
    epochs = 100
    buffer_size = 60000
    batch_size = 128
    learning_rate = 0.0002
    alpha = 0.2
    num_filters = 64
    kernel_size = 3
    stride = 2
    num_layers = 4
    hidden_dim = 128
    dropout_rate = 0.3
    noise_dim = 100
    lambda_gp = 10
    display_interval = 500
    display_dir = 'training/images'
    save_dir = './saved_models/'
    disc_patch = (28, 28, 1)
    
    train(epochs, noise_dim)
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

全部评论 (0)

还没有任何评论哟~