MLPと最適化(Optimization)の実装|scikit-learnに学ぶ機械学習アルゴリズムの実装 #1

f:id:lib-arts:20190110181457p:plain

理論系の解説が多く、実装の話は少なめだったので実装についても取り扱っていきたいと思います。自前実装をするのは大変だし、手本がある方が良いので、scikit-learnのコードリーディングを通して機械学習アルゴリズムの実装について取り扱っていければと思います。

理論系の記事は他のシリーズで取り扱っているし基本的にどこから入っても大丈夫と思われるため、適当に気が向いたところからコードリーディングしていければと思います。

#1の初回としては、MLP(Multi Layer Perceptron)について取り扱います。

以下目次になります。

1. MLPの処理の実行

2. コードリーディング

2.1 MLPClassifierの大枠の実装

2.2 SGDOptimizerの実装

3. まとめ

 

1. MLPの処理の実行

処理を実行するにあたっては、scikit-learnのユーザーガイドを参考にします。

1.17. Neural network models (supervised) — scikit-learn 0.20.3 documentation

上記にサンプルコードが載っているのでここを起点に解析していきたいと思います。

from sklearn.neural_network import MLPClassifier


X = [[0., 0.], [1., 1.]]
y = [0, 1]
clf = MLPClassifier(solver='lbfgs', alpha=1e-5,hidden_layer_sizes=(5, 2), random_state=1)
clf.fit(X, y)

 

for coef in clf.coefs_:
    print(coef.shape)

print(clf.coefs_)

print(clf.predict([[2., 2.], [-1., -2.]]))

 上記はサンプルコードをベースに諸々の値が確認できるようにしたものです。基本的には二値の分類問題なので0か1に分類されるのですが、学習データを恣意的に与えているので基本的には学習データに近い側に分類されるようです。実行結果は以下のようになります。

f:id:lib-arts:20190318225420p:plain

coefs_のそれぞれがMLPの層ごとの計算を行っています。このコードの大元であるMLPClassifierについて2節で中身を確認していきます。

(上記のようにチュートリアルコードはsolverにlbfgsを用いていますが、sgdの実装を読みたかったため、以下はsolverにsgdを設定したとして話を進めていきます)

 

2. コードリーディング

いつもの流れでインポート元(PJ_root/sklearn/neural_network/)を辿ると、『scikit-learn/__init__.py at master · scikit-learn/scikit-learn · GitHub』に行き着きます。

f:id:lib-arts:20190318225857p:plain

ここからMLPClassifierの実装を辿っていきます。

 

2.1 MLPClassifierの大枠の実装

__init__.pyまでは確認したので、次は「PJ_root/sklearn/neural_network/multilayer_perceptron.py」について確認します。具体的には『scikit-learn/multilayer_perceptron.py at master · scikit-learn/scikit-learn · GitHub』を確認します。

f:id:lib-arts:20190318230531p:plain

次にscikit-learnの実装において最も気になるのは学習部分なので、fitの実装を探します。

f:id:lib-arts:20190318230700p:plain

上記のようにfitを見つけます。ここから色々と関数の呼び出しを行っており、solverにsgdを設定した場合はfit -> _fit -> _fit_stochasticと辿れば実際にSGDの計算を行っているところに行き着きます。

f:id:lib-arts:20190318231523p:plain

*1

f:id:lib-arts:20190318231723p:plain

上記でSGDOptimizerの_backpropメソッドをバッチ単位で呼んでいるので、ここで処理が切り分けられていることがわかります。_backpropメソッドでは勾配の計算を行っています。

f:id:lib-arts:20190318235405p:plain

勾配の計算の肝はdeltasの計算で、これは二乗誤差の微分を計算しているようです。これを元に_compute_grad_lossで詳細の計算を行っているようです。

self._optimizer.update_paramsで実際にパラメータのアップデートを行っているようなのでこちらに関しては2.3節で取り扱います。

 

2.2 SGDOptimizerの実装

multilayer_perceptron.pyの冒頭では下記のようにSGDOptimizerを読み込んでいます。

f:id:lib-arts:20190318235336p:plain

これを辿ることで、_stochastic_optimizers.pyに行き着きます。具体的には『scikit-learn/_stochastic_optimizers.py at master · scikit-learn/scikit-learn · GitHub』に実装があります。このファイル内に実装されているクラスは主に三つあり、BaseOptimizerクラス、SGDOptimizerクラス、AdamOptimizerクラスの三つです。それぞれの関係性としては、BaseOptimizerの拡張としてSGDOptimizerクラスとAdamOptimizerクラスが実装されています。

update_paramsはBaseOptimizer内に実装されており、_get_updatesを呼び出しています。また、_get_updatesはSGDOptimizerとAdamOptimizerにそれぞれ実装されています。大まかな違いとしてはパラメータのアップデートにあたって、SGDOptimizerではMomentumの式を用いてアップデートしており、AdamOptimizerはAdamの手法を用いてアップデートを行っています。

一旦ざっくりと掴んだ形ではありますが、だいたいの雰囲気はわかったので今回はこのくらいにできればと思います。

 

3. まとめ

近年ではDeepLearningの発展に伴いTensorFlow、Keras、PyTorchのようなフレームワークが色々と開発されていますが、ちょっと気軽に読むにはごついので、気軽に実装を確認するにはちょうどよかったのではという印象でした。

*1:中略