您现在的位置是:首页 >技术交流 >GAN培训挑战:用于彩色图像的DCGAN网站首页技术交流

GAN培训挑战:用于彩色图像的DCGAN

程序媛一枚~ 2024-08-07 18:01:03
简介GAN培训挑战:用于彩色图像的DCGAN

这篇博客将介绍如何训练 DCGAN 以生成彩色时尚图像。将通过培训过程学习GAN常见的挑战、应对这些挑战的技术以及 GAN 评估指标。 在下一篇文章中,还将学习如何使用Wasserstein GAN(WGAN)和Wasserstein GAN with Gradient Penalty(WGAN-GP)进一步提高训练稳定性。

本课是GAN教程系列的第三篇文章:

1. 生成对抗网络 (GAN) 简介
2. 入门:面向时尚的DCGAN-MNIST
3. GAN培训 挑战:用于彩色图像的DCGAN (这篇文章)

上一篇博客介绍了如何训练DCGAN来生成灰度Fashion-MNIST图像。在这篇文章中用彩色图像训练DCGAN,以演示GAN训练的常见挑战,还将简要讨论一些改进技术和GAN评估指标。

1. 效果图

其中一个图像:
在这里插入图片描述

生成器模型:
在这里插入图片描述

鉴别器模型:
在这里插入图片描述

训练1个纪元 VS 25纪元 VS 50纪元效果图如下:
在这里插入图片描述

2. 原理

2.1 用于彩色图像的 DCGAN

DCGAN 代码将以我上一篇文章 作为基线,然后进行调整以训练彩色图像。由于在上一篇文章中详细介绍了DCGAN端到端训练,现在将只关注为彩色图像训练DCGAN所需的关键更改:

  • 数据: 从Kaggle下载彩色图像并对其进行预处理,使其 [-1, 1]。
  • 生成器: 调整如何对模型架构进行上采样以生成彩色图像。
  • 鉴别器: 调整输入图像形状 28×28×1 自 64×64×3。

通过这些更改可以在彩色图像上训练DCGAN;但是当处理彩色图像或MNIST或Fashion-MNIST以外的任何数据时,您将意识到GAN训练的挑战性。即使是使用Fashion-MNIST灰度图像进行训练也可能很棘手。

2.3 准备数据

数据集来训练DCGAN 名为Kaggle的Clothing & Models 该数据集是从 Zalando.com 抓取的服装的集合。有六个类别和超过 16k 的彩色图像 606×875,其大小将调整为 64×64 用于培训。
指定图像大小 64×64 和批量大小 32.

2.4 生成器

在build_generator函数中使用keras Sequential API创建生成器体系结构。在之前的DCGAN文章中已经详细介绍了如何创建生成器架构。下面看看如何调整上采样以生成所需的64×64×3的彩色图像大小:
为彩色图像更新CHANNELS=3,而不是为灰度图像更新1。2的步幅是宽度和高度的一半,因此可以向后计算初始图像尺寸:对于Fashion MNIST,将其上采样为7->14->28。现在使用的训练图像大小为64×64,因此将多次上采样为8->16->32->64。
这意味着再添加一组Conv2DTranspose->BatchNormalization->ReLU。
对生成器所做的另一个更改是将内核大小从5更新为4,以避免减少生成图像中的棋盘伪影(见图2)。
根据后反卷积和棋盘伪像(Deconvolution and Checkerboard Artifacts),5的内核大小不能被2的步长整除。因此,解决方案是使用4而不是5的内核大小
generator.summary(),在代码中可视化生成器体系结构

2.5 鉴别器

鉴别器架构的主要变化是图像输入形状:使用[64,64,3]的形状而不是[28,28,1]。还添加了一组Conv2D->BatchNormalization->LeakyReLU,以平衡生成器中增加的架构复杂性,如上所述。其他一切都保持不变。
discriminator.summary(),在代码中可视化鉴别器DCGAN架构

2.6 DCGAN模型及回调函数

通过子类keras.model定义DCGAN模型架构,并覆盖train_step来定义自定义训练循环。代码中唯一的细微变化是对实际标签应用单侧标签平滑。
real_labels = tf.ones((batch_size, 1))
real_labels += 0.05 * tf.random.uniform(tf.shape(real_labels))
该技术减少了鉴别器的过度自信,因此有助于稳定GAN训练。
有关标签平滑的详细信息,请参阅Adrian Rosebrock的文章《使用Keras、TensorFlow和深度学习进行标签平滑》:https://pyimagesearch.com/2019/12/30/label-smoothing-with-keras-tensorflow-and-deep-learning/。
在《训练GANs的改进技术》一文中提出了用于规范GANs训练的“单侧标签平滑”技术,在https://arxiv.org/abs/1606.03498还可以找到其他改进技术。

2.7 GAN培训挑战

现在已经完成了用彩色图像训练DCGAN。让我们讨论一下GAN培训的一些常见挑战。
GANs很难训练,以下是一些众所周知的挑战:

  • 不收敛:不稳定、梯度消失或训练缓慢(Non-convergence: instability, vanishing gradients, or slow training)
  • 模式崩溃(Mode collapse)
  • 难以评估(Difficult to evaluate )

2.8 未能聚合(Non-convergence)

与训练其他模型(如图像分类器)不同,训练期间D和G的损失或准确性仅单独测量D和G,而不测量GAN的整体性能以及生成器在创建图像方面的表现。当发生器和鉴别器之间达到平衡时,GAN模型是“好的”,通常当鉴别器的损耗约为0.5时。

GAN训练不稳定(GAN training instability):很难保持D和G的平衡以达到平衡。看看训练中的损失,你会注意到它们可能会剧烈波动。D和G都可能陷入困境,永远无法改善。长时间的训练并不总是能让生成器变得更好。生成器的图像质量可能随着时间的推移而劣化。
消失梯度(Vanishing gradient):在自定义训练循环中,介绍了如何计算鉴别器和生成器损失,计算梯度,然后使用梯度进行更新。生成器依赖于鉴别器的反馈来进行改进。如果鉴别器太强,以至于它压倒了生成器:它可以在每次出现假图像时分辨出来,那么生成器就停止了训练。
您可能会注意到,有时生成的图像即使经过一段时间的训练,质量仍然很差。这意味着该模型未能在鉴别器和生成器之间找到平衡。

实验:使D架构更强(模型架构中的参数更多)或比G训练得更快(例如,将D的学习率提高到比G高得多)。

2.9 模式崩溃(Mode collapse)

当生成器重复生成相同的图像或训练图像的一小部分时,会发生模式崩溃。一个好的生成器应该生成各种各样的图像,这些图像在所有类别中都与训练图像相似。当鉴别器无法判断生成的图像是假的时,就会发生模式崩溃,因此生成器会不断生成相同的图像来欺骗鉴别器。
实验:为了模拟代码中的模式崩溃问题,尝试将噪声向量维数从100降低到10;或者将噪声向量维度从100增加到128以增加图像多样性。

2.10 难以评估

评估GAN模型很有挑战性,因为没有简单的方法来确定生成的图像是否“好”。与图像分类器不同,根据基本事实标签,预测要么正确,要么不正确。这就引出了下面关于如何评估GAN模型的讨论。

2.11 GAN评估指标

一个成功的生成器有两个标准-它应该使用以下内容生成图像:

  • 质量好:高保真逼真,
  • 多样性(diversity or variety):训练图像的不同类型(或类别)的良好表现。

我们可以定性地(目视检查图像)或用一些指标定量地评估模型。
通过目视检查进行定性评估。正如在DCGAN训练中所做的那样,我们观察在同一种子上生成的一组图像,并在训练过程中目视检查这些图像是否看起来更好。这适用于玩具示例,但对于大规模训练来说,这太耗费人力了。

启始得分(Inception Score IS)和Fréchet启始距离(Fréchet Inception Distance FID)是定量比较GAN模型的两个常用指标。

本文介绍了**“启动得分”:训练GANs的改进技术。它测量生成图像的质量和多样性。其想法是使用初始模型对生成的图像进行分类,并使用预测来评估生成器。分数越高表示该模型越好。**
Fréchet初始距离(FID)还使用初始网络进行特征提取并计算数据分布。FID通过观察生成的图像和训练图像而不是仅孤立地观察生成的图片来改进IS。较低的FID意味着生成的图像与真实图像更相似,因此是更好的GAN模型。

2. 源码

https://github.com/margaretmz/GANs-in-Art-and-Design/blob/main/3_dcgan_color_images.ipynb

# DCGAN 代码将以我上一篇文章 作为基线,然后进行调整以训练彩色图像。
# 由于在上一篇文章中: dcgan_minist.py 详细介绍了DCGAN端到端训练,现在将只关注为彩色图像训练DCGAN所需的关键更改
# python dcgan_color_minist.py

import os

import numpy as np
import tensorflow as tf
from matplotlib import pyplot as plt
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential

os.environ['KAGGLE_USERNAME'] = ''
os.environ['KAGGLE_KEY'] = ''

# 1. 数据预处理
# 下载数据并将其解压缩到名为 dataset/
# !kaggle datasets download -d dqmonn/zalando-store-crawl -p datasets
# !unzip datasets/zalando-store-crawl.zip -d datasets/

# 下载和解压文件后,设置文件夹目录
zalando_data_dir = "color_images/"

# 使用目录下的图片及image_dataset_from_directory 创建 tf.data.Dataset 设置图像大小为64x64,批处理大小为32
train_images = tf.keras.utils.image_dataset_from_directory(zalando_data_dir, label_mode=None, image_size=(64, 64),
                                                           batch_size=32)
# 将图像归一化到[-1,1]的范围,因为生成器的最终层激活使用tanh。最后,我们通过使用tf.dataset的map函数和lambda函数来应用规范化
train_images = train_images.map(lambda x: (x - 127.5) / 127.5)

image_batch = next(iter(train_images))
random_index = np.random.choice(image_batch.shape[0])
random_image = image_batch[random_index].numpy().astype("int32")
plt.axis("off")
plt.imshow(random_image)
plt.show()

# 随机噪声的潜在维数
LATENT_DIM = 100
# Con2DTranspose层的权重初始化
WEIGHT_INIT = tf.keras.initializers.RandomNormal(mean=0.0, stddev=0.02)
# 图像的颜色通道, 1:灰色图像,3 彩色图像
CHANNELS = 3  # UPDATED from 1


# 2. 生成器
# 在build_generator函数中使用keras Sequential API创建生成器体系结构。在之前的DCGAN文章中已经详细介绍了如何创建生成器架构。下面看看如何调整上采样以生成所需的64×64×3的彩色图像大小:
# 为彩色图像更新CHANNELS=3,而不是为灰度图像更新1。2的步幅是宽度和高度的一半,因此可以向后计算初始图像尺寸:对于Fashion MNIST,将其上采样为7->14->28。现在使用的训练图像大小为64×64,因此将多次上采样为8->16->32->64。
# 这意味着再添加一组Conv2DTranspose->BatchNormalization->ReLU。
# 对生成器所做的另一个更改是将内核大小从5更新为4,以避免减少生成图像中的棋盘伪影(见图2)。
# 根据后反卷积和棋盘伪像(Deconvolution and Checkerboard Artifacts),5的内核大小不能被2的步长整除。因此,解决方案是使用4而不是5的内核大小
# generator.summary(),在代码中可视化生成器体系结构
def build_generator():
    # 使用Keras Sequential API创建模型
    model = Sequential(name='generator')

    # prepare for reshape: FC => BN => RN layers, note: input shape defined in the 1st Dense layer
    model.add(layers.Dense(8 * 8 * 512, input_dim=LATENT_DIM))
    # model.add(layers.BatchNormalization())
    model.add(layers.ReLU())
    # layers.LeakyReLU(alpha=0.2)

    # reshape 1D => 3D
    model.add(layers.Reshape((8, 8, 512)))

    # upsample to 16x16: apply a transposed CONV => BN => RELU
    model.add(layers.Conv2DTranspose(256, (4, 4), strides=(2, 2), padding="same", kernel_initializer=WEIGHT_INIT))
    # model.add(layers.BatchNormalization())
    model.add((layers.ReLU()))

    # upsample to 32x32: apply a transposed CONV => BN => RELU
    model.add(layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same", kernel_initializer=WEIGHT_INIT))
    # model.add(layers.BatchNormalization())
    model.add((layers.ReLU()))

    # upsample to 64x64: apply a transposed CONV => BN => RELU
    model.add(layers.Conv2DTranspose(64, (4, 4), strides=(2, 2), padding="same", kernel_initializer=WEIGHT_INIT))
    # model.add(layers.BatchNormalization())
    model.add((layers.ReLU()))

    # final layer: Conv2D with tanh activation
    model.add(layers.Conv2D(CHANNELS, (4, 4), padding="same", activation="tanh"))

    # return the generator model
    return model


# 3. 鉴别器
# 鉴别器架构的主要变化是图像输入形状:使用[64,64,3]的形状而不是[28,28,1]。还添加了一组Conv2D->BatchNormalization->LeakyReLU,以平衡生成器中增加的架构复杂性,如上所述。其他一切都保持不变。
# discriminator.summary(),在代码中可视化鉴别器DCGAN架构
def build_discriminator(width, height, depth, alpha=0.2):
    # 使用Keras Sequential API创建模型
    model = Sequential(name='discriminator')
    input_shape = (height, width, depth)

    # We use Conv2D, BatchNormalization, and LeakyReLU twice to downsample.
    # 使用Conv2D, BatchNormalization, and LeakyReLU 2次以进行下采样
    # first set of CONV => BN => leaky ReLU layers
    model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding="same", input_shape=input_shape))
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU(alpha=alpha))

    # second set of CONV => BN => leacy ReLU layers
    model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding="same"))
    model.add(layers.BatchNormalization())
    model.add(layers.LeakyReLU(alpha=alpha))

    # Flatten and apply dropout 展平以及应用dropout
    model.add(layers.Flatten())
    model.add(layers.Dropout(0.3))

    # 最后一层使用 sigmoid激活函数一输出二进制分类(binary classification)的结果
    model.add(layers.Dense(1, activation="sigmoid"))

    return model


# 4. DCGAN模型
# 通过子类keras.model定义DCGAN模型架构,并覆盖train_step来定义自定义训练循环。代码中唯一的细微变化是对实际标签应用单侧标签平滑。
# real_labels = tf.ones((batch_size, 1))
# real_labels += 0.05 * tf.random.uniform(tf.shape(real_labels))
# 该技术减少了鉴别器的过度自信,因此有助于稳定GAN训练。
# 有关标签平滑的详细信息,请参阅Adrian Rosebrock的文章《使用Keras、TensorFlow和深度学习进行标签平滑》:https://pyimagesearch.com/2019/12/30/label-smoothing-with-keras-tensorflow-and-deep-learning/。
# 在《训练GANs的改进技术》一文中提出了用于规范GANs训练的“单侧标签平滑”技术,在https://arxiv.org/abs/1606.03498还可以找到其他改进技术。
class DCGAN(keras.Model):
    def __init__(self, discriminator, generator, latent_dim):
        super(DCGAN, self).__init__()
        self.discriminator = discriminator
        self.generator = generator
        self.latent_dim = latent_dim

    def compile(self, d_optimizer, g_optimizer, loss_fn):
        super(DCGAN, self).compile()
        self.d_optimizer = d_optimizer
        self.g_optimizer = g_optimizer
        self.loss_fn = loss_fn
        self.d_loss_metric = keras.metrics.Mean(name="d_loss")
        self.g_loss_metric = keras.metrics.Mean(name="g_loss")

    @property
    def metrics(self):
        return [self.d_loss_metric, self.g_loss_metric]

    # DCGAN模型:覆盖train_step
    # 已经定义了生成器和鉴别器架构,并了解了损失函数是如何工作的。准备好将D和G放在一起,通过子类化keras.model并重写train_step()来训练鉴别器和生成器,从而创建DCGAN模型。
    # 以下是关于如何编写低级别代码以自定义model.fit()的文档。这种方法的优点是仍然可以使用GradientTape进行自定义训练循环,同时仍然可以受益于fit()的方便功能(例如,回调和内置分发支持等)。
    # 因此对keras.Model进行子类化,以创建DCGAN类–类DCGAN(keras.MModel)
    def train_step(self, real_images):
        batch_size = tf.shape(real_images)[0]
        noise = tf.random.normal(shape=(batch_size, self.latent_dim))

        # 用真实图像(标记为1)和伪图像(标记为0)来训练鉴别器
        with tf.GradientTape() as tape:
            # 在真实图像上计算鉴别器损失
            pred_real = self.discriminator(real_images, training=True)
            d_loss_real = self.loss_fn(tf.ones((batch_size, 1)), pred_real)

            # 在伪图像上计算鉴别器损失
            fake_images = self.generator(noise)
            pred_fake = self.discriminator(fake_images, training=True)
            d_loss_fake = self.loss_fn(tf.zeros((batch_size, 1)), pred_fake)

            # 总的鉴别器损失
            d_loss = (d_loss_real + d_loss_fake) / 2

        # 计算鉴别器梯度gradients
        grads = tape.gradient(d_loss, self.discriminator.trainable_variables)
        # 更新鉴别器权重
        self.d_optimizer.apply_gradients(zip(grads, self.discriminator.trainable_variables))

        # 不更新鉴别器权重的情况下训练生成器
        misleading_labels = tf.ones((batch_size, 1))
        with tf.GradientTape() as tape:
            fake_images = self.generator(noise, training=True)
            pred_fake = self.discriminator(fake_images, training=True)
            g_loss = self.loss_fn(misleading_labels, pred_fake)
        # 计算生成器梯度
        grads = tape.gradient(g_loss, self.generator.trainable_variables)
        # 更新生成器权重
        self.g_optimizer.apply_gradients(zip(grads, self.generator.trainable_variables))

        self.d_loss_metric.update_state(d_loss)
        self.g_loss_metric.update_state(g_loss)
        return {
            "d_loss": self.d_loss_metric.result(),
            "g_loss": self.g_loss_metric.result(),
        }


