GAN(Generative Adversarial Network) : 生成对抗网络
GAN是一种生成式模型,也是一种无监督学习模型。其最大的特点是为深度网络提供了一种对抗训练的方式,此方式有助于解决一些普通训练方式不容易解决的问题。GAN 主要包括了两个部分,即生成器 generator 与判别器 discriminator。生成器主要用来学习真实图像分布从而让自身生成的图像更加真实,以骗过判别器。判别器则需要对接收的图片进行真假判别。在整个过程中,生成器努力地让生成的图像更加真实,而判别器则努力地去识别出图像的真假,这个过程相当于一个二人博弈,随着时间的推移,生成器和判别器在不断地进行对抗,最终两个网络达到了一个动态均衡:生成器生成的图像接近于真实图像分布,而判别器识别不出真假图像,对于给定图像的预测为真的概率基本接近 0.5(相当于随机猜测类别)。
DCGAN(Deep Convolutional GAN):深度卷积生成对抗网络
生成模型和判别模型都运用了深度卷积 神经网络的生成对抗网络。DCGAN的发明者想到能不能把卷积神经网络反一反,用于生成图像。Generator(生成网络) 利用了类似反卷积的神经网络模型,Discriminator(判别网络) 利用了卷积的神经网络模型。
代码分为以下三块:
1.构建网络(network.py)
import tensorflow as tf
# Hyper parameter(超参数)
EPOCHS = 100
BATCH_SIZE = 128
LEARNING_RATE = 0.0002
BETA_1 = 0.5
# 定义判别器模型
def discriminator_model():
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Conv2D(
64, # 64 个过滤器,输出的深度(depth)是 64
(5, 5), # 过滤器在二维的大小是(5 * 5)
padding='same', # same 表示输出的大小不变,因此需要在外围补零2圈
input_shape=(64, 64, 3) # 输入形状 [64, 64, 3]。3 表示 RGB 三原色
))
model.add(tf.keras.layers.Activation("tanh")) # 添加 Tanh 激活层
model.add(tf.keras.layers.MaxPool2D(pool_size=(2, 2))) # 池化层
model.add(tf.keras.layers.Conv2D(128, (5, 5)))
model.add(tf.keras.layers.Activation("tanh"))
model.add(tf.keras.layers.MaxPool2D(pool_size=(2, 2)))
model.add(tf.keras.layers.Conv2D(128, (5, 5)))
model.add(tf.keras.layers.Activation("tanh"))
model.add(tf.keras.layers.MaxPool2D(pool_size=(2, 2)))
model.add(tf.keras.layers.Flatten()) # 扁平化
model.add(tf.keras.layers.Dense(1024)) # 1024 个神经元的全连接层
model.add(tf.keras.layers.Activation("tanh"))
model.add(tf.keras.layers.Dense(1)) # 1 个神经元的全连接层
model.add(tf.keras.layers.Activation("sigmoid")) # 添加 Sigmoid 激活层
return model
# 定义生成器模型
# 从随机数来生成图片
def generator_model():
model = tf.keras.models.Sequential()
# 输入的维度是 100, 输出维度(神经元个数)是1024 的全连接层
model.add(tf.keras.layers.Dense(input_dim=100, units=1024))
model.add(tf.keras.layers.Activation("tanh"))
model.add(tf.keras.layers.Dense(128 * 8 * 8)) # 8192 个神经元的全连接层
model.add(tf.keras.layers.BatchNormalization()) # 批标准化
model.add(tf.keras.layers.Activation("tanh"))
model.add(tf.keras.layers.Reshape((8, 8, 128), input_shape=(128 * 8 * 8, ))) # 8 x 8 像素
model.add(tf.keras.layers.UpSampling2D(size=(2, 2))) # 16 x 16像素
model.add(tf.keras.layers.Conv2D(128, (5, 5), padding="same"))
model.add(tf.keras.layers.Activation("tanh"))
model.add(tf.keras.layers.UpSampling2D(size=(2, 2))) # 32 x 32像素
model.add(tf.keras.layers.Conv2D(128, (5, 5), padding="same"))
model.add(tf.keras.layers.Activation("tanh"))
model.add(tf.keras.layers.UpSampling2D(size=(2, 2))) # 64 x 64像素
model.add(tf.keras.layers.Conv2D(3, (5, 5), padding="same"))
model.add(tf.keras.layers.Activation("tanh"))
return model
# 构造一个 Sequential 对象,包含一个 生成器 和一个 判别器
# 输入 -> 生成器 -> 判别器 -> 输出
def generator_containing_discriminator(generator, discriminator):
model = tf.keras.models.Sequential()
model.add(generator)
discriminator.trainable = False # 初始时 判别器 不可被训练
model.add(discriminator)
return model
2.训练网络(train.py)
import os
import glob
import numpy as np
from scipy import misc
import tensorflow as tf
from network import *
def train():
# 确保包含所有图片的 images 文件夹在所有 Python 文件的同级目录下
# 当然了,你也可以自定义文件夹名和路径
if not os.path.exists("images"):
raise Exception("包含所有图片的 images 文件夹不在此目录下,请添加")
# 获取训练数据
data = []
for image in glob.glob("images/*"):
image_data = misc.imread(image) # imread 利用 PIL 来读取图片数据
data.append(image_data)
input_data = np.array(data)
# 将数据标准化成 [-1, 1] 的取值, 这也是 Tanh 激活函数的输出范围
# 像素值最大255 减去一半127.5 再除以 127.5 被限制到-1到1之间。
input_data = (input_data.astype(np.float32) - 127.5) / 127.5
# 构造 生成器 和 判别器
g = generator_model()
d = discriminator_model()
# 构建 生成器 和 判别器 组成的网络模型
d_on_g = generator_containing_discriminator(g, d)
# 优化器用 Adam Optimizer
g_optimizer = tf.keras.optimizers.Adam(lr=LEARNING_RATE, beta_1=BETA_1)
d_optimizer = tf.keras.optimizers.Adam(lr=LEARNING_RATE, beta_1=BETA_1)
# 配置 生成器 和 判别器
g.compile(loss="binary_crossentropy", optimizer=g_optimizer)
d_on_g.compile(loss="binary_crossentropy", optimizer=g_optimizer)
d.trainable = True
d.compile(loss="binary_crossentropy", optimizer=d_optimizer)
# 开始训练
for epoch in range(EPOCHS):
for index in range(int(input_data.shape[0] / BATCH_SIZE)):
input_batch = input_data[index * BATCH_SIZE: (index + 1) * BATCH_SIZE]
# 连续型均匀分布的随机数据(噪声)
random_data = np.random.uniform(-1, 1, size=(BATCH_SIZE, 100))
# 生成器 生成的图片数据
generated_images = g.predict(random_data, verbose=0)
input_batch = np.concatenate((input_batch, generated_images))
output_batch = [1] * BATCH_SIZE + [0] * BATCH_SIZE
# 训练 判别器,让它具备识别不合格生成图片的能力
d_loss = d.train_on_batch(input_batch, output_batch)
# 当训练 生成器 时,让 判别器 不可被训练
d.trainable = False
# 重新生成随机数据。很关键
random_data = np.random.uniform(-1, 1, size=(BATCH_SIZE, 100))
# 训练 生成器,并通过不可被训练的 判别器 去判别
g_loss = d_on_g.train_on_batch(random_data, [1] * BATCH_SIZE)
# 恢复 判别器 可被训练
d.trainable = True
# 打印损失
print("Epoch {}, 第 {} 步, 生成器的损失: {:.3f}, 判别器的损失: {:.3f}".format(epoch, index, g_loss, d_loss))
# 保存 生成器 和 判别器 的参数
# 大家也可以设置保存时名称不同(比如后接 epoch 的数字),参数文件就不会被覆盖了
if epoch % 10 == 9:
g.save_weights("generator_weight", True)
d.save_weights("discriminator_weight", True)
if __name__ == "__main__":
train()
训练网络的部分结果如下:
3.生成网络
"""
用 DCGAN 的生成器模型 和 训练得到的生成器参数文件 来生成图片
"""
import numpy as np
from PIL import Image
import tensorflow as tf
from network import *
def generate():
# 构造生成器
g = generator_model()
# 配置 生成器
g.compile(loss="binary_crossentropy", optimizer=tf.keras.optimizers.Adam(lr=LEARNING_RATE, beta_1=BETA_1))
# 加载训练好的 生成器 参数
g.load_weights("generator_weight")
# 连续型均匀分布的随机数据(噪声)
random_data = np.random.uniform(-1, 1, size=(BATCH_SIZE, 100))
# 用随机数据作为输入,生成器 生成图片数据
images = g.predict(random_data, verbose=1)
# 用生成的图片数据生成 PNG 图片
for i in range(BATCH_SIZE):
image = images[i] * 127.5 + 127.5
Image.fromarray(image.astype(np.uint8)).save("image-%s.png" % i)
if __name__ == "__main__":
generate()