赞
踩
本文继上一篇文章继续研究深度卷积生成对抗网络(DCGAN) ,本文主要讲解实现细节,使用 DCGAN 实现手写数字生成任务,通过这一个例子,读者可以进一步巩固上一篇博客所讲内容,同时对生成对抗网络会有更加详细的认识。
完整项目代码在本人github上面已经开源,具体用法可以参见本人github
使用如下超参数训练 1000次:
batch_size=128 训练时候的批次大小,默认是128
learning_rate=0.002 默认是0.002
img_sizet=32 生成图片的大小(和训练图片的大小保持一致)
z_dim=100 输入生成器的随机向量的大小,默认是100
g_channels=[128,64,32,1] 生成器的通道数目变化列表,用于构建生成器结构
d_channels=[32,64,128,256] 判别器的通道树木变化列表,用来构建判别器
init_conv_size=4 随机向量z经过全连接之后进行reshape 生成三维矩阵的初始边长,默认是 4
beta1=0.5 AdamOptimizer 指数衰减率估计,默认是0.5
训练200次:
生成图片:
真实图片:
训练500次:
生成图片:
真实图片:
训练1000次:
生成图片:
真实图片:
训练3500次:
生成图片:
真实图片:
训练5000次:
生成图片:
真实图片:
可以看到的是训练 5000 次之后生成的图片和真实的图片已经非常像。
因为要生成手写数字,则首先需要一个手写数字的数据集来训练GAN,这里使用常见的快被用烂了的MNIST数据集,下面是加载数据集的工具文件:
dataset_loader.py
""" create by qianqianjun 2019.12.19 """ import os import struct import numpy as np def load_mnist(path,train=True): """ 加载mnist 数据集的函数 :param path: 数据集的位置 :param train: 是否加载训练数据,是返回train 用的image和lable,否则返回test用的images和label :return: 返回训练或者测试用的images 和 labels """ def get_urls(files,type='train'): """ 获取训练数据或者测试数据的二进制文件地址 :param files: 读取的数据集目录文件列表 :param type: 训练或者测试标识 :return: 返回二进制文件的完整地址 """ images_path = None labels_path = None for file in files: if file.find(type) != -1: if file.find("images") != -1: images_path = os.path.join(path, file) else: labels_path = os.path.join(path, file) if images_path == None or labels_path == None: raise Exception("请检查数据集!") return images_path,labels_path def load_data_and_label(data_path,label_path): """ 加载训练或者测试数据的lable 和 data :param data_path: 训练或者测试图片数据的二进制文件地址 :param label_path: 训练或者测试label数据的二进制文件地址 :return: 返回读取的图片 和 label 的 ndarray 数组 """ images = None labels = None with open(label_path,'rb') as label_file: struct.unpack('>II', label_file.read(8)) labels=np.fromfile(label_file,dtype=np.uint8) with open(data_path,'rb') as img_file: struct.unpack('>IIII', img_file.read(16)) images=np.fromfile(img_file,dtype=np.uint8).reshape(len(labels),784) return images,labels # 查看数据集文件夹中有多少文件。 files = os.listdir(path) if train: data_path,label_path=get_urls(files,type='train') return load_data_and_label(data_path,label_path) else: data_path,label_path=get_urls(files,type='t10k') return load_data_and_label(data_path, label_path) # 读取训练用的图片数据和训练用的labels 标签 train_images,train_labels=load_mnist("./MNIST",train=True) # 读取测试用的图片数据和测试用的labels 标签 test_images,test_labels=load_mnist("./MNIST",train=False)
这一个文件主要用来在训练的时候分批次的取数据,对数据集进行打乱,洗牌工作,防止模型学习到数据之间的顺序关联。
data_provider.py
""" write by qianqianjun 2019.12.20 """ import numpy as np from PIL import Image class MnistData(object): def __init__(self,images_data,z_dim,img_size): """ 建立一个data provider :param images_data: 传进来的图像数据的集合 :param z_dim: 生成器输入的随机向量的长度 :param img_size: 传进来的图像的大小 """ self._data=images_data self.images_num=len(self._data) # 生成随机向量的矩阵,为每一张图像都生成一个随机向量。 self._z_data=np.random.standard_normal((self.images_num,z_dim)) self._offset=0 self.init_mnist(img_size) self.random_shuffer() def random_shuffer(self): """ 数据集进行打乱操作,防止模型学习到训练数据之间的顺序性质 :return: """ p=np.random.permutation(self.images_num) self._z_data=self._z_data[p] self._data=self._data[p] def init_mnist(self,img_size): """ 调整数据集到指定的shape :param img_size: 指定大小的边长 :return: """ # 将训练数据进行resize,使其成为图片 data=np.reshape(self._data,(self.images_num,28,28)) new_data=[] for i in range(self.images_num): img=data[i] # 使用PIL 进行图像缩放变换 img=Image.fromarray(img) img=img.resize((img_size,img_size)) img=np.asarray(img) # 将图片转换为有通道的形式方便训练(3维矩阵,只有一个通道) img=img.reshape((img_size,img_size,1)) new_data.append(img) # 将列表转换为 ndarray new_data=np.asarray(new_data,dtype=np.float32) # 对图像数据进行归一化,方便训练 new_data=new_data / 127.5 -1 # 更新数据 self._data=new_data def next_batch(self,batch_size): """ 用来分批次的取数据 :param batch_size: 每一批取数据的个数 :return: 返回一批数据和一批随机向量 """ if batch_size> self.images_num: raise Exception("batch size is more than train images amount!") end_offset=self._offset+batch_size if end_offset >self.images_num: self.random_shuffer() self._offset=0 end_offset=self._offset+batch_size # 取出一批数据和一批随机向量。 batch_data=self._data[self._offset:end_offset] batch_z=self._z_data[self._offset:end_offset] self._offset=end_offset return batch_data,batch_z
generator.py
""" write by qianqianjun 2019.12.19 生成器模型实现 """ import tensorflow as tf def conv2d_transpose(inputs,out_channel,name,training,with_bn_relu=True): """ 反卷积的封装 :param inputs: :param output_channel: 输出通道数目 :param name: 名字 :param training: bool类型 ,指示是否在训练 :param with_bn_relu: 是否需要使用 batch_normalization :return: 反卷积之后的矩阵 """ with tf.variable_scope(name): conv2d_trans = tf.layers.conv2d_transpose( inputs, out_channel, [5, 5], strides=(2, 2), padding='SAME' ) if with_bn_relu: bn = tf.layers.batch_normalization(conv2d_trans, training=training) return tf.nn.relu(bn) else: return conv2d_trans class Generator(object): def __init__(self,channels,init_conv_size): """ 创建生成器模型 :param channels: 生成器反卷积过程中使用的通道数 数组 :param init_conv_size: 使用的卷积核大小 """ self._channels = channels self._init_conv_size = init_conv_size self._reuse = False def __call__(self, inputs,training): """ 一个魔法函数,用来将对象当函数使用 :param inputs: 输入的随机向量矩阵,shape 为 【batch_size ,z_dim] :param training: 是否是训练过程 :return: 返回生成的图像 """ inputs=tf.convert_to_tensor(inputs) with tf.variable_scope('generator',reuse=self._reuse): """ 下面代码实现的转换是: random vector-> fc全连接层-> self.channels[0] * self._init_conv_size **2 -> reshpe -> [init_conv_size,init_conv_size,self.channels[0] ] """ with tf.variable_scope("input_conv"): fc=tf.layers.dense( inputs, self._channels[0] * (self._init_conv_size **2 ) ) conv0=tf.reshape(fc,[-1,self._init_conv_size, self._init_conv_size,self._channels[0]]) bn0=tf.layers.batch_normalization(conv0,training=training) relu0=tf.nn.relu(bn0) # 经过全连接和BN归一化和 relu 激活,可以看做是某一个卷积层的输出 # 下面就可以进行反卷积操作了。 deconv_inputs=relu0 # 构建 decoder 网络层 for i in range(1,len(self._channels)): with_bn_relu=(i!=len(self._channels)-1) deconv_inputs=conv2d_transpose( deconv_inputs, self._channels[i], "deconv-%d" % i, training, with_bn_relu=with_bn_relu) img_inputs=deconv_inputs with tf.variable_scope('generate_imgs'): imgs=tf.tanh(img_inputs,name='imgs') self.reuse=True self.variables=tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope='generator') return imgs
discriminator.py
""" write by qianqianjun 2019.12.20 判别器简单实现 """ import tensorflow as tf def conv2d(inputs,output_channel,name,training): """ 卷积操作的封装 :param inputs: 输入的图像或者feature map :param output_channel: 输出feature map 的channel 数目 :param name: varibale_scope 名称 :param training: 是否是训练过程。 :return: 返回经过卷积层之后的结果 """ def leaky_relu(x,leak=0.2,name=''): return tf.maximum(x,x*leak,name=name) with tf.variable_scope(name): conv2d_output=tf.layers.conv2d( inputs,output_channel, [5,5],strides=(2,2), padding='SAME' ) bn=tf.layers.batch_normalization(conv2d_output,training=training) return leaky_relu(bn,name='outputs') class Discriminator(object): def __init__(self,channels): """ 创建判别器模型结构 :param channels: 输出通道数目 """ self._channels=channels self._reuse=False def __call__(self,inputs,training): """ 使用判别器输出判别的结果, :param inputs: 输入的batch_images data :param training: 是否在训练。 :return: """ inputs=tf.convert_to_tensor(inputs,dtype=tf.float32) conv_inputs=inputs with tf.variable_scope('discriminator',reuse=self._reuse): # 根据卷积通道数组来建立卷积神经网络结构: for i in range(len(self._channels)): conv_inputs=conv2d(conv_inputs,self._channels[i], 'conv-%d'%i, training=training) fc_inputs=conv_inputs # 将卷积神经网络输出的 feature map 展平并进行全连接。 with tf.variable_scope('fc'): flatten=tf.layers.flatten(fc_inputs) # 全连接输出大小为 2 # 其实可以理解为一个分类的问题,真图片还是假图片,一共两类。 logits=tf.layers.dense(flatten,2,name='logits') self._reuse=True self.variables=tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope='discriminator') return logits
DCGAN.py
""" write by qianqianjun 2019.12.20 DCGAN 网络架构实现 """ from generator import Generator from discriminater import Discriminator import tensorflow as tf class DCGAN(object): def __init__(self,hps): """ 建立一个DCGAN的网络架构 :param hps: 网络的所有超参数的集合 """ g_channels=hps.g_channels d_channels=hps.d_channels self._batch_size=hps.batch_size self._init_conv_size=hps.init_conv_size self._z_dim=hps.z_dim self._img_size=hps.img_size self._generator=Generator(g_channels,self._init_conv_size) self._discriminator=Discriminator(d_channels) def build(self): """ 构建整个计算图 :return: """ # 创建随机向量和图片的占位符 self._z_placeholder=tf.placeholder(tf.float32, (self._batch_size,self._z_dim)) self._img_placeholder=tf.placeholder(tf.float32, (self._batch_size, self._img_size, self._img_size,1)) # 将随机向量输入生成器生成图片 generated_imgs=self._generator(self._z_placeholder,training=True) # 将来生成的图片经过判别器来得到 生成图像的logits fake_img_logits=self._discriminator( generated_imgs,training=True ) # 将真实的图片经过判别器得到真实图像的 logits real_img_logits=self._discriminator( self._img_placeholder,training=True ) """ 定义损失函数 包括生成器的损失函数和判别器的损失函数。 生成器的目的是使得生成图像经过判别器之后尽量被判断为真的 判别器的目的是使得生成器生成的图像被判断为假的,同时真实图像经过判别器要被判断为真的 """ ## 生层器的损失函数,只需要使得假的图片被判断为真即可 fake_is_real_loss=tf.reduce_mean( tf.nn.sparse_softmax_cross_entropy_with_logits( labels=tf.ones([self._batch_size],dtype=tf.int64), logits=fake_img_logits ) ) ## 判别器的损失函数,只需要使得生成的图像被判断为假的,真实的图像被判断为真的即可 # 真的被判断为真的: real_is_real_loss=tf.reduce_mean( tf.nn.sparse_softmax_cross_entropy_with_logits( labels=tf.ones([self._batch_size],dtype=tf.int64), logits=real_img_logits ) ) # 假的被判断为假的: fake_is_fake_loss=tf.reduce_mean( tf.nn.sparse_softmax_cross_entropy_with_logits( labels=tf.zeros([self._batch_size],dtype=tf.int64), logits=fake_img_logits ) ) # 将损失函数集中管理: tf.add_to_collection('g_losses',fake_is_real_loss) tf.add_to_collection('d_losses',real_is_real_loss) tf.add_to_collection('d_losses',fake_is_fake_loss) loss={ 'g':tf.add_n(tf.get_collection('g_losses'),name='total_g_loss'), 'd':tf.add_n(tf.get_collection('d_losses'),name='total_d_loss') } return (self._z_placeholder,self._img_placeholder,generated_imgs,loss) def build_train_op(self,losses,learning_rate,beta1): """ 定义训练过程 :param losses: 损失函数集合 :param learning_rate: 学习率 :param beta1: 指数衰减率估计 :return: """ g_opt=tf.train.AdamOptimizer(learning_rate=learning_rate,beta1=beta1) d_opt=tf.train.AdamOptimizer(learning_rate=learning_rate,beta1=beta1) g_opt_op=g_opt.minimize( losses['g'], var_list=self._generator.variables ) d_opt_op=d_opt.minimize( losses['d'], var_list=self._discriminator.variables ) with tf.control_dependencies([g_opt_op,d_opt_op]): return tf.no_op(name='train')
train_argparse.py
""" write by qianqianjun 2019.12.20 命令行参数解释程序 如果不清楚可以参考博客: https://blog.csdn.net/qq_38863413/article/details/103305449 """ import argparse parser=argparse.ArgumentParser() parser.description="指定DCGAN网络在训练时候的超参数,使用help命令获取详细的帮助" parser.add_argument("--batch_size",type=int,default=128,help="训练时候的批次大小,默认是128") parser.add_argument("--learning_rate",type=float,default=0.002,help="训练时候的学习率,默认是0.002") parser.add_argument("--img_size",type=int,default=32,help="生成图片的大小(和训练图片的大小保持一致)") parser.add_argument("--z_dim",type=int,default=100,help="输入生成器的随机向量的大小,默认是100") parser.add_argument("--g_channels",type=list,default=[128,64,32,1],help="生成器的通道数目变化列表,用于构建生成器结构") parser.add_argument("--d_channels",type=list,default=[32,64,128,256],help="判别器的通道树木变化列表,用来构建判别器") parser.add_argument("--init_conv_size",type=int,default=4,help="随机向量z经过全连接之后进行reshape 生成三维矩阵的初始边长,默认是 4 ") parser.add_argument("--beta1",type=float,default=0.5,help="AdamOptimizer 指数衰减率估计,默认是0.5") hps=parser.parse_args()
mian.py
import os import tensorflow as tf from train_argparse import hps from dataset_loader import train_images from data_provider import MnistData from DCGAN import DCGAN from utils import combine_imgs output_dir='./out' if not os.path.exists(output_dir): os.mkdir(output_dir) dcgan=DCGAN(hps) z_placeholder,img_placeholder,generated_imgs,losses=dcgan.build() train_op=dcgan.build_train_op(losses,hps.learning_rate,hps.beta1) init_op=tf.global_variables_initializer() train_steps=200 mnist_data=MnistData(train_images,hps.z_dim,hps.img_size) with tf.Session() as sess: sess.run(init_op) for step in range(train_steps): batch_imgs,batch_z=mnist_data.next_batch(hps.batch_size) fetches=[train_op,losses['g'],losses['d']] should_sample=(step+1) %100 ==0 if should_sample: fetches+= [generated_imgs] output_values=sess.run( fetches,feed_dict={ z_placeholder:batch_z, img_placeholder:batch_imgs, } ) _,g_loss_val,d_loss_val=output_values[0:3] if (step+1) %200==0: print('step: %4d , g_loss: %4.3f , d_loss: %4.3f' % (step, g_loss_val, d_loss_val)) if should_sample: gen_imgs_val=output_values[3] gen_img_path=os.path.join(output_dir,'%05d-gen.jpg' % (step+1)) gt_img_path=os.path.join(output_dir,'%05d-gt.jpg' % (step+1)) gen_img=combine_imgs(gen_imgs_val,hps.img_size) gt_img=combine_imgs(batch_imgs,hps.img_size) gen_img.save(gen_img_path) gt_img.save(gt_img_path)
utils.py
""" write by qianqianjun 2019,12,20 工具文件 这里使用了 numpy 的一些维度变换,如果不清楚可以参考博客: https://blog.csdn.net/qq_38863413/article/details/103526645 """ import numpy as np from PIL import Image def combine_imgs(batch_images,img_size,rows=8,cols=16): """ 用于在训练过程中展示一批数据(将一批图像拼接成一张大图) :param batch_images: 批次图像数据 :param img_size: 图像大小 :param rows: 一共有多行。 :param cols: 一行放置多少图片 :return: 返回拼接之后的大图 """ #batch_img: [batch_size,img_size,img_size,1] result_big_img=[] for i in range(rows): row_imgs=[] for j in range(cols): img=batch_images[cols*i+j] img=img.reshape((img_size,img_size)) # 反归一化 img=(img+1) * 127.5 row_imgs.append(img) row_imgs=np.hstack(row_imgs) result_big_img.append(row_imgs) result_big_img=np.vstack(result_big_img) result_big_img=np.asarray(result_big_img,np.uint8) result_big_img=Image.fromarray(result_big_img) return result_big_img
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。