# 损失函数:修改后的极大极小损失
# 在创建DCGAN模型之前,先讨论一下损失函数。计算损失是DCGAN(或任何GAN)训练的核心。对于DCGAN将实现修改的极大极小损失,它使用二进制交叉熵(BCE)损失函数。随着在GAN系列中的进展将了解不同GAN变体中的其他损失函数。
# 需要计算两个损失:一个用于鉴别器损失,另一个用于生成器损失。
# 鉴别器损失
# 由于有两组图像被输入鉴别器(真实图像和伪图像),将计算每组图像的损失,并将它们组合作为鉴别器损失。
# total_D_loss = loss_from_real_images + loss_from_fake_images
# 生成器损失
# 对于生成器损失可以训练G来最大化log D(G(z)),而不是训练G来最小化log(1−D(G))。这就是修正后的极小极大损失。
# 为训练监控定义Kera回调
# 相同的代码没有更改-覆盖Keras回调,以在训练期间监控和可视化生成的图像。
class GANMonitor(keras.callbacks.Callback):
    def __init__(self, num_img=3, latent_dim=128):
        self.num_img = num_img
        self.latent_dim = latent_dim

        # 创建训练可视化的随机噪音种子
        self.seed = tf.random.normal([16, latent_dim])

    def on_epoch_end(self, epoch, logs=None):
        generated_images = self.model.generator(self.seed)
        generated_images = (generated_images * 127.5) + 127.5
        generated_images.numpy()

        fig = plt.figure(figsize=(4, 4))
        for i in range(self.num_img):
            plt.subplot(4, 4, i+1)
            img = keras.utils.array_to_img(generated_images[i])
            plt.imshow(img)
            plt.axis('off')
        plt.savefig('color_images/epoch_{:03d}.png'.format(epoch))
        plt.show()

    def on_train_end(self, logs=None):
        self.model.generator.save('generator.h5')


# 构建生成器
generator = build_generator()
print(generator.summary())
# 构建鉴定器
discriminator = build_discriminator(64, 64, 3)
print(discriminator.summary())



# 编译模型,使用Adam优化器,鉴别器学习率0.0001,生成器0.0003 二进制交叉熵损失函数(Binary Cross Entropy)
dcgan = DCGAN(discriminator=discriminator, generator=generator, latent_dim=LATENT_DIM)

D_LR = 0.0001 # UPDATED: discriminator learning rate
G_LR = 0.0003 # UPDATED: generator learning rate

dcgan.compile(
    d_optimizer=keras.optimizers.Adam(learning_rate=D_LR, beta_1 = 0.5),
    g_optimizer=keras.optimizers.Adam(learning_rate=G_LR, beta_1 = 0.5),
    loss_fn=keras.losses.BinaryCrossentropy(),
)

# 只需调用model.fit()来训练dcgan模型
NUM_EPOCHS = 50 # number of epochs
dcgan.fit(train_images, epochs=NUM_EPOCHS, callbacks=[GANMonitor(num_img=16, latent_dim=LATENT_DIM)])

参考

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。