Python3+Tensorflowで画像生成ネットワークの作成-GAN

（20/11/09 追記）

Tensorflowv2以降だと動作しなかったので、v2で動作するように修正しました。
https://github.com/sey323/tf-gan


# 概要
KerasでGANを構築してあるケースは多々見かけるが，TensorflowのみでGANを構築しているケースがあまりないのでここで解説しながら作成を行う．

GANの構成はだいたいこんな感じ．Generatorでノイズから画像を生成し，Discriminatorで正解画像と生成された画像を識別する．この２つのネットワークがお互いのネットワークの性能を超える様に敵対して学習するため，敵対的学習と呼ばれている．
![スクリーンショット 2019-06-11 22.17.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/bfbeca27-c8de-3f83-c260-2e20412ff310.png)

GANの詳しい仕組みは以下のものがとてもわかりやすいので参考に
[今さら聞けないGAN（1）　基本構造の理解 - Qiita](https://qiita.com/triwave33/items/1890ccc71fab6cbca87e)

ソースコードの全体像は以下のリンクから
https://github.com/sey323/tf-gan/tree/master

## 実行環境
OS : Ubuntu 16.04
CPU : Intel(R) Core(TM) i9-7980XE CPU @ 2.60GHz
GPU : GEFORCE RTX 2080Ti

python     :3.6
tensorflow :1.15.1

# GANのクラスの作成
GANの計算グラフの定義と学習のプロセスを記述する`GAN`クラスを作成する．


```python:models/gan.py
def __init__(self ,
                input_size,
                channel= 3,
                layers = [ 64 , 128 , 256],
                filter_size = [ 5 , 5 ],
                drop_prob = 0.5,
                zdim = 100,
                batch_num = 64,
                learn_rate = 2e-4,
                max_epoch = 100,
                gpu_config = tf.GPUOptions(per_process_gpu_memory_fraction=0.1) ,
                save_folder = 'results/GAN' ):

        self.input_size = input_size
        self.channel = channel
        self.layers = layers
        self.filter_size = filter_size
        self.drop_prob = drop_prob
        self.zdim = zdim
        self.batch_num = batch_num
        self.learn_rate = learn_rate
        self.max_epoch = max_epoch

```

| インスタンス変数 | 役割 |
|-------|---|
|input_size | ネットワークに入力する画像のサイズ |
|channel | ネットワークに入力する画像カラーチャネル(濃淡画像:1,カラー画像:3) |
|layers | ネットワークの出力層の次元数 |
|filter_size | ネットワークの層のフィルタサイズ |
|drop_prob | ネットワークのドロップアウトの確率 |
|zdim | 入力するノイズの次元数 |
|batch_num | 学習するバッチ数 |
|learn_rate | 学習率 |
|max_epoch | 最大学習回数 |
|gpu_config| GPUの設定 |
|save_folder| 学習モデルを保存するディレクトリ |

GANはクラス内で自身のネットワークの構築や学習のプロセスを行う．


- `Generator()`  
GANの画像を生成するGeneratorのネットワークが定義されているクラス．
- `Discriminator()`  
GANのGeneratorで生成された画像と正解画像の識別を行う，Discriminatorのネットワークが定義されているクラス．
- `build_modle()`   
GANのネットワークの構築や損失関数，最適化手法を定義し，学習する計算グラフを作成するメソッド．
- `train()`  
`build_modle()`で作成された計算グラフに，実際に学習画像を入力し学習を行うメソッド．

## ネットワーク
GANでは画像を生成するGeneratorと，GANの生成された画像が本物か偽物か識別するDiscriminatorの2つネットワークを用いている．それぞれのネットワークは別々に学習が行われるため，それぞれ別のメソッドで作成する．

### Discriminatorの構築
Discriminatorは，生成された画像と正解画像の入力から，どちらの画像が生成された画像か識別する2値分類を行うCNNである．これは従来のニューラルネットワークと同様に，畳み込み層の積み重ねによりネットワークを構築し，全結合層においてクラス分類を行う．


```python:models/gan.py
    def Discriminator(self, input, channel=3, reuse=False, name=""):
        """
        Discriminator

        Args
            input (tensor):
                本物か偽物か識別したい画像のTensor配列
            channel (int):
                入力するカラーチャネル
            reuse (Boolean):
                同じネットワークが呼び出された時に再定義し直すかどうか
        """
        logging.info("[NETWORK]\tDeep Convolutional Discriminator")
        with tf.compat.v1.variable_scope("Discriminator" + name, reuse=reuse) as scope:
            if reuse:
                scope.reuse_variables()

            for i, output_shape in enumerate(self.layers, 1):
                if i == 1:  # 1層目の時だけ
                    before_output = input
                # conv
                with tf.compat.v1.variable_scope("conv_layer{0}".format(i)) as scope:
                    conv = layer.conv2d(
                        input=before_output,
                        stride=2,
                        filter_size=[self.filter_size[0], self.filter_size[1]],
                        output_dim=output_shape,
                        batch_norm=True,
                        name="Conv_{}".format(i),
                    )
                    conv = layer.leakyReLU(conv)
                before_output = conv

            with tf.compat.v1.variable_scope("Discriminator_Flatten", reuse=reuse) as scope:
                # FC層
                flatten_1 = layer.flatten(before_output, "Flatten")
                output = layer.fc(flatten_1, 1, "Out", batch_norm=False)
        return output
```

層の深さは，`self.layer`に記述されているネットワークの出力層の次元数だけ繰り返す．最後の全結合層の出力は，入力された画像が生成された画像か正解画像かの2値分類なので1とする．

ここで重要なのは`tf.variable_scope`でネットワークに名前をつけていること．これを用いることで後述のパラメータの更新の際に，更新するネットワーク(GeneratorかDiscriminator)を指定可能となり，別々の指標でネットワークの学習を進めることが可能となる．

### Generatorの構築
GANのGeneratorは，1次元のランダムノイズzの入力を，学習画像のドメインに近い画像へ変換を行うネットワークである．Generatorでは，畳み込み層と逆の処理を行う逆畳み込み層(Deconvolution)を用いて，ノイズから画像を生成する．

```python:models/gan.py
def Generator(self, input, channel=3, reuse=False):
        """
        Generator

        Args
            input (tensor):
                生成の基となるランダムノイズ
            channel (int):
                出力するカラーチャネル
            reuse (Boolean):
                同じネットワークが呼び出された時に再定義し直すかどうか
        """
        logging.info("[NETWORK]\tDeep Convolutional Generator")
        with tf.compat.v1.variable_scope("Generator", reuse=reuse) as scope:
            if reuse:
                scope.reuse_variables()

            # 逆FC
            with tf.compat.v1.variable_scope("Fc{0}".format("")) as scope:
                dim_h, dim_w = imutil.calcImageSize(
                    self.input_size[0],
                    self.input_size[1],
                    stride=2,
                    num=len(self.layers),
                )
                # 1層目
                defc_1 = layer.defc(
                    input,
                    output_shape=[dim_h, dim_w,],
                    output_dim=self.layers[-1],
                    name="defc",
                )
            before_output = defc_1

            # Deconv層
            for i, input_shape in enumerate(reversed(self.layers)):
                # 初期情報
                layer_no = len(self.layers) - i
                output_dim = self.layers[layer_no - 1]
                output_h, output_w = imutil.calcImageSize(
                    self.input_size[0], self.input_size[1], stride=2, num=layer_no
                )
                logging.debug(
                    "[OUTPUT]\t(batch_size, output_height:{0}, output_width:{1}, output_dim:{2})".format(
                        output_h, output_w, output_dim
                    )
                )

                # deconv
                with tf.compat.v1.variable_scope(
                    "deconv_layer{0}".format(layer_no)
                ) as scope:
                    deconv = layer.deconv2d(
                        before_output,
                        stride=2,
                        filter_size=[self.filter_size[0], self.filter_size[1]],
                        output_shape=[output_h, output_w],
                        output_dim=output_dim,
                        batch_norm=True,
                        name="Deconv_{}".format(layer_no),
                    )
                    before_output = layer.ReLU(deconv)

            # 最後の層で画像に復元
            with tf.compat.v1.variable_scope("image_reconstract") as scope:
                deconv_out = layer.deconv2d(
                    before_output,
                    stride=2,
                    filter_size=[self.filter_size[0], self.filter_size[1]],
                    output_shape=[self.input_size[0], self.input_size[1]],
                    output_dim=channel,
                    batch_norm=False,
                    name="Deconv_Output",
                )
            output = layer.tanh(deconv_out)
        return output
```

GeneratorもDiscriminatorと同様に，self.layerに記述されているネットワークの出力層の次元数だけ繰り返す．最後の畳み込み層の出力は，学習画像の次元に合わせて`channel`の値とする．


## 損失関数と最適化関数の定義
学習に用いるネットワークと最適化関数や損失関数，実験に用いるセッションを構築する．

```python:models/gan.py
 def build_model(self):
        '''
        ネットワークの全体を作成する
        '''

        '''変数の定義'''
        self.z      = tf.placeholder( tf.float32, [None, self.zdim],name="z")
        self.y_real = tf.placeholder( tf.float32, [None, self.input_size[0], self.input_size[1], 3],name="image")

        '''Generatorのネットワークの構築'''
        print('[BUILDING]\tGenerator')
        self.y_fake   = self.Generator(self.z,self.channel)
        self.y_sample = self.Generator(self.z,self.channel,reuse=True)

        '''Discrimnatorのネットワークの構築'''
        print('[BUILDING]\tDiscriminator')
        self.d_real  = self.Discriminator(self.y_real)
        self.d_fake  = self.Discriminator(self.y_fake,reuse=True)

        '''損失関数の定義'''
        print('[BUILDING]\tLoss Function')
        self.g_loss      = loss_function.cross_entropy( x=self.d_fake,labels=tf.ones_like (self.d_fake), batch_num = self.batch_num , name = "g_loss_fake")

        self.d_loss_real = loss_function.cross_entropy( x=self.d_real,labels=tf.ones_like (self.d_real), batch_num = self.batch_num , name = "d_loss_real")
        self.d_loss_fake = loss_function.cross_entropy( x=self.d_fake,labels=tf.zeros_like(self.d_fake), batch_num = self.batch_num , name = "d_loss_fake")
        self.d_loss      = self.d_loss_real + self.d_loss_fake

        '''最適化関数の定義'''
        print('[BUILDING]\tOptimizer')
        self.g_optimizer = tf.train.AdamOptimizer(self.learn_rate,beta1=0.5).minimize(self.g_loss, var_list=[x for x in tf.trainable_variables() if "Generator"     in x.name])
        self.d_optimizer = tf.train.AdamOptimizer(self.learn_rate,beta1=0.5).minimize(self.d_loss, var_list=[x for x in tf.trainable_variables() if "Discriminator" in x.name])

        '''Tensorboadに保存する設定'''
        print('[BUILDING]\tSAVE Node')
        tf.summary.scalar( "d_loss_real" , self.d_loss_real)
        tf.summary.scalar( "d_loss_fake" , self.d_loss_fake)
        tf.summary.scalar( "d_loss" , self.d_loss)
        tf.summary.scalar( "g_loss" , self.g_loss)

        '''Sessionの定義'''
        self.sess = tf.Session(config=self.gpu_config)

        ### saver
        self.saver = tf.train.Saver()
        self.summary = tf.summary.merge_all()
        if self.save_folder: self.writer = tf.summary.FileWriter(self.save_folder, self.sess.graph)

```
### 損失関数
Discriminatorの損失関数は交差誤差を用いて偽物の画像を偽物と識別した際の損失と，本物の画像を本物と識別した際の損失の合計値を用いる．
Generatorの損失関数は，交差誤差を用いてDiscriminatorが偽物の画像を正解の画像と誤って識別した際の損失を用いる．


### 最適化関数

```python:models/gan.py
        '''最適化関数の定義'''
        print('[BUILDING]\tOptimizer')
        self.g_optimizer = tf.train.AdamOptimizer(self.learn_rate,beta1=0.5).minimize(self.g_loss, var_list=[x for x in tf.trainable_variables() if "Generator"     in x.name])
        self.d_optimizer = tf.train.AdamOptimizer(self.learn_rate,beta1=0.5).minimize(self.d_loss, var_list=[x for x in tf.trainable_variables() if "Discriminator" in x.name])
```
最適化関数の部分では，Tensorflowのname_scopeの機能を用いて行う．`tf.trainable_variables()`には定義したネットワークの計算グラフとその名称が保存されている．`train.AdamOptimizer.minimize()`メゾットでは，更新の対象となる層を指定することが可能であるため，`tf.trainable_variables()`を用いて計算グラフの名称の一覧を取得し，その名前に`Discriminator`または`Generaotr`が含まれる計算グラフのみをパラメータ更新の対象として指定する．

## 学習
学習では`util/batchgen.py`の`batch`クラスを用いて，ミニバッチ学習を行う．`batch`クラスについて簡単に説明すると，画像の教師ラベルと画像データが`batch`クラスに格納されており，それらを`batch.getBatch({バッチ枚数})`を用いることで取得できる．そして取得した画像を，ネットワークとして定義した`Discriminator`の計算グラフに代入し，学習を進める．

`batch.getEpoch()`で現在のバッチのループ回数を取得し，初期化の時に指定した`max_epoch`回になるまで学習を進める．学習回数が10回ごとに`summary`の出力，100回ごとに途中結果の出力を行う．


```python:models/gan.py

    def train(self, batch_o):
        self.build_model()
        initOP = tf.global_variables_initializer()
        self.sess.run(initOP)

        step = -1
        epoch = 0
        time_history=[]
        start = time.time()
        while batch_o.getEpoch()<self.max_epoch:
            step += 1

            # ランダムにバッチと画像を取得
            batch_images,batch_labels = batch_o.getBatch(self.batch_num)
            batch_z = np.random.uniform(-1.,+1.,[self.batch_num,self.zdim]).astype(np.float32)

             # Update Discrimnator
            _,d_loss,y_fake,y_real,summary = self.sess.run([self.d_optimizer,self.d_loss,self.y_fake,self.y_real,self.summary],feed_dict={self.z:batch_z, self.y_real:batch_images})
            # Update Generator
            _,g_loss = self.sess.run([ self.g_optimizer , self.g_loss ] , feed_dict={ self.z:batch_z })


            if step>0 and step%10==0:
                self.writer.add_summary(summary , step )

            if epoch != batch_o.getEpoch():
                # 実行時間と損失関数の出力
                train_time = time.time()-start
                print("epoch: %6, loss(D)=%.4e, loss(G)=%.4e; time/step = %.2f sec"%(batch_o.getEpoch(),d_loss,g_loss,train_time))
                self.dumper.add(batch_o.getEpoch(),step,d_loss,g_loss,train_time)

                # ノイズの作成．
                l0 = np.array([ x%10 for x in range( self.batch_num )] , dtype = np.int32)
                z1 = np.random.uniform(-1,+1,[ self.batch_num , self.zdim ])
                z2 = np.random.uniform(-1,+1,[ self.zdim ])
                z2 = np.expand_dims( z2 , axis = 0 )
                z2 = np.repeat( z2 , repeats = self.batch_num , axis = 0 )

                # 画像を作成して保存
                g_image1 = self.sess.run(self.y_sample,feed_dict={self.z:z1})
                g_image2 = self.sess.run(self.y_sample,feed_dict={self.z:z2})
                cv2.imwrite(os.path.join(self.save_folder,"images","img_%d_real.png"%step),imutil.tileImage( y_real ) * 255.+128.)
                self.create(z1 ,os.path.join(self.save_folder,"images","img_%d_fake1.png"%step))
                self.create(z2 ,os.path.join(self.save_folder,"images","img_%d_fake2.png"%step))
                self.saver.save(self.sess,os.path.join(self.save_folder,"model.ckpt"),step)

                epoch = batch_o.getEpoch()

                # 時間の計測の再開
                start = time.time()


        self.dumper.save()
```
## 実行ファイル
最後に作成したGANクラスを実行するプログラムを作成する．画像読み込みなどに使うモジュールの詳細は割愛する．tensorflowの`FLAGS`機能を使ってコンソール入力を受け取る．`layers = [64 , 128 , 256]`としてあるが，今回は64-128-256の3層のGANを構築した．

```tf-gan/train.py
import os,sys
sys.path.append('./util')
import imload
from batchgen import *

# Ganモデルの読み込み
sys.path.append('./models')
from gan import *


def main(FLAGS):

　　　'''
　　　メイン関数
　　　'''
    # パラメータの取得
    img_path = os.getenv("DATASET_FOLDER", "dataset")
    save_path = os.getenv("SAVE_FOLDER", "results")


    '''設定ファイルからパラメータの読み込み'''
    print('[LOADING]\tmodel parameters loding')

    # ファイルのパラメータ
    folder = FLAGS.folder
    resize = [ FLAGS.resize , FLAGS.resize ]
    file_num = FLAGS.file_num
    gray = FLAGS.gray
    channel = 1 if gray else 3

    # GANのクラスに関するパラメータ
    layers = [64 , 128 , 256]
    max_epoch = FLAGS.max_epoch
    batch_num = FLAGS.batch_size
    save_folder = FLAGS.save_folder
    save_path = save_path + '/' + save_folder


    '''画像の読み込み'''
    train_image ,train_label = imload.make( folder , gray = gray , train_num = file_num , img_size = resize[0] )


    '''バッチの作成'''
    batch = batchgen( train_image , train_label)


    '''モデルの作成'''
    print("[LOADING]\tGAN")
    gan = GAN(           input_size=resize,
                         channel = channel,
                         layers = layers,
                         batch_num = batch_num ,
                         max_epoch = max_epoch ,
                         save_folder = save_path)

    # 学習の開始
    gan.train( batch )


if __name__=="__main__":
    flags = tf.app.flags
    FLAGS = flags.FLAGS

    # 実行するGANモデルの指定．
    flags.DEFINE_string('type', 'gan', 'Choice GAN type.')

    # 読み込む画像周り
    flags.DEFINE_string('folder', '', 'Directory to put the training data.')
    flags.DEFINE_integer('resize', 64, 'Size of Image.')
    flags.DEFINE_integer('file_num', 0, 'Loading Images Num.')
    flags.DEFINE_boolean('gray', False, 'Convert Gray Scale?')

    # GANの学習パラメータ
    flags.DEFINE_float('learning_rate', 0.001, 'Initial learning rate.')
    flags.DEFINE_integer('max_epoch', 100, 'Number of steps to run trainer.')
    flags.DEFINE_integer('batch_size', 25, 'Batch size.  ''Must divide evenly into the dataset sizes.')

    # 保存フォルダの決定
    flags.DEFINE_string('save_folder', '', 'Data save folder')

    main(FLAGS)

```

# 実験
## データセットの準備
データセットは海外のセレブの画像のデータセットを集めた，FaceScrubを用いて行う．
画像の収集はこちらのリンクの手順で集める．
https://qiita.com/sey323/items/fc3cac3e9632c91ddd3f

集めた画像のうち，手っ取り早くA-Z順に上から10人適当に取ってきて，プログラムのディレクトリに移動する．教師データとなる正解画像は以下の様なものになる．


```sh:./tf-gan
face --- Aaron_Eckhart
      |- Adam_Brody 
      |- Adam_Mckay　　
　　　　・
　　　　・
      |- Alec_Baldwin
```

![img_0_real.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/be076aaa-5d9b-5ec0-be4d-afdd1d662e85.png)



## 学習の実行

学習の実行は以下の通り．

```sh:
$ python train.py --folder=face --batch_size=25
[LOADING]	model parameters loding
[LOADING]	Label0	Name:Alan_Alda	Pictures exit. Unit On 111
[LOADING]	Label1	Name:Alan_Rickman	Pictures exit. Unit On 119
[LOADING]	Label2	Name:Adam_McKay	Pictures exit. Unit On 45
[LOADING]	Label3	Name:Al_Pacino	Pictures exit. Unit On 102
[LOADING]	Label4	Name:Adrien_Brody	Pictures exit. Unit On 113
[LOADING]	Label5	Name:Adam_Sandler	Pictures exit. Unit On 95
[LOADING]	Label6	Name:Alec_Baldwin	Pictures exit. Unit On 125
[LOADING]	Label7	Name:Aaron_Eckhart	Pictures exit. Unit On 117
[LOADING]	Label8	Name:Alan_Arkin	Pictures exit. Unit On 94
[LOADING]	Label9	Name:Adam_Brody	Pictures exit. Unit On 107
[LOADING]	GAN
[BUILDING]	Generator
[NETWORK]
Deep Convolutional Generator
[NETWORK]
Deep Convolutional Generator
[BUILDING]	Discriminator
[NETWORK]
Deep Convolutional Discriminator
[NETWORK]
Deep Convolutional Discriminator
[BUILDING]	Loss Function
[BUILDING]	Optimizer
[BUILDING]	SAVE Node
2019-06-10 22:22:31.394602: I tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 AVX512F FMA
2019-06-10 22:22:31.584971: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:898] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2019-06-10 22:22:31.586126: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1356] Found device 0 with properties:
name: GeForce GTX 1080 Ti major: 6 minor: 1 memoryClockRate(GHz): 1.582
pciBusID: 0000:17:00.0
totalMemory: 10.92GiB freeMemory: 10.76GiB
2019-06-10 22:22:31.741673: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:898] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2019-06-10 22:22:31.742130: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1356] Found device 1 with properties:
name: GeForce GTX 1080 Ti major: 6 minor: 1 memoryClockRate(GHz): 1.582
pciBusID: 0000:65:00.0
totalMemory: 10.91GiB freeMemory: 10.76GiB
2019-06-10 22:22:31.742985: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1435] Adding visible gpu devices: 0, 1
2019-06-10 22:22:32.114452: I tensorflow/core/common_runtime/gpu/gpu_device.cc:923] Device interconnect StreamExecutor with strength 1 edge matrix:
2019-06-10 22:22:32.114491: I tensorflow/core/common_runtime/gpu/gpu_device.cc:929] 0 1
2019-06-10 22:22:32.114497: I tensorflow/core/common_runtime/gpu/gpu_device.cc:942] 0: N Y
2019-06-10 22:22:32.114501: I tensorflow/core/common_runtime/gpu/gpu_device.cc:942] 1: Y N
2019-06-10 22:22:32.114772: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1053] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 11178 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1080 Ti, pci bus id: 0000:17:00.0, compute capability: 6.1)
2019-06-10 22:22:32.217003: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1053] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:1 with 11176 MB memory) -> physical GPU (device: 1, name: GeForce GTX 1080 Ti, pci bus id: 0000:65:00.0, compute capability: 6.1)
0: loss(D)=1.4175e+00, loss(G)=2.0379e+00; time/step = 2.53 sec
100: loss(D)=2.7534e-01, loss(G)=7.1763e+00; time/step = 17.97 sec
200: loss(D)=1.4626e-01, loss(G)=8.5171e+00; time/step = 18.54 sec
　　　・
　　　・
```

## 結果
### 生成された画像

学習回数：100回
![img_100_fake1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/b90dc769-1f88-512a-444f-713f83145ca6.png)

学習回数：1000回
![img_1000_fake1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/cf449389-6645-be9c-df6e-fe497db25467.png)

学習回数：5000回
![img_5100_fake1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/2d765b45-907f-ef6c-159e-942f7f1c0b2e.png)

学習回数：7500回
![img_7500_fake1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/845779c8-2437-f8af-0466-e265d0537604.png)
学習回数：10000回
![img_10100_fake1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/72b42e50-e95e-52de-23b9-30b466670a23.png)

学習回数が向上するにつれて，ノイズが少ない品質の高い画像が生成されていることが確認できうる．

### 損失関数の推移

tensorflowだと指定したパラメータをtensorboadで視覚化することができるので，tensorboadで学習結果を見て見る．

```sh:
$ tensorboad --logdir={実行結果のパス}
```

g_loss
<img width="921" alt="スクリーンショット 2019-06-10 23.00.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/e0e66fa5-bd9c-fa7d-52c4-710ca9c8d5fe.png">

d_loss
<img width="929" alt="スクリーンショット 2019-06-10 23.00.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163680/3a2ebcba-df25-fc61-255c-8667ab5e54f7.png">

g_lossは徐々にDiscriminatorを誤認識させる確率が向上しており，d_lossも徐々に識別性能が向上しており，うまく敵対的に学習して両方のネットワークの性能を高めていることが確認できる．

# 終わりに
今回は単純なDCGANを構築して画像を生成した．単純なGANでもかなり品質の高い画像が生成できている．次はcGANを作成し，生成する人物を指定した画像の生成をしたい．

このプログラム，読み込む画像のファイル構成を今回の実験と同じ様な形式で保存してもらえれば顔以外のデータセットでも学習可能なので，適当に試してみてください．