胸部X線画像を対象とした敵対的生成ネットワーク - DCGAN -

#やりたいこと
参考書やGithubを見ながら、見様見真似でまずDCGANを試してみたい。
今回は簡易的なDCGANで、胸部X線画像を入力して、似たような画像が生成できるか試す。

#環境
* Ubuntu 18.04
* Anaconda 1.9.6
* Tensorflow-gpu 1.12.0
* Keras-gpu 2.2.4
* GeForce GTX 1050 Ti(totalMemory: 3.95GiB freeMemory: 3.89GiB) + AkiTiONODE

#必要なもの
日本放射線技術学会「標準ディジタル画像データベース[胸部腫瘤陰影像]」SuperResolution01
http://imgcom.jsrt.or.jp/minijsrtdb/

画像サイズ　256*256、色深度　Gray 8 bitのデータ(一番上段のもの)を選択してダウンロード。
ダウンロードしたら解凍。ここではわかりやすくデスクトップへ。
SuperResolution01.zipをデスクトップに移動して、右クリックから「ここで展開」を実行。

#画像の前処理
1. デスクトップに新しく「DCGAN01」フォルダを作成する。
2. さらに、この「DCGAN01」フォルダの中に「org」フォルダを作成する。
3. SuperResolution01内にある「train/org」内のすべての画像と、SuperResolution01内にある「test/org」内のすべての画像とを「DCGAN01/org」にコピー＆ペーストする。おそらく、画像は全部で247枚になるはず。
4. その後、「DCGAN01/org」内のすべての画像をリネームする。リネームは、画像を全選択して、右クリックなどで名前の変更を行う。この際、ベースになるファイル名は同じになるようにして、末尾に番号が振られるようにすると後の作業がやりやすい。例えば、small_1.png small_2.png small_3.png ...など。
5. そして、「DCGAN01/org」内の画像数を「32」で割れる数に合わせる。ここでは例として「224」枚の画像を残すようにする。つまり、225〰247枚目の画像は削除してしまう。
6. 次に、新しく「DCGAN01」内に「small_org」フォルダを作る。（この時点で、「DCGAN01」フォルダの直下には、「org」と「small_org」の2つのフォルダができた。）
7. 続けて、「DCGAN01/small_org」のフォルダに、「train」フォルダを作成する。ここは重要なので注意（ImageDataGenerater(class_mode=None)への対応）。
8. ここまでできたら、このままでは画像のマトリクスが256*256となっており、練習には重いので、マトリクスを32*32まで落とす。方法はいくつかある。

##方法１：ImageJ
ImageJで224枚の画像を一遍にスタックとして開き、リサイズ（補間法は任意でいい）して、任意の一般画像フォーマットで保存する。（おそらく、スクリプトを書かないとマンパワーが、、。）

##方法2：コードを書く
```
'''
Created on 2019/05/05

@author: tatsunidas
'''
import glob, os
import numpy as np
from skimage.io import imread
from scipy.misc import imresize
from PIL import Image

#ここは自分の環境に合わせて下さい
org_dir = '/home/tatsunidas/デスクトップ/DCGAN01/org/'
small_dir = '/home/tatsunidas/デスクトップ/DCGAN01/small_org/'

datasets = glob.glob(org_dir+'*.png')
for data in datasets:
    img = imread(data,as_gray=True)
    #array to PIL
    img = Image.fromarray(np.uint8(img))
#     img = imresize(img,(32, 32), interp='bicubic')# return as ndarray
    img = img.resize((32,32), Image.BICUBIC)
    img.save(small_dir+'train/'+os.path.basename(data))
```
ここまでで、このような状態になる。

![Screenshot from 2019-05-06 01-30-36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379410/fa410f04-81eb-0836-8ebb-8ad83209d0a2.png)

画像はすべて32*32マトリクスになっている。
![Screenshot from 2019-05-06 01-33-33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379410/b01bc595-bd91-b3fa-0201-06e5af2d30d3.png)

#コード
今回はほぼすべて参考URLを参考にさせていただき、組んでみた。DCGANの中でも比較的シンプルでわかりやすい。

```
'''
Created on 2019/05/05

設定
モデル構築（GAN）
モデルトレーニング
モデル保存

@author: tatsunidas
'''

import os
from keras.preprocessing import image
from keras.preprocessing.image import ImageDataGenerator
import keras
from keras import layers
import numpy as np

#トレーニングデータの場所
TRAIN_DIR = '/home/tatsunidas/デスクトップ/DCGAN01/small_org/' #trainまで指定しないことに注意
#生成画像の保存先：任意の場所へ（コードを実行する前にフォルダを作っておいて下さい。）
save_dir = '/home/tatsunidas/デスクトップ/DCGAN01/dcgan_sampling/' 
latent_dim = 32 # 殆どの場合、height,widthと同じ値に。
height = 32
width = 32
channels = 1

batch_size=16#とりあえず

#贋作生成（ジェネレータ）モデルを構築
generator_input = keras.Input(shape=(latent_dim,))
# 入力を16*16,128 channelの特徴マップへ
x = layers.Dense(128*16*16)(generator_input)
x = layers.LeakyReLU()(x)
x = layers.Reshape((16,16,128))(x)
#畳み込み層を追加
x = layers.Conv2D(256,5,padding='same')(x)
x = layers.LeakyReLU()(x)
#32,32へアップサンプリング
x = layers.Conv2DTranspose(256,4,strides=2,padding='same')(x)
x = layers.LeakyReLU()(x)
#さらに畳み込み層を追加
x = layers.Conv2D(256,5,padding='same')(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(256,5,padding='same')(x)
x = layers.LeakyReLU()(x)
#32.32.1の特徴マップへ
x = layers.Conv2D(channels,7,activation='tanh',padding='same')(x)

generator = keras.models.Model(generator_input,x)
generator.summary()

'''
discriminator(贋作判別器)
'''
discriminator_input = layers.Input(shape=(height,width,channels))
x = layers.Conv2D(128,3)(discriminator_input)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128,4,strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128,4,strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128,4,strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Flatten()(x)
#ドロップアウトを追加。重要
x = layers.Dropout(0.4)(x)
#判別分類
x = layers.Dense(1,activation='sigmoid')(x)
#discriminatorモデルをインスタンス化
discriminator = keras.models.Model(discriminator_input,x)
discriminator.summary()

#オプティマイザで勾配刈り込み（clipvalue）
discriminator_optimizer = keras.optimizers.RMSprop(lr=0.0008,clipvalue=1.0,decay=1e-8)
discriminator.compile(optimizer=discriminator_optimizer,loss='binary_crossentropy')

#生成と判別をつなぎ合わせる（つまり、GANを構築する）
#discriminatorの重みを凍結
discriminator.trainable = False
gan_input = keras.Input(shape=(latent_dim,))
gan_output = discriminator(generator(gan_input))
gan = keras.models.Model(gan_input,gan_output)
gan_optimizer = keras.optimizers.RMSprop(lr=0.0004,clipvalue=1.0,decay = 1e-8)
gan.compile(optimizer=gan_optimizer,loss='binary_crossentropy')

#training dataの作成
data_gen = ImageDataGenerator(rescale=1./255)
data_gen = data_gen.flow_from_directory(TRAIN_DIR,
                                        target_size=(32,32),
                                        batch_size=batch_size,
                                        class_mode=None,
                                        color_mode= "grayscale")
#GANのトレーニング開始
iterations = 10000 #エポックと同意。とりあえず10000
for step in range(iterations):
    #贋作を作るために、無作為にベクトルを作成
    random_latent_vectors = np.random.normal(size=(batch_size,latent_dim))
    #偽物画像にデコーディング
    generated_images = generator.predict(random_latent_vectors)
    #本物の画像を取得
    real_images = data_gen.next()
    combined_images = np.concatenate([generated_images,real_images])
    #本物の画像と偽物の画像を区別するラベルを組み立て
    labels = np.concatenate([np.ones((batch_size,1)),np.zeros((batch_size,1))])
    #ラベルにランダムノイズを追加,重要なトリック
    labels += 0.05 * np.random.random(labels.shape)
    #discriminator（判別器）を訓練
    d_loss = discriminator.train_on_batch(combined_images, labels)
    #潜在空間から点をランダムに抽出
    random_latent_vectors = np.random.normal(size=(batch_size,latent_dim))
    #これらは全て本物というラベルを作成
    misleading_targets = np.zeros((batch_size,1))
    #ganモデルを通じて、generatorを訓練
    a_loss = gan.train_on_batch(random_latent_vectors, misleading_targets)
    #訓練過程でモデルと結果を保存
    if step % 1000 == 0 or step == iterations-1:
        gan.save_weights('gan-discloss_%s_advloss_%s.h5' %(d_loss,a_loss))
        #成果を出力
        print('discriminator loss at step %s: %s' %(step,d_loss))
        print('adversarial loss at step %s: %s'%(step,a_loss))
        #生成画像を保存
        img = image.array_to_img(generated_images[0]*255.,scale=False)
        img.save(os.path.join(save_dir,'generated_chest_'+str(step)+'.png'))
        #比較のために本物も保存
        img = image.array_to_img(real_images[0]*255.,scale=False)
        img.save(os.path.join(save_dir,'real_chest_'+str(step)+'.png'))
        if step == iterations-1 :
            print('finish iterations')
        else:
            print('now training gan ..., last step is %s'%(step))
```
#結果
![Montage-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379410/34b7eba5-abca-6573-ad50-cb7c26c6c152.jpeg)
![Montage-11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379410/bc685027-ff84-5307-6a5a-481630db2fa7.jpeg)
上段は生成画像、下段はオリジナル画像。
左からエポック0,1000,2000,3000,4000,5000,6000,7000,8000,9000

#この記事を書いたモチベーション
勉強のために。

#その他
誤記などありましたらご指摘下さい。

#参考URL
* PythonとKerasによるディープラーニング:https://www.amazon.co.jp/dp/4839964262/ref=rdr_ext_sb_ti_hist_1
* https://github.com/Goldesel23/DCGAN-for-Bird-Generation/blob/master/traindcgan.py
* https://github.com/kozistr/Awesome-GANs
* https://hub.packtpub.com/generative-adversarial-networks-using-keras/