gan/gan.py を見ると、生成器も識別器も構造は似ていて、線形層 + ReLU (activation) を重ねた形になっている。生成器は Tanh
で終わり [-1, 1]
のフラットなテンソルを view
で画像の形状に rehape する。識別器はスカラー量を出して Sigmoid
で終わることで確率値を出している。
訓練はよくある解説の通り、「ある確率分布(このコードでは正規分布)に従い潜在空間内に広がる潜在変数(ノイズ)を入力として、生成器が生成するフェイク画像」と「本物の画像」を共に識別器に通して真偽を判定する。
これでいつの間にか生成器はノイズからリアルなフェイク画像を作成できるようになるから驚きだ。とすると、28x28 = 784 次元の(正規分布に従う)ランダムなベクトルからリアルな画像を生成する能力を生成器が獲得することになる。これはどういうことだろう?と考えると、これは 784 個のパラメータと見るのが良いだろう。識別器は基本的にリアル画像のドメインの画像を生成できるように重みとバイアスが訓練されるが(恐らく訓練で用いたすべてのリアウ画像の “平均値” のような画像)、その微妙なニュアンスが 784 個のパラメータでコントロールされるようになると。例えるなら、生成器は最初は
def generator(param1:float, param2:float, param3:float, ...):
であったものが
def generator(face_roundness:float, hair_color:float, eye_height:float, ...):
のように引数に何らかの意味1が与えられるように訓練され、ノイズとしてランダムに入力される潜在変数に応じて、微妙なニュアンスがついたリアルなフェイク画像を吐き出すようになると考えられる。ゲームのアバター作成のような感じだ。あまりこういう感じのことが本に書いていないような気がしていて、最初の素の GAN のモデルを見ると、何故ノイズからリアルなフェイク画像が出来るのか分からずに混乱する。
GAN と同年、2014 年の続く画期的な GAN は CGAN (Conditional GAN) であろう。これと比較してみよう。
diff -u gan/gan.py cgan/cgan.py
まず最初の特徴的な差分は
+ self.label_emb = nn.Embedding(opt.n_classes, opt.n_classes) +
であり、ラベル埋め込みなるものが追加されている。「埋め込み」は離散的な値を何らかの意味で “連続的”(要は比較的密集した小数値の集まり)なベクトルに変換したものである。
次に印象的なのは Generator.forward
の差分で
- def forward(self, z): - img = self.model(z) + def forward(self, noise, labels): + # Concatenate label embedding and image to produce input + gen_input = torch.cat((self.label_emb(labels), noise), -1) + img = self.model(gen_input) img = img.view(img.size(0), *img_shape) return img
である。なんと潜在空間の潜在変数と離散的なラベル(を連続なベクトルに変換した)埋め込みを結合しているのだ。識別器も同様の変更が加えられている。7 ページほどの論文を読んでも確かにこれくらいのことしか書いていないので驚く。要は生成器には「このラベルの属するカテゴリの画像を作って欲しい」という気持ちを与え、識別器には「このラベルの属するカテゴリの本物の画像であるかを識別して欲しい」という気持ちを与えるとそう言う風に訓練されるというのだ。これは大変驚きだ。処理はとても単純なのにこれは結果を出し、後の GAN の基本となった。
もう 1 つ外せないのは、やはり DCGAN であろう。
diff -u gan/gan.py dcgan/dcgan.py
をすると分かるが、要するに生成器も識別器も線形層が畳み込み層に置き換えられている。特に、識別器のそれは深さこそそれほど深くないが、要は VGG だ。こちらのモデルも後の GAN の基本となっている。
以降の GAN は畳み込み層を基本とし、条件付けにより生成物をコントロールするという形だろう。GAN の訓練は不安定なので、WGAN や SNGAN のように訓練を安定化させるノウハウも研究された。一方で、画像生成系では教師あり/なしの差はあるものの Pix2pix や CycleGAN が影響を与えているように思う。生成器の部分は一種のオートエンコーダの形になり、もはや入力はノイズではなくなった。そのノイズたる潜在変数の元となるような入力画像を与え、エンコーダが潜在変数を導き、デコーダが再び画像を生成する。この時に “ラベル” をうまいこと与えて元とは違った種類の画像を生成させる。例えば、航空写真と地図の(相互)変換だ。それぞれの画像が属する集団のことを “ドメイン” と呼び、ドメインを変更する Image-to-Image translation みたいな呼び方をしているようである。これまたちゃんと動作するのだから驚きだ。が、この時点でかなりの訓練画像と訓練時間を要するようになってきているで、GPU を使わないと厳しい。
-
この例では
param1
には意味face_roundness
が、param2
には意味hair_color
が、param3
には意味eye_height
が与えられていくようなつもりである。↩