GAN生成对抗网络基本概念及基于mnist数据集的代码实现

本文主要总结了GAN(Generative Adversarial Networks) 生成对抗网络的基本原理并通过mnist数据集展示GAN网络的应用。

GAN网络是由两个目标相对立的网络构成的,在所有GAN框架中都至少包含了两个部分,生成模型部分和判别模型部分。生成模型的目标是制造出一些与真实数据十分相似的伪造数据而判别模型的目标则恰恰相反,是找到如何分辨这些真实的数据以及伪造数据。

下图可以用来比较简明地理解GAN的工作原理 :
在这里插入图片描述
生成模型的输入是随机的噪声编码 z z z,通过这个噪声生成的数据 G ( z ) G(z) G(z) 就是我们伪造出的数据了。判别模型的输入是一组混合了真实数据 x x x以及伪造的数据 G ( z ) G(z) G(z)的混合数据并输出 D ( G ( z ) ) D(G(z)) D(G(z)) 以及 D ( x ) D(x) D(x),代表了对真实数据和伪造数据的判定。如果我们把伪造数据的标签定为0,真实数据的标签定为1,那么判别模型的训练目标就是使 D ( G ( z ) ) D(G(z)) D(G(z))无限接近0,使 D ( x ) D(x) D(x)无限接近1,以此来达到分辨真实数据和伪造数据的目的。相反的,生成模型的训练目标则是要使得 D ( G ( z ) ) D(G(z)) D(G(z))接近1,即达到欺骗判别模型,以假乱真的目的。我们不难发现,实际生成模型的训练离不开判别模型的判定,而判别模型的训练也需要生成模型生成的伪造数据,二者相辅相成。这一点在下面基于mnist数据集的训练代码中也会有所体现。

首先是import所需库并导入mnist数据,我们通过全部除以255的方法正则化用于训练和测试的图像。

import tensorflow as tf
from tensorflow import keras
from matplotlib import pyplot as plt
from keras.layers import Dense, Conv2DTranspose, BatchNormalization, Reshape, LeakyReLU, Conv2D
import numpy as np

# load data from database mnist
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data(path="mnist.npz")

# normaliser à [0,1]
x_train = x_train/255.0
x_test = x_test/255.0

训练集x_train中包含了60000张 28*28的单通道灰度图片,每张图片对应标签为0-9的十个数字,下面展示其中一张代表了数字5的图片。
在这里插入图片描述

1. 生成模型部分

首先是我们的生成模型,如上所说,生成模型的输入为随机噪声,输出为伪造的数据。在这个例子中,我们最终要输出一张与真实图片大小一致的灰度图。生成模型由两类主要的层构成,其中之一就是全连接层dense,这个层实际上类似于CNN卷积神经网络中代表特征 feature 的一层,我们可以理解为它由数个低解析度的图像组成。之后需要的就是将解析度提升至的操作,这里用了Conv2DTranspose层,可以理解为是一个反向的pooling池化层(用于还原参数和数据)和一个2D卷积层的结合。Conv2DTranspose层中的参数stride设置为(2, 2) 即保证了每经过一次该层,输出的宽度和高度都扩大一倍。如下例所示,由7 * 7 经过两次Conv2DTranspose层使得最终输出的灰度图宽度和高度为 28 * 28 。生成模型的输出层是一个简单的2D卷积层,使用activation激励函数为sigmoid,这是由于sigmoid函数可以使得输出值属于[0, 1]的区间,也对应了我们在一开始在数据预处理的时候,将数据正则化至[0, 1]的操作。

# creation of a generator
def creation_generateur(dim_latent=10):
  generator = keras.models.Sequential()
  generator.add(Dense(128*7*7, input_dim=dim_latent))
  generator.add(Reshape((7,7,128)))
  # upsampling 
  generator.add(Conv2DTranspose(filters=128,kernel_size=(5,5),strides=(2,2),padding="same"))
  generator.add(LeakyReLU(alpha=0.2))
  # upsampling 
  generator.add(Conv2DTranspose(filters=128,kernel_size=(5,5),strides=(2,2),padding="same"))
  generator.add(LeakyReLU(alpha=0.2))
  generator.add(Conv2D(1, kernel_size=(7, 7), activation='sigmoid', padding="same"))
  return generator

2. 判别模型部分

接着我们创建GAN中的判别模型,相比生成模型而言,判别模型就更加简明,其实质就是一个classifier二元分类器。他由多个卷积层构成,其中添加了drop out用于防止过拟合。输出层是一个仅有一个神经元的全连接层,使用sigmoid作为激励函数。正如我们前文所提到的,判别模型会对输入进行分类,判别输入究竟是真实图像还是由生成模型伪造的图像。

# creation of a discriminator
def creation_discriminateur():
  discriminator = keras.models.Sequential()
  discriminator.add(Conv2D(filters=64, kernel_size=(5,5),strides=(2,2), input_shape=(28,28,1), padding="same")) 
  discriminator.add(LeakyReLU(alpha=0.2))
  discriminator.add(keras.layers.Dropout(0.4))
  discriminator.add(Conv2D(filters=64, kernel_size=(3,3),strides=(2,2), padding="same"))
  discriminator.add(LeakyReLU(alpha=0.2))
  discriminator.add(keras.layers.Dropout(0.4))
  discriminator.add(keras.layers.Flatten())
  discriminator.add(keras.layers.Dense(1,activation='sigmoid'))
  return discriminator

3. 叠加模型(用于训练生成模型)

有了上述两部分代码,接下来我们可以构建基于生成模型部分以及判别模型部分的GAN神经网络。这里我们只是将两部分叠加起来,而并非构建第三个神经网络。这里构建GAN的方式与之后训练GAN是有关系的。如下代码所示,我们将生成模型与判别模型叠加起来,并让判别模型中的参数在该模型中不可训练。其实质是因为这个叠加模型GAN是用于训练生成模型的。整体的过程如下 : 输入是一组随机噪声,经过生成模型后变成了一组伪造的数字灰度图,再经过判别模型,输出一个0-1之间的值。这是梯度的正向传播过程。接着我们利用反向梯度传播来更新我们生成模型的各个权重,以此来达到使该叠加模型输出趋向于1。 这里其实就是GAN模型训练的重中之重,即生成模型的目标与判别模型相反,其目标为生成的伪造数据能更大概率被识别为是真实数据,即标签1。这一点在下一个部分GAN模型的训练中会更详细解释。

def creation_reseau_GAN(model_generateur, model_discriminateur):
  GAN = keras.models.Sequential()
  GAN.add(model_generateur)
  # 判别模型中的参数设置为不可训练
  discriminateur.trainable = False
  GAN.add(model_discriminateur)
  optimizer_GAN = keras.optimizers.Adam(learning_rate=0.0002, beta_1=0.5)
  GAN.compile(loss='binary_crossentropy', optimizer=optimizer_GAN)
  return GAN

4. GAN模型的训练过程

根据上述三个部分,我们创建完毕了所要用到的神经网络。在这个部分中我们就进入GAN模型的训练过程。

首先我们要给予生成模型一组随机噪声,以便其根据这个噪声创造不同的伪造数据,该噪声的意义在于避免所有的生成数据都是一样的。可以用高斯噪声或正态分布生成噪声,不同的噪声类型选取对模型训练的影响不大。

# 随机选取潜在编码(噪声)
def code_latent_aleatoire(dim_latent, nb_exemples):
  # distribution normal standard normal distribution
  # code_latent = np.random.normal(loc=0, scale=1, size=(nb_exemples, dim_latent))
  # code_latent = code_latent.reshape(nb_exemples, dim_latent)
  code_latent = np.random.randn(nb_exemples, dim_latent)
  return code_latent

为了判别模型的训练,我们同时需要真实的数据和伪造的数据,因此我们定义函数用于根据现有的生成模型和随机噪声来生成伪造图片,伪造图片的标签为0。同样的,定义另一个函数,用于在mnist数据中随机选取真实图片,真实图片的标签为1。

def construction_image_generateur(generateur_model, dim_latent, nb_exemples):
  # 生成随机噪声
  X = code_latent_aleatoire(dim_latent,nb_exemples)
  image_fraud = generateur_model.predict(X)
  # 生成的图片的真实标签为0
  Label_genere = np.zeros((nb_exemples,1))
  return image_fraud, Label_genere
  
def tirage_reelle_aleatoire(base_de_donnees, nb_exemples):
  mat_images_reel = np.zeros((nb_exemples, 28, 28))
  for i in range(nb_exemples):
    index = np.random.randint(0, base_de_donnees.shape[0])
    mat_images_reel[i,:,:] = base_de_donnees[index,:,:]
  mat_images_reel = mat_images_reel.reshape(nb_exemples, 28, 28, 1)
  # 从mnist数据集选取的图片的标签均为1
  labels_reel = np.ones((nb_exemples, 1))
  return mat_images_reel, labels_reel

接下来就是重头戏,GAN网络的训练过程了。要训练一个gan模型,我们常用的方法是利用train_on_batch函数来训练。在每个batch中,首先训练判别模型,我们先构建一组由真实数据和伪造数据组成的数据集,训练判别模型使其能更准确分辨哪些数据是真实的而哪些数据是伪造的。紧接着在同一个batch中,训练叠加模型,通过随机噪声进入生成模型去生成伪造的图片,并给予他们一个假的标签,即所有的伪造图片我们都标为1,以此来使得生成模型向着使判别模型判定伪造图片为真的方向进行。

# GAN 模型的训练
def entrainement_GAN_model(model_generateur, model_discriminateur, base_de_donnees, dim_latent, GAN_model, epochs=20, batch_size=128):
  vector_loss_discriminateur = []
  vector_loss_gan = []
  # 通过数据集的大小以及batchsize计算每个epoch对应的batch数量
  total_batch = np.floor(base_de_donnees.shape[0]/batch_size).astype(np.int)
  # 对于每个epoch
  for nb_epoch in range(epochs):
    # 对于每个batch
    for index_batch in range(total_batch):
      # 每个batch中有一半的图像为伪造图片
      x_fraud, y_fraud = construction_image_generateur(model_generateur, dim_latent=dim_latent, nb_exemples=int(batch_size/2))
      # 剩下的图像为真实的从mnist数据集中随机提取的图片
      x_reel, y_reel = tirage_reelle_aleatoire(base_de_donnees=base_de_donnees, nb_exemples=int(batch_size/2))
      # 叠加所有的数据 id_exemple, 28, 28
      x_chaque_batch = np.vstack((x_reel, x_fraud))
      # 叠加所有的标签 : id_exemple, label
      y_chaque_batch = np.vstack((y_reel, y_fraud))
      # 使用构建的一半真实一半伪造的数据训练判别模型,更新权重。
      loss_discriminateur = model_discriminateur.train_on_batch(x_chaque_batch, y_chaque_batch)
      # 接下来是每个Batch训练叠加模型的部分,其实质是用于训练生成模型。
      x_generateur = code_latent_aleatoire(dim_latent=dim_latent, nb_exemples=batch_size)
      # 这里我们要使得叠加模型的输出趋向于1,即使判别模型认为生成的图像也是真实的。
      y_gan = np.ones((batch_size,1))
      # 使用判别模型(叠加模型)的输出来训练生成模型
      gan_loss = GAN_model.train_on_batch(x_generateur, y_gan)
      # 显示每个batch对应的两个部分的loss
      print("%d epochs,%d batches, loss_discriminateur : %f, loss_gan : %f"%(nb_epoch+1, index_batch+1, loss_discriminateur, gan_loss))
    # 保存模型以及图片部分
    if (nb_epoch + 1)%3 == 0:
      model_generateur.save("generateur_models/generateur_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(gan_loss) + ".h5")
      model_discriminateur.save("discriminateur_models/discriminateur_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(loss_discriminateur) + ".h5") 
      GAN_model.save("gan_models/gan_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(gan_loss) + ".h5")
      generer_sauvegarde_images(model_generateur=model_generateur, after_epoch=nb_epoch)
    vector_loss_discriminateur.append(loss_discriminateur)
    vector_loss_gan.append(gan_loss)
  return vector_loss_discriminateur, vector_loss_gan

5. 附录(完整代码)及相关讨论

该部分中我将放出完整的代码以及结果。

import tensorflow as tf
from tensorflow import keras
from matplotlib import pyplot as plt
from keras.layers import Dense, Conv2DTranspose, BatchNormalization, Reshape, LeakyReLU, Conv2D
import numpy as np

# creation of a generator
def creation_generateur(dim_latent=10):
  generator = keras.models.Sequential()
  generator.add(Dense(128*7*7, input_dim=dim_latent))
  generator.add(Reshape((7,7,128)))
  # upsampling 
  generator.add(Conv2DTranspose(filters=128,kernel_size=(5,5),strides=(2,2),padding="same"))
  generator.add(LeakyReLU(alpha=0.2))
  # upsampling 
  generator.add(Conv2DTranspose(filters=128,kernel_size=(5,5),strides=(2,2),padding="same"))
  generator.add(LeakyReLU(alpha=0.2))
  generator.add(Conv2D(1, kernel_size=(7, 7), activation='sigmoid', padding="same"))
  return generator
  
# creation of a discriminator
def creation_discriminateur():
  discriminator = keras.models.Sequential()
  discriminator.add(Conv2D(filters=64, kernel_size=(5,5),strides=(2,2), input_shape=(28,28,1), padding="same")) 
  discriminator.add(LeakyReLU(alpha=0.2))
  discriminator.add(keras.layers.Dropout(0.4))
  discriminator.add(Conv2D(filters=64, kernel_size=(3,3),strides=(2,2), padding="same"))
  discriminator.add(LeakyReLU(alpha=0.2))
  discriminator.add(keras.layers.Dropout(0.4))
  discriminator.add(keras.layers.Flatten())
  discriminator.add(keras.layers.Dense(1,activation='sigmoid'))
  return discriminator
  
def creation_reseau_GAN(model_generateur, model_discriminateur):
  GAN = keras.models.Sequential()
  GAN.add(model_generateur)
  # 判别模型中的参数设置为不可训练
  discriminateur.trainable = False
  GAN.add(model_discriminateur)
  optimizer_GAN = keras.optimizers.Adam(learning_rate=0.0002, beta_1=0.5)
  GAN.compile(loss='binary_crossentropy', optimizer=optimizer_GAN)
  return GAN

# 随机选取潜在编码(噪声)
def code_latent_aleatoire(dim_latent, nb_exemples):
  # distribution normal standard normal distribution
  # code_latent = np.random.normal(loc=0, scale=1, size=(nb_exemples, dim_latent))
  # code_latent = code_latent.reshape(nb_exemples, dim_latent)
  code_latent = np.random.randn(nb_exemples, dim_latent)
  return code_latent

def construction_image_generateur(generateur_model, dim_latent, nb_exemples):
  # 生成随机噪声
  X = code_latent_aleatoire(dim_latent,nb_exemples)
  image_fraud = generateur_model.predict(X)
  # 生成的图片的真实标签为0
  Label_genere = np.zeros((nb_exemples,1))
  return image_fraud, Label_genere
  
def tirage_reelle_aleatoire(base_de_donnees, nb_exemples):
  mat_images_reel = np.zeros((nb_exemples, 28, 28))
  for i in range(nb_exemples):
    index = np.random.randint(0, base_de_donnees.shape[0])
    mat_images_reel[i,:,:] = base_de_donnees[index,:,:]
  mat_images_reel = mat_images_reel.reshape(nb_exemples, 28, 28, 1)
  # 从mnist数据集选取的图片的标签均为1
  labels_reel = np.ones((nb_exemples, 1))
  return mat_images_reel, labels_reel

# 用于保存和显示图片的函数
def generer_sauvegarde_images(model_generateur,after_epoch,nb_images_sqrt=5,dim_latent=100):
  nb_images = nb_images_sqrt * nb_images_sqrt
  images_frauds, _ = construction_image_generateur(model_generateur,dim_latent=dim_latent,nb_exemples=nb_images)
  for i in range(nb_images):
    plt.subplot(nb_images_sqrt, nb_images_sqrt, i+1)
    plt.imshow(images_frauds[i].reshape(28,28))
  filepath = "images_fraudes_genere_%d.png" %(after_epoch+1)
  plt.savefig(filepath)
  plt.close()

# GAN 模型的训练
def entrainement_GAN_model(model_generateur, model_discriminateur, base_de_donnees, dim_latent, GAN_model, epochs=20, batch_size=128):
  vector_loss_discriminateur = []
  vector_loss_gan = []
  # 通过数据集的大小以及batchsize计算每个epoch对应的batch数量
  total_batch = np.floor(base_de_donnees.shape[0]/batch_size).astype(np.int)
  # 对于每个epoch
  for nb_epoch in range(epochs):
    # 对于每个batch
    for index_batch in range(total_batch):
      # 每个batch中有一半的图像为伪造图片
      x_fraud, y_fraud = construction_image_generateur(model_generateur, dim_latent=dim_latent, nb_exemples=int(batch_size/2))
      # 剩下的图像为真实的从mnist数据集中随机提取的图片
      x_reel, y_reel = tirage_reelle_aleatoire(base_de_donnees=base_de_donnees, nb_exemples=int(batch_size/2))
      # 叠加所有的数据 id_exemple, 28, 28
      x_chaque_batch = np.vstack((x_reel, x_fraud))
      # 叠加所有的标签 : id_exemple, label
      y_chaque_batch = np.vstack((y_reel, y_fraud))
      # 使用构建的一半真实一半伪造的数据训练判别模型,更新权重。
      loss_discriminateur = model_discriminateur.train_on_batch(x_chaque_batch, y_chaque_batch)
      # 接下来是每个Batch训练叠加模型的部分,其实质是用于训练生成模型。
      x_generateur = code_latent_aleatoire(dim_latent=dim_latent, nb_exemples=batch_size)
      # 这里我们要使得叠加模型的输出趋向于1,即使判别模型认为生成的图像也是真实的。
      y_gan = np.ones((batch_size,1))
      # 使用判别模型(叠加模型)的输出来训练生成模型
      gan_loss = GAN_model.train_on_batch(x_generateur, y_gan)
      # 显示每个batch对应的两个部分的loss
      print("%d epochs,%d batches, loss_discriminateur : %f, loss_gan : %f"%(nb_epoch+1, index_batch+1, loss_discriminateur, gan_loss))
    # 保存模型以及图片部分
    if (nb_epoch + 1)%3 == 0:
      model_generateur.save("generateur_models/generateur_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(gan_loss) + ".h5")
      model_discriminateur.save("discriminateur_models/discriminateur_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(loss_discriminateur) + ".h5") 
      GAN_model.save("gan_models/gan_after_" + str(nb_epoch+1) + "_epoch_" + "loss_%3f"%(gan_loss) + ".h5")
      generer_sauvegarde_images(model_generateur=model_generateur, after_epoch=nb_epoch)
    vector_loss_discriminateur.append(loss_discriminateur)
    vector_loss_gan.append(gan_loss)
  return vector_loss_discriminateur, vector_loss_gan

# load data from database mnist
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data(path="mnist.npz")
# normaliser à [0,1]
x_train = x_train/255.0
x_test = x_test/255.0
dim_code_latent = 100 
generateur = creation_generateur(dim_latent=dim_code_latent)
discriminateur = creation_discriminateur()
opt_discriminateur = keras.optimizers.Adam(learning_rate=0.0002, beta_1=0.5)
discriminateur.compile(loss=keras.losses.binary_crossentropy, optimizer=opt_discriminateur)
GAN = creation_reseau_GAN(generateur, discriminateur)
loss_discriminateur, loss_gan = entrainement_GAN_model(model_generateur=generateur, model_discriminateur=discriminateur,base_de_donnees=x_train, GAN_model=GAN, epochs=21, batch_size=256, dim_latent=dim_code_latent)

训练过后的生成模型就可以用来生成伪造图片,下图为经过9个epoch的训练后,所生成的伪造图片,我们可以发现生成的图像中已经有可以辨认出的数字,例如5,7,9。有理由认为我们在经过更多的epoch训练后,生成模型的细节将进一步完善。
在这里插入图片描述

小tips : 在训练GAN模型时,要时刻关注判别模型的loss以及accuracy。如果判别模型的loss下降的太快,这就意味着生成模型正在给我们生成一些垃圾数据,这些数据轻易地就被判别模型给判定为伪造了。一般而言,维持判别模型的loss在一定范围内会对整个GAN模型的训练有帮助。更进一步的说,一个完美的判别模型,即可以完美分辨真伪图片的判别模型不能给予生成模型足够的信息来产生合理的伪造图片,因此在训练的过程中时刻保持判别模型的非完美性是有必要的,这也增加了在处理更复杂问题时,GAN模型在调参方面的困难程度。

相关推荐
©️2020 CSDN 皮肤主题: 点我我会动 设计师:白松林 返回首页