聞きかじりめも

主にC++やメディア処理技術などに関して気付いたことを書いていきます.ここが俺のメモ帳だ!

TensorFlow初心者でもGANを学習できた

今回は流行りのネタ,DeepなLearningをしてみます.とは言っても公式チュートリアルをなぞるだけでは恐らくその後何も作れないので,ちょっとは頭で考えながらコードを書いていきます.

この記事は,TensorFlowのチュートリアル通りにMNISTデータを学習できたものの,それが何をやっているか,或いはチュートリアルの次に進もうとしているが,自分でモデルを作成するためにどういう方針でコードを書いていくべきなのかが分からない読者を対象にしています.そう,私のことです.

TensorFlowチュートリアルの次にやるべきことは

私の場合,それはGANを自分の手で実装することでした.GANはモデルの概念は分かりやすいのでやるべきことは明確です.しかしCNNよりは複雑なので自ら実装するとなるとちゃんとTensorFlowを知らなくてはできないですし,学習結果が視覚的に分かりやすくモチベーションが高く保てるため,学習教材として最適だと考えました.

GANの実装

実装に際し,以下のgithubを特に実装の参考にしました.今回の実装はこれのほぼ写経です.

https://github.com/yihui-he/GAN-MNIST/tree/master/mnisthttps://www.tensorflow.org/tutorials/layers

また,理論や実装の考えなどは下記リンクを参考にしました.コード例がTensorFlowではなくKerasですが,それゆえに本質的な部分を理解するのに最適です.

はじめてのGAN

GAN(DCGAN)とは

GANとは,「近年のDeep Learning研究の中で最もCoooooolなアイディア」とも評価されているDeep Neural Networkで,画像生成によく用いられます.以下,概念を簡単におさらいします.

GANは,Generator(以下G)とDiscriminator(以下D)という2つのネットワークから構成されます.Gはベクトルから画像を生成するネットワークで,Dは画像を入力するとそれがG由来(偽物)かデータセット由来(本物)かを判定するネットワークです.Gは出力画像を入力したDの出力がG由来と判定されないように,Dは逆にG由来の画像を正しくG由来だと判定できるように,相互に学習を進めていきます.勿論,学習の初期はGもノイズみたいな画像しか出さず,Dもガバガバな判定しか出しません.しかし学習が進むにつれてGもDも徐々に高度になっていき,最終的にはGからはデータセットと区別のつかない画像が出力されるようになる,という仕組みです.より詳しい説明は別のサイトでいっぱい紹介されていると思うので,そちらを参照してください.

「うん,アイディアは良く分かった.確かに上手くいくんだろう.でもこれどうやって実装すんの?俺にできる気がしない...」

というのが,チュートリアルを終えたばかりの人間の正直な感想でしょう.なので具体的に見てみます.

MNISTのためのGANモデル

MNISTは0~9の手書き数字が書かれた28x28のグレースケール画像ですので,Generatorの出力とDiscriminatorの入力は28x28x1次元の3階テンソルである必要があります.このGANの目的は,手書き数字っぽい画像を生成できるようにすることです.

Generator

乱数入力ベクトルから「手書き数字っぽい画像」を出力するモデルです.

100次元ベクトル -> [全結合] -> [バッチ正則化] -> [ReLU活性化] -> 1024次元ベクトル
1024次元ベクトル -> [全結合] -> [バッチ正則化] -> [ReLU活性化] -> [7x7画像化] -> 7x7x128画像
7x7x128画像 -> [5x5転置畳み込み, 2x2ストライド] -> [バッチ正則化] -> [ReLU活性化] -> 14x14x64画像
14x14x64画像 -> [5x5転置畳み込み,2x2ストライド] -> [バッチ正則化] -> [sigmoid活性化] -> 28x28x1画像

Generatorの入力ベクトルの次元は別に100次元である必要はないと思いますが,2層目,3層目,4層目で2x2のアップサンプリングがなされていくため,出力が28x28x1であれば自然と 7x7 -> 14x14 -> 28x28 と決まります.ここで2次元転置畳み込みとは,CNNにおける2次元畳み込みの逆操作に相当し,ある意味「逆畳み込み」のようなものですが,本質的には逆畳み込み(deconvolution)ではないらしいです.2次元転置畳み込みにおける2x2ストライドは,2x2 Up-Samplingに相当します.

この部分の定義に相当するコードは以下の通り.計算グラフを作成する段階の考え方はチュートリアルと何ら変わりません.自分で構築していくモデルなので,計算グラフが正しくつながっているか(結合係数の次元数の設定が適切か)は誰も保証しません.ここは特に注意する必要があります.

※後でGとDをまとめたDCGANクラスとして実装するので,ここで提示するコードは理解のための疑似コードとして読んでください(最後にソースコードをまとめて載せます).

#不定の次元数の定義
dim_z=100,
dim_W1=1024,
dim_W2=128,
dim_W3=64,
dim_ch=1,

# TensorFlow内学習係数の定義
## Generator
self.g_W1 = tf.Variable(tf.random_normal([dim_z, dim_W1], stddev=0.02), name="g_W1")
self.g_b1 = tf.Variable(tf.random_normal([dim_W1], stddev=0.02), name="g_b1")
self.g_W2 = tf.Variable(tf.random_normal([dim_W1, dim_W2*7*7], stddev=0.02), name="g_W2")
self.g_b2 = tf.Variable(tf.random_normal([dim_W2*7*7], stddev=0.02), name="g_b2")
self.g_W3 = tf.Variable(tf.random_normal([5, 5, dim_W3, dim_W2], stddev=0.02), name="g_W3")
self.g_b3 = tf.Variable(tf.random_normal([dim_W3], stddev=0.02), name="g_b3")
self.g_W4 = tf.Variable(tf.random_normal([5, 5, dim_ch, dim_W3], stddev=0.02), name="g_W4")
self.g_b4 = tf.Variable(tf.random_normal([dim_ch], stddev=0.02), name="g_b4")
# Generaterの計算グラフ
    def generate(self, Z):
        # 1層目
        fc1 = tf.matmul(Z, self.g_W1) + self.g_b1
        bm1, bv1 = tf.nn.moments(fc1, axes=[0])  # バッチ平均,分散
        bn1 = tf.nn.batch_normalization(fc1, bm1, bv1, None, None, 1e-5)
        relu1 = tf.nn.relu(bn1)
        # 2層目
        fc2 = tf.matmul(relu1, self.g_W2) + self.g_b2
        bm2, bv2 = tf.nn.moments(fc2, axes=[0])
        bn2 = tf.nn.batch_normalization(fc2, bm2, bv2, None, None, 1e-5)
        relu2 = tf.nn.relu(bn2)
        # [batch, dim_W2*7*7] -> [batch, 7, 7, dim_W2]
        y2 = tf.reshape(relu2, [self.batch_size, 7,7,self.dim_W2])
        # 3層目
        conv_t1 = tf.nn.conv2d_transpose(y2, self.g_W3, strides=[1,2,2,1],
                                         output_shape=[self.batch_size, 14,14,self.dim_W3]) + self.g_b3
        bm3,bv3 = tf.nn.moments(conv_t1, axes=[0, 1, 2])
        bn3 = tf.nn.batch_normalization(conv_t1, bm3, bv3, None, None, 1e-5)
        relu3 = tf.nn.relu(bn3)
        # 4層目
        conv_t2 = tf.nn.conv2d_transpose(relu3, self.g_W4, strides=[1,2,2,1],
                                         output_shape=[self.batch_size, 28, 28, self.dim_ch]) + self.g_b4
        img = tf.nn.sigmoid(conv_t2)

        return img

Discriminator

Generatorの出力画像とデータセット画像とを見分けるように学習するモデルです.

