资讯专栏INFORMATION COLUMN

DCGAN(深度卷积对抗网络)案例

derek_334892 / 2981人阅读

摘要:深度卷积对抗生成网络是的变体,是一种将卷积引入模型的网络。特点是判别器使用来替代空间池化,生成器使用反卷积使用稳定学习,有助于处理初始化不良导致的训练问题生成器输出层使用激活函数,其它层使用激活函数。

介绍

如图所示,GAN网络会同时训练两个模型。生成器:负责生成数据(比如:照片);判别器:判别所生成照片的真假。训练过程中,生成器生成的照片会越来越接近真实照片,直到判别器无法区分照片真假。

DCGAN(深度卷积对抗生成网络)是GAN的变体,是一种将卷积引入模型的网络。特点是:

判别器使用strided convolutions来替代空间池化,生成器使用反卷积

使用BN稳定学习,有助于处理初始化不良导致的训练问题

生成器输出层使用Tanh激活函数,其它层使用Relu激活函数。判别器上使用Leaky Relu激活函数。

本次案例我们将使用mnist作为数据集训练DCGAN网络,程序最后将使用GIF的方式展示训练效果。

数据导入
import tensorflow as tf
import glob
import imageio
import matplotlib.pyplot as plt
import numpy as np
import os
import tensorflow.contrib as tcon
import PIL
import time
from IPython import display

# shape:(60000,28,28)
(train_images,train_labels),(_,_)=tf.keras.datasets.mnist.load_data()
# shape:[batch_size,height,width,channel]
train_images_reshape=tf.reshape(train_images,shape=(train_images.shape[0],28,28,1)).astype(tf.float32)
# 缩放图片[-1,1]
train_images_nor=(train_images-127.5)/127.5

dataset加载数据

BUFFER_SIZE=60000
BATCH_SIZE=256

# 优化输入管道需要从:读取,转换,加载三方面考虑。
train_dataset=tf.data.Dataset.from_tensor_slices(train_images).shuffle(buffer_size=BUFFER_SIZE).batch(BATCH_SIZE)
生成模型

该生成模型将使用反卷积层,我们首先创建全连接层然后通过两次上采样将图片分辨率扩充至28x28x1。我们将逐步提升分辨率降低depth,除最后一层使用tanh激活函数,其它层都使用Leaky Relu激活函数。

def make_generator_model():
    # 反卷积,从后往前
    model=tf.keras.Sequential()
    model.add(
        tf.keras.layers.Dense(
            input_dim=7*7*256,
            
            # 不使用bias的原因是我们使用了BN,BN会抵消掉bias的作用。
            # bias的作用:
            # 提升网络拟合能力,而且计算简单(只要一次加法)。
            # 能力的提升源于调整输出的整体分布
            use_bias=False,
            # noise dim
            input_shape=(100,)
        )
    )
    """
    随着神经网络的训练,网络层的输入分布会发生变动,逐渐向激活函数取值两端靠拢,如:sigmoid激活函数,
    此时会进入饱和状态,梯度更新缓慢,对输入变动不敏感,甚至梯度消失导致模型难以训练。
    BN,在网络层输入激活函数输入值之前加入,可以将分布拉到均值为0,标准差为1的正态分布,从而
    使激活函数处于对输入值敏感的区域,从而加快模型训练。此外,BN还能起到类似dropout的正则化作用,由于我们会有
    ‘强拉’操作,所以对初始化要求没有那么高,可以使用较大的学习率。
    """
    model.add(tf.keras.layers.BatchNormalization())
    """
    relu 激活函数在输入为负值的时候,激活值为0,此时神经元无法学习
    leakyrelu 激活函数在输入为负值的时候,激活值不为0(但值很小),神经元可以继续学习
    """
    model.add(tf.keras.layers.LeakyReLU())

    model.add(tf.keras.layers.Reshape(input_shape=(7,7,256)))
    assert model.output_shape == (None,7,7,256)

    model.add(tf.keras.layers.Conv2DTranspose(
        filters=128,
        kernel_size=5,
        strides=1,
        padding="same",
        use_bias="False"
    ))
    assert model.output_shape == (None,7,7,128)
    model.add(tf.keras.layers.BatchNormalization())
    model.add(tf.keras.layers.LeakyReLU())

    # 卷积核为奇数:图像两边可以对称padding 00xxxx00
    model.add(tf.keras.layers.Conv2DTranspose(
        filters=64,
        kernel_size=5,
        strides=2,
        padding="same",
        use_bias="False"
    ))
    assert model.output_shape == (None,14,14,64)
    model.add(tf.keras.layers.BatchNormalization())
    model.add(tf.keras.layers.LeakyReLU())

    model.add(tf.keras.layers.Conv2DTranspose(
        filters=1,
        kernel_size=5,
        strides=2,
        padding="same",
        use_bias="False",
        
        # tanh激活函数值区间[-1,1],均值为0关于原点中心对称。、
        # sigmoid激活函数梯度在反向传播过程中会出全正数或全负数,导致权重更新出现Z型下降。
        activation="tanh"
    ))
    assert model.output_shape == (None,28,28,1)

    return model
判别模型

判别器使用strided convolutions来替代空间池化,比如这里strided=2。卷积层使用LeakyReLU替代Relu,并使用Dropout为全连接层提供加噪声的输入。

def make_discriminator_model():
    # 常规卷积操作
    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Conv2D(64, (5, 5), strides=(2, 2), padding="same"))
    model.add(tf.keras.layers.LeakyReLU())
    
    # dropout常见于全连接层,其实卷积层也是可以使用的。
    # 这里简单翻译下dropout论文观点:
    """
    可能很多人认为因为卷积层参数较少,过拟合发生概率较低,所以dropout作用并不大。
    但是,dropout在前面几层依然有帮助,因为它为后面的全连接层提供了加噪声的输入,从而防止过拟合。
    """
    model.add(tf.keras.layers.Dropout(0.3))
      
    model.add(tf.keras.layers.Conv2D(128, (5, 5), strides=(2, 2), padding="same"))
    model.add(tf.keras.layers.LeakyReLU())
    model.add(tf.keras.layers.Dropout(0.3))
       
    model.add(tf.keras.layers.Flatten())
    model.add(tf.keras.layers.Dense(1))
     
    return model
损失函数

获取模型:

generator = make_generator_model()
discriminator = make_discriminator_model()

生成器损失函数:

损失函数使用sigmoid cross entropylabels使用值全为1的数组。

def generator_loss(generator_output):
    return tf.losses.sigmoid_cross_entropy(
        multi_class_labels=tf.ones_like(generator_output),
        logits=generator_output
    )

判别器损失函数

判别器损失函数接受两种输入,生成器生成的图像和数据集中的真实图像,损失函数计算方法如下:

使用sigmoid cross entropy损失函数计算数据集中真实图像的损失,labels使用值全为1的数组。

使用sigmoid cross entropy损失函数计算生成器图像的损失,labels使用值全为0的数组。

将以上损失相加得到判别器损失。

def discriminator_loss(real_output, generated_output):
    # real:[1,1,...,1] 
    real_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels=tf.ones_like(real_output), logits=real_output)
    #:generated:[0,0,...,0] 
    generated_loss = tf.losses.sigmoid_cross_entropy(multi_class_labels=tf.zeros_like(generated_output), logits=generated_output)
    
    # 总损失为两者相加
    total_loss = real_loss + generated_loss
    return total_loss

模型保存:

# 两种模型同时训练,自然需要使用两种优化器,学习率为:0.0001
generator_optimizer = tf.train.AdamOptimizer(1e-4)
discriminator_optimizer = tf.train.AdamOptimizer(1e-4)
checkpoint_dir = "./training_checkpoints"
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")

# checkpoint配置
checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
                                 discriminator_optimizer=discriminator_optimizer,
                                 generator=generator,
                                 discriminator=discriminator)
模型训练

训练参数配置:

# 数据集迭代次数
EPOCHS = 50
# 生成器噪声维度
noise_dim = 100

# 可视化效果数量设置
num_examples_to_generate = 16
random_vector_for_generation = tf.random_normal([num_examples_to_generate,
                                                 noise_dim])

生成器将我们设定的正态分布的噪声向量作为输入,用来生成图像。判别器将同时显示数据集真实图像和生成器生成的图像用于判别。随后,我们计算生成器和判断器损失函数对参数的梯度,然后使用梯度下降进行更新。

def train_step(images):
      # 正态分布噪声作为生成器输入
      noise = tf.random_normal([BATCH_SIZE, noise_dim])
      
      # tf.GradientTape进行记录
      with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
        generated_images = generator(noise, training=True)
        
        # 判别器中真实图像和生成器的假图像
        real_output = discriminator(images, training=True)
        generated_output = discriminator(generated_images, training=True)
        
        gen_loss = generator_loss(generated_output)
        disc_loss = discriminator_loss(real_output, generated_output)
        
      gradients_of_generator = gen_tape.gradient(gen_loss, generator.variables)
      gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.variables)
      
      generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.variables))
      discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.variables))

开始训练:

加速计算节约内存,但是不可以使用"pdb","print"。

