代码地址:https://github.com/gongpx20069/CycleGAN-TensorFlow
这是Van Huy巨佬的代码,做一个学习巨佬CycleGAN代码的小笔记。CycleGAN的一个巨大的优点就是不需要X和Y两个域(相互转化的两个域)有一一对应的关系。
不得不说神经网络这种东西很消耗GPU内存,显卡内存决定了网络层数、输入图片大小这些很重要的东西。
由于是在看作者到底如和搭建成的CycleGAN,我们的思路就应当随着train的过程慢慢深入。
目前来看,作者模型的搭建依赖关系是:
ops(神经网络某一层)
->generator(生成器类别)|discriminator(判别器类)
->model(CycleGAN具体模型)
->train(训练的批次等参数)
因此我们的学习顺序也是同一个方向:从ops.py到train.py
ops.py定义了如下几个函数:
1. def c7s1_k(input, k, reuse=False, norm=‘instance’, activation=‘relu’, is_training=True, name=‘c7s1_k’)
函数的作用:
首先为输入图片左右都填充3条边,再用一个773的过滤器,步长为1,将结果先通过normal再用激活函数(tanh或者relu)输出,输出结果的深度为k。
输入参数的解释为:
函数涉及的几个函数详解:
padded = tf.pad(tensor,
paddings,
mode='CONSTANT',
name=None)
pad的主要作用就在tensor的边缘填充,比如输入是4D-tensor,那么可以paddings=[[0,0],[3,3],[3,3],[0,0]],即第2纬左右分别加三条边,第3纬左右分别加三条边,其实也就是给一批图片长宽都加了边。
而关于mode参数:
tf.conv2d_transpose(value, filter, output_shape, strides, padding="SAME", data_format="NHWC", name=None)
参数说明:
6. Ck(input, k, slope=0.2, stride=2, reuse=False, norm=‘instance’, is_training=True, name=None)
函数作用:
函数具体参数同第一个函数,输入为一个4D-tansor,先通过4*4*(图像深度)的过滤器,步长为自定,默认为2,再通过normal函数,最后用Leaky Relu激活函数输出,输出数据深度为k。
7. last_conv(input, reuse=False, use_sigmoid=False, name=None)
函数作用:
用于判别器最后一层,输入为4D-tensor,之后通过一个4*4*(图像深度)的过滤层,步长为1,输出为1维,加上一个偏置,之后可以通过sigmod函数,也可以选择不通过sigmod函数,输出。
def __call__(self, input):
"""
Args:
input: batch_size x image_size x image_size x 3
Returns:
output: 4D tensor batch_size x out_size x out_size x 1 (default 1x5x5x1)
filled with 0.9 if real, 0.0 if fake
"""
with tf.variable_scope(self.name):
# convolution layers
C64 = ops.Ck(input, 64, reuse=self.reuse, norm=None,
is_training=self.is_training, name='C64') # (?, w/2, h/2, 64)
C128 = ops.Ck(C64, 128, reuse=self.reuse, norm=self.norm,
is_training=self.is_training, name='C128') # (?, w/4, h/4, 128)
C256 = ops.Ck(C128, 256, reuse=self.reuse, norm=self.norm,
is_training=self.is_training, name='C256') # (?, w/8, h/8, 256)
C512 = ops.Ck(C256, 512,reuse=self.reuse, norm=self.norm,
is_training=self.is_training, name='C512') # (?, w/16, h/16, 512)
# apply a convolution to produce a 1 dimensional output (1 channel?)
# use_sigmoid = False if use_lsgan = True
output = ops.last_conv(C512, reuse=self.reuse,
use_sigmoid=self.use_sigmoid, name='output') # (?, w/16, h/16, 1)
self.reuse = True
self.variables = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=self.name)
return output
似乎也没有什么好说的。
def cycle_consistency_loss(self, G, F, x, y):
""" cycle consistency loss (L1 norm)
"""
forward_loss = tf.reduce_mean(tf.abs(F(G(x))-x))
backward_loss = tf.reduce_mean(tf.abs(G(F(y))-y))
loss = self.lambda1*forward_loss + self.lambda2*backward_loss
return loss
这一部分其实很好理解,完全是按照CycleGAN原论文中的公式来说写的,其中G和F分别是两个生成器,也就是之前的Generator类的两个对象。两个对象分别转换后,我们希望得到的图像和原图像一样,也就是G(F(x))=x。其实本质上我们希望G和F是两个反函数。
tf.reduce_mean是tensorflow很常见的函数,在另一篇文章中有相关介绍,本质还是取平均数。
代码中的lambda1和lambda2默认是10。
fake_y = G(x)
loss = -tf.reduce_mean(ops.safe_log(D(fake_y))) / 2
这个函数的数学表达是-log(D(G(x)))
error_real = -tf.reduce_mean(ops.safe_log(D(y)))
error_fake = -tf.reduce_mean(ops.safe_log(1-D(fake_y)))
loss = (error_real + error_fake) / 2
该函数的数学表达是-(log(D(y))+log(1-D(G(x))))/2
这个优化器相当屌,以后可以学习这样写,Adam优化器初始的学习率是0.002,之后会每100k线性衰减到0。
这部分代码可以整体学习,很舒服。
model部分其实也比较好理解,首先是计算循环一致性损失,然后在X->Y和Y->X的变化中,分别计算G_loss和D_loss:
# 这是G|F的损失,需要加上循环一致性损失
G_gan_loss = self.generator_loss(self.D_Y, fake_y, use_lsgan=self.use_lsgan)
G_loss = G_gan_loss + cycle_loss
# 这是D的损失
D_Y_loss = self.discriminator_loss(self.D_Y, y, self.fake_y, use_lsgan=self.use_lsgan)
train.py部分的代码主要是工程代码,与神经网络的框架没有太大的关系,定义了checkpoints_dir等一些参数,同时也初始化了对象cycle_gan。包括在什么时候保存一次checkpoint(目前看是100次输出一次loss信息,10000次保存一次checkpoints)都已经定义好。
目前我有四个训练集:
1,斑马和马
2,橘子和苹果
3,哈士奇和老虎
4,风景和名画(阿弗列莫夫)
地址是:https://pan.baidu.com/s/194StB4nYr1amdCP25yRGPw
提取码:613r
可以将inference.py中的代码修改为:
"""
Translate an image to another image
An example of command-line usage is:
python export_graph.py --model pretrained/apple2orange.pb \
--input input_sample.jpg \
--output output_sample.jpg \
--image_size 256
"""
import cv2
import requests
import tensorflow as tf
import utils
FLAGS = tf.flags.FLAGS
tf.flags.DEFINE_string('model', 'blog/model/Mymodel/realman2cartoon.pb', 'model path (.pb)')
tf.flags.DEFINE_string('input', 'input_sample.jpg', 'input image path (.jpg) or input url path(http)')
tf.flags.DEFINE_string('output', 'output_sample.jpg', 'output image path (.jpg)')
tf.flags.DEFINE_integer('image_size', '256', 'image size, default: 256')
tf.flags.DEFINE_bool('isurl', False, 'is the input url?, default: False')
def inference(url="", outputpath="output.jpg",isurl = True, modelpath="zebra2horse.pb"):
graph = tf.Graph()
with graph.as_default():
if isurl:
image_data = requests.get(url=url).content
else:
with open(url,"rb") as f:
image_data = f.read()
input_image = tf.image.decode_jpeg(image_data, channels=3)
input_image = tf.image.resize_images(input_image, size=(FLAGS.image_size, FLAGS.image_size))
input_image = utils.convert2float(input_image)
input_image.set_shape([FLAGS.image_size, FLAGS.image_size, 3])
with tf.gfile.FastGFile(modelpath, 'rb') as model_file:
graph_def = tf.GraphDef()
graph_def.ParseFromString(model_file.read())
[output_image] = tf.import_graph_def(graph_def,
input_map={'input_image': input_image},
return_elements=['output_image:0'],
name='output')
with tf.Session(graph=graph) as sess:
generated = output_image.eval()
with open(outputpath, 'wb') as f:
f.write(generated)
if __name__ == '__main__':
inference(url=FLAGS.input,outputpath=FLAGS.output,isurl=FLAGS.isurl,modelpath=FLAGS.model)
在main中添加多张图像即可,其中参数含义为: