人工知能に関する断創録

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

Theanoによる積層自己符号化器の実装

Theanoによる雑音除去自己符号化器の実装(2015/12/9)の続き。今回は自己符号化器を積み上げて積層自己符号化器を実装した。

多層パーセプトロンは隠れ層の数を増やす(Deepにしていく)とより複雑なモデルを表現できるようになるが、誤差が伝播されずに重みが更新されなくなる勾配消失問題(Vanishing gradient problem)が生じる。この問題を解決するため多層パーセプトロンの重みの初期値をランダムに設定するのではなくより良い重みを設定しておく事前学習(pre-training)というテクニックが提案されている。

このより良い重みを自己符号化器(2015/12/3)を順に積み上げることで設定するのが積層自己符号化器(Stacked autoencoder)。自己符号化器で初期値を決めたらあとは普通に多層パーセプトロン(2015/6/18)と同じ誤差逆伝播法で重みを更新すればよい。この重み更新はfine-tuningと呼ばれている。

積層自己符号化器の2つの側面

積層自己符号化器の構造は

  1. 自己符号化器を複数積み上げたもの
  2. 複数の隠れ層を持つ多層パーセプトロン

という二つの側面がある。たとえば、下のようなニューラルネットを考えると自己符号化器が3つ(ただし、符号化部分のみで復号化部分は省略)という側面と隠れ層が3つの多層パーセプトロンという側面がある。出力層だけは少し特殊。ニューラルネットで何らかの分類をしたいときは多層パーセプトロン(2015/6/18)と同じようにロジスティック回帰層をくっつければよい。

f:id:aidiary:20160120194922p:plain

(注)入力のユニット数より隠れ層のユニット数が小さくないと自己符号化器として意味がないと理解していたがチュートリアルでは入力が784で隠れ層が1000と大きくなっていたため上の図でもそのようにしている。

積層自己符号化器の学習は、pre-trainingフェーズで重みの初期値を求めて、fine-tuningフェーズで重みを更新するという2つのフェーズからなる。最初のpre-trainingではこのニューラルネットを自己符号化器を積み上げたものとみなして入力を再現するような重みを教師なしで1層ずつ求める。次のfine-tuningでは自己符号化器で求めた重みをそのまま引き継いで、多層パーセプトロンとみなして教師ありで重みを更新する。出力層の重みは自己符号化器に含まれないためランダムに初期化し、fine-tuningでのみ更新する。

というのが私が理解した積層自己符号化器の概要。というわけでDeep Learning Tutorialを参考にTheanoによる実装を詳しくみていきたい。これまで自己符号化器と書いてきたが実装では過学習に強い雑音除去自己符号化器(2015/12/9)を使っている。

ソースコード全体はここ。以下のまとめではポイントになる部分のコードの抜粋しか載せていない。

ネットワークの構成・重みの共有

積層自己符号化器の実装では、自己符号化器と多層パーセプトロンの間で重みを共有するのが大きなポイント。たとえば、先の図の赤色の重み (W, b) はAutoencoder 0とHidden Layer 0が共有し、黄色の重み (W, b) はAutoencoder 1とHidden Layer 1が共有していることを意味する。

このように重みを共有しておけばpre-trainingの自己符号化器で求めた重みの初期値をfine-tuningの多層パーセプトロンに速やかに(というか何もしなくても)引き継げる。Deep Learning Tutorialの実装では、多層パーセプトロンの隠れ層の重みを自己符号化器が参照する方法で重みの共有が実装されている(逆でもよいか?)。

ネットワークを構成するコードは隠れ層の数(self.n_layers)を可変にするため下のようなforループで書かれている。入力層に近い方から順番に構築して積み上げていることがわかる。HiddenLayerクラスは多層パーセプトロン(2015/6/18)で実装したものをDenoisingAutoencoder雑音除去自己符号化器(2015/12/3)で実装したものを再利用している。pre-trainingではDenoisingAutoencoderのコスト関数とパラメータ更新式をfine-tuningではHiddenLayerのコスト関数とパラメータ更新式をそれぞれ使う。

