python深度学习---生成式对抗网络(GAN)

 生成式对抗网络(GAN,generative adversarial network)由 Goodfellow 等人于 2014 年提出,它可以替代VAE来学习图像的潜在空间。 '''

python深度学习---生成式对抗网络(GAN)_第1张图片

# 它能够迫使生成图像与真实图像在统计上几乎无法区分,从而生成相当逼真的合成图像。
#GAN工作原理:一个伪造者网络和一个专家网络,二者训练的目的都是为了打败彼此
    因此,GAN由以下两部分组成。
    生成器网络(generator network):它以一个随机向量(潜在空间中的一个随机点)作为输入,并将其解码为一张合成图像。
    判别器网络(discriminator network)或对手(adversary):以一张图像(真实的或合成的均可)作为输入,并预测该图像是来自训练集还是由生成器网络创建。

    训练生成器网络的目的是使其能够欺骗判别器网络,因此随着训练的进行,它能够逐渐生 成越来越逼真的图像,即看起来与真实图像无法区分的人造图像,以至于判别器网络无法区分二者;
    与此同时,判别器也在不断适应生成器逐渐提高的能力,为生成图像的真实性设置了很高的标准。一旦训练结束,生成器就能够将其输入空间中的任何点转换为一张可信图像
    通常来说,梯度下降是沿着静态的损失地形滚下山坡。但对于GAN而言,每下山一步, 都会对整个地形造成一点改变。它是一个动态的系统,其最优化过程寻找的不是一个最小值, 而是两股力量之间的平衡。
    因此,GAN的训练极其困难,想要让GAN正常运行,需要对模型架构和训练参数进行大量的仔细调整
'''

深度卷积生成式对抗网络(DCGAN,deep convolutional GAN),即生成器和判别器都是深度卷积神经网络的GAN。特别地,它在生成器中使用Conv2DTranspose 层进行图像上采样。

    GAN的简要实现流程如下所示。
       (1) generator网络将形状为(latent_dim,)的向量映射到形状为(32, 32, 3)的图像。
       (2) discriminator 网络将形状为(32, 32, 3)的图像映射到一个二进制分数,用于评估图像为真的概率。
       (3) gan网络将generator网络和discriminator网络连接在一起:gan(x) = discriminator (generator(x))。生成器将潜在空间向量解码为图像,判别器对这些图像的真实性进行评估,因此这个 gan 网络是将这些潜在向量映射到判别器的评估结果。
       (4) 我们使用带有“真”/“假”标签的真假图像样本来训练判别器,就和训练普通的图像分类模型一样。
       (5) 为了训练生成器,我们要使用 gan 模型的损失相对于生成器权重的梯度。这意味着, 在每一步都要移动生成器的权重,其移动方向是让判别器更有可能将生成器解码的图像划分为“真”。换句话说,我们训练生成器来欺骗判别器。

    大量技巧:
        1.我们使用tanh 作为生成器最后一层的激活,而不用 sigmoid,后者在其他类型的模型中更加常见
        2.我们使用正态分布(高斯分布)对潜在空间中的点进行采样,而不用均匀分布。
        3.随机性能够提高稳健性。训练GAN得到的是一个动态平衡,所以GAN可能以各种方式“卡住”。在训练过程中引入随机性有助于防止出现这种情况。我们通过两种方式引入随机性: 一种是在判别器中使用dropout,另一种是向判别器的标签添加随机噪声
        4.稀疏的梯度会妨碍GAN的训练。在深度学习中,稀疏性通常是我们需要的属性,但在GAN中并非如此。有两件事情可能导致梯度稀疏:最大池化运算和 ReLU 激活。我们推荐使用步进卷积代替最大池化来进行下采样,还推荐使用 LeakyReLU 层来代替 ReLU 激活。LeakyReLU 和 ReLU类似,但它允许较小的负数激活值,从而放宽了稀疏性限制。
        5.在生成的图像中,经常会见到棋盘状伪影,这是由生成器中像素空间的不均匀覆盖导致的(见图 8-17)。为了解决这个问题,每当在生成器和判别器中都使用步进的 Conv2DTranpose 或 Conv2D 时,使用的内核大小要能够被步幅大小整除。
