精华内容
下载资源
问答
  • 初学者入门GAN最好的代码。代码非常简单,下载好数据集后全部可以运行,几乎包括之前所有主流的30多个GAN代码,方便快速上手。
  • 对抗生成网络 各种GAN代码合集. ac gan, bgan, bigan, ccgan,info gan, srgan, wgan
  • GAN代码F GAN图像优化

    2018-03-20 02:58:28
    GAN图像优化 GAN图像优化 GAN图像优化 GAN图像优化 GAN图像优化 GAN图像优化 GAN图像优化 GAN图像优化 GAN图像优化
  • 详解GAN代码之逐行解析GAN代码

    万次阅读 多人点赞 2018-04-21 21:28:53
    训练数据集:手写数字识别下载链接:https://pan.baidu.com/s/1d9jX5xLHd1x3DFChVCe3LQ 密码:ws28 在本篇博客中,笔者将逐行解析一下NIPS 2014的Generative Adversarial Networks(生成对抗网络,简称GAN)代码,该...

    训练数据集:手写数字识别

    下载链接:https://pan.baidu.com/s/1d9jX5xLHd1x3DFChVCe3LQ 密码:ws28

       在本篇博客中,笔者将逐行解析一下NIPS 2014的Generative Adversarial Networks(生成对抗网络,简称GAN)代码,该篇文章作为GAN系列的开山之作,在近3年吸引了无数学者的目光。在2017-2018年,各大计算机顶会中也都能看到各种GAN的身影。因此,本篇博客就来逐行解析一下使用GAN生成手写数字的代码

       在正式开始之前,笔者想说的是,如果要使得本篇博客对各位读者朋友的学习有帮助,请各位读者朋友们先熟悉生成对抗网络的基本原理。由于对于生成对抗网络的原理详解网络上的资源比较多,在本篇博客中笔者就不再对生成对抗网络的原理进行解释,而是给大家推荐一些对生成对抗网络原理进行了解的链接:

    1. 直接进行论文阅读:https://arxiv.org/abs/1406.2661

    2. 一篇通俗易懂,形象的GAN原理解释:一文看懂生成式对抗网络GANs:介绍指南及前景展望

    3. 一篇比较详细的CSDN博文:生成式对抗网络GAN研究进展(二)——原始GAN

    4. 知乎专栏上的文章:GAN原理学习笔记

       如果对生成对抗网络原理已经熟稔的读者朋友,请自动忽略以上链接。并且,笔者以下放出的代码注释是参考了github上面的代码,链接https://github.com/wiseodd/generative-models

       在这里笔者也想衷心感谢一下这位wiseodd大神,在他的generative-models下面的关于生成模型的代码非常全面,本文解析的代码路径是该工程下面的GAN/vanilla_gan/gan_tensorflow.py文件。笔者沿用了作者的代码,只是增加了模型保存与summary记录的少量代码,下面放出代码及注释:

    import tensorflow as tf #导入tensorflow
    from tensorflow.examples.tutorials.mnist import input_data #导入手写数字数据集
    import numpy as np #导入numpy
    import matplotlib.pyplot as plt #plt是绘图工具,在训练过程中用于输出可视化结果
    import matplotlib.gridspec as gridspec #gridspec是图片排列工具,在训练过程中用于输出可视化结果
    import os #导入os
    
    def save(saver, sess, logdir, step): #保存模型的save函数
       model_name = 'model' #模型名前缀
       checkpoint_path = os.path.join(logdir, model_name) #保存路径
       saver.save(sess, checkpoint_path, global_step=step) #保存模型
       print('The checkpoint has been created.')
    
    def xavier_init(size): #初始化参数时使用的xavier_init函数
        in_dim = size[0] 
        xavier_stddev = 1. / tf.sqrt(in_dim / 2.) #初始化标准差
        return tf.random_normal(shape=size, stddev=xavier_stddev) #返回初始化的结果
    
    X = tf.placeholder(tf.float32, shape=[None, 784]) #X表示真的样本(即真实的手写数字)
    
    D_W1 = tf.Variable(xavier_init([784, 128])) #表示使用xavier方式初始化的判别器的D_W1参数,是一个784行128列的矩阵
    D_b1 = tf.Variable(tf.zeros(shape=[128])) #表示全零方式初始化的判别器的D_1参数,是一个长度为128的向量
    
    D_W2 = tf.Variable(xavier_init([128, 1])) #表示使用xavier方式初始化的判别器的D_W2参数,是一个128行1列的矩阵
    D_b2 = tf.Variable(tf.zeros(shape=[1])) ##表示全零方式初始化的判别器的D_1参数,是一个长度为1的向量
    
    theta_D = [D_W1, D_W2, D_b1, D_b2] #theta_D表示判别器的可训练参数集合
    
    
    Z = tf.placeholder(tf.float32, shape=[None, 100]) #Z表示生成器的输入(在这里是噪声),是一个N列100行的矩阵
    
    G_W1 = tf.Variable(xavier_init([100, 128])) #表示使用xavier方式初始化的生成器的G_W1参数,是一个100行128列的矩阵
    G_b1 = tf.Variable(tf.zeros(shape=[128])) #表示全零方式初始化的生成器的G_b1参数,是一个长度为128的向量
    
    G_W2 = tf.Variable(xavier_init([128, 784])) #表示使用xavier方式初始化的生成器的G_W2参数,是一个128行784列的矩阵
    G_b2 = tf.Variable(tf.zeros(shape=[784])) #表示全零方式初始化的生成器的G_b2参数,是一个长度为784的向量
    
    theta_G = [G_W1, G_W2, G_b1, G_b2] #theta_G表示生成器的可训练参数集合
    
    
    def sample_Z(m, n): #生成维度为[m, n]的随机噪声作为生成器G的输入
        return np.random.uniform(-1., 1., size=[m, n])
    
    
    def generator(z): #生成器,z的维度为[N, 100]
        G_h1 = tf.nn.relu(tf.matmul(z, G_W1) + G_b1) #输入的随机噪声乘以G_W1矩阵加上偏置G_b1,G_h1维度为[N, 128]
        G_log_prob = tf.matmul(G_h1, G_W2) + G_b2 #G_h1乘以G_W2矩阵加上偏置G_b2,G_log_prob维度为[N, 784]
        G_prob = tf.nn.sigmoid(G_log_prob) #G_log_prob经过一个sigmoid函数,G_prob维度为[N, 784]
    
        return G_prob #返回G_prob
    
    
    def discriminator(x): #判别器,x的维度为[N, 784]
        D_h1 = tf.nn.relu(tf.matmul(x, D_W1) + D_b1) #输入乘以D_W1矩阵加上偏置D_b1,D_h1维度为[N, 128]
        D_logit = tf.matmul(D_h1, D_W2) + D_b2 #D_h1乘以D_W2矩阵加上偏置D_b2,D_logit维度为[N, 1]
        D_prob = tf.nn.sigmoid(D_logit) #D_logit经过一个sigmoid函数,D_prob维度为[N, 1]
    
        return D_prob, D_logit #返回D_prob, D_logit
    
    
    def plot(samples): #保存图片时使用的plot函数
        fig = plt.figure(figsize=(4, 4)) #初始化一个4行4列包含16张子图像的图片
        gs = gridspec.GridSpec(4, 4) #调整子图的位置
        gs.update(wspace=0.05, hspace=0.05) #置子图间的间距
    
        for i, sample in enumerate(samples): #依次将16张子图填充进需要保存的图像
            ax = plt.subplot(gs[i])
            plt.axis('off')
            ax.set_xticklabels([])
            ax.set_yticklabels([])
            ax.set_aspect('equal')
            plt.imshow(sample.reshape(28, 28), cmap='Greys_r')
    
        return fig
    
    
    G_sample = generator(Z) #取得生成器的生成结果
    D_real, D_logit_real = discriminator(X) #取得判别器判别的真实手写数字的结果
    D_fake, D_logit_fake = discriminator(G_sample) #取得判别器判别的生成的手写数字的结果
    
    D_loss_real = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_real, labels=tf.ones_like(D_logit_real))) #对判别器对真实样本的判别结果计算误差(将结果与1比较)
    D_loss_fake = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, labels=tf.zeros_like(D_logit_fake))) #对判别器对虚假样本(即生成器生成的手写数字)的判别结果计算误差(将结果与0比较)
    D_loss = D_loss_real + D_loss_fake #判别器的误差
    G_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=D_logit_fake, labels=tf.ones_like(D_logit_fake))) #生成器的误差(将判别器返回的对虚假样本的判别结果与1比较)
    
    dreal_loss_sum = tf.summary.scalar("dreal_loss", D_loss_real) #记录判别器判别真实样本的误差
    dfake_loss_sum = tf.summary.scalar("dfake_loss", D_loss_fake) #记录判别器判别虚假样本的误差
    d_loss_sum = tf.summary.scalar("d_loss", D_loss) #记录判别器的误差
    g_loss_sum = tf.summary.scalar("g_loss", G_loss) #记录生成器的误差
    
    summary_writer = tf.summary.FileWriter('snapshots/', graph=tf.get_default_graph()) #日志记录器
    
    D_solver = tf.train.AdamOptimizer().minimize(D_loss, var_list=theta_D) #判别器的训练器
    G_solver = tf.train.AdamOptimizer().minimize(G_loss, var_list=theta_G) #生成器的训练器
    
    mb_size = 128 #训练的batch_size
    Z_dim = 100 #生成器输入的随机噪声的列的维度
    
    mnist = input_data.read_data_sets('../../MNIST_data', one_hot=True) #mnist是手写数字数据集
    
    sess = tf.Session() #会话层
    sess.run(tf.global_variables_initializer()) #初始化所有可训练参数
    
    if not os.path.exists('out/'): #初始化训练过程中的可视化结果的输出文件夹
        os.makedirs('out/')
    
    if not os.path.exists('snapshots/'): #初始化训练过程中的模型保存文件夹
        os.makedirs('snapshots/')
    
    saver = tf.train.Saver(var_list=tf.global_variables(), max_to_keep=50) #模型的保存器
    
    i = 0 #训练过程中保存的可视化结果的索引
    
    for it in range(1000000): #训练100万次
        if it % 1000 == 0: #每训练1000次就保存一下结果
            samples = sess.run(G_sample, feed_dict={Z: sample_Z(16, Z_dim)})
    
            fig = plot(samples) #通过plot函数生成可视化结果
            plt.savefig('out/{}.png'.format(str(i).zfill(3)), bbox_inches='tight') #保存可视化结果
            i += 1
            plt.close(fig)
    
        X_mb, _ = mnist.train.next_batch(mb_size) #得到训练一个batch所需的真实手写数字(作为判别器的输入)
    
        #下面是得到训练一次的结果,通过sess来run出来
        _, D_loss_curr, dreal_loss_sum_value, dfake_loss_sum_value, d_loss_sum_value = sess.run([D_solver, D_loss, dreal_loss_sum, dfake_loss_sum, d_loss_sum], feed_dict={X: X_mb, Z: sample_Z(mb_size, Z_dim)})
        _, G_loss_curr, g_loss_sum_value = sess.run([G_solver, G_loss, g_loss_sum], feed_dict={Z: sample_Z(mb_size, Z_dim)})
    
        if it%100 ==0: #每过100次记录一下日志,可以通过tensorboard查看
            summary_writer.add_summary(dreal_loss_sum_value, it)
            summary_writer.add_summary(dfake_loss_sum_value, it)
            summary_writer.add_summary(d_loss_sum_value, it)
            summary_writer.add_summary(g_loss_sum_value, it)
    
        if it % 1000 == 0: #每训练1000次输出一下结果
            save(saver, sess, 'snapshots/', it)
            print('Iter: {}'.format(it))
            print('D loss: {:.4}'. format(D_loss_curr))
            print('G_loss: {:.4}'.format(G_loss_curr))
            print()

       在上面的代码中,各位读者朋友可以看到,生成器与判别器都是使用多层感知机实现的(没有使用卷积神经网络)。生成器的输入是随机噪声,生成的是手写数字,生成器与判别器均使用Adam优化器进行训练并训练100w次。

       在上面的代码中,笔者添加了各种summary保存了训练中的误差,结果如下所示。

       判别器判别真实样本的误差变化:


       判别器判别虚假样本(即生成器G生成的手写数字)的误差变化:


       判别器的误差变化(上面两者之和):


       生成器的误差变化:


       下面是训练过程中输出的可视化结果,笔者选择了一些,大家可以看到,生成器输出结果最开始非常糟糕,但是随着训练的进行到训练中期输出效果越来越好:

       训练2k次的输出:


       训练6k次的输出:


       训练4.2w次的输出


       训练14.4w次的输出:


       训练24.4w次的输出:


       训练31.6w次的输出:


       在训练的后期(训练80w次之后),大家从生成器的误差曲线可以看出,生成器的误差陡增,生成效果也相应变差了(如下图所示),这是生成器与判别器失衡的结果。

       训练85.7w次的输出:


       训练93.6w次的输出:


       训练97.2w次的输出:


       到这里,生成对抗网络的代码讲解就接近尾声了,衷心希望笔者的本篇博客对大家有帮助!

       欢迎阅读笔者后续博客,各位读者朋友的支持与鼓励是我最大的动力!


    written by jiong

    日出入安穷?时世不与人同。

    故春非我春,夏非我夏,秋非我秋,

    冬,亦非我冬。

    展开全文
  • CycleGAN代码.zip

    2020-03-05 16:44:44
    PyTorch的CyCleGAN代码,标注清晰,适合新手免费学习。包括datasets、models、utils、train等4个文件,有问题可以一起讨论一下,我也是初学^_^
  • MMD-GAN代码

    2018-11-10 23:38:19
    MMD-GAN代码,python编写。。
  • 详解GAN代码之简单搭建并详细解析CycleGAN

    万次阅读 多人点赞 2018-04-29 21:29:06
     笔者搭建的CycleGAN代码分成5大部分,分别是: (1) train.py 训练的主控程序 (2) train_image_reader.py 训练数据读取接口 (3) net.py 定义网络结构 (4) evaluate.py 测试的主控程序 (5) test_image_reader.py ...

    训练数据集1:斑马与马

    下载链接:https://pan.baidu.com/s/1Zf6hvoDMsMi51WIPEOoqzg 密码:gua5

    训练数据集2:橘子与苹果

    下载链接:https://pan.baidu.com/s/1R0s2eaBxMCozbCCs7_1Juw 密码:z9y1

    训练数据集3:填充轮廓->建筑照片

    下载链接:https://pan.baidu.com/s/1xUg8AC7NEXyKebSUNtRvdg 密码:2kw1

       在笔者看来,CycleGAN是2017年度最有趣的深度学习成果之一,因为实现了两个域的图像的互相转换(风格迁移),比如下面两个论文中所举的例子(截图来自论文):

    (1) 将一副印象派画家莫奈的画作转化为相片(同时可将相片转化成印象派画作)


    (2) 将拍摄的冬日景象转化成夏日景象(同时可将夏日景象转化成冬日景象)


       在本篇博文中,笔者就教大家使用简单的代码搭建CycleGAN,亲自动手实践两个域的图片的相互转换。

       在开始搭建CycleGAN与代码解析之前,笔者想说的是:要亲自搭建CycleGAN框架,还需各位读者朋友明白CycleGAN的原理。鉴于CycleGAN的原理网上已经有很多资料进行解析,笔者在下面提供一些笔者认为比较好的途径:

    (1) 直接进行论文阅读:https://arxiv.org/abs/1703.10593

    (2) 推荐看这篇之乎专栏解析的CycleGAN:可能是近期最好玩的深度学习模型:CycleGAN的原理与实验详解,里面不仅介绍了CycleGAN的原理,还介绍了其他应用,比如如何把女人变成男人,男人变成女人,猫狗之间的互相转换,如何去除图片中的马赛克(额,车速较快请握紧扶手。)

    (3) 笔者也在本篇博客中简单介绍一下CycleGAN的原理(截图来自论文):


       CycleGAN是一个环形的结构,主要由两个生成器及两个判别器组成,如上图所示。X表示X域的图像,Y表示Y域的图像。X域的图像通过生成器G生成Y域的图像,再通过生成器F重构回X域输入的原图像;Y域的图像通过生成器F生成X域图像,再通过生成器G重构回Y域输入的原图像。判别器Dx和Dy起到判别作用,确保图像的风格迁移。

       CycleGAN的训练的损失函数如下所示(截图来自论文):




       可以看到,CycleGAN的loss函数分成两大部分,一共四块。其中两块GAN loss,两块L1 loss。整个CycleGAN框架采用end-to-end方式训练。


       总而言之,CycleGAN想要达到的目的是,完成两个域之间的风格转换,在风格转换的同时,又要确保图中物体的几何形状和空间关系不发生变化。

       下面,笔者就放出搭建CycleGAN的源码,首先列举一下笔者主要使用的工具和库:

    (1) Python 3.5.2

    (2) numpy

    (3) Tensorflow 1.2

    (4) argparse 用来解析命令行参数

    (5) random 用来打乱输入顺序

    (6) os 用来读取图片路径和文件名

    (7) glob 用来读取图片路径和文件名

    (8) cv2 用来读取图片

       笔者搭建的CycleGAN代码分成5大部分,分别是:

    (1) train.py 训练的主控程序

    (2) train_image_reader.py 训练数据读取接口

    (3) net.py 定义网络结构

    (4) evaluate.py 测试的主控程序

    (5) test_image_reader.py 训练数据读取接口

       其中,训练时使用到的文件是(1),(2),(3)项,测试时使用到的文件时(3),(4),(5)。

       下面,笔者放出代码与注释:

    首先是train.py文件的代码:

    from __future__ import print_function
    
    import argparse
    from datetime import datetime
    from random import shuffle
    import random
    import os
    import sys
    import time
    import math
    import tensorflow as tf
    import numpy as np
    import glob
    import cv2
    
    from train_image_reader import *
    from net import *
    
    parser = argparse.ArgumentParser(description='')
    
    parser.add_argument("--snapshot_dir", default='./snapshots', help="path of snapshots") #保存模型的路径
    parser.add_argument("--out_dir", default='./train_out', help="path of train outputs") #训练时保存可视化输出的路径
    parser.add_argument("--image_size", type=int, default=256, help="load image size") #网络输入的尺度
    parser.add_argument("--random_seed", type=int, default=1234, help="random seed") #随机数种子
    parser.add_argument('--base_lr', type=float, default=0.0002, help='initial learning rate for adam') #基础学习率
    parser.add_argument('--epoch', dest='epoch', type=int, default=200, help='# of epoch') #训练的epoch数量
    parser.add_argument('--epoch_step', dest='epoch_step', type=int, default=100, help='# of epoch to decay lr') #训练中保持学习率不变的epoch数量
    parser.add_argument("--lamda", type=float, default=10.0, help="L1 lamda") #训练中L1_Loss前的乘数
    parser.add_argument('--beta1', dest='beta1', type=float, default=0.5, help='momentum term of adam') #adam优化器的beta1参数
    parser.add_argument("--summary_pred_every", type=int, default=200, help="times to summary.") #训练中每过多少step保存训练日志(记录一下loss值)
    parser.add_argument("--write_pred_every", type=int, default=100, help="times to write.") #训练中每过多少step保存可视化结果
    parser.add_argument("--save_pred_every", type=int, default=10000, help="times to save.") #训练中每过多少step保存模型(可训练参数)
    parser.add_argument("--x_train_data_path", default='./dataset/horse2zebra/trainA/', help="path of x training datas.") #x域的训练图片路径
    parser.add_argument("--y_train_data_path", default='./dataset/horse2zebra/trainB/', help="path of y training datas.") #y域的训练图片路径
    
    args = parser.parse_args()
    
    def save(saver, sess, logdir, step): #保存模型的save函数
       model_name = 'model' #保存的模型名前缀
       checkpoint_path = os.path.join(logdir, model_name) #模型的保存路径与名称
       if not os.path.exists(logdir): #如果路径不存在即创建
          os.makedirs(logdir)
       saver.save(sess, checkpoint_path, global_step=step) #保存模型
       print('The checkpoint has been created.')
    
    def cv_inv_proc(img): #cv_inv_proc函数将读取图片时归一化的图片还原成原图
        img_rgb = (img + 1.) * 127.5
        return img_rgb.astype(np.float32) #返回bgr格式的图像,方便cv2写图像
    
    def get_write_picture(x_image, y_image, fake_y, fake_x_, fake_x, fake_y_): #get_write_picture函数得到训练过程中的可视化结果
        x_image = cv_inv_proc(x_image) #还原x域的图像
        y_image = cv_inv_proc(y_image) #还原y域的图像
        fake_y = cv_inv_proc(fake_y[0]) #还原生成的y域的图像
        fake_x_ = cv_inv_proc(fake_x_[0]) #还原重建的x域的图像
        fake_x = cv_inv_proc(fake_x[0]) #还原生成的x域的图像
        fake_y_ = cv_inv_proc(fake_y_[0]) #还原重建的y域的图像
        row1 = np.concatenate((x_image, fake_y, fake_x_), axis=1) #得到训练中可视化结果的第一行
        row2 = np.concatenate((y_image, fake_x, fake_y_), axis=1) #得到训练中可视化结果的第二行
        output = np.concatenate((row1, row2), axis=0) #得到训练中可视化结果
        return output
    
    def make_train_data_list(x_data_path, y_data_path): #make_train_data_list函数得到训练中的x域和y域的图像路径名称列表
        x_input_images_raw = glob.glob(os.path.join(x_data_path, "*")) #读取全部的x域图像路径名称列表
        y_input_images_raw = glob.glob(os.path.join(y_data_path, "*")) #读取全部的y域图像路径名称列表
        x_input_images, y_input_images = add_train_list(x_input_images_raw, y_input_images_raw) #将x域图像数量与y域图像数量对齐
        return x_input_images, y_input_images
    
    def add_train_list(x_input_images_raw, y_input_images_raw): #add_train_list函数将x域和y域的图像数量变成一致
        if len(x_input_images_raw) == len(y_input_images_raw): #如果x域和y域图像数量本来就一致,直接返回
            return shuffle(x_input_images_raw), shuffle(y_input_images_raw)
        elif len(x_input_images_raw) > len(y_input_images_raw): #如果x域的训练图像数量大于y域的训练图像数量,则随机选择y域的图像补充y域
            mul_num = int(len(x_input_images_raw)/len(y_input_images_raw)) #计算两域图像数量相差的倍数
            y_append_num = len(x_input_images_raw) - len(y_input_images_raw)*mul_num #计算需要随机出的y域图像数量
            append_list = [random.randint(0,len(y_input_images_raw)-1) for i in range(y_append_num)] #得到需要补充的y域图像下标
            y_append_images = [] #初始化需要被补充的y域图像路径名称列表
            for a in append_list:
                y_append_images.append(y_input_images_raw[a])
            y_input_images = y_input_images_raw * mul_num + y_append_images #得到数量与x域一致的y域图像
            shuffle(x_input_images_raw) #随机打乱x域图像顺序
            shuffle(y_input_images) #随机打乱y域图像顺序
            return x_input_images_raw, y_input_images #返回数量一致的x域和y域图像路径名称列表
        else: #与elif中的逻辑一致,只是x与y互换,不再赘述
            mul_num = int(len(y_input_images_raw)/len(x_input_images_raw))
            x_append_num = len(y_input_images_raw) - len(x_input_images_raw)*mul_num
            append_list = [random.randint(0,len(x_input_images_raw)-1) for i in range(x_append_num)]
            x_append_images = []
            for a in append_list:
                x_append_images.append(x_input_images_raw[a])
            x_input_images = x_input_images_raw * mul_num + x_append_images
            shuffle(y_input_images_raw)
            shuffle(x_input_images)
            return x_input_images, y_input_images_raw
        
    def l1_loss(src, dst): #定义l1_loss
        return tf.reduce_mean(tf.abs(src - dst))
    
    def gan_loss(src, dst): #定义gan_loss,在这里用了二范数
        return tf.reduce_mean((src-dst)**2)
    
    def main():
        if not os.path.exists(args.snapshot_dir): #如果保存模型参数的文件夹不存在则创建
            os.makedirs(args.snapshot_dir)
        if not os.path.exists(args.out_dir): #如果保存训练中可视化输出的文件夹不存在则创建
            os.makedirs(args.out_dir)
        x_datalists, y_datalists = make_train_data_list(args.x_train_data_path, args.y_train_data_path) #得到数量相同的x域和y域图像路径名称列表
        tf.set_random_seed(args.random_seed) #初始一下随机数
        x_img = tf.placeholder(tf.float32,shape=[1, args.image_size, args.image_size,3],name='x_img') #输入的x域图像
        y_img = tf.placeholder(tf.float32,shape=[1, args.image_size, args.image_size,3],name='y_img') #输入的y域图像
    
        fake_y = generator(image=x_img, reuse=False, name='generator_x2y') #生成的y域图像
        fake_x_ = generator(image=fake_y, reuse=False, name='generator_y2x') #重建的x域图像
        fake_x = generator(image=y_img, reuse=True, name='generator_y2x') #生成的x域图像
        fake_y_ = generator(image=fake_x, reuse=True, name='generator_x2y') #重建的y域图像
    
        dy_fake = discriminator(image=fake_y, reuse=False, name='discriminator_y') #判别器返回的对生成的y域图像的判别结果
        dx_fake = discriminator(image=fake_x, reuse=False, name='discriminator_x') #判别器返回的对生成的x域图像的判别结果
        dy_real = discriminator(image=y_img, reuse=True, name='discriminator_y') #判别器返回的对真实的y域图像的判别结果
        dx_real = discriminator(image=x_img, reuse=True, name='discriminator_x') #判别器返回的对真实的x域图像的判别结果
    
        gen_loss = gan_loss(dy_fake, tf.ones_like(dy_fake)) + gan_loss(dx_fake, tf.ones_like(dx_fake)) + args.lamda*l1_loss(x_img, fake_x_) + args.lamda*l1_loss(y_img, fake_y_) #计算生成器的loss
    
        dy_loss_real = gan_loss(dy_real, tf.ones_like(dy_real)) #计算判别器判别的真实的y域图像的loss
        dy_loss_fake = gan_loss(dy_fake, tf.zeros_like(dy_fake)) #计算判别器判别的生成的y域图像的loss
        dy_loss = (dy_loss_real + dy_loss_fake) / 2 #计算判别器判别的y域图像的loss
    
        dx_loss_real = gan_loss(dx_real, tf.ones_like(dx_real)) #计算判别器判别的真实的x域图像的loss
        dx_loss_fake = gan_loss(dx_fake, tf.zeros_like(dx_fake)) #计算判别器判别的生成的x域图像的loss
        dx_loss = (dx_loss_real + dx_loss_fake) / 2 #计算判别器判别的x域图像的loss
    
        dis_loss = dy_loss + dx_loss #计算判别器的loss
    
        gen_loss_sum = tf.summary.scalar("final_objective", gen_loss) #记录生成器loss的日志
    
        dx_loss_sum = tf.summary.scalar("dx_loss", dx_loss) #记录判别器判别的x域图像的loss的日志
        dy_loss_sum = tf.summary.scalar("dy_loss", dy_loss) #记录判别器判别的y域图像的loss的日志
        dis_loss_sum = tf.summary.scalar("dis_loss", dis_loss) #记录判别器的loss的日志
        discriminator_sum = tf.summary.merge([dx_loss_sum, dy_loss_sum, dis_loss_sum])
    
        summary_writer = tf.summary.FileWriter(args.snapshot_dir, graph=tf.get_default_graph()) #日志记录器
    
        g_vars = [v for v in tf.trainable_variables() if 'generator' in v.name] #所有生成器的可训练参数
        d_vars = [v for v in tf.trainable_variables() if 'discriminator' in v.name] #所有判别器的可训练参数
    
        lr = tf.placeholder(tf.float32, None, name='learning_rate') #训练中的学习率
        d_optim = tf.train.AdamOptimizer(lr, beta1=args.beta1) #判别器训练器
        g_optim = tf.train.AdamOptimizer(lr, beta1=args.beta1) #生成器训练器
    
        d_grads_and_vars = d_optim.compute_gradients(dis_loss, var_list=d_vars) #计算判别器参数梯度
        d_train = d_optim.apply_gradients(d_grads_and_vars) #更新判别器参数
        g_grads_and_vars = g_optim.compute_gradients(gen_loss, var_list=g_vars) #计算生成器参数梯度
        g_train = g_optim.apply_gradients(g_grads_and_vars) #更新生成器参数
    
        train_op = tf.group(d_train, g_train) #train_op表示了参数更新操作
        config = tf.ConfigProto()
        config.gpu_options.allow_growth = True #设定显存不超量使用
        sess = tf.Session(config=config) #新建会话层
        init = tf.global_variables_initializer() #参数初始化器
    
        sess.run(init) #初始化所有可训练参数
    
        saver = tf.train.Saver(var_list=tf.global_variables(), max_to_keep=50) #模型保存器
    
        counter = 0 #counter记录训练步数
    
        for epoch in range(args.epoch): #训练epoch数
            shuffle(x_datalists) #每训练一个epoch,就打乱一下x域图像顺序
            shuffle(y_datalists) #每训练一个epoch,就打乱一下y域图像顺序
            lrate = args.base_lr if epoch < args.epoch_step else args.base_lr*(args.epoch-epoch)/(args.epoch-args.epoch_step) #得到该训练epoch的学习率
            for step in range(len(x_datalists)): #每个训练epoch中的训练step数
                counter += 1
                x_image_resize, y_image_resize = TrainImageReader(x_datalists, y_datalists, step, args.image_size) #读取x域图像和y域图像
                batch_x_image = np.expand_dims(np.array(x_image_resize).astype(np.float32), axis = 0) #填充维度
                batch_y_image = np.expand_dims(np.array(y_image_resize).astype(np.float32), axis = 0) #填充维度
                feed_dict = { lr : lrate, x_img : batch_x_image, y_img : batch_y_image} #得到feed_dict
                gen_loss_value, dis_loss_value, _ = sess.run([gen_loss, dis_loss, train_op], feed_dict=feed_dict) #得到每个step中的生成器和判别器loss
                if counter % args.save_pred_every == 0: #每过save_pred_every次保存模型
                    save(saver, sess, args.snapshot_dir, counter)
                if counter % args.summary_pred_every == 0: #每过summary_pred_every次保存训练日志
                    gen_loss_sum_value, discriminator_sum_value = sess.run([gen_loss_sum, discriminator_sum], feed_dict=feed_dict)
                    summary_writer.add_summary(gen_loss_sum_value, counter)
                    summary_writer.add_summary(discriminator_sum_value, counter)
                if counter % args.write_pred_every == 0: #每过write_pred_every次写一下训练的可视化结果
                    fake_y_value, fake_x__value, fake_x_value, fake_y__value = sess.run([fake_y, fake_x_, fake_x, fake_y_], feed_dict=feed_dict) #run出网络输出
                    write_image = get_write_picture(x_image_resize, y_image_resize, fake_y_value, fake_x__value, fake_x_value, fake_y__value) #得到训练的可视化结果
                    write_image_name = args.out_dir + "/out"+ str(counter) + ".png" #待保存的训练可视化结果路径与名称
                    cv2.imwrite(write_image_name, write_image) #保存训练的可视化结果
                print('epoch {:d} step {:d} \t gen_loss = {:.3f}, dis_loss = {:.3f}'.format(epoch, step, gen_loss_value, dis_loss_value))
        
    if __name__ == '__main__':
        main()
    

    然后是train_image_reader.py文件:

    import os
    
    import numpy as np
    import tensorflow as tf
    import cv2
    
    def TrainImageReader(x_file_list, y_file_list, step, size): #训练数据读取接口
        file_length = len(x_file_list) #获取图片列表总长度
        line_idx = step % file_length #获取一张待读取图片的下标
        x_line_content = x_file_list[line_idx] #获取一张x域图片路径与名称
        y_line_content = y_file_list[line_idx] #获取一张y域图片路径与名称
        x_image = cv2.imread(x_line_content,1) #读取一张x域的图片
        y_image = cv2.imread(y_line_content,1) #读取一张y域的图片
        x_image_resize_t = cv2.resize(x_image, (size, size)) #改变读取的x域图片的大小
        x_image_resize = x_image_resize_t/127.5-1. #归一化x域的图片
        y_image_resize_t = cv2.resize(y_image, (size, size)) #改变读取的y域图片的大小
        y_image_resize = y_image_resize_t/127.5-1. #归一化y域的图片
        return x_image_resize, y_image_resize #返回读取并处理的一张x域图片和y域图片
    

    接着是net.py文件:

    import numpy as np
    import tensorflow as tf
    import math
    
    #构造可训练参数
    def make_var(name, shape, trainable = True):
        return tf.get_variable(name, shape, trainable = trainable)
    
    #定义卷积层
    def conv2d(input_, output_dim, kernel_size, stride, padding = "SAME", name = "conv2d", biased = False):
        input_dim = input_.get_shape()[-1]
        with tf.variable_scope(name):
            kernel = make_var(name = 'weights', shape=[kernel_size, kernel_size, input_dim, output_dim])
            output = tf.nn.conv2d(input_, kernel, [1, stride, stride, 1], padding = padding)
            if biased:
                biases = make_var(name = 'biases', shape = [output_dim])
                output = tf.nn.bias_add(output, biases)
            return output
    
    #定义空洞卷积层
    def atrous_conv2d(input_, output_dim, kernel_size, dilation, padding = "SAME", name = "atrous_conv2d", biased = False):
        input_dim = input_.get_shape()[-1]
        with tf.variable_scope(name):
            kernel = make_var(name = 'weights', shape = [kernel_size, kernel_size, input_dim, output_dim])
            output = tf.nn.atrous_conv2d(input_, kernel, dilation, padding = padding)
            if biased:
                biases = make_var(name = 'biases', shape = [output_dim])
                output = tf.nn.bias_add(output, biases)
            return output
    
    #定义反卷积层
    def deconv2d(input_, output_dim, kernel_size, stride, padding = "SAME", name = "deconv2d"):
        input_dim = input_.get_shape()[-1]
        input_height = int(input_.get_shape()[1])
        input_width = int(input_.get_shape()[2])
        with tf.variable_scope(name):
            kernel = make_var(name = 'weights', shape = [kernel_size, kernel_size, output_dim, input_dim])
            output = tf.nn.conv2d_transpose(input_, kernel, [1, input_height * 2, input_width * 2, output_dim], [1, 2, 2, 1], padding = "SAME")
            return output
    
    #定义batchnorm(批次归一化)层
    def batch_norm(input_, name="batch_norm"):
        with tf.variable_scope(name):
            input_dim = input_.get_shape()[-1]
            scale = tf.get_variable("scale", [input_dim], initializer=tf.random_normal_initializer(1.0, 0.02, dtype=tf.float32))
            offset = tf.get_variable("offset", [input_dim], initializer=tf.constant_initializer(0.0))
            mean, variance = tf.nn.moments(input_, axes=[1,2], keep_dims=True)
            epsilon = 1e-5
            inv = tf.rsqrt(variance + epsilon)
            normalized = (input_-mean)*inv
            output = scale*normalized + offset
            return output
    
    #定义最大池化层
    def max_pooling(input_, kernel_size, stride, name, padding = "SAME"):
        return tf.nn.max_pool(input_, ksize=[1, kernel_size, kernel_size, 1], strides=[1, stride, stride, 1], padding=padding, name=name)
    
    #定义平均池化层
    def avg_pooling(input_, kernel_size, stride, name, padding = "SAME"):
        return tf.nn.avg_pool(input_, ksize=[1, kernel_size, kernel_size, 1], strides=[1, stride, stride, 1], padding=padding, name=name)
    
    #定义lrelu激活层
    def lrelu(x, leak=0.2, name = "lrelu"):
        return tf.maximum(x, leak*x)
    
    #定义relu激活层
    def relu(input_, name = "relu"):
        return tf.nn.relu(input_, name = name)
    
    #定义残差块
    def residule_block_33(input_, output_dim, kernel_size = 3, stride = 1, dilation = 2, atrous = False, name = "res"):
        if atrous:
            conv2dc0 = atrous_conv2d(input_ = input_, output_dim = output_dim, kernel_size = kernel_size, dilation = dilation, name = (name + '_c0'))
            conv2dc0_norm = batch_norm(input_ = conv2dc0, name = (name + '_bn0'))
            conv2dc0_relu = relu(input_ = conv2dc0_norm)
            conv2dc1 = atrous_conv2d(input_ = conv2dc0_relu, output_dim = output_dim, kernel_size = kernel_size, dilation = dilation, name = (name + '_c1'))
            conv2dc1_norm = batch_norm(input_ = conv2dc1, name = (name + '_bn1'))
        else:
            conv2dc0 = conv2d(input_ = input_, output_dim = output_dim, kernel_size = kernel_size, stride = stride, name = (name + '_c0'))
            conv2dc0_norm = batch_norm(input_ = conv2dc0, name = (name + '_bn0'))
            conv2dc0_relu = relu(input_ = conv2dc0_norm)
            conv2dc1 = conv2d(input_ = conv2dc0_relu, output_dim = output_dim, kernel_size = kernel_size, stride = stride, name = (name + '_c1'))
            conv2dc1_norm = batch_norm(input_ = conv2dc1, name = (name + '_bn1'))
        add_raw = input_ + conv2dc1_norm
        output = relu(input_ = add_raw)
        return output
    
    #定义生成器
    def generator(image, gf_dim=64, reuse=False, name="generator"): 
        #生成器输入尺度: 1*256*256*3  
        input_dim = image.get_shape()[-1]
        with tf.variable_scope(name):
            if reuse:
                tf.get_variable_scope().reuse_variables()
            else:
                assert tf.get_variable_scope().reuse is False
            #第1个卷积模块,输出尺度: 1*256*256*64  
            c0 = relu(batch_norm(conv2d(input_ = image, output_dim = gf_dim, kernel_size = 7, stride = 1, name = 'g_e0_c'), name = 'g_e0_bn'))
            #第2个卷积模块,输出尺度: 1*128*128*128
            c1 = relu(batch_norm(conv2d(input_ = c0, output_dim = gf_dim * 2, kernel_size = 3, stride = 2, name = 'g_e1_c'), name = 'g_e1_bn'))
            #第3个卷积模块,输出尺度: 1*64*64*256
            c2 = relu(batch_norm(conv2d(input_ = c1, output_dim = gf_dim * 4, kernel_size = 3, stride = 2, name = 'g_e2_c'), name = 'g_e2_bn'))
            
            #9个残差块:
            r1 = residule_block_33(input_ = c2, output_dim = gf_dim*4, atrous = False, name='g_r1')
            r2 = residule_block_33(input_ = r1, output_dim = gf_dim*4, atrous = False, name='g_r2')
            r3 = residule_block_33(input_ = r2, output_dim = gf_dim*4, atrous = False, name='g_r3')
            r4 = residule_block_33(input_ = r3, output_dim = gf_dim*4, atrous = False, name='g_r4')
            r5 = residule_block_33(input_ = r4, output_dim = gf_dim*4, atrous = False, name='g_r5')
            r6 = residule_block_33(input_ = r5, output_dim = gf_dim*4, atrous = False, name='g_r6')
            r7 = residule_block_33(input_ = r6, output_dim = gf_dim*4, atrous = False, name='g_r7')
            r8 = residule_block_33(input_ = r7, output_dim = gf_dim*4, atrous = False, name='g_r8')
            r9 = residule_block_33(input_ = r8, output_dim = gf_dim*4, atrous = False, name='g_r9')
            #第9个残差块的输出尺度: 1*64*64*256
    
    		#第1个反卷积模块,输出尺度: 1*128*128*128
            d1 = relu(batch_norm(deconv2d(input_ = r9, output_dim = gf_dim * 2, kernel_size = 3, stride = 2, name = 'g_d1_dc'),name = 'g_d1_bn'))
    		#第2个反卷积模块,输出尺度: 1*256*256*64
            d2 = relu(batch_norm(deconv2d(input_ = d1, output_dim = gf_dim, kernel_size = 3, stride = 2, name = 'g_d2_dc'),name = 'g_d2_bn'))
    		#最后一个卷积模块,输出尺度: 1*256*256*3
            d3 = conv2d(input_=d2, output_dim  = input_dim, kernel_size = 7, stride = 1, name = 'g_d3_c')
    		#经过tanh函数激活得到生成的输出
            output = tf.nn.tanh(d3)
            return output
    
    #定义判别器
    def discriminator(image, df_dim=64, reuse=False, name="discriminator"):
        with tf.variable_scope(name):
            if reuse:
                tf.get_variable_scope().reuse_variables()
            else:
                assert tf.get_variable_scope().reuse is False
    		#第1个卷积模块,输出尺度: 1*128*128*64
            h0 = lrelu(conv2d(input_ = image, output_dim = df_dim, kernel_size = 4, stride = 2, name='d_h0_conv'))
    		#第2个卷积模块,输出尺度: 1*64*64*128
            h1 = lrelu(batch_norm(conv2d(input_ = h0, output_dim = df_dim*2, kernel_size = 4, stride = 2, name='d_h1_conv'), 'd_bn1'))
    		#第3个卷积模块,输出尺度: 1*32*32*256
            h2 = lrelu(batch_norm(conv2d(input_ = h1, output_dim = df_dim*4, kernel_size = 4, stride = 2, name='d_h2_conv'), 'd_bn2'))
    		#第4个卷积模块,输出尺度: 1*32*32*512
            h3 = lrelu(batch_norm(conv2d(input_ = h2, output_dim = df_dim*8, kernel_size = 4, stride = 1, name='d_h3_conv'), 'd_bn3'))
    		#最后一个卷积模块,输出尺度: 1*32*32*1
            output = conv2d(input_ = h3, output_dim = 1, kernel_size = 4, stride = 1, name='d_h4_conv')
            return output

       上面的三个文件就是训练时所需的全部代码,如果要启动训练,只需改动两个参数即可即train.py中参数中的最后两个(即x_train_data_path和Y_train_data_path,指X域和Y域的训练输入图像路径)。

    下面是evaluate.py文件:

    from __future__ import print_function
    
    import argparse
    from datetime import datetime
    from random import shuffle
    import os
    import sys
    import time
    import math
    import tensorflow as tf
    import numpy as np
    import glob
    import cv2
    
    from test_image_reader import *
    from net import *
    
    parser = argparse.ArgumentParser(description='')
    
    parser.add_argument("--x_test_data_path", default='./dataset/horse2zebra/testA/', help="path of x test datas.") #x域的测试图片路径
    parser.add_argument("--y_test_data_path", default='./dataset/horse2zebra/testB/', help="path of y test datas.") #y域的测试图片路径
    parser.add_argument("--image_size", type=int, default=256, help="load image size") #网络输入的尺度
    parser.add_argument("--snapshots", default='./snapshots/',help="Path of Snapshots") #读取训练好的模型参数的路径
    parser.add_argument("--out_dir_x", default='./test_output_x/',help="Output Folder") #保存x域的输入图片与生成的y域图片的路径
    parser.add_argument("--out_dir_y", default='./test_output_y/',help="Output Folder") #保存y域的输入图片与生成的x域图片的路径
    
    args = parser.parse_args()
    
    def make_test_data_list(x_data_path, y_data_path): #make_test_data_list函数得到测试中的x域和y域的图像路径名称列表
        x_input_images = glob.glob(os.path.join(x_data_path, "*")) #读取全部的x域图像路径名称列表
        y_input_images = glob.glob(os.path.join(y_data_path, "*")) #读取全部的y域图像路径名称列表
        return x_input_images, y_input_images
    
    def cv_inv_proc(img): #cv_inv_proc函数将读取图片时归一化的图片还原成原图
        img_rgb = (img + 1.) * 127.5
        return img_rgb.astype(np.float32) #bgr
    
    def get_write_picture(x_image, y_image, fake_y, fake_x): #get_write_picture函数得到网络测试结果
        x_image = cv_inv_proc(x_image) #还原x域的图像
        y_image = cv_inv_proc(y_image) #还原y域的图像
        fake_y = cv_inv_proc(fake_y[0]) #还原生成的y域的图像
        fake_x = cv_inv_proc(fake_x[0]) #还原生成的x域的图像
        x_output = np.concatenate((x_image, fake_y), axis=1) #得到x域的输入图像以及对应的生成的y域图像
        y_output = np.concatenate((y_image, fake_x), axis=1) #得到y域的输入图像以及对应的生成的x域图像
        return x_output, y_output
    
    def main():
        if not os.path.exists(args.out_dir_x): #如果保存x域测试结果的文件夹不存在则创建
            os.makedirs(args.out_dir_x)
        if not os.path.exists(args.out_dir_y): #如果保存y域测试结果的文件夹不存在则创建
            os.makedirs(args.out_dir_y)
    
        x_datalists, y_datalists = make_test_data_list(args.x_test_data_path, args.y_test_data_path) #得到待测试的x域和y域图像路径名称列表
        test_x_image = tf.placeholder(tf.float32,shape=[1, 256, 256, 3], name = 'test_x_image') #输入的x域图像
        test_y_image = tf.placeholder(tf.float32,shape=[1, 256, 256, 3], name = 'test_y_image') #输入的y域图像
    
        fake_y = generator(image=test_x_image, reuse=False, name='generator_x2y') #得到生成的y域图像
        fake_x = generator(image=test_y_image, reuse=False, name='generator_y2x') #得到生成的x域图像
    
        restore_var = [v for v in tf.global_variables() if 'generator' in v.name] #需要载入的已训练的模型参数
    
        config = tf.ConfigProto()
        config.gpu_options.allow_growth = True #设定显存不超量使用
        sess = tf.Session(config=config) #建立会话层
        
        saver = tf.train.Saver(var_list=restore_var,max_to_keep=1) #导入模型参数时使用
        checkpoint = tf.train.latest_checkpoint(args.snapshots) #读取模型参数
        saver.restore(sess, checkpoint) #导入模型参数
    
        total_step = len(x_datalists) if len(x_datalists) > len(y_datalists) else len(y_datalists) #测试的总步数
        for step in range(total_step):
            test_ximage_name, test_ximage = TestImageReader(x_datalists, step, args.image_size) #得到x域的输入及名称
            test_yimage_name, test_yimage = TestImageReader(y_datalists, step, args.image_size) #得到y域的输入及名称
            batch_x_image = np.expand_dims(np.array(test_ximage).astype(np.float32), axis = 0) #填充维度
            batch_y_image = np.expand_dims(np.array(test_yimage).astype(np.float32), axis = 0) #填充维度
            feed_dict = { test_x_image : batch_x_image, test_y_image : batch_y_image} #建立feed_dict
            fake_y_value, fake_x_value = sess.run([fake_y, fake_x], feed_dict=feed_dict) #得到生成的y域图像与x域图像
            x_write_image, y_write_image = get_write_picture(test_ximage, test_yimage, fake_y_value, fake_x_value) #得到最终的图片结果
            x_write_image_name = args.out_dir_x + "/"+ test_ximage_name + ".png" #待保存的x域图像与其对应的y域生成结果名字
            y_write_image_name = args.out_dir_y + "/"+ test_yimage_name + ".png" #待保存的y域图像与其对应的x域生成结果名字
            cv2.imwrite(x_write_image_name, x_write_image) #保存图像
            cv2.imwrite(y_write_image_name, y_write_image) #保存图像
            print('step {:d}'.format(step))
    
    if __name__ == '__main__':
        main()

    最后是test_image_reader.py文件的代码:

    import os
    
    import numpy as np
    import tensorflow as tf
    import cv2
    
    def TestImageReader(file_list, step, size): #训练数据读取接口
        file_length = len(file_list) #获取图片列表总长度
        line_idx = step % file_length #获取一张待读取图片的下标
        test_line_content = file_list[line_idx] #获取一张测试图片路径与名称
        test_image_name, _ = os.path.splitext(os.path.basename(test_line_content)) #获取该张测试图片名
        test_image = cv2.imread(test_line_content, 1) #读取一张测试图片
        test_image_resize_t = cv2.resize(test_image, (size, size)) #改变读取的测试图片的大小
        test_image_resize = test_image_resize_t/127.5-1 #归一化测试图片
        return test_image_name, test_image_resize #返回读取并处理的一张测试图片与它的名称
    

       如果需要测试训练好的模型,在evaluate.py文件中设置三个参数即可。分别是第1个(x_test_data_path,指定X域测试输入图片的路径),第2个(y_test_data_path,指定Y域测试输入图片的路径)和第四个(snapshots,设置为训练的模型保存路径)

       下面,笔者就以训练的马和斑马相互转换为例,展示一下CycleGAN的实际效果:

       首先是训练时的可视化输出图像,第一行从左往右三张依次是X域输入图像(马),生成的Y域图像(斑马),重建回的X域输入图像;第二行从左往右三张依次是Y域输入图像(斑马),生成的X域图像(马),重建回的Y域输入图像

    训练200次时的效果:


    训练16500次的效果:


    训练35000次的效果:


    训练86200次的效果:


    训练160800次的效果:


    训练209300次的效果:


    训练265500次的效果:


    下面展示一下训练过程中的loss变化:

    首先是判别器Dx的loss曲线:


    然后是判别器Dy的loss曲线:


    接着是判别loss曲线:


    最后是CycleGAN总的目标函数loss曲线:


       下面展示一些模型测试时斑马与马互相转换的效果:

       每张图片中,左边的是输入图像,右边的是生成结果

    先展示一些成功的转换:

    马->斑马



    斑马->马


    再展示一些失败的转换案例:

    马->斑马


    斑马->马


       笔者还跑了一下苹果和橘子的互相转换,也展示一下效果:

       先展示一些成功的转换:

    苹果->橘子







    橘子->苹果







    最后展示一些失败的转换案例:

    苹果->橘子




    橘子->苹果





       上面详细地展示了CycleGAN在两个数据集上的结果。

       很敬佩CycleGAN的作者,Berkeley AI Research laboratory的Jun-Yan Zhu,同时也感谢他为大家带来如此有意义的研究成果。笔者也衷心希望,此篇博客对大家的学习研究有帮助。

       欢迎阅读笔者后续博客,各位读者朋友的支持与鼓励是我最大的动力!


    written by jiong

    岂不罹凝寒,松柏有本性

    展开全文
  • 【StyleGAN代码学习】StyleGAN模型架构

    千次阅读 多人点赞 2019-12-10 19:19:20
    —————————————————————————————————    第二章 StyleGAN代码解读(上)   这一章将对StyleGAN的代码进行非常细致的分析和解读。一方面有助于对StyleGA...

    完整StyleGAN笔记:http://www.gwylab.com/pdf/Note_StyleGAN.pdf

    基于StyleGAN的一个好玩的网站:www.seeprettyface.com
    —————————————————————————————————
      

    第二章 StyleGAN代码解读(上)

      这一章将对StyleGAN的代码进行非常细致的分析和解读。一方面有助于对StyleGAN的架构和原理有更深的认识,另一方面是觉得AdaIN的思想很有价值,希望把它写代码的技巧学习下来,以后应该在GANs中会有很多能用得上的地方(其它paper里挺多出现了AdaIN的地方)。含有中文注释的代码可以在这里获得。

    2.1 StyleGAN代码架构总览

    在这里插入图片描述
          图2.1 StyleGAN官方代码架构

      如图2.1所示,StyleGAN代码的封装与解耦做的非常细致,可见作者的代码功底是非常扎实的。简单来说,在dnnlib文件夹下封装了日志提交工具、tensorflow环境配置与网络处理工具以及一些杂项实用类函数,这个文件夹尽量不要去动;在metrics文件夹下定义了许多指标计算方法,包括FID、LS、PPL指标以及一些GANs的通用指标计算方法;而training文件夹是需重点关注的部分,里面包含了数据处理、模型架构、损失函数和训练过程等基于StyleGAN的核心内容,在接下来的笔记中也会重点对这一部分进行细致讲解;最后,在主目录下,有一些全局配置、功能展示和运行接口的代码,其中train.py值得细读一下,它是训练StyleGAN网络的主要切入点。
      在接下来的笔记中,将从三个部分解读StyleGAN的代码,分别是:模型架构、损失函数和训练过程。至于其它部分的代码,由于我并不是特别关注,就不再赘述了。

    2.2 网络架构代码解读

      StyleGAN的网络架构全都写在training/networks_stylegan.py下,主要包括四个组成部分(代码302行-659行):G_style(),G_mapping(),G_synthesis()和D_basic()。
    在这里插入图片描述
      如上图所示,G_style表示整个生成器的网络架构,它由两个子网络组成,分别是映射网络G_mapping和合成网络G_synthesis;然后D_basic表示整个判别器的网络架构,它沿用了ProGAN中的模型设计。

    2.2.1 G_style网络

      G_style网络位于代码302-379行。在G_style中定义的组件包括:参数验证->设置子网络->设置变量->计算映射网络输出->更新W的移动平均值->执行样式混合正则化->应用截断技巧->计算合成网络输出。其中设置子网络就是调用构建G_mapping和G_synthesis的过程,两个子网络的定义将在下两节介绍。
      · G_style输入参数(line303-315)
    在这里插入图片描述
      输入参数包括512维的Z码向量和条件标签(在FFHQ数据集上没有使用标签),和一些可选的参数,包括截断参数、计算移动平均值W时的参数、样式混合参数、网络状态参数和子网络的参数们等。

      · 参数验证(line318-330)
    在这里插入图片描述
      对输入参数进行验证,主要是对网络状态和其对截断率、W平均值衰减率和样式混合概率的关系之间进行验证。

      · 设置子网络(line333-338)
    在这里插入图片描述
      直接使用tflib.Network()类(充当参数化网络构建功能的便捷包装,提供多种实用方法并方便地访问输入/输出/权重)创建两个子网络,子网络的内容在后面的函数(func_name = G_synthesis或func_name = G_mapping)中被定义。

      · 设置变量(line341-342)
    在这里插入图片描述
      设置两个变量lod_in和dlatent_avg。前者决定当前处在第几层分辨率,即lod=resolution_log2–res(其中res表示当前层对应的分辨率级别(2-10));后者决定截断操作的基准,即生成人脸的dlatent码的平均值。

      · 计算映射网络输出(line345)
    在这里插入图片描述
      得到映射网络的输出,即中间向量W’。

      · 更新W的移动平均值(line348-353)
    在这里插入图片描述
      把batch的dlatent平均值朝着总dlatent平均值以dlatent_avg_beta步幅靠近,作为新的人脸dlatent平均值,即dlatent_avg。

      · 执行样式混合正则化(line356-366)
    在这里插入图片描述
      样式混合正则化其实很好理解,就是随机创建一个新的潜码,这个潜码以一定概率与原始潜码交换某一部分,对于交换后的混合潜码,其生成的图片也要能够逼真,这就是样式混合正则化的实现。

      · 应用截断技巧(line369-374)
    在这里插入图片描述
      截断是指,用平均脸dlatent_avg朝着当前脸dlatents以coefs步幅靠近,得到的结果就是截断的dlatents。

      · 计算合成网络输出(line377-379)
    在这里插入图片描述
      将截断的dlatents传给G_synthesis网络进行合成,得到的结果就是整个生成网络G_style的输出结果。

    2.2.2 G_mapping网络

    在这里插入图片描述
      G_mapping网络位于代码384-435行。如上图所示,G_mapping网络实现了从初始生成码到中间向量的映射过程。在G_mapping中定义的组件包括:输入->连接标签->归一化潜码->映射层->广播->输出。其中映射层由8个全连接层组成。

      · G_mapping输入参数(line385-398)
    在这里插入图片描述
      输入参数包括512维的Z码向量和条件标签(在FFHQ数据集上没有使用标签),和一些可选的参数,包括初始向量Z参数、中间向量W参数、映射层设置、激活函数设置、学习率设置以及归一化设置等。

      · 网络输入(line403-407)
    在这里插入图片描述
      处理好latent的大小和格式后,其值赋给x,即用x标识网络的输入。

      · 连接标签(line410-414)
    在这里插入图片描述
      原始StyleGAN是无标签训练集,这部分不会被调用。

      · 归一化潜码(line417-418)
    在这里插入图片描述
      pixel_norm()(line239-242)
    在这里插入图片描述
      逐像素归一化的实现方式为:x=x/√(1/N ∑_(i=0)(N-1)▒〖x_i〗2 +ϵ),其中ϵ=10^-8。
      为何要使用pixel_norm呢? Pixel norm,它是local response normalization的变种,具有避免生成器梯度爆炸的作用。Pixel norm沿着channel维度做归一化(axis=1),这样归一化的一个好处在于,feature map的每个位置都具有单位长度。这个归一化策略与作者设计的Generator输出有较大关系,注意到Generator的输出层并没有Tanh或者Sigmoid激活函数。

      · 映射层(line421-426)
    在这里插入图片描述
      构建了mapping_layers层映射层,每个映射层有三个组成部分:全连接层dense()、偏置函数apply_bias()和激活函数act()。
      1)全连接层dense()(line 154-159)
    在这里插入图片描述
      dense()函数中首先将输出全部展平以计算输出维度,然后调用get_weight()创建全连接层w,最后返回x与w的矩阵相乘的结果,作为dense()层的输出。
      get_weight()(line 135-149)
    在这里插入图片描述
      get_weight()函数是用来创建卷积层或完全连接层,且获取权重张量的函数。
      值得注意的是,get_weight()采用了He的初始化方法。He的初始化方法能够确保网络初始化的时候,随机初始化的参数不会大幅度地改变输入信号的强度。StyleGAN中不仅限初始状态scale而且也是实时scale。
      2)添加偏置apply_bias()(line 213-218)
    在这里插入图片描述
      对给定的激活张量施加偏差。
      3)激活函数act()(line 400)
    在这里插入图片描述
      激活函数采用mapping_nonlinearity的值,StyleGAN中选用’lrelu’,且增益值为√2。
      注意这儿的gain是一个增益值,增益值是指的非线性函数稳态时输入幅度与输出幅度的比值,通常被用来乘在激活函数之后使激活函数更加稳定。常见的增益值包括:Sigmoid推荐的增益值是1,Relu推荐的增益值是√2,LeakyRelu推荐的增益值是√(2/(1+〖negative_slope〗^2 ))

      · 广播(line429-431)
    在这里插入图片描述

      这儿只定义了简单的复制扩充,广播前x的维度是(?,512),广播后x的维度是(?,18,512)。

      · 输出(line 434-435)
    在这里插入图片描述
      广播后的中间向量,就是G_mapping网络的最终输出。

    2.2.3 G_synthesis网络

    在这里插入图片描述
      G_synthesis网络位于代码441-560行。如上图所示,G_synthesis网络实现了从广播得到的中间向量到生成图片的合成过程。在G_synthesis中定义的组件包括:预处理->主要输入->噪音输入->★每层层末的调制功能->早期层(4*4)结构->剩余层的block块->★网络增长变换过程->输出。其中,每层层末的调制功能是指的在卷积之后,融入噪音与样式控制的过程(上图的⊕与AdaIN过程);网络增长变换过程是指的在训练时期,合成网络的架构动态增长,以生成更高分辨率图片的过程。上述两个内容是重点值得学习的部分。

      · G_synthesis输入参数(line441-462)
    在这里插入图片描述
      输入参数包括512维的中间向量(W)和输出图片的分辨率及通道,和一些可选的参数,包括各层特征图的设置、样式/网络起始值/噪音设置、激活函数设置、数据处理设置以及网络增长架构的设置等。

      · 预处理(line464-474)
    在这里插入图片描述
      预处理部分除了进一步细化网络配置以外,还定义了两个函数——nf()返回在第stage层中特征图的数量;blur()对图片进行滤波模糊操作,有利于降噪,其中blur的函数实现方式为blur2d()。
      blur2d ()(line 96-106)
    在这里插入图片描述
      在blur2d()里定义了模糊的返回函数为_blur2d(),同时blur2d()的一阶导和二阶导也被定义了出来,都是直接使用_blur2d()函数作为近似。
      _blur2d ()(line 22-49)
    在这里插入图片描述
      _blur2d ()的实现主要有两个部分,第一个部分是对于卷积核的处理,包括维度的规范和归一化处理;第二个部分是模糊的实现,即用卷积核对x实行depthwise_conv2d卷积。
      注意:depthwise_conv2d与普通的卷积有些不同。普通的卷积对卷积核每一个out_channel的两个通道分别和输入的两个通道做卷积相加,得到feature map的一个channel,而depthwise_conv2d卷积对每一个对应的in_channel,分别卷积生成两个out_channel,所以获得的feature map的通道数量可以用in_channel * channel_multiplier来表达,这个channel_multiplier,就可以理解为卷积核的第四维。参见博客:https://blog.csdn.net/mao_xiao_feng/article/details/78003476

      · 主要输入(line 477-479)
    在这里插入图片描述
      主要输入除了dlatents_in之外,还有一个lod_in参数。lod_in是一个指定当前输入分辨率级别的参数,规定lod = resolution_log2 – res。lod_in在递归构建模型架构的部分中被使用。

      · 创建噪音(line 482-487)
    在这里插入图片描述
      最初创建噪音时,只是依据对应层的分辨率创建对应的shape,然后随机初始化即为一个噪音。

      · ★层末调制(含AdaIN,line490-501)
    在这里插入图片描述
      层末调制,是在每个block的卷积之后对特征的处理,包含6种(可选)内容:应用噪音apply_noise()、应用偏置apply_bias()、应用激活函数act()、逐像素归一化pixel_norm()、实例归一化instance_norm()和样式调制(AdaIN)style_mod()。其中apply_bias()、act()与pixel_norm()在前文中已提及过,下面将不再赘述。
      1)apply_noise()(line 270-278)
    在这里插入图片描述
      应用噪音,直接将噪音加在特征x上就行了,注意按channel叠加。
      2)instance_norm()(line 247-256)
    在这里插入图片描述
      实例归一化是一个在生成模型中应用非常广泛的归一化方式,它的主要特点是仅对特征的HW(高和宽)维度做归一化,对图像的风格影响明显。
      3)★style_mod ()(line 261-265)
    在这里插入图片描述
      样式控制(AdaIN)的代码只有3行。第1行是仿射变化A,它通过全连接层将dlatent扩大一倍;第2行将扩大后的dlatent转换为放缩因子y_(s,i)和偏置因子y_(b,i);第3行是将这两个因子对卷积后的x实施自适应实例归一化。

      · 早期层结构(line 504-514)
    在这里插入图片描述
      由于StyleGAN的网络结构随训练进行是动态变化的,所以代码中定义了训练最开始时的网络结构,即4*4分辨率的生成网络。StyleGAN的生成起点选用维度为(1,512,4,4)的常量,通过一个卷积层(conv2d)得到了通道数为nf(1)(即512维)的特征图。
      · conv2d(line 164-168)
    在这里插入图片描述
      conv2d通过简单的卷积实现,将x的通道数由x.shape[1]变为fmaps,而x的大小不变。

      · 剩余层的block块(line 517-527)
    在这里插入图片描述
      StyleGAN将剩余分辨率的网络层封装成了block函数,方便在训练过程中依据输入的res(由lod_in计算出来)实时构建及调整网络的架构。其中每个block都包括了一个上采样层(upscaled_conv2d)和一个卷积层,上采样层后置滤波处理与层末调制,卷积层后置层末调制。另外在训练过程中网络需实时输出图片,StyleGAN中定义了torgb()函数,负责将对应分辨率的特征图转换为RGB图像。
      · upscaled_conv2d()(line 174-191)
    在这里插入图片描述
      upscaled_conv2d利用tf.nn.conv2d_transpose反卷积操作实现将特征图放大一倍。其中一个值得注意的操作是,其卷积核被轻微平移了四次并对自身做了叠加,这样或许对于提取特征有帮助,但我查阅不到相关资料证明这一点。

      · ★网络增长变换过程(line 530-556)
      StyleGAN的生成网络需具备动态变换的能力,代码中定义了三种结构组合方式,分别是:固定结构、线性结构与递归结构。
      1)固定结构(line 530-533)
    在这里插入图片描述
      固定结构构建了直达1024*1024分辨率的生成器网络,简单高效但不支持渐进式增长。
      2)线性结构(line 536-544)
    在这里插入图片描述
      线性结构构建了upscale2d()的上采样层,能将当前分辨率放大一倍。另外在不同分辨率间变换时,线性结构采用了含大小值裁剪的线性插值,实现了不同分辨率下的平滑过渡。
      upscale2d()(line 108-118)
    在这里插入图片描述
      在upscale2d()里定义了上采样的返回函数为_upscale2d(),同时upscale2d()的一阶导和二阶导也被定义了出来,都是直接使用_upscale2d()函数作为近似。
      _upscale2d()(line 51-68)
    在这里插入图片描述
      _upscale2d()的实现使用了tf.tile()的骚操作,其实很简单,就是复制扩展了像素值。总而言之,线性结构的实现简单但效率低下。

      3)递归结构(line 547-556)
    在这里插入图片描述
      递归结构下定义了递归函数grow(),使得只需要调用一次grow()就能够实现所有分辨率层的构建。它的实现逻辑主要是:比较lod_in和lod的关系——当lod_in超过lod时,在同一层级上实现分辨率的线性扩增;当lod_in小于lod且不是最后一层时,跳转到下一级的分辨率层上。

      · 输出(line 558-559)
    在这里插入图片描述
      网络的最终输出为之前结构中得到的images_out。

      · G_style架构总览
      最后,通过一张G_style的完整网络架构图,让我们对各个层的名称、参数量、输入维度和输出维度有更具体的理解。
    在这里插入图片描述

    2.2.4 D_basic网络

    在这里插入图片描述
      D_basic网络位于代码566-661行。如上图所示,D_basic网络实现区分合成图片与真实图片的功能,沿用了ProGAN判别器的架构,基本上是生成器反过来的样子。在D_basic中定义的组件包括:预处理->构建block块->★网络增长变换过程->标签计算->输出。其中,网络增长变换过程是指的在训练时期,随着生成图片的分辨率提升,判别网络的架构动态增长的过程。另外,标签计算是指在训练集使用了含标签数据时,会将标签值与判别分数的乘积作为最终判别网络的输出值。

      · D_basic输入参数(line565-582)
    在这里插入图片描述
      输入参数包括图片、标签以及这二者的相关配置,和一些可选的参数,包括各层特征图的设置、激活函数设置、小批量标准偏差层设置、数据处理设置以及网络增长架构的设置等。

      · 预处理(line584-596)
    在这里插入图片描述
      预处理主要包括细化网络配置、输入数据的处理以及输出数据的定义,包含内容与G_synthesis中的预处理过程类似。

      · 构建block块(line 599-618)
    在这里插入图片描述
      在训练过程中网络需实时处理图片,StyleGAN中定义了fromrgb()函数,负责将对应分辨率的RGB图像转换为特征图。
      在block函数中,当分辨率不低于88时,一个block包含一个卷积层和一个下采样层(conv2d_downscale2d);而当分辨率为最开始的44时,一个block包含一个小批量标准偏差层(minibatch_stddev_layer)、一个卷积层和两个全连接层。
      conv2d_downscale2d()(line 193-208)
    在这里插入图片描述
      conv2d_downscale2d利用tf.nn.conv2d卷积操作实现将特征图缩小一倍。其中一个值得注意的操作是,其卷积核被轻微平移了四次并对自身做了叠加然后取平均值,这样或许对于提取特征有帮助,但我查阅不到相关资料证明这一点。
      minibatch_stddev_layer()(line 283-296)
    在这里插入图片描述
      在4*4分辨率的block中,构建了一个小批量标准偏差层,将特征图标准化处理,这样能让判别网络收敛得更快。

      · ★网络增长变换过程(line 621-650)
      StyleGAN的判别网络需具备动态变换的能力,代码中定义了三种结构组合方式,分别是:固定结构、线性结构与递归结构。
      1)固定结构(line 621-625)
    在这里插入图片描述
      固定结构构建了直达10241024分辨率的判别器网络,简单高效但不支持渐进式增长。
      2)线性结构(line 628-638)
    在这里插入图片描述
      线性结构构建了downscale2d()的下采样层,能将当前分辨率缩小一倍。另外在不同分辨率间变换时,线性结构采用了含大小值裁剪的线性插值,实现了不同分辨率下的平滑过渡。
      downscale2d()(line 120-130)
    在这里插入图片描述
      在downscale2d()里定义了下采样的返回函数为_downscale2d(),同时downscale2d()的一阶导和二阶导也被定义了出来,都是直接使用_downscale2d ()函数作为近似。
      _downscale2d()(line 70-90)
    在这里插入图片描述
      _downscale2d()中,如果卷积核大小为2
    2,则直接返回_blur2d()的结果;否则采用平均池化的方式实现下采样。

      3)递归结构(line 641-650)
    在这里插入图片描述
      递归结构下定义了递归函数grow(),使得只需要调用一次grow()就能够实现所有分辨率层的构建。它的实现逻辑比较复杂,请参见代码注释;值得注意的是,构建判别网络时是lod从大往小构建,所以递归的过程是与生成器相反的。
      · 标签计算(line 653-655)
    在这里插入图片描述
      如果使用了标签的话,将标签值与判别分数的乘积作为最终判别网络的输出值。

      · 输出(line 657-659)
    在这里插入图片描述
      网络的最终输出为scores_out。

      · D_basic架构总览
      最后,通过一张D_basic的完整网络架构图,让我们对各个层的名称、参数量、输入维度和输出维度有更具体的理解。
    在这里插入图片描述

    展开全文
  • StyleGAN代码解读

    2021-03-25 19:57:25
     注:__所有解读__均参考了tvi和http://www.seeprettyface.com/mydataset.html。

     注:__所有解读__均参考了tvihttp://www.seeprettyface.com/mydataset.html

    展开全文
  • 本例使用tensorflow框架实现深度学习模型,包括CNN、RNN、GAN等,有源码和数据
  • 完整StyleGAN笔记:...—————————————————————————————————    第二章 StyleGAN代码解读(下) 2.3 损失函数代码解读   StyleGAN的损失函数写在training/loss.p...
  • 代码可通过配置,并在GPU / TPU / CPU上运行。 几篇研究论文都利用了该存储库,其中包括: Mario Lucic *,Karol Kurach *,Marcin Michalski,Sylvain Gelly,Olivier Bousquet [NeurIPS 2018] Karol Kurach *...
  • GAN代码实操(github代码实操)

    千次阅读 2019-09-08 22:59:24
    这也是本人第一次运行GAN程序。在这里仅仅将自己的代码实操过程分享出来。附件的tf.subtract()和tf.multiply()函数相对于github有所改动,不然版本配不上会出错。这里附上github源代码链接:...
  •  在本篇博客中,笔者将逐行解析一下NIPS 2014的Generative Adversarial Networks(生成对抗网络,简称GAN)代码,该篇文章作为GAN系列的开山之作,在近3年吸引了无数学者的目光。在2017-2018年,各大计...
  • 能够让代码跑起来,生成对应的图片,保存相应的模型</p>
  • gan简单代码实现python

    2018-09-03 11:46:42
    生成式对抗网络(GAN, Generative Adversarial Networks )是一种深度学习模型,是近年来复杂分布上无监督学习最具前景的方法之一。
  • 用matlab编写的GAN代码,亲试可以运行,适合深度学习的朋友用,代码全,可用于深度学习,就是运行时间较长,但是不影响学习使用
  • 库仑 为了运行项目,MNIST和CIFAR-10是必需的MNIST,它包含在Git存储库中。 CIFAR-10可以从以下获得: ://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz要下载CIFAR-10数据库,您必须运行scripts / download_...
  • GAN学习之路(三):tensorflow-CycleGAN代码详解

    千次阅读 多人点赞 2019-01-29 19:37:35
    代码地址:... 这是Van Huy巨佬的代码,做一个学习巨佬CycleGAN代码的小笔记。CycleGAN的一个巨大的优点就是不需要X和Y两个域(相互转化的两个域)有一一对应的关系。 总体代码笔记 整体来看, ...
  • GAN代码整理

    千次阅读 2018-06-23 17:25:26
    from datetime import datetime import os import matplotlib.pyplot as plt import numpy as np import tensorflow as tf from six.moves import xrange data = np.load('data/final37.npy') data = data[:,:,0:60]...
  • Cycle-GAN代码实现

    2019-09-03 23:21:11
    cyclegan.py 1. import,定义参数及创建存放数据的文件夹 import argparse import os import numpy as np import math import itertools import datetime import time import torchvision.transforms as transfor.....
  • 我们建议以样例信息的形式使用要绘制区域的参考图像或描述该对象的感知代码。 与以前的条件GAN公式不同,可以在对抗网络内的多个点插入此额外信息,从而提高其描述能力。 我们展示了ExGAN可以将其应用于自然图片中...
  • CycleGAN_pytorchlightning:使用pytorchlightning的cyclegan代码进行研究
  • 详解GAN代码之搭建并详解CGAN代码

    万次阅读 多人点赞 2018-05-05 22:16:00
    parser.add_argument("--lamda_gan_weight", type=float, default=1.0, help="GAN lamda") #训练中GAN_Loss前的乘数 parser.add_argument("--train_picture_format", default='.png', help="format of training ...
  • StarGAN代码解析

    千次阅读 2018-06-20 16:16:05
    pytorch原版github地址:https://github.com/yunjey/StarGAN tensorflow版github地址:https://github.com/taki0112/StarGAN-Tensorflow 两个版本实现相差不大,以pytorch版来介绍。 以celebA数据为例,下载后的...
  • pytorch实现GAN代码详解

    千次阅读 2018-07-20 19:07:20
    设置超参数。 (name, preprocess, d_input_func) = ("Data and variances", lambda data: decorate_with_diffs(data, 2.0), ...代码来源: https://github.com/devnag/pytorch-generative-adversarial-networks
  • #Description iPro-GAN的Python代码:一种基于生成式对抗学习方法的新颖模型,用于识别启动子及其强度,可实现一维特征分类。 #提示:U必须运行MSA.m才能获取要素数据。 #Date创建于2020年12月12日星期六15:07:26#...
  • Social GAN代码要点记录

    千次阅读 2019-10-04 01:23:01
    近日在阅读Social GAN文献的实验代码,加深对模型的理解,发现源代码的工程化很强,也比较适合构建实验模型的学习,故细致阅读。下文是笔者阅读中一些要点总结,有关于pytorch,也有关于模型自身的。 GPU -> CPU ...
  • pytorch cycleGAN代码学习1

    万次阅读 2018-07-27 12:08:45
    (字体MC-GAN: Discriminator 引用了 PatchGAN [1]的思想,即在公共网络加了 3 层卷积层采用了 21 × 21 Local Discriminator 去衡量局部真假,然后又在公共网络上平行加了 2 层作为 Global Discriminator 去衡量...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,257
精华内容 8,502
关键字:

gan代码