人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

ボルツマンマシン(隠れ変数あり)の導出

ボルツマンマシン(可視変数のみ)の導出(2016/3/11)のつづき。前回はボルツマンマシンを構成するノードがすべて可視変数(観測データが与えられる)ケースだったけれど今回は一部のノードが隠れ変数(観測データが与えられない)ケースのボルツマンマシンの学習方程式を導出する。これは深層学習の2.5節に当たる。前回と同様に表記法はこの本に準拠する。

深層学習 Deep Learning (監修:人工知能学会)

深層学習 Deep Learning (監修:人工知能学会)

  • 作者: 麻生英樹,安田宗樹,前田新一,岡野原大輔,岡谷貴之,久保陽太郎,ボレガラダヌシカ,人工知能学会,神嶌敏弘
  • 出版社/メーカー: 近代科学社
  • 発売日: 2015/11/05
  • メディア: 単行本
  • この商品を含むブログ (1件) を見る

  1. 可視変数のみのボルツマンマシンの導出(2.4節)
  2. 隠れ変数ありのボルツマンマシンの導出(2.5節)
  3. 制限ボルツマンマシンの導出(2.7節)

定義

エネルギー関数

f:id:aidiary:20160312205848p:plain

ここで、vは可視変数、hは隠れ変数を表す。x_iはi番目のノードが可視変数か隠れ変数かによって変換される表記法。数式では特に具体的なグラフ構造を想定しないのでこの表記法をしておくと何かと便利。

f:id:aidiary:20160312210020p:plain

ボルツマン分布

f:id:aidiary:20160312210208p:plain

可視変数と隠れ変数の同時分布で表されるのがポイント。

分配関数

f:id:aidiary:20160312210843p:plain

対数尤度関数

尤度関数を定義するにあたって観測データが与えられる可視変数のみの分布が必要になるため隠れ変数を周辺化して削除する。

f:id:aidiary:20160312211145p:plain

尤度関数

f:id:aidiary:20160312211216p:plain

対数尤度関数

f:id:aidiary:20160312211314p:plain

隠れ変数があると周辺化による  \displaystyle \sum_{h} がくっつくため可視変数のみの場合と比べると少し複雑になる。今回はエネルギー関数はこれ以上展開しないでそのままにしておく。

対数尤度関数のバイアスパラメータに対する勾配(2.27)の導出

f:id:aidiary:20160312211632p:plain

偏微分の項が2つあるので1つずつ展開していこう。まずは1つ目の偏微分。

f:id:aidiary:20160312211732p:plain

最初の展開は  ln(A) という塊で合成関数の微分をしている。3つ目の展開は  exp(B) という塊で合成関数の微分をしている。エネルギー関数のバイアスパラメータに対する微分は下のようにb_iに対応するx_iしか残らない。ここでは、グラフのi番目のノードが可視変数なのか隠れ変数なのかわからないのでx_iを使っている。

f:id:aidiary:20160312212215p:plain

一番最後の展開が今回の導出で個人的に一番わかりづらかったポイントだがで下のように導出できる。ここでは観測データのインデックスは省略。実際は逆にたどって当てはめる。

f:id:aidiary:20160312212248p:plain

次は2つ目の偏微分を展開していこう。vとhの記号がだぶるのでv'とh'も使っている。

f:id:aidiary:20160312212437p:plain

以上の2つの展開結果をもとの式に代入すると

f:id:aidiary:20160312212642p:plain

やった導出終わったーと思いきや、式 (2.27)と比べてみるとまだ少し違う。1項目が微妙に違う。

実際はここで終わったほうがよりわかりやすいと個人的に思うが、さらに経験分布(観測データのヒストグラム)を使って書き直そう。経験分布を使うと下のように観測データのインデックスを使わない形式に変形できる。

f:id:aidiary:20160312212742p:plain

よって第1項目は下のように変形できる。

f:id:aidiary:20160312212818p:plain

これを代入すると式 (2.27) が得られる。やったね!

f:id:aidiary:20160312213130p:plain

