生成对抗网络系列
【生成对抗网络】GAN入门与代码实现(一)
【生成对抗网络】GAN入门与代码实现(二)
【生成对抗网络】基于DCGAN的二次元人物头像生成(TensorFlow2)
【生成对抗网络】ACGAN的代码实现
上篇博客:【生成对抗网络】GAN入门与代码实现(一)
本篇主要介绍简单GAN的另一种实现方法(不使用卷积),依然使用TensorFlow2进行搭建,主要运用了TensorFlow2中的求导机制进行自定义训练,自由度更高。对比上篇博客中的实现方法可加深对GAN的编写理解。
import tensorflow as tf # 2.3版本
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np
import os
import matplotlib.pyplot as plt
%matplotlib inline
我们使用MNIST手写数据集作为训练生成的数据。
# 加载数据
(train_images,train_labels),(_,_) = tf.keras.datasets.mnist.load_data() # train_images的shape为(60000,28,28)
# 把数据改为float类型,然后归一化,目的是使数据落在0的周围,因为激活函数在0的周围能发挥好的作用
train_images = train_images.reshape(train_images.shape[0],28,28,1).astype('float32') # numpy方法,重塑shape为(60000,28,28,1)
train_images = (train_images - 127.5)/127.5 # 把0-255的数据范围变为-1到1之间
定义相关参数。
BATCH_SIZE = 300 # batch大小
BUFFER_SIZE = 60000 # 训练集有6w张图片
EPOCHS = 300 # 批次数量
noise_dim = 100 # 随机数的维度
将原数据创建为Dataset数据,便于训练。
datasets = tf.data.Dataset.from_tensor_slices(train_images)
#
datasets = datasets.shuffle(BUFFER_SIZE).batch(BATCH_SIZE) # 在全部范围内做一个乱序,设置一个batch为256
#
# 测试datasets:
for item in datasets:
print(item.shape)
print(type(item))
break
# (300, 28, 28, 1) # batch为300 图片为28*28的单通道照片
#
输入100维的随机向量,输出一张(28,28,1)维的图片。
def generator_model():
generator = keras.models.Sequential([
keras.layers.Input(shape=(100,)), # 输入为长度100点随机向量
keras.layers.Dense(256),
keras.layers.LeakyReLU(alpha = 0.2),
keras.layers.BatchNormalization(momentum = 0.8),
keras.layers.Dense(512),
keras.layers.LeakyReLU(alpha = 0.2),
keras.layers.BatchNormalization(momentum = 0.8),
keras.layers.Dense(1024),
keras.layers.LeakyReLU(alpha = 0.2),
keras.layers.BatchNormalization(momentum = 0.8),
keras.layers.Dense(np.prod((28,28,1)),activation='tanh'),
keras.layers.Reshape((28,28,1)) # 将向量重塑shape为(28,28,1),输出图片
])
return generator
输入图片,输出1维的判定结果(最后没有使用激活函数)。
def discriminator_model():
discriminator = keras.models.Sequential([
keras.layers.Flatten(), # 将输入的多维数据展平为一维
keras.layers.Dense(512),
keras.layers.LeakyReLU(alpha = 0.2),
keras.layers.Dense(256),
keras.layers.LeakyReLU(alpha = 0.2),
keras.layers.Dense(1)
])
return discriminator
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)
# 因为判别器最后没有使用激活 所以我们添加from_logits=True
判别器损失:
判别器的目标是让真实图片判别为1,生成的图片判别为0,因此损失函数中真实图片与1比较,生成图片与0比较,从而计算损失。
# 求判别器损失的函数
def discriminator_loss(real_out,fake_out):
real_loss = cross_entropy(tf.ones_like(real_out),real_out) # 真实图片的输出与1比较
fake_loss = cross_entropy(tf.zeros_like(fake_out),fake_out) # 生成图片的输出与0比较
return real_loss + fake_loss
生成器损失:
生成器的目标是使得自己生成的图片在判别器中判别为1(真实),因此损失函数中需要与1对比。
# 求生成器损失的函数
def generator_loss(fake_out):
fake_loss = cross_entropy(tf.ones_like(fake_out),fake_out)
return fake_loss
定义优化器:
learning_rate:0.0002
beta_1:0.5
# 优化器
generator_opt = tf.keras.optimizers.Adam(2e-4,0.5)
discriminator_opt = tf.keras.optimizers.Adam(2e-4,0.5)
参数简介:
learning_rate:一个张量,浮点值,或者是一个tf.keras.optimizer .schedules时间表。LearningRateSchedule,或者一个不带参数并返回要使用的实际值的可调用对象,即学习速率。默认为0.001。
beta_1:一个浮点值或一个常量浮点张量,或者一个不带参数并返回实际值的可调用对象。一阶矩的指数衰减率估计。默认为0.9
generator = generator_model() # 获取生成器模型
discriminator = discriminator_model() # 获取判别器模型
定义训练函数,使用Tensorflow中的自动求导与根据梯度更新参数的方法来训练生成器与判别器。
@tf.function
# 接收一个批次的图片,对其进行训练
def train_step(images):
noise = tf.random.normal([BATCH_SIZE,noise_dim]) # 生成BATCH_SIZE个长度为100的随机向量
# images 是真实图片的输入
# noise 是噪声输入
with tf.GradientTape() as gen_tape,tf.GradientTape() as disc_tape:
real_out = discriminator(images,training = True) # 输出真实图片在判别器中的判别结果
gen_image = generator(noise,training = True) # 用随机向量在生成器中生成图片
fake_out = discriminator(gen_image,training = True) # 输出生成图片在判别器中的判别结果
# 调用步骤5中的方法,计算损失
gen_loss = generator_loss(fake_out)
disc_loss = discriminator_loss(real_out,fake_out)
# 自动计算机损失函数关于自变量(模型参数)的梯度
gradient_gen = gen_tape.gradient(gen_loss,generator.trainable_variables)
gradient_disc = disc_tape.gradient(disc_loss,discriminator.trainable_variables)
# 根据梯度更新参数
generator_opt.apply_gradients(zip(gradient_gen,generator.trainable_variables))
discriminator_opt.apply_gradients(zip(gradient_disc,discriminator.trainable_variables))
为了在训练的过程中查看生成器输出图片的效果,我们定义6个100维度的随机数来检测训练过程中的生成器模型,使用matlibplot中的方法绘制图片。
num_example_to_generate = 6 # 用于绘图过程中生成图片的数量
seed = np.random.normal(0,1,(num_example_to_generate,noise_dim)) # 生成6个长度为100的随机向量
# 画图函数
def generate_plot_image(test_noise):
pre_image = generator(test_noise,training = False) # 用生成器,生成手写图片
# print(pre_image.shape) # (6,28,28,1)
fig = plt.figure(figsize=(16,3)) # figsize:指定figure的宽和高,单位为英寸
for i in range(pre_image.shape[0]): # pre_image的shape的第一个维度就是个数,这里是6
plt.subplot(1,6,i+1) # 几行几列的 第i+1个图片(从1开始)
plt.imshow((pre_image[i,:,:,:] + 1)/2) # 加1除2: 将生成的-1~1的图片弄到0-1之间,
plt.axis('off') # 不要坐标
plt.show()
训练epochs次,每次epoch中从dataset依次取出batch个数据调用步骤6中的方法进行训练,每次epoch结束后调用步骤7中的方法绘制几张图片查看生成器的生成效果。
# 训练(主方法)
def train(dataset,epochs):
for epoch in range(1,epochs+1): # 总过训练epochs次
print("epoch:",epoch)
for image_batch in dataset: # 从数据集中遍历所有batch
train_step(image_batch)# 训练一个batch
print(".",end="")
generate_plot_image(seed) # 绘图,使用前面定义的随机数seed在生成器中生成图片并展示
train(datasets,EPOCHS)
epoch 1:
epoch 10:
epoch 20:
epoch 50:
epoch 100:
epoch 290: