こんにちは、人間です。
ちょっと前に AI による画像の自動生成が話題になってたので自分も似た様なものを作ろうと思いました。
…とは言っても、公開されている既存の製品の様な高性能・高品質なものは技術的にもPCスペック的にも難しいので、己の身の丈にあったレベルの画像生成AIの作成を試みました。

具体的にいうと、我が家で飼ってる猫「こてつ君」の写真 622 枚を生成的敵対的畳み込みネットワーク(DCGAN) に学習させて、こてつ君の写真を自動生成するAIを作ろうとしました。

この記事はその前編です。

導入

生成的敵対的ネットワークとは?

生成的敵対的ネットーワーク (GAN) とは深層学習の学習手法の一つです。その名の通り、2種類のニューラルネットワークモデルを用意してそれぞれを互いに競わせることで学習させる手法です。特に、畳み込みニューラルネットワークモデルを使用したGANをDCGANと言います。
一つ目のモデルは、標準正規分布に従うベクトル (潜在ベクトル) から画像を生成するモデルで「生成器」と呼ばれます。もう一つのモデルは、生成器が生成した画像(偽画像)と学習データの画像(本物画像)を識別するモデルで「識別器」と呼ばれます。
生成器の目的は、識別器を騙せる様な偽画像を生成できる様になることで限りなく本物に近い画像を生成できるモデルになることです。
一方、識別器の目的は、本物画像と偽物画像を正しく鑑定できる様になることです。
学習データを使用してこの二つのモデルを交互に繰り返し学習・最適化させて、「本物と偽物が区別できなくなる」所まで二つが拮抗していれば学習の成功です。 少年漫画の主人公とライバルの関係と同じですね。
Fig1. モデルの学習イメージ

このモデルの重要なことは、学習自体が不安定で難しいという部分です。具体的に、

  • 生成器 vs 識別器 が拮抗せず、どちらかが圧勝してしまう
  • 同じような画像しか生成できない (モード崩壊)

等の問題が多発します。その為、うまく拮抗させる為に工夫を凝らし、試行錯誤を何度も繰り返す必要があります。身をもって体験しました。

実装環境

  • 開発環境 : Google Colaboratory
  • ランタイム : GPU (ハイメモリ、プレミアム)

モデル説明

使用した生成器・識別器の構造

生成器

生成器は転置畳み込み層によって構成されています。転置畳み込みとは入力データを拡大してから畳み込みを行う処理です。
生成器はこの転置畳み込みで、まず入ってきた多要素の潜在ベクトルを「多数 (多チャンネル) の小規模の二次元のデータ」に一度拡大します。
その後、各画像データの要素を更に再配置する様に転置畳み込みを行い、データの解像度をで叩き伸ばしていきます。
そして最終的には「 3 チャンネルの二次元のデータ」を出力します。

今回、A.Radford,L.Metz, S. Chintala ら著の論文にて使用されたモデルの一つと同様のニューラルネットワークを生成器として構築・使用します。
Fig 2. DCGAN論文より引用、生成器の構造 (64×64 pixels の画像生成)。最初は 4×4 pixel の小さい画像 1024 枚に畳み込みで拡張し、その後徐々にサイズを叩き伸ばしています。
(DCGAN論文より引用、64×64 pixels の画像生成する生成器の構造)

識別器

識別器は畳み込み層によって構成されています。 生成器とは逆に、入力された画像データを畳み込みで多チャンネルの小規模二次元データにまで縮小します。そして最後は、 スカラー値へ畳み込み、各要素を (0,1) の範囲にマッピングし直したものを出力します。この出力される要素は、入力された画像が学習データから取ってきた本物の画像である確率を表しています。

今回、Pytorch turtorial で記述された DCGAN の識別器と同様のニューラルネットワークを識別器として構築・使用します。

損失関数・最適化

損失関数 : 生成器

生成器と識別器で学習目的が異なる為、損失関数のも異なってきます。

生成器の目的は生成物が識別器を騙せる出来になることです。言い換えると「生成した画像 G(z) を識別器 D が本物である確率が高い D(G(z))\sim1 と判断させること」が生成器の目的です。従って、生成器が最適化すべき損失は以下の様に書けます。
\text{loss G}=\mathbb{E}_{z\sim p_{z}(z)} \bigl[\log\left(D\left(G\left(z\right)\right)\right)\bigr]
ここで、 \mathbb{E}_{z\sim p_z(z)} は確率 z\sim p_z(z) の下での期待値を示しています。
本実装では、 (x,y) をそれぞれ「識別器から出た本物である確率」と「実際に本物かどうかのラベル」とした、バイナリクロスエントロピーエントロピーをこの損失関数として使用します。
\ell(x,y)=-\mathbb{E}_n\bigl[x_n\log\left(y_n)+(1-y_n)\log\left(1-x_n\right)\right)\bigr]
この損失 \text{loss G} が最小化する様に生成器を最適化していくことで学習を進めていきます。

損失関数 : 識別器

識別器の目的は、生成画像 G(z) を偽物と D(G(z))\sim0 、学習データからの画像 x を正解 D(x)\sim1 と正しく識別することです。従って識別器の損失は以下の様に書くことができます。
\text{loss D}=\mathbb{E}_{z\sim p_{z}(z)} \bigl[\log\left(D\left(x\right)\right)\bigr]+\mathbb{E}_{z\sim p_{z}(z)} \bigl[\log\left(1-D\left(G\left(z\right)\right)\right)\bigr]
本実装では、生成器と同様に (x,y) をそれぞれ「識別器から出た本物である確率」と「実際に本物かどうかのラベル」とした、バイナリクロスエントロピーエントロピーをこの損失関数として使用します。
この損失 \text{loss D} が最小化する様に生成器を最適化していくことで学習を進めていきます。

最適化アルゴリズム

生成器・識別器の双方に最適化のアルゴリズムとして、今回はDCGANの論文や Pytorch turtorial の例と同様に Adam を使用します。

モデル学習・画像生成の結果

以上の生成器と識別器を使用して DCGAN の学習をしてみました。Python のコードは長すぎるので、ここでは結果とハイパーパラメータのみ載せることにします。
後編の記事を書き終わったらコードを公開する予定です。

まず、使用するデータセットである我が家のこてつ君の画像を一部列挙します。

Fig 3. こてつ君の画像。元々の写真を256×256にサイズ変更し、その後中央 64×64 pixel にトリミング、グリッドマスク等している。

元々の写真そのものではサイズ的に厳しいので、一度 256×256にサイズ変更した後 中央 64×64 pixel に画像をトリミングしています。なので、かなり画質が荒いですが、十分に猫と認識できるので許容範囲です。
また、学習データの水増しの為に以下のデータ拡張 (Data Augmentation) を行なっています。

  • ランダムに縦横の反転 ( RandomHorizontalFlip , RandomVerticalFlip)

モデルパラメータの設定

学習するにあたって、モデルのパラメータを以下の様に設定します。

パラメータ名
訓練時に実行するエポック数 400
訓練に使用するバッチサイズ 4
生成器・識別器を伝播する特徴マップの深さ 64
潜在ベクトルの要素数 100
生成器・識別器の学習率 0.0002
Adam (生成器・識別器) のハイパーパラメータ1 (beta_1) 0.5
Adam (生成器・識別器) のハイパーパラメータ1 (beta_2) 0.999

今回データセットは 600枚程度と非常に少ない為に1バッチ辺りのデータ数は 4枚と非常に少なくしました。そして、学習を繰り返すエポック数は今回似た画像(茶色い猫)しか無いので長めに 400 回としておきます。
それ以外のパラメータはDCGANの論文のそれをそのまま使用しています。

学習の結果

400エポック分学習した生成器に適当な潜在ベクトルを入れて画像を生成・出力したものが以下になります。
Fig. 4 DCGAN 500 epoch 学習させた生成器から出力された画像 (3枚)
これは……ねこ???(困惑)
一番右の画像が辛うじて猫の頭と耳っぽいといえばそうですが、それ以外は….どう見てもただの茶色の塊ですね。

今度は64枚画像を生成して確認してみよう…
Fig. 5 DCGAN 500 epoch 学習させた生成器から出力された画像 (64枚)
うーんやはり大半が茶色の塊ですね。一応猫の顔っぽいのも幾つかあるのですが... (右から2列・3行目の画像など)

そして似た様な構図が多いですね。
これはまだマシな方で、より悪い学習の場合は「学習したモデルがほぼ同じ様な画像しか出力しない」状態に陥ることもあります。この状態のことをモード崩壊と言います。GAN の難点の一つです。

次に、生成器の学習の様子を動画で確認します。以下の動画は500イテレーション (= 約4エポック) 毎に画像を生成して動画にしたものです。

途中までは学習が上手くいっている様に見えます。しかし、それ以降はずっと同じ様な画像の連続であり学習の進みが悪そうに見えます。

学習の良し悪しを判断する為に、各イテレーション毎の生成器・識別器の損失を見ます。
Fig. 6 DCGAN 各イテレーションでの生成器 (G) と識別器 (D) の損失

GAN は生成器と識別器を「本物と偽物が区別できなくなる」状態を維持しながら学習させることで、精度を高め合う学習アルゴリズムです。
即ちGANが成功している状態というのは、D(G(z))\simeq D(x) \sim 0.5
となる時のことを言います。
この時の生成器と識別器の損失は、大体以下の値に収束します。
\text{loss D},\text{loss G}\sim -[\log(0.5)+\log(0.5)]\sim 1.4
しかし、これはどう見ても損失が拮抗していません。識別器に対して生成器の損失の方が圧倒的に大きいです。
識別器が完全勝利していますね。こうなってしまえばもう生成器に勝ち目はありません。

これが GAN の難点の一つである「生成器と識別器拮抗しない問題」です。 冒険始めたばかりの主人公にラスボスぶつけたら、いくら主人公でも勝ち目ないよという理論ですね

後編へ向けて

この様に GAN の学習には複数の難点が存在します。
この問題点を解決して上手く学習させる為には、生成器と識別器の学習プロセスの工夫やハイパーパラメータチューニングを行い、「如何に片方のモデルを弱体化しつつ上手く学習させるか」の試行錯誤を行う必要があります。
例えば GAN の改良のテクニックの一部として以下の方法が存在します。

  • 生成器と識別器の学習回数の比率を変えて、二つのモデルの学習速度を調節する
  • 生成器と識別器で学習率・最適化アルゴリズムのハイパーパラメータの大きさを変える
  • 識別器の出力を一定の確率で値を弄って、識別器を強制的に弱体化させる

後編の記事では、私なりに DCGAN の生成器・識別器・学習プロセスに工夫をして試行錯誤を繰り返した記録を記述する予定です。
後編に続く…

参考資料