対数尤度関数のバイアスパラメータに対する勾配(2.28)の導出

といきたいところだけどこれまでとほとんど同じなので省略。エネルギー関数の偏微分の対象が異なるので出てくる変数が変わるだけ。まあ練習でやってもよいと思う。

f:id:aidiary:20160312213238p:plain

ボルツマンマシンの学習方程式

あとは前回と一緒。対数尤度関数の最大点では下の条件を満たす

f:id:aidiary:20160312213556p:plain

よって、左辺にこれまでの結果を代入するとボルツマンマシンの学習方程式

f:id:aidiary:20160312213611p:plain

が得られる。式 (2.29) と式 (2.30) が導出できた!

次回はいよいよDeep Learningで使われる制限ボルツマンマシンを導出していきたい。

ボルツマンマシン(可視変数のみ)の導出

制限ボルツマンマシンのアルゴリズムの導出が難しかったので忘れないようにまとめておいた。今回から数回にわたって

  1. 可視変数のみのボルツマンマシンの導出(2.4節)
  2. 隠れ変数ありのボルツマンマシンの導出(2.5節)
  3. 制限ボルツマンマシンの導出(2.7節)

の順番に書いてみる予定。表記法は下の深層学習に準じた。この本は紙面の都合か教育的配慮かわからないが最終結果しか書いてない。この記事ではなるべく1ステップずつ展開していって無理なく理解できるレベルにまで落とし込みたいと思っている。ボルツマンマシン自体の説明は非常にわかりやすいこの本に譲ってここでは純粋に式展開とポイントだけをまとめる。

深層学習 Deep Learning (監修:人工知能学会)

深層学習 Deep Learning (監修:人工知能学会)

  • 作者: 麻生英樹,安田宗樹,前田新一,岡野原大輔,岡谷貴之,久保陽太郎,ボレガラダヌシカ,人工知能学会,神嶌敏弘
  • 出版社/メーカー: 近代科学社
  • 発売日: 2015/11/05
  • メディア: 単行本
  • この商品を含むブログ (1件) を見る

今回は可視変数のみのボルツマンマシンの学習方程式(式2.17・2.18)の導出が目標!

定義

エネルギー関数(energy function)

f:id:aidiary:20160311201746p:plain

f:id:aidiary:20160311210947p:plain

bは各ノードに割り当てられたバイアスパラメータ、Wは各結合に割り当てられた重みパラメータ。

ボルツマン分布(Boltzmann distribution)

f:id:aidiary:20160311210740p:plain

分配関数(partition function)

f:id:aidiary:20160311201932p:plain

このvは観測データとは無関係でノードが取り得るあらゆるパターンの集合を表す。ノード数が多いボルツマンマシンでは、この「ノードが取り得るあらゆるパターン」が爆発的に増加することで厳密な計算が困難になる。

データ集合

f:id:aidiary:20160311202956p:plain

対数尤度関数

尤度関数

データはi.i.d.なので各データの生成確率をすべて掛け算したものがデータ全体を生成する確率になる。

f:id:aidiary:20160311203001p:plain

なぜインデックスに \mu を使うんだー入力が面倒だーと愚痴りたくなった。

対数尤度関数

尤度関数のままだとアンダーフローして扱いにくいため対数を取って足し算に変換する。対数をとっても尤度関数を最大化するパラメータは変わらない。

f:id:aidiary:20160311203023p:plain

ここは一番最後の展開がポイント。

 \ln (\exp(x)) = x

 \ln \frac{M}{N} = \ln M - \ln N

の自然対数に関する公式を使った。

対数尤度関数のバイアスパラメータに対する勾配(2.14)の導出

f:id:aidiary:20160311203052p:plain

先の対数尤度関数をバイアスパラメータ b_i で偏微分する。対数尤度関数は3つの項があった。1項目は b_i に対応する v_i しか残らない。2項目は b_i とは無関係の定数なので消えてしまう。ここで3項目が曲者で Z(\theta) をパラメータに対する定数だと思ってはいけない!\thetab_i を含むので偏微分の対象なのだ。最後の展開は合成関数の微分。

