機械学習で猫を創造する(失敗)

![01.png](https://qiita-image-store.s3.amazonaws.com/0/26088/30eeb51e-50d0-4d73-9e41-1395dea81b47.png)

猫の正面顔写真集……ではありません。
全てDCGANによって創造された画像です。

以下は[How AI can learn to generate pictures of cats](https://medium.freecodecamp.org/how-ai-can-learn-to-generate-pictures-of-cats-ba692cb6eae4)の日本語訳です。

# How AI can learn to generate pictures of cats

2014年、Ian J. Goodfellowらによって発表された[Generative Adversarial Nets](https://arxiv.org/pdf/1406.2661.pdf)論文は、モデル生成の分野において画期的なものでした。

ライバル会社の有力な研究者Yann Lecunも、この研究を「この20年で最もクールなアイデアだ」と認めました。

本日は、この素晴らしいアーキテクチャを使って、猫のフォトリアルな写真を生成するAIを構築していきます。
これはなんと幸福なことでしょう。

全てのコードは[Github](https://github.com/simoninithomas/CatDCGAN)で公開されています。
Python、ディープラーニング、Tensorflow、CNN(Convolutional neural network)の経験があるならすぐにわかると思います。

ディープラーニングを初めて学習する場合は、以下の優れた記事を読むとよいでしょう。

[Machine Learning is Fun!](https://medium.com/@ageitgey/machine-learning-is-fun-80ea3ec3c471)

## What is DCGAN?

Deep Convolutional Generative Adverserial Networks、略してDCGANとは、学習データと似たようなデータを出力するディープラーニングのアーキテクチャです。

このモデルは、GANモデルの全結合層をconvolutionレイヤと置き替えます。

DCGANの仕組みを説明するために、鑑定士と贋作者の喩えを使ってみましょう。
偽造者(generator)は、ゴッホの画像の贋作を作成し、それを売り払おうとします。

![03.png](https://qiita-image-store.s3.amazonaws.com/0/26088/c76df026-cd07-0fa1-2add-2133b13114af.png)

他方、鑑定士(discriminator)は、本物のゴッホの画像の知識を使って贋作を見極めようとします。

![04.png](https://qiita-image-store.s3.amazonaws.com/0/26088/1059344d-7d13-60a7-6f83-a698c5b7ffe6.png)

時間が経つにつれ、鑑定士の目利きレベルは上昇し、贋作者の偽造レベルも上昇します。

![05.png](https://qiita-image-store.s3.amazonaws.com/0/26088/d1abe488-8688-af81-a58f-5f693cc16e7f.png)

DCGANは、互いに競合するふたつのニューラルネットワークで構成されています。

・generatorは、本物っぽいデータを作ろうとする贋作者です。
本物のデータがどのようなものかは知りませんが、discriminatorからのフィードバックを元に学習することができます。
・discriminatorは、本物のデータと比較することによって入力が贋作であるかどうかを判断する鑑定士です。
本物のデータには誤検出を起こさないよう(false positive)に調整されています。
出力はgeneratorに逆伝播します。

![06.png](https://qiita-image-store.s3.amazonaws.com/0/26088/ea239d7b-e52b-9507-48c1-421cdb447cd0.png)

・generatorはランダムノイズから画像を生成してdiscriminatorに送る。
・discriminatorは画像を本物の画像と比較する。
・discriminatorは0(完全に偽物)から1(本物)の間の数値をgeneratorに返す。

## Let’s create a DCGAN!

AIを作成する準備が整いました。

このパートでは主要な要素のみ話します。
詳細なコードを知りたい場合は[こちら](https://github.com/simoninithomas/CatDCGAN/blob/master/Cat%20DCGAN.ipynb)を参照してください。

### Inputs

ここでは入力モデルを作成します。
inputs_realはdiscriminator用、inputs_zはgenerator用です。

2個の学習率を使うことに注意してください。
ひとつはdiscriminator用で、もうひとつはgenerator用です。

DCGANはハイパーパラメータにとてもに敏感で、適切にチューニングすることが非常に重要です。

```py
def model_inputs(real_dim, z_dim):
    """
    Create the model inputs
    :param real_dim: tuple containing width, height and channels
    :param z_dim: The dimension of Z
    :return: Tuple of (tensor of real input images, tensor of z data, learning rate G, learning rate D)
    """
    # inputs_real for Discriminator
    inputs_real = tf.placeholder(tf.float32, (None, *real_dim), name='inputs_real')
    
    # inputs_z for Generator
    inputs_z = tf.placeholder(tf.float32, (None, z_dim), name="input_z")
    
    # Two different learning rate : one for the generator, one for the discriminator
    learning_rate_G = tf.placeholder(tf.float32, name="learning_rate_G")
    
    learning_rate_D = tf.placeholder(tf.float32, name="learning_rate_D")
    
return inputs_real, inputs_z, learning_rate_G, learning_rate_D
```

### The discriminator and the generator

ふたつの理由で`tf.variable_scope`を使用しています。

まず、全ての変数名がgeneratorかdiscriminatorで始まるようにできます。
これは後でAIのトレーニングをする際に役立ちます。

次に、これらのネットワークを別の入力で再利用したいと考えています。
・generatorはトレーニングを行いますが、トレーニング後に偽物の画像を出力する必要があります。
・discriminatorは偽物と本物の画像でパラメータを共有する必要があります。

![07.png](https://qiita-image-store.s3.amazonaws.com/0/26088/32bf5a5b-70f6-ae82-bfaf-8a054a140723.png)

さてdiscriminatorを作成しましょう。
discriminatorは、本物もしくは偽物の画像をインプットし、その点数を出力します。

・各[Convolution Layer](https://qiita.com/icoxfog417/items/5fd55fad152231d706c2)でフィルタの数を倍にします。
・downsamplingは推奨されません。かわりにConvolution Layerを[Stride](https://deepage.net/deep_learning/2016/11/07/convolutional_neural_network.html#%E3%82%B9%E3%83%88%E3%83%A9%E3%82%A4%E3%83%89)します。
・各レイヤで[バッチ正規化](https://qiita.com/satopirka/items/4f6e3702947f7ef8f1bf)を使用します。詳細については[こちら](https://towardsdatascience.com/batch-normalization-in-neural-networks-1ac91516821c)を参照してください。
・勾配消失を避けるため、活性化関数として[Leaky ReLU](https://qiita.com/hokekiyoo/items/bf7be0ae3bf4aa3905ef)を使用します。

```python
def discriminator(x, is_reuse=False, alpha = 0.2):
    ''' Build the discriminator network.
    
        Arguments
        ---------
        x : Input tensor for the discriminator
        n_units: Number of units in hidden layer
        reuse : Reuse the variables with tf.variable_scope
        alpha : leak parameter for leaky ReLU
        
        Returns
        -------
        out, logits: 
    '''
    with tf.variable_scope("discriminator", reuse = is_reuse): 
        
        # Input layer 128*128*3 --> 64x64x64
        # Conv --> BatchNorm --> LeakyReLU   
        conv1 = tf.layers.conv2d(inputs = x,
                                filters = 64,
                                kernel_size = [5,5],
                                strides = [2,2],
                                padding = "SAME",
                                kernel_initializer=tf.truncated_normal_initializer(stddev=0.02),
                                name='conv1')
        
        batch_norm1 = tf.layers.batch_normalization(conv1,
                                                   training = True,
                                                   epsilon = 1e-5,
                                                     name = 'batch_norm1')

        conv1_out = tf.nn.leaky_relu(batch_norm1, alpha=alpha, name="conv1_out")
        
        
        # 64x64x64--> 32x32x128
        # Conv --> BatchNorm --> LeakyReLU   
        conv2 = tf.layers.conv2d(inputs = conv1_out,
                                filters = 128,
                                kernel_size = [5, 5],
                                strides = [2, 2],
                                padding = "SAME",
                                kernel_initializer=tf.truncated_normal_initializer(stddev=0.02),
                                name='conv2')
        
        batch_norm2 = tf.layers.batch_normalization(conv2,
                                                   training = True,
                                                   epsilon = 1e-5,
                                                     name = 'batch_norm2')
        
        conv2_out = tf.nn.leaky_relu(batch_norm2, alpha=alpha, name="conv2_out")

        
        
        # 32x32x128 --> 16x16x256
        # Conv --> BatchNorm --> LeakyReLU   
        conv3 = tf.layers.conv2d(inputs = conv2_out,
                                filters = 256,
                                kernel_size = [5, 5],
                                strides = [2, 2],
                                padding = "SAME",
                                kernel_initializer=tf.truncated_normal_initializer(stddev=0.02),
                                name='conv3')
        
        batch_norm3 = tf.layers.batch_normalization(conv3,
                                                   training = True,
                                                   epsilon = 1e-5,
                                                name = 'batch_norm3')
        
        conv3_out = tf.nn.leaky_relu(batch_norm3, alpha=alpha, name="conv3_out")

        
        
        # 16x16x256 --> 16x16x512
        # Conv --> BatchNorm --> LeakyReLU   
        conv4 = tf.layers.conv2d(inputs = conv3_out,
                                filters = 512,
                                kernel_size = [5, 5],
                                strides = [1, 1],
                                padding = "SAME",
                                kernel_initializer=tf.truncated_normal_initializer(stddev=0.02),
                                name='conv4')
        
        batch_norm4 = tf.layers.batch_normalization(conv4,
                                                   training = True,
                                                   epsilon = 1e-5,
                                                name = 'batch_norm4')
        
        conv4_out = tf.nn.leaky_relu(batch_norm4, alpha=alpha, name="conv4_out")

        
        
        # 16x16x512 --> 8x8x1024
        # Conv --> BatchNorm --> LeakyReLU   
        conv5 = tf.layers.conv2d(inputs = conv4_out,
                                filters = 1024,
                                kernel_size = [5, 5],
                                strides = [2, 2],
                                padding = "SAME",
                                kernel_initializer=tf.truncated_normal_initializer(stddev=0.02),
                                name='conv5')
        
        batch_norm5 = tf.layers.batch_normalization(conv5,
                                                   training = True,
                                                   epsilon = 1e-5,
                                                name = 'batch_norm5')
        
        conv5_out = tf.nn.leaky_relu(batch_norm5, alpha=alpha, name="conv5_out")

         
        # Flatten it
        flatten = tf.reshape(conv5_out, (-1, 8*8*1024))
        
        # Logits
        logits = tf.layers.dense(inputs = flatten,
                                units = 1,
                                activation = None)
        
        
        out = tf.sigmoid(logits)
        
return out, logits
```

![08.png](https://qiita-image-store.s3.amazonaws.com/0/26088/47576e77-05e0-1a35-ea6c-cc1d7530cd5e.png)

次にgeneratorを作成します。
generatorは、ランダムなノイズベクトルを受け取り、[transposed convolution layer](https://qiita.com/elm200/items/621b410e69719df0e6f4)を通して偽物の画像を出力します。

そのアイデアは、各レイヤでフィルタサイズを半分にし、画像のサイズを倍にするというものです。
generatorの活性化関数としてはtanhを使うのがベストだとされています。

```python
def generator(z, output_channel_dim, is_train=True):
    ''' Build the generator network.
    
        Arguments
        ---------
        z : Input tensor for the generator
        output_channel_dim : Shape of the generator output
        n_units : Number of units in hidden layer
        reuse : Reuse the variables with tf.variable_scope
        alpha : leak parameter for leaky ReLU
        
        Returns
        -------
        out: 
    '''
    with tf.variable_scope("generator", reuse= not is_train):
        
        # First FC layer --> 8x8x1024
        fc1 = tf.layers.dense(z, 8*8*1024)
        
        # Reshape it
        fc1 = tf.reshape(fc1, (-1, 8, 8, 1024))
        
        # Leaky ReLU
        fc1 = tf.nn.leaky_relu(fc1, alpha=alpha)

        
        # Transposed conv 1 --> BatchNorm --> LeakyReLU
        # 8x8x1024 --> 16x16x512
        trans_conv1 = tf.layers.conv2d_transpose(inputs = fc1,
                                  filters = 512,
                                  kernel_size = [5,5],
                                  strides = [2,2],
                                  padding = "SAME",
                                kernel_initializer=tf.truncated_normal_initializer(stddev=0.02),
                                name="trans_conv1")
        
        batch_trans_conv1 = tf.layers.batch_normalization(inputs = trans_conv1, training=is_train, epsilon=1e-5, name="batch_trans_conv1")
       
        trans_conv1_out = tf.nn.leaky_relu(batch_trans_conv1, alpha=alpha, name="trans_conv1_out")
        
        
        # Transposed conv 2 --> BatchNorm --> LeakyReLU
        # 16x16x512 --> 32x32x256
        trans_conv2 = tf.layers.conv2d_transpose(inputs = trans_conv1_out,
                                  filters = 256,
                                  kernel_size = [5,5],
                                  strides = [2,2],
                                  padding = "SAME",
                                kernel_initializer=tf.truncated_normal_initializer(stddev=0.02),
                                name="trans_conv2")
        
        batch_trans_conv2 = tf.layers.batch_normalization(inputs = trans_conv2, training=is_train, epsilon=1e-5, name="batch_trans_conv2")
       
        trans_conv2_out = tf.nn.leaky_relu(batch_trans_conv2, alpha=alpha, name="trans_conv2_out")
        
        
        # Transposed conv 3 --> BatchNorm --> LeakyReLU
        # 32x32x256 --> 64x64x128
        trans_conv3 = tf.layers.conv2d_transpose(inputs = trans_conv2_out,
                                  filters = 128,
                                  kernel_size = [5,5],
                                  strides = [2,2],
                                  padding = "SAME",
                                kernel_initializer=tf.truncated_normal_initializer(stddev=0.02),
                                name="trans_conv3")
        
        batch_trans_conv3 = tf.layers.batch_normalization(inputs = trans_conv3, training=is_train, epsilon=1e-5, name="batch_trans_conv3")
       
        trans_conv3_out = tf.nn.leaky_relu(batch_trans_conv3, alpha=alpha, name="trans_conv3_out")

        
        # Transposed conv 4 --> BatchNorm --> LeakyReLU
        # 64x64x128 --> 128x128x64
        trans_conv4 = tf.layers.conv2d_transpose(inputs = trans_conv3_out,
                                  filters = 64,
                                  kernel_size = [5,5],
                                  strides = [2,2],
                                  padding = "SAME",
                                kernel_initializer=tf.truncated_normal_initializer(stddev=0.02),
                                name="trans_conv4")
        
        batch_trans_conv4 = tf.layers.batch_normalization(inputs = trans_conv4, training=is_train, epsilon=1e-5, name="batch_trans_conv4")
       
        trans_conv4_out = tf.nn.leaky_relu(batch_trans_conv4, alpha=alpha, name="trans_conv4_out")

        
        # Transposed conv 5 --> tanh
        # 128x128x64 --> 128x128x3
        logits = tf.layers.conv2d_transpose(inputs = trans_conv4_out,
                                  filters = 3,
                                  kernel_size = [5,5],
                                  strides = [1,1],
                                  padding = "SAME",
                                kernel_initializer=tf.truncated_normal_initializer(stddev=0.02),
                                name="logits")
         
        out = tf.tanh(logits, name="out")
        
        return out
```

### Discriminator and generator losses

generatorとdiscriminatorを同時にトレーニングするので、両方の損失を算出する必要があります。

discriminatorには、画像が本物であるときには1を、完全に偽物であるときは0を出力してほしいので、それを反映するために損失を設定する必要があります。
discriminatorの損失は、実画像の損失と偽画像の損失の合計です。

```python
d_loss = d_loss_real + d_loss_fake 
```

`d_loss_real`は、実際は本物の画像であるのに、discriminatorが偽物であると判定してしまったときの損失です。
これは以下のように算出されます。
・`d_logits_real`のラベルは全て1である。
・discriminatorのをよりよくするため、`labels = tf.ones_like(tensor) * (1 - smooth)`を適用する。

`d_loss_fake`は、実際は偽物の画像であるのに、discriminatorが本物であると判定してしまったときの損失です。
これは以下のように算出されます。
・`d_logits_fakeand`のラベルは全て0である。

generatorはdiscriminatorの`d_loss_fake`を再度利用します。
今度のラベルは全て1で、これはgeneratorがdiscriminatorを騙したいからです。

```python
def model_loss(input_real, input_z, output_channel_dim, alpha):
    """
    Get the loss for the discriminator and generator
    :param input_real: Images from the real dataset
    :param input_z: Z input
    :param out_channel_dim: The number of channels in the output image
    :return: A tuple of (discriminator loss, generator loss)
    """
    # Generator network here
    g_model = generator(input_z, output_channel_dim)   
    # g_model is the generator output
    
    # Discriminator network here
    d_model_real, d_logits_real = discriminator(input_real, alpha=alpha)
    d_model_fake, d_logits_fake = discriminator(g_model,is_reuse=True, alpha=alpha)
    
    # Calculate losses
    d_loss_real = tf.reduce_mean(
                  tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_real, 
                                                          labels=tf.ones_like(d_model_real)))
    d_loss_fake = tf.reduce_mean(
                  tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, 
                                                          labels=tf.zeros_like(d_model_fake)))
    d_loss = d_loss_real + d_loss_fake

    g_loss = tf.reduce_mean(
             tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake,
                                                     labels=tf.ones_like(d_model_fake)))
    
return d_loss, g_loss
```

### Optimizers

損失の計算後、generatorとdiscriminatorをそれぞれアップデートします。
そのために`tf.trainable_variables()`を使って変数を取得します。
これは使用した全ての変数を返します。

```python
def model_optimizers(d_loss, g_loss, lr_D, lr_G, beta1):
    """
    Get optimization operations
    :param d_loss: Discriminator loss Tensor
    :param g_loss: Generator loss Tensor
    :param learning_rate: Learning Rate Placeholder
    :param beta1: The exponential decay rate for the 1st moment in the optimizer
    :return: A tuple of (discriminator training operation, generator training operation)
    """    
    # Get the trainable_variables, split into G and D parts
    t_vars = tf.trainable_variables()
    g_vars = [var for var in t_vars if var.name.startswith("generator")]
    d_vars = [var for var in t_vars if var.name.startswith("discriminator")]
    
    update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
    
    # Generator update
    gen_updates = [op for op in update_ops if op.name.startswith('generator')]
    
    # Optimizers
    with tf.control_dependencies(gen_updates):
        d_train_opt = tf.train.AdamOptimizer(learning_rate=lr_D, beta1=beta1).minimize(d_loss, var_list=d_vars)
        g_train_opt = tf.train.AdamOptimizer(learning_rate=lr_G, beta1=beta1).minimize(g_loss, var_list=g_vars)
        
return d_train_opt, g_train_opt
```

### Training

ここではトレーニング機能を実装します。
構造は比較的単純です。
・epoch_count5回ごとにモデルを更新する。
・トレーニング10回毎に画像を保存する。
・Jupyter notebookは大量の画像を出力するとバグるため、g_lossとd_lossと画像をepoch_count15回毎に画面出力する。
・予め20時間トレーニングしたモデルを読み込んで画像出力することもできる。

```python
def train(epoch_count, batch_size, z_dim, learning_rate_D, learning_rate_G, beta1, get_batches, data_shape, data_image_mode, alpha):
    """
    Train the GAN
    :param epoch_count: Number of epochs
    :param batch_size: Batch Size
    :param z_dim: Z dimension
    :param learning_rate: Learning Rate
    :param beta1: The exponential decay rate for the 1st moment in the optimizer
    :param get_batches: Function to get batches
    :param data_shape: Shape of the data
    :param data_image_mode: The image mode to use for images ("RGB" or "L")
    """
    # Create our input placeholders
    input_images, input_z, lr_G, lr_D = model_inputs(data_shape[1:], z_dim)
        
    # Losses
    d_loss, g_loss = model_loss(input_images, input_z, data_shape[3], alpha)
    
    # Optimizers
    d_opt, g_opt = model_optimizers(d_loss, g_loss, lr_D, lr_G, beta1)
    
    i = 0
    
    version = "firstTrain"
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        
        # Saver
        saver = tf.train.Saver()
        
        num_epoch = 0
        
        if from_checkpoint == True:
            saver.restore(sess, "./models/model.ckpt")
            
            show_generator_output(sess, 4, input_z, data_shape[3], data_image_mode, image_path, True, False)
            
        else:
            for epoch_i in range(epoch_count):        
                num_epoch += 1

                if num_epoch % 5 == 0:

                    # Save model every 5 epochs
                    #if not os.path.exists("models/" + version):
                    #    os.makedirs("models/" + version)
                    save_path = saver.save(sess, "./models/model.ckpt")
                    print("Model saved")

                for batch_images in get_batches(batch_size):
                    # Random noise
                    batch_z = np.random.uniform(-1, 1, size=(batch_size, z_dim))

                    i += 1

                    # Run optimizers
                    _ = sess.run(d_opt, feed_dict={input_images: batch_images, input_z: batch_z, lr_D: learning_rate_D})
                    _ = sess.run(g_opt, feed_dict={input_images: batch_images, input_z: batch_z, lr_G: learning_rate_G})

                    if i % 10 == 0:
                        train_loss_d = d_loss.eval({input_z: batch_z, input_images: batch_images})
                        train_loss_g = g_loss.eval({input_z: batch_z})

                        # Save it
                        image_name = str(i) + ".jpg"
                        image_path = "./images/" + image_name
                        show_generator_output(sess, 4, input_z, data_shape[3], data_image_mode, image_path, True, False) 

                    # Print every 5 epochs (for stability overwize the jupyter notebook will bug)
                    if i % 1500 == 0:

                        image_name = str(i) + ".jpg"
                        image_path = "./images/" + image_name
                        print("Epoch {}/{}...".format(epoch_i+1, epochs),
                              "Discriminator Loss: {:.4f}...".format(train_loss_d),
                              "Generator Loss: {:.4f}".format(train_loss_g))
                        show_generator_output(sess, 4, input_z, data_shape[3], data_image_mode, image_path, False, True)
                
            
                    
    return losses, samples
```

### How to run it

GPUファームを揃えるか10年待つか、何れかを選ばないかぎり手元のPCで動作させるのは非現実的です。
かわりに、AWSやFloydHubといったクラウドGPUサービスを使用する必要があります。

学習データは、Microsoft Azureの[Deep Learning Virtual Machine](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/microsoft-ads.dsvm-deep-learning)を20時間回して作成しました。
注意：私はAzureの利害関係はありません。素晴らしいサービスだと考えているだけです。

仮想マシンでの動作に問題がある場合、[こちらの記事](https://medium.com/@manikantayadunanda/setting-up-deeplearning-machine-and-fast-ai-on-azure-a22eb6bd6429)を参照してください。

このチュートリアルが役に立つことを祈っています。

モデルを改善したら遠慮無くプルリクを送ってください。

![09.gif](https://qiita-image-store.s3.amazonaws.com/0/26088/0923a2cb-d303-1bdc-3bc8-9b21d860bed2.gif)

アイデアやコメントなどがありましたら`hello@simoninithomas.com`にメールするか、[@ThomasSimonini](https://twitter.com/ThomasSimonini)にツイートしてください。

[この記事](https://medium.freecodecamp.org/how-ai-can-learn-to-generate-pictures-of-cats-ba692cb6eae4)が気に入ったら拍手ください。あとフォローもください。


# 試してみた

Githubのほうの[Getting Started](https://github.com/simoninithomas/CatDCGAN#getting-started-)に手順が書いてあったので、実際に試してみた。

## Download the dataset here : [https://www.kaggle.com/crawford/cat-dataset](https://www.kaggle.com/crawford/cat-dataset)

猫画像のデータセットをダウンロードした。
既に目の位置などのメタデータが入っているので、顔の切り出しとかそのあたりを考えなくてよくなる。

## Download the model checkpoint : [https://drive.google.com/drive/folders/1zdZZ91fjOUiOsIdAQKZkUTATXzqy7hiz?usp=sharing](https://drive.google.com/drive/folders/1zdZZ91fjOUiOsIdAQKZkUTATXzqy7hiz?usp=sharing)

リンク先にあった4ファイルをダウンロードした。
これが予め20時間ほどトレーニング済みの学習データであるようだ。

## Type sh start.sh it will handle extract, remove outliers, normalization and face centering

`start.sh`を実行してデータセットを整形しろと書かれているが、手元はWindows環境なのでそのままでは動かなかった。
[中身](https://github.com/simoninithomas/CatDCGAN/blob/master/start.sh)は単にファイルを解凍して異常値を削除して`preprocess_cat_dataset.py`を実行しているだけなので、そのあたりを手動で行った。

だがpreprocess_cat_dataset.pyが置いてなくて、探してみたら[元ネタらしきところ](https://github.com/AlexiaJM/Deep-learning-with-cats)を見付けたのだが、よく見るとそっちの[start.sh的なもの](https://github.com/AlexiaJM/Deep-learning-with-cats/blob/master/Setting%20up%20the%20data/setting_up_script.sh)とは色々差異があったりしてて何が正解なのかよくわからん。

なおpreprocess_cat_dataset.pyはメタデータを元に画像から顔部分の切り出しを行うだけで、特にAI的なことはしていない。

## Launch Jupyter Notebook jupyter notebook --no-browser

Anaconda Navigatorからjupyter notebookを起動した。
Anaconda Navigatorで`no-browser`の指定方法がわからないので気にせず起動。
初期ディレクトリが`Users\ユーザ名`で、シンボリックリンクを追ってくれないので色々と不便。

## Launch CatDCGAN

Githubのプロジェクトをダウンロードして`Users\ユーザ名\AnacondaProjects`以下に配置。
checkpointのファイルは`model`以下に配置、
`images`、`resized_data`ディレクトリを`cats_bigger_than_128x128`と同じ場所に作っておく。

notebookで`Cat DCGAN.ipynb`を選択するとプロジェクトが開く。

## Change do_preprocess = True !!! important!

`do_preprocess = False`の行があったのでTrueに変更した。
これは画像の下準備を行うだけなので、一度実行して`resized_data`ディレクトリに画像が入ったら、以降はFalseに戻してよい。

## If you want to train from scratch : change from_checkpoint = False
## If you want to train from last model saved (you save 20 hours of training tada) : change from_checkpoint = True

学習を最初から行うならFalse、学習済みのモデルを使うならTrueにする。
先ほど学習済みモデルを入れたのでTrueにした。

## 実行

上から順に実行していたら色々エラーが出た。

・`ImportError: No module named 'PIL'`と言われた。
`pip install Pillow`で解決。

・`ImportError: No module named 'scipy'`と言われた。
`pip install scipy`で解決。

・`ImportError: No module named 'matplotlib'`と言われた。
`pip install matplotlib`で解決。

・`UnboundLocalError: local variable 'image_path' referenced before assignment`と言われた。

`train`関数内でimage_pathという変数が使われているのだが、これが場合によっては未定義のまま使用される。
なにこれバグ？

直前に`saver.restore(sess, "./models/model.ckpt")`というコードがあるので、おそらくimage_pathもここからリストアされるのだろう。
従って学習済みデータからの読み込みがうまくいってないと思われるのだが、中身のことなんてさっぱりわからないのでどうしようもなかった。

学習済みモデルを使わないように`from_checkpoint = False`も試してみたのだが、そうすると`train`メソッドから帰ってこなくなった。
一回の学習に数分かかり、それが数万回とかループしてるので死ぬ。
実際[GPUファームを揃えるか10年待つ](https://github.com/simoninithomas/CatDCGAN#important-note-)か[Azureに170ドル払え](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/microsoft-ads.dsvm-deep-learning)って書いてあって、ローカルで試すのは無謀なようだ。

とりあえず100回学習してみた結果は以下のとおり。

![image99.png](https://qiita-image-store.s3.amazonaws.com/0/26088/a4ac14b0-d679-89d6-cab2-b94e1b7c4034.png)

最初の完全ランダム領域から全く抜け出せてない。
後半はなんとなくうすぼんやりと輪郭が見えてきたような気がしないでもないので、20時間ほど放置していたらそれっぽくなってくるかもしれない。


## Cat DCGAN.ipynbの概要

`Cat DCGAN.ipynb`のコード全体をGitHub等から見ることはできないようだが、概ね上記のコードを順番に実行している。
コードをざっくり説明すると、

・上の準備編で作成したcats_bigger_than_128x128ディレクトリの画像を、128*128に揃えてresized_dataディレクトリに格納
・discriminatorやらgeneratorやらのモデル定義
・画像出力メソッドshow_generator_outputの定義
・学習メソッドtrainの定義
・学習に使うパラメータを設定
・resized_dataディレクトリの画像を読み出して学習実行

と全体的な流れはわかりやすい。

個別に何をやってるのかはさっぱりわからない。


# 感想

ディープラーニングもTensorFlowも、そもそもPythonもほぼ初心者なのでよくわからなかった。
DCGANってあれか、要するにアキネイターみたいなものか。

機械学習はとにかく時間かお金がかかることが問題だな。
誰か170ドル払って続きを頼んだ。