DCGANで湖岸道路ぽい画像を生成する

f:id:ksknw:20160625232128g:plain:w300

はじめに

先日、小倉であった某学会に参加した。 面白そうな話はいくつかあったけど、中でもDNNを使った画像の生成について興味を持った。 深層学習ウェイ系の某先生もマルチモーダルとか生成とか言ってた気がするし、 判別するより生成するほうが見た目に楽しそうなので、 こちらの発表でも使われていたDCGANを使って、画像を生成してみることにした。

Deep Convolutional Generative Adversarial Networks

論文はこちらとてもわかりやすいこちらのブログがわかりやすい。

GANはAdversarialという名の通り、2つのネットワークを競合させて学習を行うアルゴリズム。 GANでは普通のAutoEncoderなんかで画像を生成する場合と違って、Discriminatorというやつを作る。 Discriminatorは入力された画像が、Generatorの生成した画像か元画像かを判別する。 GeneratorはDiscriminatorを騙せるように画像の生成を学習し、Discriminatorはそれを判別できるように学習を行う。 これによって、最終的に単に入力画像だけからGeneratorを学習するよりも良くなるということらしい。

具体的には以下のような式を最適化する。

f:id:ksknw:20160626130749p:plain

右辺第1項は入力画像に対してDが出力を正しく出したときに大きくなり、第2項はDがGの画像に反応しないときに大きくなる。 Gはこの評価関数を小さくする、つまりDが騙される方に学習し、Dは逆に正しく見分けられるように学習する。

面白いのは、画像をGeneratorに直接学習させて、例えば入力画像との誤差を最小化するのではなく、 Discriminatorを騙せさえすればいいというところ(と思う)。 実際には入力画像にはなくても、Dが騙される「ぽい」画像を作れば評価関数の値を下げることができる。

DCGANはDiscriminatorにConvNet、GaneratorにDeconvNetを使う

mattyaさんによるchainerの実装

とてもおもしろい研究だし、arxivを見る感じ、最初にDCGANの論文がでたのが2015年の11月と少し古いのもあって、 既に色々なライブラリを使った実装が公開されていて、自分でコードを書く必要はなさそう。 chainerがお気に入りなので、その中から、mattyaさんによる実装を使わせてもらう。

git clone して動かそうとすると、残念ながら2GしかないGPUではメモリ足りなくて動かなかった。 コードを見ていると順次メモリを解放してやればギリギリいけそうだったので、210行目あたりに

del z
del x
del x2
del yl
del yl2
del L_dis
del L_gen

と書いてやると何とか動くようになった。

入力データ

入力データとして琵琶湖(マザーレイク)周辺の道路画像を使う。 動画を撮影しに行くのは大変なので、これを使ってGoogleMapのストリートビューから画像を取ってくる。

とってきた画像は以下のようなもの。 f:id:ksknw:20160612114759g:plain

道路に対して前だけでなく、横を向いている画像もある。 画像データは合計で600枚ぐらい。 この画像を237x119にリサイズしたあと、96x96の画像をランダムに切り出して4560枚の画像を作った。 入力画像からランダムに10枚選んだものを以下に示す。 思ったよりちゃんと道が写っているものが少ない。

f:id:ksknw:20160626005852p:plainf:id:ksknw:20160626005853p:plainf:id:ksknw:20160626005854p:plainf:id:ksknw:20160626005855p:plainf:id:ksknw:20160626005916p:plainf:id:ksknw:20160626005917p:plainf:id:ksknw:20160626005918p:plainf:id:ksknw:20160626005919p:plainf:id:ksknw:20160626010125p:plainf:id:ksknw:20160626010126p:plain

画像の中にはかなり類似したものもあるだろうし、そもそもDNNの学習に数千枚の画像というのは少ないように思うけど、JSAIの発表でもそれぐらいの枚数だったように思うので、まずはやってみる。

結果

GTX750Tiで学習。うるさいパソコンとともに数日過ごした。 10万ぐらいのグラボがほしい。

学習途中で生成された画像は以下。 上半分(50枚)の画像はエポック間で同じzベクトルで生成している画像。下半分は毎回ランダムにサンプリングしなおしたzベクトルで生成している。

最初 f:id:ksknw:20160626000823p:plain

1エポック後 f:id:ksknw:20160626000902p:plain

5エポック後 f:id:ksknw:20160626000936p:plain

10エポック後 f:id:ksknw:20160626001032p:plain

61エポック後 f:id:ksknw:20160625230705p:plain

62エポック後 f:id:ksknw:20160626003455p:plain

63エポック後 f:id:ksknw:20160626003511p:plain

大体10エポックぐらいから遠目にはそんなに変わってないように思う。 あと、上半分は同じzベクトルから生成しているはずなのに、61から63エポックで毎回結構大きく変わっているのも気になる。学習が収束している感じがあまりしない。画像が少ないとかそういうことが関係しているのだろうか。

なんか赤いなにかを作ったりしているし、まだもうちょっと学習してほしい感もある。あと1週間ぐらい回したほうがいいのかもしれない。

zベクトルをいじって色々画像を作る

DCGANでは学習時に乱数のベクトル(zベクトル)をDeconvNetworkに入力して画像を生成する。 生成した全ての画像において、Discriminatorを騙せるように学習するので、 理想的には学習後はどのzベクトルを選択しても、それっぽい画像を生成できるようになるはずである。 しかも、論文を読んでるとzベクトルを動かしていくと連続的に画像が変化していくらしい。楽しそう。

ということで適当に画像を生成した後、そこから3枚選んでそれらの間にある画像を生成してみる。 以下のように適当にプログラムを書く。