28x28x1画像 -> [5x5畳み込み,2x2ストライド] -> [leaky ReLU活性化] -> 14x14x64画像
14x14x64画像 -> [5x5畳み込み,2x2ストライド] -> [leaky ReLU活性化] -> 7x7x128画像
7x7x128画像 -> [ベクトル化] -> [全結合, dropoutあり] -> [leaky ReLU活性化] -> [dropout] -> 256次元ベクトル
256次元ベクトル -> [全結合] -> [sigmoid活性化] -> 1次元ベクトル(判定確率)

こちらは 画像入力→ベクトル出力 という,TensorFlowチュートリアルでも組んだ覚えのあるモデルなので,そこまで理解に苦しむことはないでしょう.違いは出力層が1次元ベクトル(=スカラー量)であることだけです.出力層が1の時はデータセット,0の時はGeneratorの出力と判定します.層が深いDNNの場合,全結合層の代わりにglobal average poolingを使うとよいとされています.ただしこれは収束が遅くなるため,今回のように浅い場合は全結合のままで構わないそうです.この部分に該当するコードは次の通り.

#不定の次元数の定義
dim_z=100,
dim_W1=1024,
dim_W2=128,
dim_W3=64,
dim_ch=1,

        # TensorFlow内学習係数の定義
        ## Discriminator
        self.d_W1 = tf.Variable(tf.random_normal([5,5,dim_ch,dim_W3], stddev=0.02), name="d_W1")
        self.d_b1 = tf.Variable(tf.random_normal([dim_W3], stddev=0.02), name="d_b1")
        self.d_W2 = tf.Variable(tf.random_normal([5,5,dim_W3,dim_W2], stddev=0.02), name="d_W2")
        self.d_b2 = tf.Variable(tf.random_normal([dim_W2], stddev=0.02), name="d_b2")
        self.d_W3 = tf.Variable(tf.random_normal([dim_W2*7*7,dim_W1], stddev=0.02), name="d_W3")
        self.d_b3 = tf.Variable(tf.random_normal([dim_W1], stddev=0.02), name="d_b3")
        self.d_W4 = tf.Variable(tf.random_normal([dim_W1, 1], stddev=0.02), name="d_W4")
        self.d_b4 = tf.Variable(tf.random_normal([1], stddev=0.02), name="d_b4")
# Discriminaterの計算グラフ
    def discriminate(self, img):
        # 1層目
        conv1 = tf.nn.conv2d(img, self.d_W1, strides=[1,2,2,1], padding="SAME") + self.d_b1
        y1 = leaky_relu(conv1)
        # 2層目
        conv2 = tf.nn.conv2d(y1, self.d_W2, strides=[1,2,2,1], padding="SAME") + self.d_b2
        y2 = leaky_relu(conv2)
        # 3層目
        vec, _ = tensor_to_vector(y2)
        fc1 = tf.matmul(vec, self.d_W3) + self.d_b3
        y3 = leaky_relu(fc1)
        # 4層目
        fc2 = tf.matmul(y3, self.d_W4) + self.d_b4
        y4 = tf.nn.sigmoid(fc2)
        
        return y4

コスト関数

判定確率Dは0から1の値なので,対数を取ると  (-\infty, 0) の範囲をとります.これを利用して,確率の対数の負値を最小化することで確率を1に近づけます.

Gに入力される乱数ベクトルをzとすると,D(G(z)) はGの出力がDによってデータセットの一部だと判定される確率です.Dには間違えてほしいので,D(G(z))=1 が望ましいです.従って,Gのコスト関数は次の式で表せます.

$$ J_G = {1 \over batch}\sum_{batch} {-log(D(G(z)))} $$

一方で,Dの目的はGの出力とデータセットxを正しく見分ける事です.つまり,D(G(z)) = 0でかつD(x) = 1であることが望ましいです.このことをコスト関数を使って表すと次のようになります.

$$ J_D = {1 \over batch}\sum_{batch} {-log(1-D(G(z))) - log(D(x)) } $$

