はじめに

Variational Autoencoder(VAE)とは、オートエンコーダーを用いた生成モデルの1つです。
論文はこちら:https://arxiv.org/pdf/1312.6114.pdf
kerasでの実装例があまりなかったのと、忘れないようにということでまとめておきます。実装メインのため、原理、数式などはその他の解説記事に任せます。

今回は、kerasの公式ブログ[*1] を参考にしつつ実装を行なっていきます。公式ブログでは、そのほかのオートエンコーダーの実装も書いてあるので非常におすすめです。また、VAEの解説はこちら[*2]がわかりやすかったので、参考にさせていただきました。

VAEとは

まず、通常のオートエンコーダーでは、出力データが入力データとが同じになるように学習を行なって行きます。この時、中間層の素子数を入力層と出力層の素子数より少なくすることで、主成分分析のように入力データの特徴量(潜在変数)を獲得することができます。

vae_ae.png

VAEでは、通常のオートエンコーダーとは異なり、潜在変数が正規分布となることを仮定します。エンコーダーでは、正規分布のパラメタである、平均と分散を出力させます。

vae.png

ネットワークの構造がわかれば、実装していけるかと思います。損失関数は後ほど。

モデルの構築

まずは、エンコーダーです。平均と分散を出力する簡単なモデルです。

def build_encoder(self):
    x = Input(shape=(original_dim, ))

    hidden = Dense(intermediate_dim, activation='relu')(x)
    z_mean = Dense(latent_dim, activation='linear')(hidden)
    z_sigma = Dense(latent_dim, activation='linear')(hidden)
    return Model(x, [z_mean, z_sigma])

入力次元、出力次元はあらかじめ設定しておきます。
次にデコーダーです。

def build_decoder(self):
    z_mean = Input(shape=(latent_dim ))
    z_sigma = Input(shape=(latent_dim, ))
    z = Lambda(self.sampling, output_shape=(latent_dim))([z_mean, z_sigma])
    h_decoded = Dense(intermediate_dim, activation='relu')(z)
    x_decoded_mean = Dense(original_dim, activation='sigmoid')(h_decoded)

    return Model([z_mean, z_sigma], x_decoded_mean)

エンコーダーの出力した平均と分散を入力として受け取り、入力データと同じ次元のものが出力されるようにモデルを組んで行きます。

ここで、3行目にKerasのカスタムレイヤー[*3]を用いています。

理由は、平均と分散から潜在変数zを求めるのですが、直接、潜在変数の確率分布を求めるのでは、逆誤差伝播方が使えなくなるという問題が発生するため、zを次のように置き換える必要があるからです。

次のように書き換えます。

$z=\mu(X) + \epsilon * \sigma (X)$
ただし、$\epsilon〜N(0,I)$
これは、Reparameterization Trickという手法だそうです。

モデルは次のようになります。

vae2.png

このようなレイヤーを実装するため、layer.core.Lambdaを用いてカスタムレイヤーを作成しています。

def sampling(self, args):
    z_mean, z_sigma = args
    epsilon = K.random_normal(shape=(self.z_dim,), mean=0., stddev=epsilon_std)
    return z_mean + z_sigma * epsilon

エンコーダーとデコーダーのモデルができたのであとは、これらをつなぎ合わせて行きましょう。

    def build_vae(self, encoder, decoder):
        _, encoder_dense, encoder_mean, encoder_sigma = encoder.layers

        x = Input(shape=(self.input_dim, ))
        hidden = encoder_dense(x)
        z_mean = encoder_mean(hidden)
        z_sigma = encoder_sigma(hidden)

        self.z_m = z_mean # 損失関数で使う
        self.z_s = z_sigma # 損失関数で使う

        _, _, decoder_lambda, decoder_dense1, decoder_dense2 = decoder.layers
        z = decoder_lambda([z_mean, z_sigma])
        h_decoded = decoder_dense1(z)
        x_decoded_mean = decoder_dense2(h_decoded)
        return Model(x, x_decoded_mean)

オートエンコーダーを使うときは、エンコーダーにのみ、または、デコーダーにのみ、入力したかったり出力が欲しい場合があるので、それぞれ別のモデルとして定義しています。

損失関数 

VAEでは、損失関数は次のようになります。

スクリーンショット 2018-02-12 17.23.44.png

1項目は、デコーダーの出力分布$p_\theta(z)$とエンコーダーの出力分布$q_\phi(z|X)$のカルバックライブラー距離です。2項目は、エンコーダーで予測した$z$を用いた$q_\phi(z|X)$に関する、対数尤度$log\ p_\theta(X|z)$の期待値を表します。1項目は小さく、2項目は大きくなるように学習します。

さらに、損失関数の1項目と2項目を近似式で置き換えると次のようになります。

スクリーンショット 2018-02-12 17.47.40.png

この損失関数はKerasにはないので、自ら作成する必要があります。kerasの損失関数を作る場合[*4]は、各データ点に対してスカラを返し、正解値(y_true)、予測値(y_pred)をとる関数をオブジェクトとして渡してあげれば良いみたいです。

def binary_crossentropy(self, y_true, y_pred):
    return K.sum(K.binary_crossentropy(y_pred, y_true), axis=-1)


def vae_loss(self, x, x_decoded_mean):
    z_mean = self.z_m
    z_sigma = self.z_s

    # 1項目の計算
    latent_loss =  - 0.5 * K.mean(K.sum(1 + K.log(K.square(z_sigma)) - K.square(z_mean) - K.square(z_sigma), axis=-1))
    # 2項目の計算
    reconst_loss = K.mean(self.binary_crossentropy(x, x_decoded_mean),axis=-1)

    return latent_loss + reconst_loss

def model_compile(self, model):
    model.compile(optimizer='rmsprop', loss=self.vae_loss)

kerasの公式では、2項目の計算は、

xent_loss = objectives.binary_crossentropy(x, x_decoded_mean)

となっていますが、このままではうまくいかない[*5]ので、修正を行なっています。
1項目、2項目ともにK.meanを入れてますが、こちらも参考サイト[*5]と同じ仕様にしてます。

まとめ

Kerasを使ってVAEを実装してみました。モデルができたので、あとは好きなデータを渡していろいろ遊んでみることができるかと思います。モデルの大まかな構造がわかれば、その後数式を追うのもわかりやすくなるかと思います。レイヤーや損失関数の作成は、初めてやったので今後の参考になりそうです。

引用

  1. The Keras Blog :https://blog.keras.io/building-autoencoders-in-keras.html
  2. Variational Autoencoder徹底解説: https://qiita.com/kenmatsu4/items/b029d697e9995d93aa24
  3. Kerasレイヤーを作成:https://keras.io/ja/layers/writing-your-own-keras-layers/
  4. 損失関数:https://keras.io/ja/losses/
  5. KerasでVariational AutoEncoder:http://sh-tatsuno.com/blog/index.php/2016/07/30/variationalautoencoder/
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.