DCGAN(深度卷积生成对抗网络Deep Convolutional GAN)的论文地址: 论文地址
生成对抗网络系列
【生成对抗网络】GAN入门与代码实现(一)
【生成对抗网络】GAN入门与代码实现(二)
【生成对抗网络】基于DCGAN的二次元人物头像生成(TensorFlow2)
【生成对抗网络】ACGAN的代码实现
1 导包
tensorflow版本为2.6.2
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np
import os
import glob # 用于读取
import matplotlib.pyplot as plt
%matplotlib inline
2 数据准备
数据下载地址:kaggle anime-faces数据集,共21551张人物头像,每张图片size为64*64。
image_path = glob.glob(r"../input/anime-faces/data/*.png") # 数据读取
原数据介绍:
This is a dataset consisting of 21551 anime faces scraped from www.getchu.com, which are then cropped using the anime face detection algorithm in https://github.com/nagadomi/lbpcascade_animeface. All images are resized to 64 * 64 for the sake of convenience. Please also cite the two sources when using this dataset.
数据预处理:
def load_preprosess_image(path): # 定义每张图片的处理函数
image = tf.io.read_file(path) # 读取路径文件
image = tf.image.decode_png(image,channels=3) # 解码png图片,通道为3
image = tf.cast(image,tf.float32) # 转换为float类型
image = (image/127.5) - 1 # -1至1之间
return image
image_ds = tf.data.Dataset.from_tensor_slices(image_path) # 创建Dataset对象
AUTOTUNE = tf.data.experimental.AUTOTUNE # 让TensorFlow自动选择合适的数值,充分利用计算机资源
image_ds = image_ds.map(load_preprosess_image,num_parallel_calls = AUTOTUNE) # 对于每张图片应用load_preprosess_image函数进行处理
设置Batch大小,定义图片总数
BATCH_SIZE = 64
image_count = len(image_path) # 21551
image_ds = image_ds.shuffle(image_count).batch(BATCH_SIZE) # 打乱图片顺序,设置batch大小
image_ds = image_ds.prefetch(AUTOTUNE) # 在GPU进行训练的同时 CPU进行数据预加载,提高训练效率
image_ds为<PrefetchDataset shapes: (None, None, None, 3), types: tf.float32>
其中每个batch的shape为(64, 64, 64, 3)
3 定义生成器
输入为100维的noise,输出维度为(64,64,3)
def generator_model():
model = tf.keras.Sequential()
model.add(layers.Dense(8*8*256, use_bias=False, input_shape=(100,)))
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
model.add( layers.Reshape((8,8,256)) ) # 输出8*8*256
# 反卷积
model.add(layers.Conv2DTranspose(128,(5,5),strides=(1,1),padding='same',use_bias='false'))
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU()) # 输出8*8*128
model.add(layers.Conv2DTranspose(64,(5,5),strides=(2,2),padding='same',use_bias='false'))
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU()) # 输出16*16*64
model.add(layers.Conv2DTranspose(32,(5,5),strides=(2,2),padding='same',use_bias='false'))
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU()) # 输出32*32*32
model.add(layers.Conv2DTranspose(3,(5,5),
strides=(2,2),
padding='same',
use_bias=False,
activation='tanh')) # 输出64*64*3
return model
4 定义判别器
输入维度(64,64,3),输出1维判定结果
def discriminator_model():
# 第一层 不用BatchNormalization
model = tf.keras.Sequential()
model.add(layers.Conv2D(32,
(5,5),
strides = (2,2),
padding='same',
input_shape=(64,64,3))) # 输入为64*64*3
model.add(layers.LeakyReLU())
model.add(layers.Dropout(0.3)) # 32*32*32
model.add(layers.Conv2D(64,
(5,5),
strides = (2,2),
padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
model.add(layers.Dropout(0.3)) # 16*16*64
model.add(layers.Conv2D(128,
(5,5),
strides = (2,2),
padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
model.add(layers.Dropout(0.3)) # 8*8*128
model.add(layers.Conv2D(256,
(5,5),
strides = (2,2),
padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU()) # 4*4*256
model.add(layers.GlobalAveragePooling2D())
# Global Average Pooling(简称GAP,全局池化层),被认为是可以替代全连接层的一种新技术
model.add(layers.Dense(1024))
model.add(layers.BatchNormalization())
model.add(layers.LeakyReLU())
model.add(layers.Dense(1))
return model
为了避免训练震荡和模型的不稳定,生成器的输出层和判别器的输入层不使用BatchNormalization()
5 定义损失函数和优化器
判别器损失:
- 将判别器中对于真实图像的判别结果与1进行比较,求交叉熵损失
- 将判别器中对于生成图像的判别结果与0进行比较,求交叉熵损失
- 两者相加即为判别器损失
生成器损失:
- 将判别器中对于生成图像结果与0进行比较,求交叉熵损失
cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True) # 因为判别器最后没有使用激活 所以我们添加from_logits=True
# 求判别器损失的函数
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
# 求生成器损失的函数
def generator_loss(fake_out):
fake_loss = cross_entropy(tf.ones_like(fake_out),fake_out)
return fake_loss
优化器:
# 优化器
generator_opt = keras.optimizers.Adam(1e-5)
discriminator_opt = keras.optimizers.Adam(1e-5)
定义损失评价指标,用于损失绘图
epoch_loss_avg_gen = tf.keras.metrics.Mean('g_loss')
epoch_loss_avg_disc = tf.keras.metrics.Mean('d_loss')
g_loss_results = []
d_loss_results = []
6 定义训练批次函数
EPOCHS = 500
noise_dim = 100 # 随机数的维度
num_example_to_generate = 6 # 用于绘图过程中生成图片的数量
seed = tf.random.normal([num_example_to_generate,noise_dim]) # 生成6个长度为100的随机向量
获取生成器模型和判别器模型
generator = generator_model() # 获取生成器模型
discriminator = discriminator_model() # 获取判别器模型
利用自动求导机制定义模型参数更新函数
# 接收一个批次的图片,对其进行训练
@tf.function
def train_step(images):
noise = tf.random.normal([BATCH_SIZE,noise_dim]) # 生成BTATH_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)
# 通过损失函数计算损失
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))
return gen_loss, disc_loss
7 定义可视化训练结果函数
使用固定的一个noise再训练的过程中来测试生成器生成的人物头像效果
# 画图函数
def generate_plot_image(gen_model,test_noise):
pre_image = gen_model(test_noise,training = False)
print(pre_image.shape)
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()
8 定义训练主函数
在每个epoch中循环取出一个batch大小真实图像进行生成器与判别器训练,同时记录每次的loss信息
def train(dataset,epochs):
for epoch in range(1,epochs+1): # 总共训练epochs次
print("\n epoch:",epoch)
for image_batch in dataset: # 从数据集中遍历所有batch
gen_loss, disc_loss = train_step(image_batch)# 训练一个batch
epoch_loss_avg_gen(gen_loss)
epoch_loss_avg_disc(disc_loss)
print(".",end="")
g_loss_results.append(epoch_loss_avg_gen.result())
d_loss_results.append(epoch_loss_avg_disc.result())
epoch_loss_avg_gen.reset_states()
epoch_loss_avg_disc.reset_states()
if epoch%10 == 0: # 每10个epoch绘图一次
generate_plot_image(generator,seed) # 绘图,用随机数seed在生成器
generate_plot_image(generator,seed) # 绘图,用随机数seed在生成器
9 训练
train(image_ds,EPOCHS)
# 绘制loss变化图
plt.plot(range(1, len(g_loss_results)+1), g_loss_results, label='g_loss')
plt.plot(range(1, len(d_loss_results)+1), d_loss_results, label='d_loss')
plt.legend()
10 结果
epoch 10:
epoch 20:
epoch 30:
epoch 100:
epoch 200:
epoch 500:
11 使用生成器
seed2 = tf.random.normal([30,noise_dim]) # 生成30个长度为100的随机向量
a = seed2.numpy()
pre = generator.predict(a)
print(pre[0].shape) # 查看输出的第一个图片的shape为(64, 64, 3)
plt.imshow((pre[0] + 1)/2, cmap = 'gray')
如上是600epoch效果,还是有些糊啦
代码下载(GitHub):DCGAN-anime-faces