読者です 読者をやめる 読者になる 読者になる

verilog書く人

自称ASIC設計者です。どなたかkaggle一緒に出ましょう。

chainerで気軽にスケールできる事前学習器付きニューラルネット生成

のためのコードを書きました。

 

やりたいこと

1.スケーラブル/コンフィギュラブルなニューラルネット生成

ニューラルネットでは難しい問題を解かせるためには中間層の数を増やす必要があります、

 

chainerでは普通は↓こんな風にハードコーディングして層を増やしていきますが、

import chainer.functions as F
h1 = F.dropout(F.relu(self.model.l1(x)), train=train) h2 = F.dropout(F.relu(self.model.l2(h1)), train=train) h3 = F.dropout(F.relu(self.model.l3(h2)), train=train) FunctionSet(l1=F.Linear(784, 100), l2=F.Linear(100, 100), l3=F.Linear(100, 10),


そうではなく、[784,100,100,10]と配列を食わせるだけで、
784次元の入力層、100次元の第一中間層、100次元の第二中間層、10次元の出力層という風にネットを生成し気軽に層数、次元数を変えながら解析が進められるようにします。

 

 

2.事前学習できること

中間層数を増やしていくと、バックプロパゲーションにおいて計算される勾配が入力側に近づくにつれて消えていく…ことにより学習が進まなくなる問題(勾配消失問題)があります。
これを解消するのが各層ごとの事前学習です。
事前学習には各層ごとに自己符号化器(オートエンコーダ)として計算して初期値を決めるやり方と、
制約付きボルツマンマシンとして初期値を決めるやり方の二つがあります。

後者は良く分かっていないので前者は簡単そうなので作ってみます、、
ここで、自己符号化器にはエンコーダとデコーダの重みが独立しているもの(united)と、デコーダの重みがエンコーダの転置行列であるもの(tied weight)ありますが、Bengio先生の論文(http://arxiv.org/pdf/1206.5538.pdf)では後者が使われているので、そのようにしました。

(でも、手元の深層学習(機械学習プロフェッショナルシリーズ)には特にtied weightであるかは明記されていません。どっちがいいのかしら?)

 

使い方

インストール:

$ pip install PreTrainingChain
でインストールできます。
PreTrainingChain.AbstractChain.AbstractChainをimportして継承してください。
MNISTでの使用例はExample.pyにあります。

$ python Example.py

で走ります。

編集

AbstractChainの中でオーバーライドが必要な関数
loss_function:損失関数

    def loss_function(self, x, y):
        return F.softmax_cross_entropy(x, y) #MNISTの場合分類問題なのでソフトマックス交差エントロピーを使用


add_last_layer:最終層のリンク

    def add_last_layer(self):
        self.add_link(F.Linear(self.n_units[-1], self.last_unit))

最終層は事前学習されません。

全体の学習であるlearnとoptimizerを定義するset_optimizerは必要に応じてオーバーライドしてください。

 

実行

Example.pyではAbstractChainを継承したPreTrainingDNN(n_units)によってインスタンシエートしています。
n_unitsはリストで、例えばn_units=[784,400,300,150,100,10]とします。
これだけでn_units[0]が入力層(例えばMNISTなら28*28=784)、n_units[-1]が出力層(MNISTなら10種類の分類なので10)、残りが中間層の次元数となるニューラルネットが生成されます。楽チンですね。
AbstractChain.pre_trainingを呼ぶことにより、pre_trainingを実行します。
AbstractChain.learnを呼ぶことにより、全層としての学習を行います。

こんな感じで叩きます↓

    pc = PreTrainingDNN([784,400,300,150,100,10]) #ニューラルネットの生成

  #x_pre_trainは事前学習用のデータ(正解不要)、サイズがゼロなら事前学習をスキップ
  #x_pre_testはオートエンコーダに対してテストを行いオートエンコーダ損失を計算
  #サイズがゼロなら損失計算スキップ
    pc.pre_training(x_pre_train, x_pre_test) 

    #x_trainは学習用データ、x_testが学習用の正解
    #y_trainはテスト用データ、y_testがテスト用の正解   
    #isClassificationは分類問題かどうか?
    #分類問題なら各エポックごとの正解率を標準出力する
    pc.learn(x_train, y_train, x_test, y_test, isClassification=True)


実装の中身

学習方式管理

各層の学習方式を事前学習中とそうでない時とで切り替える必要があるので、
SingletonのPT_Managerクラスでフラグ管理します。

(※追記、今にして思うと事前学習にて学習済の重みWをinitialWとして改めてF.Linearを呼び出すほうがシンプルで正解な気がしています。ここでやってるフラグでforwardの動きを変えるのはトリッキーな気が。あまりまねして欲しくない感じです。)

 

class PT_manager(object):
    """ [CLASSES]
        事前学習を管理するためのシングルトンクラス。
    PT_manager().pre_trainingにbooleanを代入することで、
        PT_Linear.forward()とPT_Linear.backward()の振る舞いが変わる。
    """
    _singleton = None
    def __new__(cls, activation=None):
        if cls._singleton == None:
            cls._singleton = object.__new__(cls)
            cls.is_pre_training = True
            assert activation is not None
        return cls._singleton

    def get_pt_info(self):
        return self.is_pre_training, F.ReLU #事前学習時の活性化関数をReLUに

 

事前学習器付きLinear(PT_Linear)

chainer.functions.Linearの子クラスです。
forwardはPT_Manager().is_pre_training == Trueの時はエンコード結果をデコードしたものであるWT(W*x+b)+b2を出力し、そうでないときはエンコード結果W*x+bを出力します。ここでWTはWの転置行列です。

    def forward(self, inputs):
        is_pre_training, activation = PT_manager().get_pt_info()
        x = _as_mat(inputs[0])
        W = inputs[1]
        h = x.dot(W.T)
        if len(inputs) == 4:
            b = inputs[2]
            h += b
        if not is_pre_training:
            return h, #事前学習していないときはエンコーダ結果を返す

        self.x_activation = h
        h, = activation().forward((h,))
        self.x_decode = h
        y = h.dot(W)
        if len(inputs) == 4:
            b2 = inputs[3]
            y += b2
        return y, #事前学習中はエンコーダ結果をさらにデコーダした結果を返す


backwardの振る舞いもPT_Manager().is_pre_trainingで切り替えます。(Falseなら通常のLinear、Trueならtied weirht自己符号化器としてのバックプロパゲーション)

tied weightな自己符号化器は↓こちらを参考にしてPreTrainableLinear実装しました。
これがまた何気にややこしい…

Chainerでtied weightなAutoencoderを作った - Qiita

実装の詳細はこちら↓


結果

MNISTだと同じサンプル数ならネットは浅めのほうが正解率は出ますが、
あえて深めにして事前学習の効果を見ています。

 

 中間層:3 [784,400,300,150,10]中間層:4 [784,400,300,150,100,10]
事前学習あり(サンプル5000) 81.2% 73.0%
事前学習なし 78.5% 69.5%

んー…事前学習で正解率は上がってるけど微妙ですね。

でも実行するとCPUがほのかに暖かくなるので寒い冬の夜におすすめ。

 

おまけ

 

ぼくが考えた最強のニューラルネット

    PreTrainingDNN([1000 for i in range(1000)])

暖を取るには最適(筆者環境)。

それではみなさまよいお年をー