上の式の第2項の偏微分をさらに展開していく。ここのvは観測データではなく、可能なあらゆるvを表すため(\mu)はつかない。

f:id:aidiary:20160311203201p:plain

2式目から3式目は合成関数の微分だが \exp (A) の微分はそのまま \exp (A) なので消えない。中身のAの微分は b_i に対応する v_i 以外は残らない。

以上の結果をまとめると

f:id:aidiary:20160311203248p:plain

となり、式 (2.14) が導出できた。最後は期待値の定義

f:id:aidiary:20160311213029p:plain

による。

対数尤度関数の重みパラメータに対する勾配(2.15)の導出

f:id:aidiary:20160311203317p:plain

こちらもバイアスパラメータのときとほとんど同じように進めればよい。偏微分するパラメータが異なるので残る変数が異なるだけ。

上の式の第2項の偏微分をさらに展開していく。

f:id:aidiary:20160311203328p:plain

よって、

f:id:aidiary:20160311203341p:plain

となり式 (2.15) が導出できた。

ボルツマンマシンの学習方程式

対数尤度関数の最大点では下の条件を満たす

f:id:aidiary:20160311203411p:plain

よって、左辺にこれまでの結果を代入するとボルツマンマシンの学習方程式

f:id:aidiary:20160311203422p:plain

が得られる。式 (2.17) と式 (2.18) が導出できた。やったね!

何か誤りを見つけたり、ここの展開は不明だという点があったらぜひ指摘してほしいです。

次回は隠れ変数があるボルツマンマシンを導出していきたい。

積層自己符号化器の性能評価

Theanoによる積層自己符号化器の実装(2016/1/22)の続き。

今回は、積層自己符号化器の事前学習(pretraining)の効果が本当にあったのか検証してみたい。ついでにもう一つTheanoによる雑音除去自己符号化器の実装(2015/12/9)で書いた雑音除去(denoising)の効果も本当にあったのか合わせて検証してみたい。

自己符号化器自体は教師なし学習で特徴抽出しか行わないためその特徴が本当に分類に役立つのか直接検証できなかった。前回実装した積層自己符号化器は最後の層にロジスティック回帰をくっつけることで抽出した特徴を用いてMNISTの数字認識ができるようにしたので認識精度を使って雑音除去や事前学習の効果が比較できる。

ソースコード全体はここ

スクリプトの修正

前回の積層自己符号化器のスクリプトを少しだけ修正した。

  • メインの関数に事前学習を行うか行わないかのフラグ(pretrain_flag)を追加
  • pretrain_flag=Trueのときのみ事前学習フェーズを実行、pretrain_flag=Falseのときはランダムなパラメータから学習
  • 訓練ループをシンプルにし、training_epochs回数だけ回すようにした
  • テスト誤差を出力するファイルを引数で指定できるようにした
