Help us understand the problem. What is going on with this article?

VAEをkerasで実装

More than 1 year has passed since last update.

はじめに

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/
iss-f
自分の作った人工知能とおしゃべりしたい 普段はフロント/バックエンドのエンジニア 主な言語はpython、scala
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした