TensorflowでDCGANを実装した

前回でMLPでのGANの実装が大体できたので、次はDCGANを実装に挑戦する。
DCGANのDCは Deep Convolution のDCだから畳み込み層を追加してパワーアップした感じのGANなんだろかというのが論文を読む前のイメージだったりする。

コードはここ

github.com

いつも通り先に論文を読んだ

[1511.06434] Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks

前回読んだ論文で提案されたGANでは学習が不安定だったり、生成物にノイズがあったり、キメラみたいな意味的に、視覚的に理解が難しいものが生成されることがあると書いてあった。その後LAPGANというGANを拡張したものも出たがそれでもノイズなどは残ったらしい

DCGANではそのような学習の不安定さを低減するようにしたり、より精密なものを生成できるように改善されたGANのアーキテクチャと書いてある。すごいやん・・・。変更点としては生成モデルと識別モデルのネットワーク構造にCNNを用いるのと、3つのアプローチを採用するとある

  1. CNNといっても、Max Pooling とかを無くし、畳み込み層オンリーのネットワーク all convolution net というものを採用する。モデル自体が独自に空間ダウンサンプリング、アップサンプリングできるようにするためとか何とか。

  2. 分類問題においてのCNNでは畳み込み層のあと、それを平坦化(flatten)して全結合層に渡してクラス数に対応する出力を得るといったものが多い。Alexnetは何層もの畳み込みと最大プーリングのあと、全結合層が3層くらい続いている。DCGANでのCNNはこういった畳み込みの後の全結合層を生成器と識別器の両ネットワークにおいて排除する
    生成器では最後の畳み込み層で入力と同じ画像サイズ、チャンネルのものを返して、識別器では最後の畳み込み層の値を平均プーリングを適用して(バッチ数、クラス数)といった形の出力を返すようにする感じで合ってるはず

  3. 学習の不安定さの改善策として、両モデルの中間層にバッチ正規化(Batch Normalization)を適用する。それと生成器では出力層以外の層での活性化関数をRelu関数を、出力層はtanh関数([-1,1]の値を返す)を用いる。識別器には出力層にはシグモイド関数を、出力層以外にはLeakyRelu関数を用いる。まだLeakyReluについては知見はないけどこの関数のハイパーパラメータとしてalpha=0.2がいいらしい

変更点の次に実際のネットワーク構造は次のような図が論文に載ってあった
f:id:Owatank:20180514174053p:plain

相変わらず生成器の入力は100次元で一様分布から取ってくるっぽい。畳み込みの後の全結合層がダメだから、その前の全結合層はいいのかな。100次元の入力を全結合層に渡して 4*4*1024 の次元にする感じか。
よくチュートリアルとかで見る畳み込みは上の図で言えば右にいくにつれて四角が小さくなっていくけど、これは逆で四角が大きくなっていく。アップサンプリングってそういうことらしい。誤ってデコンボリューションと呼ばれると論文に書いてあった。

試したいデータセットがMNISTの手書き数字 28*28の1チャンネルの画像なので上の図のまんま適用ができない。困った。
上の図と似た次のようなネットワークを自分で考えて最初試すことにする。

f:id:Owatank:20180514175000j:plain

すごく・・・アナログです・・・

前回でのGANで5の手書き数字のみを訓練データとして渡して学習したところ、精一杯学習させてできたものは次のものが限界だった
f:id:Owatank:20180515123324p:plain

エポック数としては1エポック6000枚程度で3000エポックほど回した。2000エポックほどでは左のようにすごくぼやけた感じのものしかできていなかった。後心なしか生成できるものが同じ形状の5ばっかだった。

DCGANだし畳み込みを導入して前よりパワフルなはずだし、手書き数字5だけじゃなくて形状が似てる手書き数字3も訓練データとして渡してみよ〜^^と考えて入力のノイズを一様分布の [-1,1] の 100次元から取ってきて、約11000枚ほどのデータで100エポックほど回したところ

f:id:Owatank:20180515123739p:plain

こんなものしかできなかった。なんだこれは・・・
学習係数を変えてみたり、ネットワーク自体のパラメータを変更したりしてみたけど、大体上と同じ結果しか生成されなかった
背景が黒だから学習自体は似せるように学習しているとは思うんだけど

原因として考えられそうな仮説をいくつか考えて、試行錯誤しつつ検証していって何とか期待できる生成物が得られた。

f:id:Owatank:20180515131941g:plain

次のような変更をした

  • ネットワークは 4*4*256 -> 7*7*128 -> 14*14*64 -> 28*28*1 とユニット数を少なくした

  • 入力の次元数を手書き数字の5と3といった一部のみを渡すときには 100次元ではなく50次元と次元数を削減すること

  • 識別器のネットワークにはバッチ正規化層を適用しないこと、また最後の出力層に全結合層を用いること

  • 重み減衰を両モデルのパラメータに適用すること

2つめに関しては、MNISTの手書き数字0~9全体のおいて入力を100次元で [-1,1]の一様分布から取ってくるんだから、一部だったら次元数をその分少なくしたり、範囲を [-0.2,0.2]とか小さくした方が表現の幅として適切なのかなと考えた。結果的には範囲の制限より次元数の削減の方がかなり効いていた。

他もすごい効いた。ただ3つ目に関しては最初、識別器の出力を

h4 = tf.reduce_mean(
    tf.nn.avg_pool(
        h3, ksize=[1, 4, 4, 1], strides=[1, 1, 1, 1], padding='VALID'),
    axis=[3])

といった形で実装していたのだが、ミスってるのかな・・・と思い一旦全結合層で試してしまった。割とよかったのでそのまんまにした。

入力が次元数50で[-0.2,0.2]の範囲の一様分布から値をとってくるもので、重み減衰ありで手書き数字3と5を渡したときの結果

f:id:Owatank:20180515125310p:plain

希望が見えてきた

入力が次元数50で[-1,1]の範囲の一様分布から値をとってくるもので、重み減衰ありで手書き数字3と5を渡したときの結果

f:id:Owatank:20180515125355p:plain

こっちのが早く学習しているように見える。前回のGANと比べて生成物の表現の幅が広くて嬉しい

入力が次元数50で[-1,1]の範囲の一様分布から値で重み減衰なしだと
f:id:Owatank:20180515125924p:plain

50エポックまででこんな感じだった。有り無しで結構変わっている。

最後にMNISTの手書き数字の全訓練データ(約5万枚)を渡して学習させてみる
上と同じネットワークで入力が次元数50で[-1,1]の範囲の一様分布、重み減衰ありの結果
f:id:Owatank:20180515130735p:plain

上と同じネットワークで入力が次元数100で[-1,1]の範囲の一様分布、重み減衰ありの結果

f:id:Owatank:20180515131107p:plain

途中で学習が打ち切られてしまったのでエポック100まで回し損ねた
次元数100の方が次元数50の方より鮮明な感じがある。数字0〜9全体においては次元数は少し増やした方がいいのか

論文には一様分布から取ると書いてあったが、特に書いてなかったけど実際VAEみたいに正規分布から取ってくると精度が悪かったりするのか気になった

解決策を見つけた後にtensorflowのライブラリにtf.image.resize_imagesというメソッドがあることを知った。
これでMNISTの画像を 28*28から64*64にして論文通りネットワークを実装すればよかった・・・