以上をまとめた計算グラフのソースコードは次のようになります.

        # プレースホルダーの用意
        Z = tf.placeholder(tf.float32, [self.batch_size, self.dim_z])  # Generatorへの入力
        # 画像の用意
        img_real = tf.placeholder(tf.float32, [self.batch_size]+self.image_shape)  # Discriminatorへの入力
        img_gen = self.generate(Z)
        # 出力
        raw_real = self.discriminate(img_real)
        raw_gen = self.discriminate(img_gen)
        # 確率
        p_real = tf.nn.sigmoid(raw_real)
        p_gen = tf.nn.sigmoid(raw_gen)
        # コスト関数の定義
        discrim_cost = tf.reduce_mean(
            -tf.reduce_sum(tf.log(p_real) + \
                           tf.log(tf.ones(self.batch_size, tf.float32) - p_gen), axis=1))
        gen_cost = tf.reduce_mean(-tf.reduce_sum(tf.log(p_gen), axis=1))

Dのコスト関数において,1の代わりにtf.ones(batch_size, tf.float32)を使用します.これは,コスト関数には1個の画像だけでなくまとまった量のバッチが流れてくるため,そのバッチサイズ分の1を用意する必要があるからです.

また,チュートリアルで組んだCNNとは異なり,G用とD用でコスト関数が2個になっています.ただしどちらのコスト関数でも互いのモデルを使用するため,Gを学習するときとDを学習するときでそれぞれのパラメータは共通でなければなりません.もしGとDを別々のクラスとして実装する場合は,この点に注意しましょう(うっかりD学習時とG学習時で別のインスタンスにしてしまうと,メンバ変数がそれぞれの学習で共有されず学習が進まないなんて事態も考えられます).

学習の戦略

GとDの目的関数は相反する内容であるため,すべての変数をどちらの最適化でも考慮してしまうと学習が全く進みません.従って,Gの学習プロセスではGに関わる変数だけ,逆にDの学習プロセスではDの変数だけを最適化計算させなくてはなりません.さて,実は上のコードで,tf.Variable()に学習係数を登録する際にひっそりと名前(name=)を個別につけていました.

        # Generator
        self.g_W2 = tf.Variable(tf.random_normal([dim_W1, dim_W2*7*7], stddev=0.02), name="g_W2")
        self.g_b2 = tf.Variable(tf.random_normal([dim_W2*7*7], stddev=0.02), name="g_b2")
        # Discriminator
        self.d_W2 = tf.Variable(tf.random_normal([5,5,dim_W3,dim_W2], stddev=0.02), name="d_W2")
        self.d_b2 = tf.Variable(tf.random_normal([dim_W2], stddev=0.02), name="d_b2")

この時,名前の添え字を見るだけでG用かD用か見分けられるようにしてありました.こうすることで,D用とG用それぞれの最適化メソッドにバインドする変数リストを簡単に与えられます.

# tensorflow変数の準備
discrim_vars = [x for x in tf.trainable_variables() if "d_" in x.name]
gen_vars = [x for x in tf.trainable_variables() if "g_" in x.name]
# 最適化メソッドの用意
optimizer_d = tf.train.AdamOptimizer(0.0002, beta1=0.5).minimize(d_cost_tf, var_list=discrim_vars)
optimizer_g = tf.train.AdamOptimizer(0.0002, beta1=0.5).minimize(g_cost_tf, var_list=gen_vars)

GとDの2つのモデルは同時に(交互に)学習する必要があります.どちらから学習を始めても構いませんが,私は偶数番目をG,奇数番目をDの学習に充てました.バッチサイズ毎に最適化させるモデルを切り替えつつ,データセットからバッチを取り終わったら(1 epoch回ったら)またデータセットをシャッフルしてループを回す,というような方法になっています.

# 学習開始
    itr = 0
    # epoch毎のfor
    for epoch in range(n_epochs):
        index = np.arange(len(train_imgs))
        np.random.shuffle(index)
        trX = train_imgs[index]
        
        # batch_size毎のfor
        for start, end in zip(
                range(0, len(trX), batch_size),
                range(batch_size, len(trX), batch_size)):
            # 画像は0-1に正規化
            Xs = trX[start:end].reshape([-1, 28,28,1]) / 255.
            Zs = np.random.uniform(-1,1, size=[batch_size, dcgan_model.dim_z]).astype(np.float32)
            
            if np.mod(itr, 2) != 0 :
                # 偶数番目はGeneratorを学習
                _, gen_loss_val = sess.run([optimizer_g, g_cost_tf], feed_dict={Z_tf:Zs})
                discrim_loss_val, p_real_val, p_gen_val \
                        = sess.run([d_cost_tf, p_real, p_gen], feed_dict={Z_tf:Zs, image_tf:Xs})
                #print("=========== updating G ==========")
                #print("iteration:", itr)
                #print("gen loss:", gen_loss_val)
                #print("discrim loss:", discrim_loss_val)
            else:
                # 奇数番目はDiscriminatorを学習
                _, discrim_loss_val = sess.run([optimizer_d, d_cost_tf],
                                               feed_dict={Z_tf:Zs, image_tf:Xs})
                gen_loss_val, p_real_val, p_gen_val = sess.run([g_cost_tf, p_real, p_gen], 
                                                               feed_dict={Z_tf:Zs, image_tf:Xs})
                #print("=========== updating D ==========")
                #print("iteration:", itr)
                #print("gen loss:", gen_loss_val)
                #print("discrim loss:", discrim_loss_val)
                
            
            #print("Average P(real)=", p_real_val.mean())
            #print("Average P(gen)=", p_gen_val.mean())
            itr += 1

以上がTensorFlowで実装するGANのあらましです.

遭遇したエラー

ValueError: No gradients provided for any variable, check your graph for ops that do not support gradients, between variables

これは微分演算ができないことを示しています.理由は書き方のミスで,計算グラフがどこかで途絶えているからです.目を凝らして順々に計算グラフを追っていき,余計な変数や誤った変数に接続していないか,結合係数の次元数が適切かを確かめてみましょう.

The value of a feed cannot be a tf.Tensor object. Acceptable feed values include Python scalars, strings, lists, numpy ndarrays, or TensorHandles.

バッチをfeedさせるときにtf.Tensorオブジェクトに変換できなかったことを示します..eval()メソッドを使うとうまくいくらしいです.

学習例

最初20epochくらいまではほぼ真っ暗だったので本当に学習できているか心配になりましたが,250epoch以上からかなりはっきりと数字が生成されました.CPUで計算するととんでもなく時間がかかるので,GPUが利用可能なTensorFlowを推奨します.私の環境の場合,8時間くらいかかって100epochだったのが,10分程度で500epochまで学習できるようになりました.

f:id:Mzawa2:20180301232718p:plain


f:id:Mzawa2:20180301232736p:plain


f:id:Mzawa2:20180301232745p:plain


f:id:Mzawa2:20180301232802p:plain


f:id:Mzawa2:20180301232812p:plain


f:id:Mzawa2:20180301232822p:plain


f:id:Mzawa2:20180301232830p:plain

ソースコード

Jupyter環境のブロックに分けています

# tensorflowのロード
import tensorflow as tf
import numpy as np
# MNISTデータのダウンロード
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
# 補助関数の定義
# 1次元ベクトル化
def tensor_to_vector(input):
    shape = input.get_shape()[1:].as_list()
    dim = np.prod(shape)
    return tf.reshape(input, [-1, dim]), dim

# leaky ReLU活性化関数
def leaky_relu(input):
    return tf.maximum(0.2*input, input)