def test_stacked_autoencoder(finetune_lr=0.1, pretraining_epochs=15,
                             pretrain_lr=0.001, training_epochs=100,
                             dataset='mnist.pkl.gz', batch_size=1,
                             hidden_layers_sizes=[1000, 1000, 1000],
                             corruption_levels=[0.1, 0.2, 0.3],
                             pretrain_flag=True,
                             testerr_file='test_error.txt'):
    datasets = load_data(dataset)
    train_set_x = datasets[0][0]
    n_train_batches = train_set_x.get_value(borrow=True).shape[0] / batch_size
    numpy_rng = np.random.RandomState(89677)

    print "building the model ..."

    sda = StackedDenoisingAutoencoder(
        numpy_rng,
        28 * 28,
        hidden_layers_sizes,
        10,
        corruption_levels)

    # Pre-training
    if pretrain_flag:
        print "getting the pre-training functions ..."
        pretraining_functions = sda.pretraining_functions(train_set_x=train_set_x,
                                                      batch_size=batch_size)

        print "pre-training the model ..."
        for i in xrange(sda.n_layers):
            for epoch in xrange(pretraining_epochs):
                c = []
                for batch_index in xrange(n_train_batches):
                    c.append(pretraining_functions[i](index=batch_index,
                                                      corruption=corruption_levels[i],
                                                      lr=pretrain_lr))
                print "Pre-training layer %i, epoch %d, cost %f" % (i, epoch, np.mean(c))

    # Fine-tuning
    print "getting the fine-tuning functions ..."
    train_model, _, test_model = sda.build_finetune_functions(
        datasets=datasets,
        batch_size=batch_size,
        learning_rate=finetune_lr
    )

    print "fine-tuning the model ..."

    epoch = 0
    fp = open(testerr_file, "w")
    while (epoch < training_epochs):
        epoch = epoch + 1
        for minibatch_index in xrange(n_train_batches):
            train_model(minibatch_index)
        test_losses = test_model()
        test_score = np.mean(test_losses)
        print "Fine-tuning, epoch %d, test error %f" % (epoch, test_score * 100)
        fp.write("%d\t%f\n" % (epoch, test_score * 100))
    fp.close()

雑音除去の効果

まずは雑音除去を入れた場合と入れない場合でテスト誤差の推移を比較してみた。それ以外の条件は同じで隠れ層が3層から成るニューラルネットを構成した。

def test_denoising():
    test_stacked_autoencoder(hidden_layers_sizes=[1000, 1000, 1000],
                             corruption_levels=[0, 0, 0],
                             testerr_file="sa_error_without_denoising.txt")

    test_stacked_autoencoder(hidden_layers_sizes=[1000, 1000, 1000],
                             corruption_levels=[0.1, 0.2, 0.3],
                             testerr_file="sa_error_with_denoising.txt")

テスト誤差をプロットすると下のような結果が得られた。確かに雑音除去を入れると最終的な分類精度が高くなることがわかる。雑音除去により抽出された特徴がより良いことを示唆している。

f:id:aidiary:20160206111419p:plain

事前学習の効果

次に事前学習を入れた場合と入れない場合でテスト誤差の推移を比較してみた。それ以外の条件は同じ。

def test_pretraining():
    test_stacked_autoencoder(hidden_layers_sizes=[1000, 1000, 1000],
                             corruption_levels=[0.1, 0.2, 0.3],
                             testerr_file="sa_error_with_pretraining.txt",
                             pretrain_flag=True)

    test_stacked_autoencoder(hidden_layers_sizes=[1000, 1000, 1000],
                             corruption_levels=[0.1, 0.2, 0.3],
                             testerr_file="sa_error_without_pretraining.txt",
                             pretrain_flag=False)

テスト誤差をプロットすると下のような結果が得られた。確かに事前学習を入れると誤差が低くなることがわかる。

f:id:aidiary:20160206111615p:plain

今回は隠れ層が3層程度で比較的浅いニューラルネットだがより深くしていくとさらに事前学習の効果が出るのかもしれない(1回の実行に10時間くらいかかったので実験してないけど・・・)。ただMNISTは比較的単純なタスクなのでこれ以上モデルを複雑にしても過学習してしまう可能性が高い。もっと複雑なタスクで比較してみたほうがよいかもしれない。

『深層学習(人工知能学会編)』の3.2節でも事前学習の有用性を評価した研究がいくつか紹介されている(ただし積層自己符号化器でなく次回取り上げる深層ボルツマンマシンによる事前学習の結果)。一方で、事前学習を用いなくても十分よい性能が出せることを示す研究もあるとのこと。

まあ今回の実験でも改善は0.3%だし、前にも書いたけど事前学習フェーズはけっこう時間がかかるので費用対効果が見合うかは微妙なラインかも。

(追記)事前学習は教師なし学習なので容易に入手できる大量のラベルなしデータが使えることが利点として挙げられていた。事前学習をやっておけば少量のラベル付きデータでFine-tuningすればよい。事前学習なしでFine-tuningだけだと大量のラベル付きデータが必要になってしまう。