# ネットワークを構築
# 隠れ層の数だけループして積み上げていく
for i in xrange(self.n_layers):
    # ユニット数
    if i == 0:
        input_size = n_ins
    else:
        input_size = hidden_layers_sizes[i - 1]

    # 隠れ層への入力データ
    if i == 0:
        layer_input = self.x
    else:
        layer_input = self.hidden_layers[-1].output

    # 多層パーセプトロンの隠れ層
    # fine-tuningで重みを更新するため
    hidden_layer = HiddenLayer(rng=numpy_rng,
                               input=layer_input,
                               n_in=input_size,
                               n_out=hidden_layers_sizes[i],
                               activation=T.nnet.sigmoid)
    self.hidden_layers.append(hidden_layer)
    self.params.extend(hidden_layer.params)

    # 自己符号化器だが重みは多層パーセプトロンの隠れ層と共有
    autoencoder_layer = DenoisingAutoencoder(numpy_rng=numpy_rng,
                                             theano_rng=theano_rng,
                                             input=layer_input,
                                             n_visible=input_size,
                                             n_hidden=hidden_layers_sizes[i],
                                             W=hidden_layer.W,
                                             bhid=hidden_layer.b)
    self.autoencoder_layers.append(autoencoder_layer)

# MNISTの分類ができるように最後にロジスティック回帰層を追加
self.log_layer = LogisticRegression(
                    input=self.hidden_layers[-1].output,
                    n_in=hidden_layers_sizes[-1],
                    n_out=n_outs)
self.params.extend(self.log_layer.params)

self.hidden_layersのリストはforループを回すたびに隠れ層のオブジェクトが追加されていく。そのためself.hidden_layers[-1]で一つ前の隠れ層にアクセスできる。自己符号化器の重みとバイアスは隠れ層の重みとバイアスを設定して共有している。そのため自己符号化器のWbself.paramsに追加する必要がない。

8.4 Tips and Tricksを読むとこの実装は効率が悪いとのこと。2つ目や3つ目の自己符号化器への入力を得たいときすでに前の層において出力を計算済みなのにそれを保存していないため再度入力から順にフィードフォワードして出力を再計算する必要がある。ただフィードフォワードの計算は高速だし、GPUのメモリ不足の方が心配の種だから実装的にこれでよいのかもしれない。

pre-training関数の作成

次はpre-training。pre-trainingでは先のニューラルネットを自己符号化器を積み上げたものとみなして浅い層から順に訓練していく。チュートリアルの実装ではpre-trainingを行う関数のリスト(自己符号化器が3つのときは3つの関数がある)を生成して返すメソッドを定義している。

    def pretraining_functions(self, train_set_x, batch_size):
        """自己符号化器を学習するpre-training用の関数リストを返す
        教師なし学習なのでxのみを渡す"""
        # 学習に使うミニバッチのインデックス
        index = T.lscalar('index')

        # 複数の自己符号化器で異なる値を指定できるようにシンボル化する
        corruption_level = T.scalar('corruption')
        learning_rate = T.scalar('lr')

        batch_begin = index * batch_size
        batch_end = batch_begin + batch_size

        # 自己符号化器を学習する関数を生成
        # 入力層に近い方から順番に追加する
        pretrain_functions = []
        for autoencoder in self.autoencoder_layers:
            # 誤差と更新式を計算するシンボルを取得
            cost, updates = autoencoder.get_cost_updates(corruption_level, learning_rate)
            fn = theano.function(
                inputs=[
                    index,
                    # Paramにした引数を関数呼び出し時に与えるときはPython変数名ではなく、
                    # Tensorの引数の名前(corruption, lr)で指定できる
                    theano.Param(corruption_level, default=0.2),
                    theano.Param(learning_rate, default=0.1)
                ],
                outputs=cost,
                updates=updates,
                givens={
                    self.x: train_set_x[batch_begin:batch_end]
                }
            )
            pretrain_functions.append(fn)

        return pretrain_functions

自己符号化器は教師なし学習なので引数にはtrain_set_xのみでラベルは必要ない。自己符号化器が複数あるだけで訓練方法はTheanoによる自己符号化器の実装(2015/12/3)と同じで目新しい部分はない。自己符号化器ごとに独立に学習率と汚染度を設定できるようにinputsに引数を与えているのが少し新しい。

Theanoのややこしい部分だがこの時点では自己符号化器の学習を行う関数リストを返すだけで実行はしない。

fine-tuning関数の作成

次にfine-tuning。自己符号化器の訓練が終わった後に実行される。ニューラルネットを多層パーセプトロンとみなして教師あり(分類ラベルを使う)で学習する。こちらも訓練を行う関数を返すだけで実行はしない。こちらも書き方が少し違うだけでTheanoによる多層パーセプトロンの実装(2015/6/18)とほぼ同じ。

    def build_finetune_functions(self, datasets, batch_size, learning_rate):
        """fine-tuning用の関数を返す"""
        train_set_x, train_set_y = datasets[0]
        valid_set_x, valid_set_y = datasets[1]
        test_set_x, test_set_y = datasets[2]

        n_valid_batches = valid_set_x.get_value(borrow=True).shape[0] / batch_size
        n_test_batches = test_set_x.get_value(borrow=True).shape[0] / batch_size

        index = T.lscalar('index')

        gparams = T.grad(self.finetune_cost, self.params)

        updates = [
            (param, param - gparam * learning_rate) for param, gparam in zip(self.params, gparams)
        ]

        train_model = theano.function(
            inputs=[index],
            outputs=self.finetune_cost,
            updates=updates,
            givens={
                self.x: train_set_x[index * batch_size: (index + 1) * batch_size],
                self.y: train_set_y[index * batch_size: (index + 1) * batch_size]
            },
            name='train')

        test_score_i = theano.function(
            inputs=[index],
            outputs=self.errors,
            givens={
                self.x: test_set_x[index * batch_size: (index + 1) * batch_size],
                self.y: test_set_y[index * batch_size: (index + 1) * batch_size]
            },
            name='test')

        valid_score_i = theano.function(
            inputs=[index],
            outputs=self.errors,
            givens={
                self.x: valid_set_x[index * batch_size: (index + 1) * batch_size],
                self.y: valid_set_y[index * batch_size: (index + 1) * batch_size]
            },
            name='validate')

        def valid_score():
            """各ミニバッチのvalid誤差のリストを返す"""
            return [valid_score_i(i) for i in xrange(n_valid_batches)]

        def test_score():
            """各ミニバッチのtest誤差のリストを返す"""
            return [test_score_i(i) for i in xrange(n_test_batches)]

        return train_model, valid_score, test_score

test_score_ivalid_score_iのように_iがついているのはindexで指定した一つのミニバッチの誤差(スコア)を返すからみたいだ。実際は全ミニバッチの誤差をまとめて計算して誤差リストを返す関数を返している。

積層自己符号化器の訓練と評価

ネットワークの構築

最後にこれまでの関数を使ってモデルを訓練する流れを整理したい。まず、積層自己符号化器のオブジェクトを作る。これで先の図のようなニューラルネットが構築される。

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

例では、hidden_layers_sizes=[1000, 1000, 1000]corruption_levels=[0.1, 0.2, 0.3]となっている。入力が28x28=784ユニットなのに隠れ層が1000ユニットで自己符号化器が砂時計型にならないけれどよいのだろうか?

pre-training

まずはpre-trainingで重みの初期値を決める。

    # Pre-training
    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):
        # pre-trainingのエポック数は固定
        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))

最初のforループで浅い層から深い層へ順番に自己符号化器を学習していることがわかる。エポック数は固定でpretraining_epochs=15となっている。初期値を決めるだけなので厳密な収束判定は行わずに割と適当でよいのだろうか。実行すると

building the model ...
getting the pre-training functions ...
pre-training the model ...
Pre-training layer 0, epoch 0, cost 71.648659
Pre-training layer 0, epoch 1, cost 63.687855
・・・
Pre-training layer 0, epoch 13, cost 59.473030
Pre-training layer 0, epoch 14, cost 59.331242
Pre-training layer 1, epoch 0, cost 482.215973
Pre-training layer 1, epoch 1, cost 464.952728
・・・
Pre-training layer 1, epoch 13, cost 451.862030
Pre-training layer 1, epoch 14, cost 451.500214
Pre-training layer 2, epoch 0, cost 196.515244
Pre-training layer 2, epoch 1, cost 183.888687
・・・
Pre-training layer 2, epoch 13, cost 176.494202
Pre-training layer 2, epoch 14, cost 176.348206
The pretraining code for file stacked_autoencoder.py ran for 72.73m