# DCGANクラスの定義
class DCGAN():
    def __init__(
            self,
            batch_size=100,
            image_shape=[28,28,1],
            dim_z=100,
            dim_W1=1024,
            dim_W2=128,
            dim_W3=64,
            dim_ch=1,
            ):
        # クラス内変数の初期化
        self.batch_size = batch_size
        self.image_shape = image_shape
        self.dim_z = dim_z
        self.dim_W1 = dim_W1
        self.dim_W2 = dim_W2
        self.dim_W3 = dim_W3
        self.dim_ch = dim_ch
        # TensorFlow内学習係数の定義
        ## Generator
        self.g_W1 = tf.Variable(tf.random_normal([dim_z, dim_W1], stddev=0.02), name="g_W1")
        self.g_b1 = tf.Variable(tf.random_normal([dim_W1], stddev=0.02), name="g_b1")
        self.g_W2 = tf.Variable(tf.random_normal([dim_W1, dim_W2*7*7], stddev=0.02), name="g_W2")
        self.g_b2 = tf.Variable(tf.random_normal([dim_W2*7*7], stddev=0.02), name="g_b2")
        self.g_W3 = tf.Variable(tf.random_normal([5, 5, dim_W3, dim_W2], stddev=0.02), name="g_W3")
        self.g_b3 = tf.Variable(tf.random_normal([dim_W3], stddev=0.02), name="g_b3")
        self.g_W4 = tf.Variable(tf.random_normal([5, 5, dim_ch, dim_W3], stddev=0.02), name="g_W4")
        self.g_b4 = tf.Variable(tf.random_normal([dim_ch], stddev=0.02), name="g_b4")
        ## Discriminator
        self.d_W1 = tf.Variable(tf.random_normal([5,5,dim_ch,dim_W3], stddev=0.02), name="d_W1")
        self.d_b1 = tf.Variable(tf.random_normal([dim_W3], stddev=0.02), name="d_b1")
        self.d_W2 = tf.Variable(tf.random_normal([5,5,dim_W3,dim_W2], stddev=0.02), name="d_W2")
        self.d_b2 = tf.Variable(tf.random_normal([dim_W2], stddev=0.02), name="d_b2")
        self.d_W3 = tf.Variable(tf.random_normal([dim_W2*7*7,dim_W1], stddev=0.02), name="d_W3")
        self.d_b3 = tf.Variable(tf.random_normal([dim_W1], stddev=0.02), name="d_b3")
        self.d_W4 = tf.Variable(tf.random_normal([dim_W1, 1], stddev=0.02), name="d_W4")
        self.d_b4 = tf.Variable(tf.random_normal([1], stddev=0.02), name="d_b4")
        
    def build_model(self):
        # プレースホルダーの用意
        Z = tf.placeholder(tf.float32, [self.batch_size, self.dim_z])  # Generatorへの入力
        # 画像の用意
        img_real = tf.placeholder(tf.float32, [self.batch_size]+self.image_shape)  # Discriminatorへの入力
        img_gen = self.generate(Z)
        # 出力
        raw_real = self.discriminate(img_real)
        raw_gen = self.discriminate(img_gen)
        # 確率
        p_real = tf.nn.sigmoid(raw_real)
        p_gen = tf.nn.sigmoid(raw_gen)
        # コスト関数の定義
        discrim_cost = tf.reduce_mean(
            -tf.reduce_sum(tf.log(p_real) + \
                           tf.log(tf.ones(self.batch_size, tf.float32) - p_gen), axis=1))
        gen_cost = tf.reduce_mean(-tf.reduce_sum(tf.log(p_gen), axis=1))
        
        return Z, img_real, discrim_cost, gen_cost, p_real, p_gen
        
    def generate(self, Z):
        # 1層目
        fc1 = tf.matmul(Z, self.g_W1) + self.g_b1
        bm1, bv1 = tf.nn.moments(fc1, axes=[0])
        bn1 = tf.nn.batch_normalization(fc1, bm1, bv1, None, None, 1e-5)
        relu1 = tf.nn.relu(bn1)
        # 2層目
        fc2 = tf.matmul(relu1, self.g_W2) + self.g_b2
        bm2, bv2 = tf.nn.moments(fc2, axes=[0])
        bn2 = tf.nn.batch_normalization(fc2, bm2, bv2, None, None, 1e-5)
        relu2 = tf.nn.relu(bn2)
        # [batch, dim_W2*7*7] -> [batch, 7, 7, dim_W2]
        y2 = tf.reshape(relu2, [self.batch_size, 7,7,self.dim_W2])
        # 3層目
        conv_t1 = tf.nn.conv2d_transpose(y2, self.g_W3, strides=[1,2,2,1],
                                         output_shape=[self.batch_size, 14,14,self.dim_W3]) + self.g_b3
        bm3,bv3 = tf.nn.moments(conv_t1, axes=[0, 1, 2])
        bn3 = tf.nn.batch_normalization(conv_t1, bm3, bv3, None, None, 1e-5)
        relu3 = tf.nn.relu(bn3)
        # 4層目
        conv_t2 = tf.nn.conv2d_transpose(relu3, self.g_W4, strides=[1,2,2,1],
                                         output_shape=[self.batch_size, 28, 28, self.dim_ch]) + self.g_b4
        img = tf.nn.sigmoid(conv_t2)
        
        return img
    
    def discriminate(self, img):
        # 1層目
        conv1 = tf.nn.conv2d(img, self.d_W1, strides=[1,2,2,1], padding="SAME") + self.d_b1
        y1 = leaky_relu(conv1)
        # 2層目
        conv2 = tf.nn.conv2d(y1, self.d_W2, strides=[1,2,2,1], padding="SAME") + self.d_b2
        y2 = leaky_relu(conv2)
        # 3層目
        vec, _ = tensor_to_vector(y2)
        fc1 = tf.matmul(vec, self.d_W3) + self.d_b3
        y3 = leaky_relu(fc1)
        # 4層目
        fc2 = tf.matmul(y3, self.d_W4) + self.d_b4
        #y4 = tf.nn.sigmoid(fc2)
        
        return fc2
    
    def generate_samples(self, batch_size):
        # ここでは指定したbatch_sizeでサンプルを生成するため,self.batch_sizeは使わない
        Z = tf.placeholder(tf.float32, [batch_size, self.dim_z])
        # 1層目
        fc1 = tf.matmul(Z, self.g_W1) + self.g_b1
        bm1, bv1 = tf.nn.moments(fc1, axes=[0])
        bn1 = tf.nn.batch_normalization(fc1, bm1, bv1, None, None, 1e-5)
        relu1 = tf.nn.relu(bn1)
        # 2層目
        fc2 = tf.matmul(relu1, self.g_W2) + self.g_b2
        bm2, bv2 = tf.nn.moments(fc2, axes=[0])
        bn2 = tf.nn.batch_normalization(fc2, bm2, bv2, None, None, 1e-5)
        relu2 = tf.nn.relu(bn2)
        # [batch, dim_W2*7*7] -> [batch, 7, 7, dim_W2]
        y2 = tf.reshape(relu2, [batch_size, 7,7,self.dim_W2])
        # 3層目
        conv_t1 = tf.nn.conv2d_transpose(y2, self.g_W3, strides=[1,2,2,1],
                                         output_shape=[batch_size, 14,14,self.dim_W3]) + self.g_b3
        bm3,bv3 = tf.nn.moments(conv_t1, axes=[0, 1, 2])
        bn3 = tf.nn.batch_normalization(conv_t1, bm3, bv3, None, None, 1e-5)
        relu3 = tf.nn.relu(bn3)
        # 4層目
        conv_t2 = tf.nn.conv2d_transpose(relu3, self.g_W4, strides=[1,2,2,1],
                                         output_shape=[batch_size, 28, 28, self.dim_ch]) + self.g_b4
        img = tf.nn.sigmoid(conv_t2)
        return Z, img