'''

python深度学习---生成式对抗网络(GAN)_第2张图片

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pylab
from pandas import DataFrame, Series
from keras import models, layers, optimizers, losses, metrics
from keras.utils.np_utils import to_categorical

plt.rcParams['font.sans-serif'] = ['SimHei']  #指定默认字体
plt.rcParams['axes.unicode_minus'] = False  #解决保存图像是负号'-'显示为方块的问题


#GAN生成器网络
#generator 模型,它将一个向量(来自潜在空间,训练过程中对其随机 采样)转换为一张候选图像。GAN常见的诸多问题之一,就是生成器“卡在”看似噪声的生成图像上。一种可行的解决方案是在判别器和生成器中都使用 dropout。
import keras

latent_dim=32
height=32
width=32
channels=3

generator_input=keras.Input(shape=(latent_dim,))
x=layers.Dense(128*16*16)(generator_input)
x=layers.LeakyReLU()(x)
x=layers.Reshape((16,16,128))(x)#将输入转换为大小为 16×16 的 128 个通道的特征图

x=layers.Conv2D(256,5,padding='same')(x)
x=layers.LeakyReLU()(x)

x=layers.Conv2DTranspose(256,4,strides=2,padding='same')(x)#上采样为32*32
x=layers.LeakyReLU()(x)

x=layers.Conv2D(256,5,padding='same')(x)
x=layers.LeakyReLU()(x)
x=layers.Conv2D(256,5,padding='same')(x)
x=layers.LeakyReLU()(x)

x=layers.Conv2D(channels,7,activation='tanh',padding='same')(x)#生成一个大小为 32×32 的单通道特征图 (即 CIFAR10 图像的形状)
generator=keras.models.Model(generator_input,x)
generator.summary()


#GAN判别器网络
#discriminator模型,它接收一张候选图像(真实的或合成的)作为输入,并将其划分到这两个类别之一:“生成图像”或“来自训练集的真实图像”

discrimination_input=layers.Input(shape=(height,width,channels))#判别器输入为生成图像与真实图像的拼接,以判断图像的‘真假’
x=layers.Conv2D(128,3)(discrimination_input)
x=layers.LeakyReLU()(x)
x=layers.Conv2D(128,4,strides=2)(x)#卷积窗口4*4,步幅为2
x=layers.LeakyReLU()(x)
x=layers.Conv2D(128,4,strides=2)(x)
x=layers.LeakyReLU()(x)
x=layers.Conv2D(128,4,strides=2)(x)
x=layers.LeakyReLU()(x)
x=layers.Flatten()(x)
x=layers.Dropout(0.4)(x)
x=layers.Dense(1,activation='sigmoid')(x)#分类层(真或假)
discriminator=keras.models.Model(discrimination_input,x)#将判别器模型实例化,这里它将形状为 (32, 32, 3)的输入转换为一个二进制分类决策(真/假)
discriminator.summary()
discriminator_optimizer=optimizers.RMSprop(
    lr=0.0008,
    clipvalue=1.0,#优化器中使用梯度裁剪(限制梯度的范围)[它是一个动态的系统,其最优化过程寻找的不是一个最小值,而是两股力量之间的平衡。]
    decay=1e-8#为了稳定训练过程,使用学习率衰减
)
discriminator.compile(optimizer=discriminator_optimizer,loss='binary_crossentropy')


#对抗网络
'''
    最后,我们要设置GAN,将生成器和判别器连接在一起。
    训练时,这个模型将让生成器向某个方向移动,从而提高它欺骗判别器的能力。这个模型将潜在空间的点转换为一个分类决策(即“真”或“假”),它训练的标签都是“真实图像”。
    因此,训练 gan 将会更新 generator 的权重,使得 discriminator 在观察假图像时更有可能预测为“真”。
    请注意,有一点很重要,就是在训练过程中需要将判别器设置为冻结(即不可训练),这样在训练 gan 时它的权重才不会更新。
    如果在此过程中可以对判别器的权重进行更新,那么我们就是在训练判别器始终预测“真”,但这并不是我们想要的!
