Autoencoderのバリエーション②　Deep AutoencoderとConvolutional Autoencoder

#初めに
AutoEncoderの手法を纏める２本目の記事です。１本目は[Autoencoderのバリエーション①](https://qiita.com/shincomcom/items/74e326663570a76b483e)です。

この記事では前回同様に全結合層を使い、層の数を増やしたDeep Autoencoderと、畳み込みを使ったConvolutional Autoencderの２つを扱います。適度に複雑なモデルを使うので、手書き文字の再現精度は上がっていきます。

#Deep AutoEncoder
前回は1層のモデルだけを扱っていました。やはり層を増やして”ディープ”にしたいものです。

```python
# this is the size of our encoded representations
encoding_dim = 32  # 32 floats -> compression of factor 24.5, assuming the input is 784 floats

# this is our input placeholder
input_img = Input(shape=(784,))

(x_train, _), (x_test, _) = mnist.load_data()

x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
print(x_train.shape)
print(x_test.shape)

print(x_train.shape[1:])


x_train = x_train.reshape((len(x_train), np.prod(x_train.shape[1:])))
x_test = x_test.reshape((len(x_test), np.prod(x_test.shape[1:])))
print(x_train.shape)
print(x_test.shape)

input_img = Input(shape=(784,))
encoded = Dense(128, activation='relu')(input_img)
encoded = Dense(64, activation='relu')(encoded)
encoded = Dense(32, activation='relu')(encoded)

decoded = Dense(64, activation='relu')(encoded)
decoded = Dense(128, activation='relu')(decoded)
decoded = Dense(784, activation='sigmoid')(decoded)

autoencoder = Model(input_img, decoded)

adam = Adam(lr=0.001)
#損失関数は平均二乗誤差（mse）
autoencoder.compile(optimizer=adam, loss='mse')

history = autoencoder.fit(x_train, x_train,
                epochs=100,
                batch_size=256,
                shuffle=True,
                validation_data=(x_test, x_test))
```

モデルの構造をmodel_plotを使って図示します。

```python
from keras.utils import plot_model
autoencoder.summary()
plot_model(autoencoder, show_shapes=True)
```

![001_Deep_Autoencoder_modelplot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/af65bdfb-8c04-6a17-a002-c1f836043aba.png)

損失関数は以下の様に減っていきました。
![002_Deep_Autoencoder_loss.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/87d7d53d-b3f5-593b-2d00-62b6f053a3b1.png)

ここでも、もとの手書き数字（上）と、Autoencoderで復元した数字（下）を比較します。

![003_Deep_Autoencoder_reconstructed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/99f4e2cb-19a5-ae36-4894-2655eaf37a19.png)

前回の[Autoencoderのバリエーション①](https://qiita.com/shincomcom/items/74e326663570a76b483e)で示した単純なAutoencoderの結果よりも、少し良く復元できているのではないでしょうか。また、前回と同様に復元の誤差が大きかったデータを確認してみます。

![004_Deep_Autoencoder_bad_samples.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/ebd8cfa1-c787-5e3f-aa75-99483ca0fa12.png)

うーん、そもそも入力の手書き数字が汚いですね。復元の精度が低くても、致し方ないか。。。

#Convolutional Autoencoderとは
今はMNIST（手書き数字）という画像データを扱っているので、全結合層より畳み込み層を使ったネットワークを使った方が、低次元空間にもより良く圧縮できるのでは、と考えますね。実際にやってみます。まずは必要なkerasのインポートとMNISTの読み込みです。2次元畳み込み Conv2Dを行うためnp.reshape(x_train, [-1,image_size, image_size, 1 ])を使って適切に型を変換します。

```python
from keras.layers import Input, Dense
from keras.layers import Conv2D, Flatten, Reshape, Conv2DTranspose
from keras.models import Model
from keras.utils import plot_model
from keras import backend as K

import numpy as np
import matplotlib.pyplot as plt

#MNISTデータの読み込み
from keras.datasets import mnist

(x_train, _), (x_test, _) = mnist.load_data()

image_size = x_train.shape[1]
print(image_size)

#Conv2D用にnumpy arrayの型を変換
x_train = np.reshape(x_train, [-1,image_size, image_size, 1 ])
x_test = np.reshape(x_test, [-1,image_size, image_size, 1 ])

x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
```
次にエンコーダを構築します。

```python
#ネットワークのパラメータ
input_shape = (image_size, image_size, 1)
batch_size = 128
kernel_size = 3
latent_dim = 32

epochs=100

#エンコーダとデコーダのCNNレイヤーごとのフィルタ数
layer_filters = [32, 64]

#オートエンコーダモデルの構築

#初めはエンコーダの構築
inputs = Input(shape=input_shape, name='encoder_input')
x = inputs

#フィルタ数32,62のConv2Dを重ねる
for filters in layer_filters:
  x = Conv2D(filters = filters, kernel_size = kernel_size, activation = 'relu', strides=2, padding='same')(x)

#バックエンドを使って型を取得する
shape = K.int_shape(x)

x = Flatten()(x)
latent = Dense(latent_dim, name='latent_vector')(x)

#エンコーダのインスタンス化
encoder = Model(inputs, latent, name='encoder')
encoder.summary()
plot_model(encoder, show_shapes=True)
```
エンコーダのモデルのネットワークです。28x28から7x7、フィルタ数64まで畳み込んで、Flattenを使って潜在空間のベクトルに変換します。
![101_Conv_Autoencoder_encoder_plot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/af17f310-674e-c445-b7d7-831d8cdbc109.png)


この次はデコーダです。畳み込みで7x7までに圧縮した画像を、デコーダでは再び解像度をもとに戻す必要があります。このため、Conv2DTransposeを使います。Transposeは「転置」という意味ですが、なぜこう呼ばれるかは[Up-sampling with Transposed Convolution](https://naokishibuya.medium.com/up-sampling-with-transposed-convolution-9ae4f2df52d0)が参考になりました。実装したコードは以下の通りです。

```python
#後半のデコーダの構築
latent_inputs = Input(shape=(latent_dim,), name='decoder_input')
x = Dense(shape[1] * shape[2] * shape[3])(latent_inputs)
#Conv2Dに適した型に変換
x = Reshape((shape[1], shape[2], shape[3]))(x)

#フィルタ数64,32のConv2DTransposeを重ねる。ここではupsamplingが必要なのでConv2Dを使う
for filters in layer_filters[::-1]:
  x = Conv2DTranspose(filters=filters, kernel_size = kernel_size, activation='relu', strides=2, padding='same')(x)

#入力の再現
outputs = Conv2DTranspose(filters=1, kernel_size = kernel_size, activation='sigmoid', padding='same', name='decoder_outputs')(x)

#デコーダのインスタンス化
decoder = Model(latent_inputs, outputs, name='decoder')
decoder.summary()
plot_model(decoder, show_shapes=True)
```

デコーダモデルのネットワークです。2DTransposeを使ってアップサンプリングがじっしされて、元の28x28に復元されています。

![102_Conv_Autoencoder_decoder_plot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/bbd596d6-27bb-b491-6434-6af3c211bc47.png)

エンコーダとデコーダを合わせて一つのオートエンコーダを定義します。

```python
#全体をオートエンコーダとして定義
autoencoder =Model( inputs, decoder(encoder(inputs)), name='autoencoder' )
autoencoder.summary()
plot_model(autoencoder, show_shapes=True)
```

![103_Conv_Autoencoder_decoder_plot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/c506fee8-beb1-a392-b35d-f40c1c398a50.png)

このオートエンコーダのモデルをコンパイルして、学習を実施します。

```python
from keras.optimizers import Adam
#adam = keras.optimizers.Adam(lr=0.001)
adam = Adam(lr=0.001)
#損失関数は平均二乗誤差（mse）
autoencoder.compile(optimizer=adam, loss='mse')

history = autoencoder.fit(x_train, x_train,
                epochs=epochs,
                batch_size=batch_size,
                shuffle=True,
                validation_data=(x_test, x_test))
```

損失はエポックごとで以下の様になり、Deep Autoencoderよりもだいぶ小さくなりました。

![104_Conv_Autoencoder_loss.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/2002478b-a878-e30e-bf95-1f08ba5fdea5.png)

Convolutional Autoencoderでの、もとの手書き数字（上）と、Autoencoderで復元した数字（下）を比較します。

![105_Conv_Autoencoder_reconstructed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/6435c31e-86c3-9566-d7bd-2290af0379ac.png)

Deep Autoencoderと比較してもだいぶ再現できています。

ここでも再現の精度が悪かった画像を確認してみます。

![106_Conv_Autoencoder_bad_samples.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/225870/da2797c1-789e-5fb7-e4eb-c5b12930c871.png)

元の画像にノイズが入っていますね。こういう少数の画像の特徴はなかなか圧縮、復元できなくても仕方ないでしょう。


今回はここまで。次はcolorization autoencoderをやってみたいと思います。