# モデルの構築
dcgan_model = DCGAN(batch_size=128, image_shape=[28,28,1])
Z_tf, image_tf, d_cost_tf, g_cost_tf, p_real, p_gen = dcgan_model.build_model()
Z_gen, image_gen = dcgan_model.generate_samples(batch_size=32)
# セッション開始
sess = tf.InteractiveSession()
saver = tf.train.Saver(max_to_keep=10)
# tensorflow変数の準備
discrim_vars = [x for x in tf.trainable_variables() if "d_" in x.name]
gen_vars = [x for x in tf.trainable_variables() if "g_" in x.name]
# 最適化メソッドの用意
optimizer_d = tf.train.AdamOptimizer(0.0002, beta1=0.5).minimize(d_cost_tf, var_list=discrim_vars)
optimizer_g = tf.train.AdamOptimizer(0.0002, beta1=0.5).minimize(g_cost_tf, var_list=gen_vars)
# TensorFlow内のグローバル変数の初期化
tf.global_variables_initializer().run()
from matplotlib import pyplot as plt

# 生成画像の可視化
def visualize(images, num_itr, rows, cols):
    # タイトル表示
    plt.title(num_itr, color="red")
    for index, data in enumerate(images):
        # 画像データはrows * colsの行列上に配置
        plt.subplot(rows, cols, index + 1)
        # 軸表示は無効
        plt.axis("off")
        # データをグレースケール画像として表示
        plt.imshow(data.reshape(28,28), cmap="gray", interpolation="nearest")
    plt.show()
