ロボット研究者の戯言

Chainer公式チュートリアル1/5(日本語訳)

   

Chinerチュートリアルの日本語訳を記載します.ところどころ意訳をしていますが,おかしな訳があった場合はご一報いただければ幸いです.

Introduction to Chainer | Chainer Official Tutorial
http://docs.chainer.org/en/stable/tutorial/basic.html

Chainerの紹介

この章はChainerチュートリアルの第1章です.以下について学んでいきましょう.

  • 既存フレームワークの長所短所とChainerを開発している理由
  • 順伝播計算と逆伝播計算の簡単な例
  • 結合と勾配計算の使用法
  • パラメータ最適化
  • 結合とオプティマイザの連結

この章を読むと

  • 勾配計算
  • Chainerを使った多層構造の記述

ができるようになります.




Chainerの核となる概念 “Define-by-Run”

前節で述べた通り,Chainerはニューラルネットワークの柔軟なフレームワークです.主要な目的の1つが柔軟性であり,これにより複雑な構造を簡潔かつ直感的に記述することが可能になります.

既存の多くのディープラーニング用フレームワークは”Define-and-Run”という構成に基づいて作られています.これは最初にネットワークを定義し固定するというもので,ミニバッチを用いて周期的にネットワークに入力を与えます.つまり,順伝播/逆伝播計算の前にネットワークは静的に定義されるため,すべてのロジックはネットワーク構造に埋め込まれます.結果として,このような(例えばCaffeなどの)システムにおけるネットワーク構造の定義は叙述的になるのです.ただし,命令形言語を用いて静的ネットワークの定義を行うものもあることに注意してください(Torch7やTheanoなど).

対象的に,Chainerは”Define-by-Run”という仕組みを採用しています.これは実際に順伝播計算を行う時に初めてネットワークが定義されるというものです.さらに正確に言うと,Chainerではプログラムされたロジックの代わりに計算履歴を記憶して使います.この方法によりpythonでプログラミングされたロジックの真価を発揮させることが可能になります.例えばネットワークを定義する際,Chainerでは条件分岐や繰り返しループを記述する必要がありません.Define-by-RunはChainerの核となる概念なのです.このチュートリアルではネットワークの動的定義の手法をご紹介します.

Define-by-Runではロジックはネットワーク操作により近いものとして扱われるので,複数のGPUの並列化が簡単になります.なお,どれくらい簡単になるのかはこのチュートリアルの後節でレビューします.

注意!
このチュートリアルのサンプルコードでは,簡単のために以下のようなシンボルがすでにインポートされているものとします.

これらのインポートはChainerのコードや例題の中でよく出てきます.簡単のため,このチュートリアルではこれらのインポートに関する記述を省略しています.

順伝播/逆伝播計算

上で述べたようにChainerは”Define-by-Run”方式を使用しているため,順伝播計算を行う際にネットワークの定義を行います.順伝播計算を行うためには入力する配列をVariableオブジェクトに設定しなければなりません.ここでは1要素からなる簡単なndarrayを用いて始めてみましょう.

<警告!>
現状ではChainerは大半の計算において32ビット浮動小数点数しかサポートしていません.

変数オブジェクトでは基本的な算術演算子が用いられます.例えば[math]y=x^2-2x+1[/math]を計算したいときは以下のように記述します.

結果的に[math]y[/math]も変数オブジェクトとなり,その値はdata属性にアクセスすることによって取り出すことができます.

このとき[math]y[/math]は数値だけでなく計算履歴(もしくは計算グラフ)を保持するので,微分計算が可能になります.微分計算を行うときはbackward()メソッドを呼び出します.

このようにすると誤差逆伝播の計算が行われます.このとき勾配も計算され,入力変数である[math]x[/math]のgrad属性に格納されます.

中間変数の勾配を計算することもできます.ただし,デフォルトではメモリを有効に使うために中間変数の勾配配列は解放されていることに注意して下さい.勾配の情報を保持するためには,以下のようにbackwardメソッドでretain_gradという引数を渡す必要があります.

これらの計算は多次元配列に対しても行うことができるように一般化されています.ただし,多次元配列から逆伝播計算を始める場合は,初期誤差を手動で設定しなければなりません.初期誤差の設定は出力変数のgrad属性を設定することで簡単に行うことができます.

<注意!>
変数を操作する多くの関数は関数モジュール内で定義されており,それらを自動逆伝播計算と統合することで複雑な関数を新たに定義することが可能です.

結合(Links)

ニューラルネットワークを記述するためにはある関数とパラメータを統合し,パラメータを最適化しなければなりません.そのためにlinks結合)を使います.linksはパラメータ(例えば最適化の評価関数)を保持することができるオブジェクトです.

linksは基本的に引数を取り替える間もずっと普通の関数のように振舞います.後でよりレベルの高いlinksを紹介しますが,ここでは単にパラメータを持った関数のようなものだと思っておけば良いでしょう.

<注意!>
実際にv1.4では”parameterized function(パラメータ化された関数)”という名前で定義されています.

最も頻繁に使われる結合の一つとして線形結合(全結合もしくはアフィン変換ともいいます)が挙げられます.これは数式では[math]f(x)=Wx+b[/math]と表され,行列[math]W[/math]とベクトル[math]b[/math]はパラメータです.この結合はlinear()という関数と対応しており,引数として[math]x,W,b[/math]をとります.3次元から2次元への線形結合を記述する場合,以下のように定義します.

<注意!>
ほとんどの関数や結合はミニバッチの入力のみを受け取ります.ここでは入力配列の最初の次元はバッチの次元であると考えてください.上で述べたのような線形結合の場合,Nをミニバッチのサイズだとすると入力はサイズを(N,3)にしなければなりません.

結合のパラメータは属性として保持されます.各パラメータは変数のインスタンスとして扱われます.線形結合の場合,2つのパラメータW,bが保持されます.デフォルト設定では行列Wはランダムに初期化され,ベクトルbはゼロに初期化されます.

また,線形結合のインスタンスは普通の関数のように振る舞います.

パラメータの勾配はbackward()メソッドで計算されますが,計算された勾配は上書きではなくメソッドにより蓄積されることに注意して下さい.よって,新しく計算を行う際は以下のようにして勾配をゼロに初期化する必要があります.

ここまでの内容を用いると,backwardメソッドの呼び出しにより簡単にパラメータの勾配を計算することができるようになります.

鎖状モデルの記述

ニューラルネットワーク構造の大半は複数の結合を有しています.例えば多層パーセプトロンは複数の線形結合層から成ります.そこで以下のようにして複数の結合を統合することにより,パラメータを用いた複雑な処理を記述することができます.

ここでLchainer.linksモジュールを示します.このようにして定義された処理は再利用が困難です.よりPythonらしい方法として,次のようなクラスに結合や処理を統合する手法があります.

より再利用しやすくするために,パラメータ管理,CPU/GPU間での移行,ロバストかつ柔軟な書込み/読み込みなどが必要になります.これらはChainerの中のChainというクラスによりサポートされています.結局はChainのサブクラスとして次のような定義をするだけで良いのです.

<注意!>
結合の順伝播計算方法を__call__オペレータによって定義することが良くありますが,このような結合や鎖状構造は呼び出し可能で,変数にある普通の関数のような振る舞いをします.

次に,簡単な結合によってどのように複雑な鎖状構造が構築されるのかを見てみましょう.l1l2のような結合はMyChainのchild linksと呼ばれます.Chainは自身を継承することに注意してください.つまり,MyChainオブジェクトをchild linkとして保持するようなより複雑な鎖状構造を定義することができるということです.

鎖状構造を定義するもう一つの方法はChainListクラスを用いる方法です.それは以下のように結合のリストのように振る舞います.

ChainListは任意の結合数を用いるときに便利です.もし結合数が上記のように固定されている場合,Chainクラスはベースクラスとすることが推奨されます.




オプティマイザ

良いパラメータ値を得るためには,Optimizerクラスでパラメータを最適化しなければなりません.Optimizerクラスでは結合に対して数値的な最適化アルゴリズムが実行されます.また,oprimizersモジュールには数多くのアルゴリズムが実装されています.ここでは確率的勾配降下法と呼ばれる,最も簡単なアルゴリズムを使います.

結合を最適化するためにsetup()というメソッドが用意されています.

最適化を行うには2つの方法があります.まず一つ目は手動で勾配を計算し,それからupdate()メソッドを引数なしで呼び出す方法です.この方法を用いる場合は前もって勾配の値をリセットすることを忘れないようにしてください!

もう一つの方法は損失関数をupdate()メソッドに引数として渡す,という方法です.この場合,updateメソッドによりzerograds()が自動的に呼び出されるため,勾配の値のリセットを手動で行う必要がありません.

荷重減衰や勾配の抜き取りのようなパラメータや勾配に関する操作は,オプティマイザにフック関数をセットすることにより行われます.フック関数は実際の更新の前にupdate()メソッドによって呼び出されます.例えば,前もって以下のようなコマンドを実行することで,荷重減衰の正則化を設定することができます.