こんな感じで層ごとに自己符号化器を学習してエポックが進むと誤差が低下することがわかる。層を深くして自己符号化器の数が多くなると学習にけっこう時間がかかる。たった3つの隠れ層の初期値を決めるだけでGPUでも72分かかった。事前学習の効果は本当にあるのだろうか・・・

fine-tuning

pre-trainingが終わったら最後にfine-tuning。これはTheanoによる多層パーセプトロンの実装(2015/6/18)とほとんど同じでearly-stoppingで収束判定している。

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

    print "fine-tuning the model ..."
    while (epoch < training_epochs) and (not done_looping):
        epoch = epoch + 1
        for minibatch_index in xrange(n_train_batches):
            train_model(minibatch_index)
            iter = (epoch - 1) * n_train_batches + minibatch_index

            if (iter + 1) % validation_frequency == 0:
                validation_losses = validate_model()
                this_validation_loss = np.mean(validation_losses)

                if this_validation_loss < best_validation_loss:
                    if this_validation_loss < best_validation_loss * improvement_threshold:
                        patience = max(patience, iter * patience_increase)
                    best_validation_loss = this_validation_loss
                    best_iter = iter

                    test_losses = test_model()
                    test_score = np.mean(test_losses)

            # patienceを超えたらループを終了
            if patience <= iter:
                done_looping = True
                break

Validation誤差は以下のように収束する。

f:id:aidiary:20160122224815p:plain

最終的に75エポックで収束し、テスト誤差は1.4%となった。つまり、正解率は98.6%。実行時間はGPUで162分・・・

Optimization complete with the best validation score of 1.360000 %, on iteration 1900000, with test performance 1.400000 %
The training code for file stacked_autoencoder.py ran for 162.11m

初期値をランダムではなく、なるべく良いように設定しておくと深いニューラルネットがうまく学習できるというのが事前学習のアイデアだ。ニューラルネットではないが同様のアイデアは他のアルゴリズムでもあった。たとえば、混合ガウス分布(GMM)の学習(2010/5/21)で初期クラスタの位置をランダムに設定せずにK-meansの結果を使うなど。比較的シンプルなアイデアに見えるが事前学習が発見されるのにこんなに長い時間かかったのが不思議に思える。知ってみれば簡単でも最初に思いついて実行するのはやはり難しいものなのだろうか。

今回の積層自己符号化器の実装自体は以前に実装した自己符号化器と多層パーセプトロンの組み合わせなのでそんなに難しくなかった。なかったけれど・・・うーん、事前学習で初期値を決めた効果は本当にあったのだろうか?次回はここら辺を実験で検証してみたい。

参考

2016年の目標

あけましておめでとうございます。

今年もいくつか目標を立てた。メインは去年と同じく深層学習(Deep Learning)の深耕にした。

機械学習・深層学習の深耕

情報理論 (ちくま学芸文庫)

情報理論 (ちくま学芸文庫)

Information Theory, Inference and Learning Algorithms

Information Theory, Inference and Learning Algorithms

スパース性に基づく機械学習 (機械学習プロフェッショナルシリーズ)

スパース性に基づく機械学習 (機械学習プロフェッショナルシリーズ)

その他

  • Python3への移行
  • MOT・イノベーション・マーケティング関係の本を月1冊以上読む
  • 英語ライティングを鍛える

入門 Python 3

入門 Python 3

  • 作者: Bill Lubanovic,斎藤康毅,長尾高弘
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2015/12/01
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る
イノベーションの最終解 (ハーバード・ビジネス・セレクション)

イノベーションの最終解 (ハーバード・ビジネス・セレクション)

  • 作者: クレイトン・M・クリステンセン,スコット・D・アンソニー,エリック・A・ロス,玉田俊平太,櫻井祐子
  • 出版社/メーカー: 翔泳社
  • 発売日: 2014/07/08
  • メディア: 単行本
  • この商品を含むブログ (6件) を見る
The Elements of Style, Fourth Edition

The Elements of Style, Fourth Edition

今年は技術だけではなく、ビジネス寄りの話(技術をどう役立てるか?お金にするか?)も少し勉強してみたいと考えている。実践で身につけるのが一般的なのだろうけれど・・・とりあえず評価の高い本を片っ端から読んでみることから始めたい。