# 学習
def train(train_imgs, n_epochs, batch_size):
    itr = 0
    for epoch in range(n_epochs):
        index = np.arange(len(train_imgs))
        np.random.shuffle(index)
        trX = train_imgs[index]
        
        # batch_size毎のfor
        for start, end in zip(
                range(0, len(trX), batch_size),
                range(batch_size, len(trX), batch_size)):
            # 画像は0-1に正規化
            Xs = trX[start:end].reshape([-1, 28,28,1]) / 255.
            Zs = np.random.uniform(-1,1, size=[batch_size, dcgan_model.dim_z]).astype(np.float32)
            
            if np.mod(itr, 2) != 0 :
                # 偶数番目はGeneratorを学習
                _, gen_loss_val = sess.run([optimizer_g, g_cost_tf], feed_dict={Z_tf:Zs})
                discrim_loss_val, p_real_val, p_gen_val \
                        = sess.run([d_cost_tf, p_real, p_gen], feed_dict={Z_tf:Zs, image_tf:Xs})
                #print("=========== updating G ==========")
                #print("iteration:", itr)
                #print("gen loss:", gen_loss_val)
                #print("discrim loss:", discrim_loss_val)
            else:
                # 奇数番目はDiscriminatorを学習
                _, discrim_loss_val = sess.run([optimizer_d, d_cost_tf],
                                               feed_dict={Z_tf:Zs, image_tf:Xs})
                gen_loss_val, p_real_val, p_gen_val = sess.run([g_cost_tf, p_real, p_gen], 
                                                               feed_dict={Z_tf:Zs, image_tf:Xs})
                #print("=========== updating D ==========")
                #print("iteration:", itr)
                #print("gen loss:", gen_loss_val)
                #print("discrim loss:", discrim_loss_val)
                
            
            #print("Average P(real)=", p_real_val.mean())
            #print("Average P(gen)=", p_gen_val.mean())
            itr += 1
        
        # サンプルを表示
        z = np.random.uniform(-1,1, size=[32, dcgan_model.dim_z]).astype(np.float32)
        generated_samples = sess.run([image_gen], feed_dict={Z_gen:z})
        #plt.imshow(generated_samples[0][0].reshape(28,28), cmap="gray", interpolation="nearest")
        visualize(generated_samples[0], epoch, 4,8)
        print("epoch = ", epoch)
train(mnist.test.images, 500, 128)