train_step = tf.contrib.eager.defun(train_step)
def train(dataset, epochs):  
  for epoch in range(epochs):
    start = time.time()
    
    # 迭代数据集
    for images in dataset:
      train_step(images)

    display.clear_output(wait=True)
    
    # 保存图像用于后面的可视化
    generate_and_save_images(generator,
                               epoch + 1,
                               random_vector_for_generation)
    
    # 每迭代15次数据集保存一次模型
    # 如需部署至tensorflow serving需要使用savemodel
    if (epoch + 1) % 15 == 0:
      checkpoint.save(file_prefix = checkpoint_prefix)
    
    print ("Time taken for epoch {} is {} sec".format(epoch + 1,
                                                      time.time()-start))
  display.clear_output(wait=True)
  generate_and_save_images(generator,
                           epochs,
                           random_vector_for_generation)

可视化生成器图像:

def generate_and_save_images(model, epoch, test_input):
  # training:False 不训练BN
  predictions = model(test_input, 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] * 127.5 + 127.5, cmap="gray")
      plt.axis("off")
        
  plt.savefig("image_at_epoch_{:04d}.png".format(epoch))
  plt.show()

train(train_dataset, EPOCHS)
可视化模型训练结果

展示照片:

def display_image(epoch_no):
  return PIL.Image.open("image_at_epoch_{:04d}.png".format(epoch_no))

动画展示训练结果:

with imageio.get_writer("dcgan.gif", mode="I") as writer:
  filenames = glob.glob("image*.png")
  filenames = sorted(filenames)
  last = -1
  for i,filename in enumerate(filenames):
    frame = 2*(i**0.5)
    if round(frame) > round(last):
      last = frame
    else:
      continue
    image = imageio.imread(filename)
    writer.append_data(image)
  image = imageio.imread(filename)
  writer.append_data(image)
    
os.system("cp dcgan.gif dcgan.gif.png")
display.Image(filename="dcgan.gif.png")
总结

DCGAN中生成器判别器都使用卷积网络来提升生成和判别能力,其中生成器利用反卷积,判别器利用常规卷积。生成器用随机噪声向量作为输入来生成假图像,判别器通过对真实样本的学习判断生成器图像真伪,如果判断为假,生成器重新调校训练,直到判别器无法区分真实样本图像和生成器的图像。

本文代码部分参考Yash Katariya,在此表示感谢。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/42754.html

相关文章

  • 到底什么是生成式对抗网络GAN?

    摘要:很多人可能会问这个故事和生成式对抗网络有什么关系其实,只要你能理解这段故事,就可以了解生成式对抗网络的工作原理。 男:哎,你看我给你拍的好不好?女:这是什么鬼,你不能学学XXX的构图吗?男:哦……男:这次你看我拍的行不行?女:你看看你的后期,再看看YYY的后期吧,呵呵男:哦……男:这次好点了吧?女:呵呵,我看你这辈子是学不会摄影了……男:这次呢?女:嗯,我拿去当头像了上面这段对话讲述了一位男...

    GitCafe 评论0 收藏0
  • 生成式对抗网络(GAN)如何快速理解?

    摘要:目前,生成对抗网络的大部分应用都是在计算机视觉领域。生成对抗网络生成对抗网络框架是由等人于年设计的生成模型。在设置中,两个由神经网络进行表示的可微函数被锁定在一个游戏中。我们提出了深度卷积生成对抗网络的实现。 让我们假设这样一种情景:你的邻居正在举办一场非常酷的聚会,你非常想去参加。但有要参加聚会的话,你需要一张特价票,而这个票早就已经卖完了。而对于这次聚会的组织者来说,为了让聚会能够成功举...

    Leo_chen 评论0 收藏0
  • 火热的生成对抗网络(GAN),你究竟好在哪里

    摘要:自年提出生成对抗网络的概念后,生成对抗网络变成为了学术界的一个火热的研究热点,更是称之为过去十年间机器学习领域最让人激动的点子。 自2014年Ian Goodfellow提出生成对抗网络(GAN)的概念后,生成对抗网络变成为了学术界的一个火热的研究热点,Yann LeCun更是称之为过去十年间机器学习领域最让人激动的点子。生成对抗网络的简单介绍如下,训练一个生成器(Generator,简称G...

    mist14 评论0 收藏0
  • 谷歌大脑发布GAN全景图:看百家争鸣的生成对抗网络

    摘要:近日,谷歌大脑发布了一篇全面梳理的论文,该研究从损失函数对抗架构正则化归一化和度量方法等几大方向整理生成对抗网络的特性与变体。他们首先定义了全景图损失函数归一化和正则化方案,以及最常用架构的集合。 近日,谷歌大脑发布了一篇全面梳理 GAN 的论文,该研究从损失函数、对抗架构、正则化、归一化和度量方法等几大方向整理生成对抗网络的特性与变体。作者们复现了当前较佳的模型并公平地对比与探索 GAN ...

    asoren 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<