def interpolate(index1, index2, img_i):
    import pandas as pd

    epoch = 61

    all_z = pd.read_csv("z.csv").get_values()
    print all_z.shape
    sub = all_z[index2] - all_z[index1]
    gen = Generator().to_gpu()
    dis = Discriminator().to_gpu()

    serializers.load_hdf5("%s/dcgan_model_dis_%d.h5" % (out_model_dir, epoch), dis)
    serializers.load_hdf5("%s/dcgan_model_gen_%d.h5" % (out_model_dir, epoch), gen)
    gen.to_gpu()
    dis.to_gpu()
    z = xp.random.uniform(-1, 1, (batchsize, nz), dtype=np.float32)
    for i in range(100):
        z[i, :] = xp.array(all_z[index1] + sub / 100.0 * i)
    pylab.rcParams['figure.figsize'] = (0.96, 0.96)

    print z
    z = Variable(z)
    x = gen(z, test=True)
    x = x.data.get()

    for i_ in range(100):
        tmp = ((np.vectorize(clip_img)(x[i_, :, :, :]) + 1) / 2).transpose(1, 2, 0)
        pylab.imshow(tmp)
        pylab.axis('off')
        pylab.savefig('interpolate_imgs/%09d.png' % (img_i))
        img_i += 1

        pylab.clf()
    f.close()

interpolate(4, 10, 0)  # index+1がtest_imgsの画像の番号と対応する
interpolate(10, 23, 100)
interpolate(23, 4, 200)

深い理由はあまりないけど、なんとなく色々な画像を作っていて良さそうなので、61エポックのときの学習モデルを使うことにした。 以下のような3枚を選んで、それらの間のzベクトルを線形につないで、それぞれの間ごとに100枚ずつ画像を生成する。 f:id:ksknw:20160625235309p:plain:w300

生成された画像をつなげて動かしてみると以下のようになった。 f:id:ksknw:20160625232128g:plain:w300

なんか、ぽいものが生成されている。

最初の何枚かを静止画で見る。

f:id:ksknw:20160626012429p:plainf:id:ksknw:20160626012430p:plainf:id:ksknw:20160626012431p:plainf:id:ksknw:20160626012432p:plainf:id:ksknw:20160626012433p:plainf:id:ksknw:20160626012434p:plainf:id:ksknw:20160626012435p:plainf:id:ksknw:20160626012436p:plainf:id:ksknw:20160626012437p:plainf:id:ksknw:20160626012438p:plainf:id:ksknw:20160626012439p:plainf:id:ksknw:20160626012440p:plainf:id:ksknw:20160626012441p:plainf:id:ksknw:20160626012442p:plainf:id:ksknw:20160626012443p:plainf:id:ksknw:20160626012444p:plainf:id:ksknw:20160626012445p:plainf:id:ksknw:20160626012446p:plainf:id:ksknw:20160626012447p:plainf:id:ksknw:20160626012448p:plainf:id:ksknw:20160626012449p:plainf:id:ksknw:20160626012450p:plainf:id:ksknw:20160626012451p:plainf:id:ksknw:20160626012452p:plain f:id:ksknw:20160626012453p:plainf:id:ksknw:20160626012454p:plainf:id:ksknw:20160626012455p:plainf:id:ksknw:20160626012456p:plainf:id:ksknw:20160626012457p:plainf:id:ksknw:20160626012458p:plainf:id:ksknw:20160626012459p:plainf:id:ksknw:20160626012500p:plainf:id:ksknw:20160626012501p:plainf:id:ksknw:20160626012502p:plainf:id:ksknw:20160626012503p:plainf:id:ksknw:20160626012504p:plainf:id:ksknw:20160626012505p:plainf:id:ksknw:20160626012506p:plainf:id:ksknw:20160626012507p:plainf:id:ksknw:20160626012508p:plainf:id:ksknw:20160626012509p:plainf:id:ksknw:20160626012510p:plainf:id:ksknw:20160626012511p:plainf:id:ksknw:20160626012512p:plainf:id:ksknw:20160626012513p:plainf:id:ksknw:20160626012514p:plainf:id:ksknw:20160626012515p:plainf:id:ksknw:20160626012516p:plainf:id:ksknw:20160626012517p:plainf:id:ksknw:20160626012518p:plainf:id:ksknw:20160626012519p:plainf:id:ksknw:20160626012520p:plainf:id:ksknw:20160626012521p:plainf:id:ksknw:20160626012522p:plainf:id:ksknw:20160626012523p:plainf:id:ksknw:20160626012524p:plainf:id:ksknw:20160626012525p:plainf:id:ksknw:20160626012526p:plainf:id:ksknw:20160626012527p:plainf:id:ksknw:20160626012528p:plainf:id:ksknw:20160626012529p:plainf:id:ksknw:20160626012530p:plainf:id:ksknw:20160626012531p:plainf:id:ksknw:20160626012532p:plainf:id:ksknw:20160626012533p:plainf:id:ksknw:20160626012534p:plainf:id:ksknw:20160626012535p:plainf:id:ksknw:20160626012536p:plainf:id:ksknw:20160626012537p:plainf:id:ksknw:20160626012538p:plain

木が建物っぽい何かになりつつ、道の向きも気づいたら変わっている(気がする)。

まとめ

DCGANを使って、琵琶湖周辺の道路っぽい画像を生成した。 zを連続的に変化させると画像も連続的に変化していった。 ぱっと見はそれなりにできているように思うけれど、1枚1枚ちゃんと見ると、全体的にまだ線がぼやっとしていたり、自然な画像はまだ少し遠い。

誰か早くLSTM-GAN発表してコードをくれ。どうせ半年後までには誰かやるんだろ、わかってんだぞ

参考