ローリングコンバットピッチなう!

AIとか仮想化とかペーパークラフトとか

AutoEncoderの実験

[technology]Autoencoderを使った顔写真の合成

ニューラルネットワークで人の顔写真を合成したいと思い、DCGANとか実験していてそれはそれで動いたのですが、ふと思いついて以下の様な実験をしました。

  1. Autoencoderを用意する

    実験に使ったのは入力と出力が4096ノード(64x64ピクセル)。で中間層が16ノード。

    入力層4096→中間層16→出力層4096

    ネットで人の顔写真でたくさん集めてAutoencoderを訓練します。
  2. ある程度訓練できたら、任意の二人の顔写真をAutonencoderに入れて、中間層のそれぞれ16ノードを取り出す。
  3. 二人分の中間層の値を先頭からランダムにどちらか選択して新たに16ノード分の値を作る。

    ちょっと解りずらいですが、簡単化のために仮に4ノードの場合で、一人目が(A0,A1,A2,A3),二人目が(B0,B1,B2,B3)という値を持っていたとして、
    • (A0,B1,B2,A3)
    • (B0,B1,A2,A3)
    • (A0,A1,A2,B3)
    みたいな感じで、AとBのベクトルを適当に混ぜます。
  4. 出来たベクトルをAutoencoderの中間層に入れて後半のデコーダー部分から出力する。

発想としては

  • Autoencoderの中間層のノード数をうんと絞れば、人間の顔をかなり抽象化した情報(輪郭の形、髪型、眉の形、目の形、etc...)に変換してエンコードしてくれるのではないか?
  • そのレベルまで抽象化できれば遺伝子を切り貼りするがごとく、複数の人の顔写真のエンコード結果を切り貼りして別の顔が作れるのではないか?
です。
色々と実験して、Autoencoderの構成としては下記の様なConvolution,De-Convolutionを含む形にしました。概ねDCGAN用のDescriminatorとGeneratorを参考にした感じです。

class AutoEnc6(Chain):
    def __init__(self):
        super(AutoEnc6, self).__init__(
            conv1 = L.Convolution2D(in_channels=1, out_channels=32, ksize=5, stride=2, pad=2),
            conv2 = L.Convolution2D(in_channels=32, out_channels=64, ksize=5, stride=2, pad=2),
            conv3 = L.Convolution2D(in_channels=64, out_channels=128, ksize=5, stride=2, pad=2),
            conv4 = L.Convolution2D(in_channels=128, out_channels=256, ksize=5, stride=2, pad=2),            
            bn1 = L.BatchNormalization(32),
            bn2 = L.BatchNormalization(64),
            bn3 = L.BatchNormalization(128),
            bn4 = L.BatchNormalization(256),
            l0 = L.Linear(None, 16),
            l1 = L.Linear(None,256 * 4 * 4),
            deconv1 = L.Deconvolution2D(in_channels=256,out_channels=128,ksize=5,stride=2,pad=1),
            deconv2 = L.Deconvolution2D(in_channels=128,out_channels=64,ksize=5,stride=2,pad=2),
            deconv3 = L.Deconvolution2D(in_channels=64,out_channels=32 ,ksize=5,stride=2,pad=2),
            deconv4 = L.Deconvolution2D(in_channels=32 ,out_channels=1  ,ksize=5,stride=2,pad=2),
            bn5 = L.BatchNormalization(256 * 4 * 4),
            bn6 = L.BatchNormalization(128),
            bn7 = L.BatchNormalization(64),
            bn8 = L.BatchNormalization(32)        
        )
    def enc(self, x,ratio=0.5):
        h = F.reshape(x,(len(x),1,64,64))
        h = F.dropout(F.leaky_relu(self.bn1(self.conv1(h))),ratio=ratio)
        h = F.dropout(F.leaky_relu(self.bn2(self.conv2(h))),ratio=ratio)
        h = F.dropout(F.leaky_relu(self.bn3(self.conv3(h))),ratio=ratio)
        h = F.dropout(F.leaky_relu(self.bn4(self.conv4(h))),ratio=ratio)
        h = self.l0(h)
        return h
    def dec(self,x,ratio=0.5):
        h = F.dropout(F.leaky_relu(self.bn5(self.l1(x))),ratio=ratio)
        h = F.reshape(h,(len(h),256,4,4))
        h = F.dropout(F.leaky_relu(self.bn6(self.deconv1(h))),ratio=ratio)
        h = F.dropout(F.leaky_relu(self.bn7(self.deconv2(h))),ratio=ratio)
        h = F.dropout(F.leaky_relu(self.bn8(self.deconv3(h))),ratio=ratio)
#        h = F.tanh(self.deconv4(h))
        h = F.sigmoid(self.deconv4(h))
        h = h[:,:,:-1,:-1]
        h = F.reshape(h,(len(h),4096))
        return h
    def forward(self,x,ratio=0.5):
        h = self.enc(x,ratio=ratio)
        h = self.dec(h,ratio=ratio)
        return h

とりあえずのサンプルです。

まず、合成の元の顔写真

肖像権的に微妙なので、元データではなく訓練したAutoencoderの出力を並べています。
訓練が足りないのか、元画像の再現度が低いので、元が誰だったのか判らない程度にボケています。

上記2枚のAutoencoderの中間層の出力をランダムに繋いでデコードした写真。

どうでしょうか?そこそこ見られるものだけを選びました。
色々実験した結果では、元になる2枚が例えば、逆向きを向いているとか、髪型や顔の輪郭等が大きく異なると綺麗な合成ができませんでした。
同じ写真を元にしても左右の目が大きく異なったりして、面白いけど実用には?という感じです。

どこかに同じことをやった先人がいそうな気がしますが、ググっても良く判らなかったので。

ちなみのこの実験のモチベーションはDCGANだとやはり学習に時間が掛かり過ぎるので、もうちょっと軽い方法無いかな?ということなのですが、ちょっとDCGANには勝てそうもありません。

実験に使ったソースはGithubに置いてあります。

github.com