また今年はカンファレンス、講演会、展示会、勉強会などもう少し積極的に参加して見聞を広めてみようと思っている。昔はよく参加していたのだけれど最近はとんと出不精になっていた。

そんなわけで今年もブログは続けようと思いますのでよろしくお願いいたします。

2015年まとめ

2015年目標(2015/1/11)のつづき。今年ももう終わりということで目標に対する振り返り。今年は4つ目標を立てていた。

(1) Deep Learningの勉強

Deep Learningの勉強・研究は今年から本格的に始めた。主にDeep Learning Tutorialを読み、Theanoで実装しながら基本的なアルゴリズムを理解することに集中した。ロジスティック回帰、多層パーセプトロン、畳み込みニューラルネットワーク、自己符号化器、制約ボルツマンマシン、Deep Belief Networkあたりまで教科書に載っているような手法はだいたい理解できた。話題のTensorFlowは来年かな?

あとはこの二冊を熟読した。基礎は一通り抑えたのでこれで最新の論文も読めるようになるかな?

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習: Deep Learning

深層学習: Deep Learning

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

(2) Pythonによるモンテカルロ法入門

Pythonによるモンテカルロ法の実装はやらなかった。ただ、制約ボルツマンマシンの理解にMCMCが必要になりアルゴリズムは抑えたので△。

(3) pandasとscikit-learnの習得と活用

Pythonのデータ分析ライブラリpandasと機械学習ライブラリscikit-learnは使えるようになった。scikit-learnに関してはSciPy2015のチュートリアルを参考に4つ記事を書いた。

(4) 複雑系の勉強 ×

複雑系の勉強は残念ながらやらなかった。Deep Learningの方で手一杯だった。

(5) その他

統計的声質変換

Pythonで音声信号処理(2011/5/14)の応用の一環として統計的声質変換の実験をした。音声の扱い方がわかってきた。

ベイズ推定とグラフィカルモデル:コンピュータビジョン基礎

機械学習とコンピュータビジョンの勉強のためにC­omputer vision: models, learning and inferenceを読み始めた。テキストは無料で手に入る。あと玉木先生がYoutubeで講義を配信している。PRMLよりわかりやすい。これもまだ途中なので来年も続けたい。

Synapses, Neurons and Brains

人工知能学会誌の脳神経系シミュレーションの特集をよりよく理解するためCourseraで Synapses, Neurons and Brains という神経科学の講義を受講した。ニューロンの発火の仕組みから脳シミュレーションまで。Idan Segev教授の説明が非常に熱心だったのが印象に残った。でも試験はすごく細かくてものすごーくイライラした(笑)この講義内容に関してはTwitterでときおりつぶやいただけでブログには全くまとめていなかった。Twitterはあとから見直しにくいから少し問題だな。

読んだ or 読んでいる本

今年(じゃないのもあるけど)は人工知能に関する本が豊作だったのでいくつか読んだ。

人工知能は人間を超えるか (角川EPUB選書)

人工知能は人間を超えるか (角川EPUB選書)

AIの衝撃 人工知能は人類の敵か (講談社現代新書)

AIの衝撃 人工知能は人類の敵か (講談社現代新書)

人工知能 人類最悪にして最後の発明

人工知能 人類最悪にして最後の発明

AIは「心」を持てるのか 脳に近いアーキテクチャ

AIは「心」を持てるのか 脳に近いアーキテクチャ

The Quest for Artificial Intelligence

The Quest for Artificial Intelligence

フューチャー・オブ・マインド 心の未来を科学する

フューチャー・オブ・マインド 心の未来を科学する

〈わたし〉はどこにあるのか: ガザニガ脳科学講義

〈わたし〉はどこにあるのか: ガザニガ脳科学講義

意識をめぐる冒険

意識をめぐる冒険

今年はKindle Paperwhiteを買ったこともあって電子書籍を頻繁に買うようになったのが大きな変化かな。著作権が切れて大安売りしている吉川英治の歴史小説(三国志、新水滸伝、新平家物語、私本太平記、新書太閤記、宮本武蔵)に無茶苦茶はまっていた。

最後に

Fallout4最高!

Fallout4

Fallout4