前回まではAIを使って実務上必要な知識を紹介していきました。業務上使うかどうかは別として、今までのところは知識として知っておく必要がある技術ばかりです。
今回は、実用となるのはかなり遠そうですが楽しい技術であるGAN、pix2pixをやっていきます。上手くやれば画像の水増しとして使えるかもしれません。mixupなどのような画像の間を取るのが精度向上に効果があるならGANで崩れた画像を学習に入れても精度向上するかなとは思っています。
キルミーベイベーでのGANはもうすでに素晴らしい記事があるので、「GANについて概念から実装まで ~DCGANによるキルミーベイベー生成~」、今回はそれを参考にpix2pixを使ってみたいと思います。
#6ヶ月目でやること
- DCGANで白黒画像を作成する
- pix2pixで作成した白黒画像に色を付ける
でいきます。先行記事では一発で画像生成を狙っていますが、今回はそれを白黒画像の生成、色塗りと2段階に分けることでよりそれっぽい画像ができないかと狙います。
#GANについて
GANというのは敵対的画像生成とも呼ばれます。詳しい説明は上記のキルミーベイベー生成、もしくは今さら聞けないGAN(1) 基本構造の理解を参考にすると良いと思います。
完璧な理解は難しいので、会社の偉い人に聞かれたとき用に概念だけは捉えておきましょう。重要なのは上司をなんとなくわかった気分にさせることです。相手は何も知らないので正確さよりもわかりやすさと自信が大事です。
ポイントは
①2つの異なるAIを交互に学習させてだんだん賢くさせていくこと
②初めはどちらもアホであること。
決して損失関数、ラベルなどという言葉を話してはいけません。混乱の元です。でも使う側は知っておく必要があるのでちゃんと記事の内容は納得できるようにしておきましょう。
ちなみにGANで思ったとおりの画像を取り出すのはかなり難しいです。収束というのはあくまで「Discriminatorをちょうどよく騙せる」ようになっただけで、「人がほしい画像を安定して作れる」とは異なるところが難しい。
それと使ってみた感想としては、教えた画像に偏りがあると多いカテゴリのものばかり作られるので、水増ししたい画像が少ない場合狙って得るのは難しいなという感じです。ConditionalGANとかもありますが、いまいち上手く作れませんでした。
コードは上記のをほぼ流用です。MNISTまでは自分で組んだネットワークで行けたのですが、本番のデータセットでは上手く収束しなかったので、記事のネットワークにしました。ちょっとだけ工夫として重みを残すようにしました。うまく行ったら使い回せるように。あとは読み込ませる画像にIDG加えたりなど。
(一部抜粋)
class GAN:
def __init__(self):
#~略
# discriminatorモデル
self.discriminator = self.build_discriminator()
self.discriminator.compile(loss='binary_crossentropy', optimizer=self.d_optimizer, metrics=['accuracy'])
if os.path.exists('weights/discriminator_ganBVH.h5'):
self.discriminator.load_weights('weights/discriminator_ganBVH.h5')
print('load discriminator model')
# Generatorモデル
self.generator = self.build_generator()
# generatorは単体で学習しないのでコンパイルは必要ない
# self.generator.compile(loss='binary_crossentropy', optimizer=optimizer)
self.combined = self.build_combined1()
# self.combined = self.build_combined2()
self.combined.compile(loss='binary_crossentropy', optimizer=self.c_optimizer)
if os.path.exists('weights/generator_ganBVH.h5'):
self.generator.load_weights('weights/generator_ganBVH.h5')
print('load generator model')
# 入力画像にIDGをかける
def image_data_generate(self, images):
# img.shape=(num, hight, weide, channel)
datagen = ImageDataGenerator(width_shift_range=0.1, height_shift_range=0.1, horizontal_flip=True)
batch = datagen.flow(images, batch_size=images.shape[0], shuffle=False).next()
return batch
def train(self, epochs, batch_size=128, save_interval=50):
#~略
for epoch in range(epochs):
for iteration in range(num_batches):
#~略
# 指定した間隔で生成画像を保存
if iters % save_interval == 0:
self.save_imgs(iters)
self.generator.save_weights('generator_ganBVH.h5')
self.discriminator.save_weights('discriminator_ganBVH.h5')
下記は30,000iterationの画像です。やはり枚数が少ないのもあって安定はしないですね。見るに堪えない画像がたくさんあります。グロ注意。
まあとりあえず沢山作ってもらって良いのを選ぶという形で済ませます。実際のデータは要らないからといって使わないとデータを取ってくれた人が不満を覚えますが、AIはそうではないので沢山作って沢山捨てるでいいです。
#pix2pix
pix2pixはconditionalGANの一つと言われていて、入力画像の変換を行うものです。conditionalGANというと「乱数+情報」のイメージだったので、pix2pixは入力にノイズが無くなくなって画像だけなのでかなり違うイメージです。
単純に言うとU-netの損失関数をdiscriminator+L1lossにしたものです。L1lossで全体的な近さを担保しつつ、discriminatorでぼやけた画像を作ってくるのを防ぎます。ここが普通のU-netとの違いですね。
それとdiscriminatorのところでpatchGANというやり方が使われています。これは画像を分割してdiscriminatorにかけることでより局所的な一致を狙います。
実装はKeras で pix2pix を実装する。【 cGAN 考慮】を参考というかほとんど流用させていただきました。Unetのところや損失関数の変更は理解できましたが、patchGANの理解が及ばず自分なりの実装はまだ難しい・・・
実際に作ってみたのが以下となります。上の白黒がDCGANで作られた画像で、下がそれにpix2pixをかけたものです。やはりpix2pixは色を塗るだけなので安定しますね。色塗りと絵を書くのどちらが難しいかと言ったら前者なのでDCGANが安定しにくいのも当然でしょう。個人的には一発でカラー作るよりうまく行ったような気がします。GANの評価はなかなかしにくいです。
#まとめ
キルミーベイベーのデータセットから新たなキルミーベイベー画像を生成しました。人のやり方を参考にしてDCGANで白黒画像を作ってから、pix2pixで色塗りをする、のように段階を踏むことでより安定してカラー画像を手に入れられたんじゃないかと思っています。
というかやってから思いましたけど、普通は線画からの着色ですね。そのほうがより良かったかも。今はpix2pixがかなり楽をしているので、両者の負担を半々にしたいです。
コードはgithubに置いてありますんで、もし参考にしたいという方がいればどうぞ。