'''
discriminator.trainable=False#将判别器权重设置为不可训练 (仅应用于 gan 模型)

gan_input=keras.Input(shape=(latent_dim,))
gan_output=discriminator(generator(gan_input))
gan=keras.models.Model(gan_input,gan_output)
gan_optimizer = keras.optimizers.RMSprop(
    lr=0.0004,
    clipvalue=1.0,
    decay=1e-8
)
gan.compile(optimizer=gan_optimizer, loss='binary_crossentropy')

#训练DCGAN
'''
每轮都进行以下操作:
    (1) 从潜在空间中抽取随机的点(随机噪声)。
    (2) 利用这个随机噪声用 generator 生成图像。
    (3) 将生成图像与真实图像混合。
    (4) 使用这些混合后的图像以及相应的标签(真实图像为“真”,生成图像为“假”)来训练 discriminator,如图 8-18 所示。
    (5) 在潜在空间中随机抽取新的点。
    (6) 使用这些随机向量以及全部是“真实图像”的标签来训练gan。这会更新生成器的权重(只更新生成器的权重,因为判别器在 gan中被冻结),其更新方向是使得判别器能够将生成图像预测为“真实图像”。这个过程是训练生成器去欺骗判别器。
'''
import os
import keras
from  keras.preprocessing import image

(x_train, y_train), (_, _) = keras.datasets.cifar10.load_data()
x_train = x_train[y_train.flatten() == 6]#选择青蛙图像(类别编号为 6)
print(x_train.shape)#(5000, 32, 32, 3)
x_train = x_train.reshape(
    (x_train.shape[0],) +
    (height, width, channels)).astype('float32') / 255.#数据标准化
iterations=10000
batch_size=20
save_dir='datasets/gan_output'
start=0#记录当前批处理的位置
for step in range(iterations):
    random_latent_vectors=np.random.normal(size=(batch_size,latent_dim))#潜在空间中采样随机点
    generated_images=generator.predict(random_latent_vectors)#利用生成器解码为虚假图像
    stop=start+batch_size
    real_images=x_train[start:stop]#
    combined_images=np.concatenate([generated_images,real_images])#拼接,默认0轴(纵向)
    labels=np.concatenate([np.ones((batch_size,1)),np.zeros((batch_size,1))])#列向量,1表示生成的图像,0表示真实的图像
    labels+=0.05*np.random.random(labels.shape)#向标签中添加随机噪声
    d_loss=discriminator.train_on_batch(combined_images,labels)#返回判别器损失:使用的是二进制交叉熵
    random_latent_vectors=np.random.normal(size=(batch_size,latent_dim))
    misleading_targets=np.zeros((batch_size,1))
    a_loss=gan.train_on_batch(#通过gan模型训练生成器
        random_latent_vectors,
        misleading_targets#冻结判别器权重(置0)
    )
    start+=batch_size
    if start>len(x_train)-batch_size:
        start=0
    if step%100==0:#每100步保存并绘图
        gan.save_weights('gan.h5')#保存模型权重
        print('discriminator loss:', d_loss)
        print('adversarial loss',a_loss)
        img = image.array_to_img(generated_images[0] * 255., scale=False)#转换成图像并保存
        img.save(os.path.join(save_dir, 'generated_frog' + str(step) + '.png'))
        img=image.array_to_img(real_images[0]*255.,scale=False)
        img.save(os.path.join(save_dir,'real_frog'+str(step)+'.png'))

 训练时你可能会看到,对抗损失开始大幅增加,而判别损失则趋向于零,即判别器最终支配了生成器。如果出现了这种情况,你可以尝试减小判别器的学习率,并增大判别器的 dropout 比率。

 

 

你可能感兴趣的:(DeepLearning)