もちろん任意のフック関数を記述することもできます.ただし,フック関数はオプティマイザを引数にとるような関数形式のもの,もしくは呼び出し可能なオブジェクトである必要があります.

シリアライザ

このページで紹介する主な特徴の最後がシリアライザです.シリアライザはオブジェクトを直列化,もしくは非直列化する簡単なインターフェイスです.LinkOptimizerはシリアライザにより直列化がサポートされています.

具体的なシリアライザはserializersモジュールの中で定義されており,NumPy NPZやHDF5形式をサポートしています.

例えば,serializers.save_npz関数を使うと結合オブジェクトを並列化し,NPZファイルとして保存することができます.

このようなコマンドを実行することで,モデルのパラメータをNPZ形式で‘my.model’というファイルに保存できます.保存されたモデルは以下のようにserializers.load_npz()関数を用いることで読み込むことができます.

<注意!>
これらの直列化コードではパラメータや永続的な値のみが直列化されることに注意してください.他の属性の場合は自動的に保存されません.Link.add_persistent()メソッドによって配列やスカラ値,ほかの直列化可能なオブジェクトを永続的な値として登録することができます.さらに登録された値はadd_persistentメソッドに渡された名前の属性でアクセス可能です.

また,オプティマイザの状態は上と同じ関数を用いることで保存することができます.

<注意!>
オプティマイザの直列化は繰り返し回数,MomentumSGDの勾配ベクトルなどを含む内部状態のみを保存します.対象となる結合のパラメータや永続的値は保存されません.保存した状態から最適化を再開するには対象の結合をオプティマイザを用いて明示的に保存する必要があります.

HDF5形式のサポートはh5pyパッケージがインストールされている場合のみ可能です.HDF5形式での直列化,非直列化はNPZ形式の場合とほとんど同じで,save_npz()とload_npz()をそれぞれsave_hdf5()とload_hdf5()に置き換えるだけです.

例:MNISTにおける多層パーセプトロン

ここまでの内容を理解すると,多層パーセプトロンを使った多クラス分類の問題が解けるようになります.ここではMNISTと呼ばれる手書き数字を使います.MNISTは機械学習においてはデファクトスタンダードとなっています(プログラミングでいうと”hello world”のようなもの).今回のMNISTを用いた例は公式レポジトリのexamples/mnistからダウンロードできます.

まず,MNISTを使うためにexamples/mnist/data.pyload_mnist_dataを用意しましょう.

MNISTのデータセットは28×28ピクセル(784ピクセル)の手書き数字のグレースケール画像70,000枚から成ります.また,これらの画像には手書き数字に対応する数字のラベルがセットになっています.まず最初にピクセルの値を0と1に二値化し,トレーニング用データ60,000枚とテスト用データ10,000枚に分けます.

次に多層パーセプトロンの構造を定義します.例として1層あたり100ユニットから成る3層の簡単なネットワークを使います.

この結合には活性化関数としてrelu()を使っています.最後のそうである結合’l3’の出力が10個の数字に対応することに注意して下さい.

損失の計算もしくは予測精度の評価ををこなうために,上述のMLP chainの上に分類のためのchainを定義します.

Classifierクラスは精度と損失を計算し,損失の値を返します.softmax_cross_entropy()は予測値と真値から求まる損失の値を計算し,accuracy()は予測精度を計算します.また,classifierのインスタンスには任意の予測器を設定することができます.

chainer.links.Classifierという似たクラスが定義されていることに注意してください.上述の例を使用する代わりに,事前に定義されているClassifier chainを使用します.

最後に,以下のような学習ループを記述します.

このサンプルコードではChainerに関連するコードは最後の3行だけです.最終行では損失関数としてmodelを渡していることに注意してください.

この3行は以下のように明示的な勾配計算の記述を用いて書き換えが可能です.

各繰り返し計算においてネットワークが順伝播計算により定義され,逆伝播計算を行い,廃棄されていることが分かるかと思います.また,この”Define-by-Run”手法を応用し,繰り返し計算の各回で異なる長さの入力に対して計算を行うことで,可変長入力を用いた再帰型ニューラルネットワークが簡潔に処理されることがお分かり頂けると思います.

最適化後または最適化中にテストセットでモデルの評価したいときは,次のようにして簡単に評価することができます.

examples/mnistディレクトリにあるサンプルコードはGPUサポートしていますが,GPUを使用する場合でもこのチュートリアルのコードと基本的にはほとんど同じです.GPUの使用方法に関しては後節で述べます.

 - Chainer, Ubuntu, 機械学習 , , , , , , , , ,