動物の神経細胞を「ニューロン ( Neuron )」といいます。それぞれの神経細胞には「樹状突起 ( Dendrite )」と呼ばれる複雑に枝分かれした部位があり、これが他の神経細胞から信号を受け取る働きを持ちます。樹状突起によって神経細胞は複雑なネットワーク「神経回路 ( Neural Network )」を形成しています。
神経回路をコンピュータなどによって人工的に表現しようとする試みは「ウォーレン・マカロック ( Warren Sturgis McCulloch )」と「ウォルター・ピッツ ( Walter J. Pitts )」による 1943 年の「形式ニューロン ( Formal Neuron )」というモデルが最初と言われています。このようなモデルのことを総称して「人工ニューラル・ネットワーク ( Artifical Neural Network ; ANN )」といいます。前章で紹介した「パーセプトロン Perceptron )」もニューラル・ネットワークの一つに位置づけられています。この章では、ニューラル・ネットワークの一種である「多層パーセプトロン ( Multilayer Perceptron ; MLP )」を中心に紹介したいと思います。
「一般化線形モデル ( Generalized Linear Model )」は、次のような式で表されるモデルでした。
g( μp ) = g( E[yp] ) | = | xpTw |
= | Σj{0→M}( xpjwj ) |
xp は p 番目のサンプル・データを意味し、w が求めようとする係数、μp = E[yp] が従属変数 yp の期待値を表すのでした。また g(・) は「連結関数 ( Link Function )」と呼ばれる関数です。パターン認識の分野では、この式は次のように表されるのが一般的です。
f(・) は連結関数の逆関数で「活性化関数 ( Activation Function )」と呼ばれます。
"線形" モデルとは係数 w の関して線形であるという意味で、xp に関しては線形である必要はありません。xp を非線形関数 φ( xp ) = ( φ0( xp ), φ1( xp ), ... φM( xp ) )T に置き換えて
μp = E[yp] | = | f( φ( xp )Tw ) |
= | f( Σj{0→M}( φj( xp )wj ) ) |
とすることもできます。例えば φj( xp ) = xpj、f(x) = x ( 恒等関数 ) とすれば M 次の多項式を表すことになります。
φj( xp ) も上式と同じ形の線形和を用いてみます。すなわち
とします。但し h(・) も活性化関数を表します。全体の式は
となります。出力される μp = E[yp] が K 個あるとして、k 番目の出力値 μkp を
としたとき、この関数は下図のようなネットワーク図で表現することができます。z0 から zM までのユニットを「隠れユニット ( Hidden Unit )」または「隠れ層 ( Hidden Layer )」といい、これが非線形関数 φ( xp ) を意味します。このことは、
zj = h( Σi{0→D}( xiwji ) )
yk = f( Σj{0→M}( zjwkj ) )
と二段階に分けて表すことで容易に理解できます。なお、xi は「入力層 ( Input Layer )」、yk は「出力層 ( Output Layer )」と呼ばれます。また、wj0 と wk0 はそれぞれ「バイアス・パラメータ ( Bias Parameter )」と呼ばれ、いわゆる定数項 ( x0 = 1, z0 = 1 ) の係数を意味します。隠れ層の定数項は入力層との結合はしていないことに注意して下さい。
上図からもわかるように、このモデルは「パーセプトロン ( Perceptron )」を複数個つないだ形によく似ていることから「多層パーセプトロン ( Multilayer Perceptron ; MLP )」と呼ばれます。しかし、パーセプトロンが「しきい値関数 ( Threshold Function )」という非連続な関数で線形和を二値に変換するのに対し、多層パーセプトロンでは「シグモイド関数 ( Sigmoid Function )」という連続な関数を使うという点が異なります。シグモイド関数を使う理由は微分可能である必要があるからで、これが後述する「誤差逆伝播 ( Error Backpropagation )」で重要となります。シグモイド関数として一般的には「ロジスティック・シグモイド関数 ( Logistic Sigmoid Function )」や「双曲線正接関数 ( Hyperbolic Tangent Function ; tanh )」などが用いられます。
ロジスティック・シグモイド関数 : σ(a) = 1 / ( 1 + e-a )
双曲線正接関数 : tanh(a) = ( ea - e-a ) / ( ea + e-a )
lim{a→-∞} σ(a) = 0, lim{a→+∞} σ(a) = 1 であり、dσ/da = e-a / ( 1 + e-a )2 > 0 より σ(a) は単調増加なので、σ(a) の値域は 0 と 1 の間となります。グラフは下図のようになり、しきい値関数を連続にしたような形をしていることから、微分不可のしきい値関数の代わりに用いられています。なお、σ(a) の逆関数 σ-1(a) = log( a / ( 1 - a ) ) は「ロジット関数 ( Logit Function )」と呼ばれ、「ロジスティック回帰 ( Logistic Regression Model )」の連結関数として利用されます。
tanh(a) = ( e2a - 1 ) / ( e2a + 1 ) と変形できることから lim{a→-∞} tanh(a) = -1 であり、tanh(a) = ( 1 - e-2a ) / ( 1 + e-2a ) でもあるので lim{a→+∞} tanh(a) = 1 です。導関数との間には
dtanh / da | = | [ ( ea + e-a )2 - ( ea - e-a )2 ] / ( ea + e-a )2 |
= | 1 - ( ea - e-a )2 / ( ea + e-a )2 | |
= | 1 - tanh(a)2 |
という関係式が成り立ち、実際に計算すると dtanh / da = 4 / ( ea + e-a )2 > 0 なので、tanh(a) は単調増加であり、値域は -1 と 1 の間となります。これもグラフはしきい値関数を連続にしたような形になります。
ロジスティック・シグモイド関数 | 双曲線正接関数 |
---|---|
シグモイド関数とは異なる関数で、厳密には a = 0 で微分可能ではありませんが、以下の「ReLU ( Rectified Linear Unit ) 関数」は隠れ層に用いるとよいとされています。
max( a, b ) は a, b のうちより大きい側を返す「マックス関数 ( Max Function )」です。導関数は次のように定義します。
dφ/dx | = | 0 ( x < 0 ) |
= | 1 ( x ≥ 0 ) |
活性化関数のサンプル・プログラムを以下に示します。
namespace NeuralNetwork { /** ActivationFunction : 活性化関数 **/ struct ActivationFunction { /// f(x) virtual double f( double x ) const = 0; /// df/dx virtual double df( double x ) const = 0; }; /** Identity : 恒等関数 **/ struct Identity : public ActivationFunction { /// f(x) = x virtual double f( double x ) const { return( x ); } /// df/dx = 1 virtual double df( double x ) const { return( 1 ); } }; /** LogisticSigmoid : ロジスティック・シグモイド活性化関数 **/ struct LogisticSigmoid : public ActivationFunction { /// f(x) = 1 / ( 1 + e^-x ) virtual double f( double x ) const { return( 1.0 / ( 1 + std::exp( -x ) ) ); } /// df/dx = e^x / ( 1 + e^x )^2 virtual double df( double x ) const { return( std::exp( x ) / std::pow( 1 + std::exp( x ), 2 ) ); } }; /** Tanh : 双曲線正接関数 (tanh) **/ struct Tanh : public ActivationFunction { /// f(x) = tanh( x ) virtual double f( double x ) const { return( std::tanh( x ) ); } /// df/dx = 1 - tanh(x)^2 virtual double df( double x ) const { return( 1.0 - std::pow( std::tanh( x ), 2 ) ); } }; /** ReLU : Rectified Linear Unit **/ struct ReLU : public ActivationFunction { /// f(x) = max( 0, x ) virtual double f( double x ) const { return( ( x < 0 ) ? 0 : x ); } /// df/dx = 0 ( x < 0 ) ; 1 ( x >= 0 ) virtual double df( double x ) const { return( ( x < 0 ) ? 0 : 1 ); } }; }
ActivationFunction は抽象クラスで、純粋仮想関数 f と df を持ち、前者は関数の、後者は導関数の値をそれぞれ表します。こららを実装する形で 4 つのクラス、Identity ( 恒等関数 )、LogisticSigmoid ( ロジスティック・シグモイド関数 )、Tanh ( 双曲線正接関数 )、ReLU ( ReLU 関数 ) を定義しています。
上図において、隠れ層は一つだけとしていましたが、これは複数の層に拡張することが簡単にできます。また、各層を順番につなぐのではなく、層を飛び越えて結合させたり、一部の結合を省いたりすることも可能です。但し、そこには「フィード-フォワード ( Feed-forward )」構造でなければならないという制限があります。つまり、線形和を計算する流れが閉じた形にならない必要があります。多層パーセプトロンを拡張しても処理の流れに変化はなく、入力層からの入力値を使って隠れ層で線形和を計算し非線形関数で変換した結果を使って、また次の隠れ層で同じ処理を繰り返して、最終的に出力層に結果が出力されます。この過程は「順伝播 ( Forward Propagation )」と呼ばれます。下図は一般化した多層パーセプトロンを示したものになります。
多層パーセプトロンそのものは一般化線形モデルと同じ形式でいわゆる回帰式なので、与えられた入力値に対して、何らかの予測値を返すことになります。2 クラスの分類問題に適用したい場合は出力層を一つとして活性化関数にロジスティック・シグモイド関数や双曲線正接関数を利用することで実現できます。同様の活性化関数で K 個の出力層にすれば、K 個の異なる 2 クラス分類問題を解くことになります。
「名義ロジスティック回帰 ( Nomial Logistic Regression )」では連結関数は以下のような式で表されていました。
活性化関数として表すと
となります。ここで、Σk( πpk ) = 1 であることを利用すれば
Σj( πpj / πp1 ) | = | 1 / πp1 |
= | Σj( exp( xpTwj ) ) |
より πp1 = 1 / Σj( exp( xpTwj ) ) となるので、
と表すことができます。この関数は「ソフトマックス関数 ( Softmax Function )」として知られています。K 個のクラスに分類する問題に対してはソフトマックス関数を利用することができます。
係数 w がすでに求められていれば、順伝播によって出力値を求めることは簡単にできます。しかし、問題はどのように学習をするかという点です。入力値 xn に対して出力値 yn が得られ、正しい値として教師信号 tn があり、それと比較することで係数を変化させたいのですが、「Widrow-Hoff の学習規則 ( Widrow-Hoff Learning Rule )」に従えばそれは出力値と教師信号の誤差の二乗和を評価することで実現していたのでした。多層パーセプトロンでは隠れ層での係数を求めるために何らかの形で同様の評価を行う必要があります。これを実現するための手法として「誤差逆伝播 ( Error Backpropagation )」が考案されました。
多層パーセプトロンのある層に対して、その j 番目のユニットを U(2)j、一つ前の層の i 番目のユニットを U(1)i、一つ後の層の k 番目のユニットを U(3)k で表します。U(1)i から U(2)j への結合の重み係数を wji、U(2)j から U(3)k への結合の重み係数を wkj とし、U(1)i からの出力値を zi、U(2)j への入力値を aj とすると、
が成り立ち、U(2)j からの出力時には aj は非線形活性化関数 h(・) によって変換され
となります。入力値 xi から出力値 yk が得られるまでのこの処理の流れは「順伝播 ( Forward Propagation )」を表しています。
「Widrow-Hoff の学習規則 ( Widrow-Hoff Learning Rule )」では出力値と教師信号の誤差の二乗和 J が最小になるような係数を得るため微分値 ∂J / ∂wji = 0 になるような wji を求めました。今回もこの微分値を利用することを検討します。まず、J が出力値 zj すなわち aj の関数であり、aj を通してのみ wji に依存していることに着目して、偏微分の連鎖法則から
と変形します。
であり、
と定義すれば
と表すことができます。問題は δj をどのように計算するかですが、出力層に対しては、例えば活性化関数が恒等関数ならば、k 番目の出力ユニットに対する教師信号を tk として
なので
となります。ここで一つ前の隠れ層に対する δj を考えると、それは ak についての関数であり、ak は zj の線形和であることから、再び偏微分の連鎖法則を使って
と表すことができます。ここで、zj は出力層の各 ak の線形和の成分となっていることから、連結する全てのユニットについて和をとる必要があることに注意して下さい (*2-1)。
∂J / ∂ak = δk
∂ak / ∂zj = ( ∂ / ∂zj )Σk( wkjzj ) = wkj
∂zj / ∂aj = h'( aj )
より
となり、出力層の一つ前の層についても δk を利用して δj を計算することができます。以下、一つ後の層の δk を利用して δj を求める操作を繰り返していけば、すべてのユニットに対する δj が得られます。この操作は、出力層から逆向きに値が求められることから「逆伝播 ( Backpropagation )」と呼ばれ、求める δj は「誤差 ( Error )」と呼ばれるので、併せて「誤差逆伝播 ( Error Backpropagation )」といいます。全てのユニットの誤差 δj が得られたら、それを使って ∂J / ∂wji を計算することができます。
多層パーセプトロンを構築するためのサンプル・プログラムを以下に示します。まずは、ユニットを定義するためのクラス Unit です。
namespace NeuralNetwork { /** Unit : 多層パーセプトロンのユニット **/ struct Unit { // 前のノードとのリンク struct ForLink { // 入力値(ポインタ) double* z_i; // 重みベクトル double w_ji; ForLink( double* z, double w ) : z_i( z ), w_ji( w ) {} }; // 後ろのノードとのリンク struct BackLink { // Δk(ポインタ) double* delta_k; // 重みベクトル(ポインタ) double* w_kj; BackLink( double* d, double* w ) : delta_k( d ), w_kj( w ) {} }; std::vector< ForLink > forLink; // 前のノードとのリンク std::vector< BackLink > backLink; // 後ろのノードとのリンク // aj = Σwji・zi double a_j; // zj = f(aj) double z_j; // Δj = f'(aj)Σwkj・Δk double delta_j; // 活性化関数 const ActivationFunction* f; Unit() : a_j( double() ), z_j( double() ), delta_j( double() ), f( 0 ) {} }; }
順伝播を行うときには、前の層との重みベクトルと入力値が必要になります。その値を Unit 内のクラス ForLink で定義しています。実体を持つのは重みベクトルの要素 w_ji のみで、入力値 z_i はポインタを保持していることに注意して下さい。実体は前の層が保持します。また、逆伝播時には後ろの層との重みベクトルと誤差が必要になるので、同じく内部クラスの BackLink で定義します。ここでも後ろの層が保持しているデータのポインタとして保持することに注意して下さい。
前後の層のユニットは複数あるので、ForLink と BackLink の配列としてそれぞれ forLink, backLink をメンバ変数として持ちます。その他に、線形和を保持する a_j、その活性化関数による変換結果 z_j、誤差 delta_j がメンバ変数として定義されています。z_j は後ろの層のユニットに z_i として、delta_j は前の層に delta_k としてポインタでリンクされます。また、ForLink が保持している w_ji は前の層に w_kj としてポインタでリンクされます。
メンバ変数 f は活性化関数へのポインタで、前に紹介した ActivationFunction からの派生クラスを利用することを想定しています。活性化関数は、ユニットごとに変更することが可能です。
次に順伝播と逆伝播を行うための関数を示します。
namespace NeuralNetwork { // 層を表す型の定義 typedef std::vector< Unit > Layer; /* ForwardPropagation : 順伝播を行う x : 入力データ mlp : 対象の多層パーセプトロン */ void ForwardPropagation( const vector< double >& x, vector< Layer >* mlp ) { if ( mlp->empty() ) return; Layer& in = mlp->front(); // 入力層 // 入力層に入力値をコピーする ErrLib::CheckBivariable( x.begin(), x.end(), in.begin(), in.end() ); for ( Layer::size_type i = 0 ; i < in.size() ; ++i ) in[i].z_j = x[i]; // z_j = f( Σw_ji・z_i ) for ( vector< Layer >::iterator l = mlp->begin() + 1 ; l != mlp->end() ; ++l ) { for ( Layer::iterator u = l->begin() ; u != l->end() ; ++u ) { if ( ( u->forLink ).empty() ) continue; u->a_j = double(); for ( vector< Unit::ForLink >::const_iterator f = ( u->forLink ).begin() ; f != ( u->forLink ).end() ; ++f ) { u->a_j += f->w_ji * *( f->z_i ); } if ( u->f != 0 ) u->z_j = ( u->f->f )( u->a_j ); else u->z_j = u->a_j; } } } /* BackPropagation : 逆伝播を行う t : 教師信号 mlp : 対象の多層パーセプトロン rho : 最急降下法での学習率 threshold : 収束判定のしきい値 */ bool BackPropagation( const vector< double >& t, vector< Layer >* mlp, double rho, double threshold ) { if ( mlp->empty() ) return( true ); Layer& out = mlp->back(); // 出力層 // 出力層のΔjを求める ErrLib::CheckBivariable( t.begin(), t.end(), out.begin(), out.end() ); for ( Layer::size_type i = 0 ; i < out.size() ; ++i ) { out[i].delta_j = out[i].z_j - t[i]; } // Δj = h'(a_j)Σw_kj・Δk for ( vector< Layer >::reverse_iterator l = mlp->rbegin() + 1 ; l + 1 < mlp->rend() ; ++l ) { for ( Layer::iterator u = l->begin() ; u != l->end() ; ++u ) { if ( ( u->backLink ).empty() ) continue; u->delta_j = double(); for ( vector< Unit::BackLink >::const_iterator b = ( u->backLink ).begin() ; b != ( u->backLink ).end() ; ++b ) u->delta_j += *( b->w_kj ) * *( b->delta_k ); if ( u->f != 0 ) u->delta_j *= ( u->f->df )( u->a_j ); } } // ∂En/∂w_ji = z_i・Δj = z_i・h'(a_j)Σw_kj・Δk bool ans = true; for ( vector< Layer >::reverse_iterator l = mlp->rbegin() ; l + 1 < mlp->rend() ; ++l ) { for ( Layer::iterator u = l->begin() ; u != l->end() ; ++u ) { for ( vector< Unit::ForLink >::iterator f = ( u->forLink ).begin() ; f != ( u->forLink ).end() ; ++f ) { double d = u->delta_j * *( f->z_i ); if ( std::abs( d ) > threshold ) ans = false; f->w_ji -= rho * d; } } } return( ans ); } }
層を表す型 Layer はユニット Unit の配列です。さらに Layer の配列として多層パーセプトロンが構築されます。前後の層とのリンクはメンバ変数 forLink と backLink への定義の仕方によって自由に設定できるので、結合を省略したり、層を飛び越えて結合させることも可能です。
順伝播は関数 ForwardPropagation が行います。最初に入力層 ( mlp の最初の要素 ) に入力値 x をコピーし、その次の層から順番に順伝播処理を行います。なお、Unit のメンバ変数で活性化関数へのポインタを表す f は、もし NULL 値なら恒等関数と同じ扱いをしています。
逆伝播を関数 BackPropagation が受け持ちます。最初に出力層 ( mlp の最後の要素 ) の delta_j の値を z_j と教師信号 t との差分として求め、次に誤差 delta_j の値を後側から順に求めていきます。最後に微分値 ∂J / ∂wji を delta_j と z_i の積から求め、その値が全ユニットにおいてしきい値 threshold 以下になったら返り値として true を返します。
なお、ForwardPropagation と BackPropagation の中で呼び出している関数 ErrLib::CheckBivariable は、二つの配列が適正でかつ両者のサイズが同じであるかをチェックするためのもので、この中では実装していません。
多層パーセプトロンは「万能近似器 ( Universal Approximation )」と呼ばれるように、関数を任意の精度で一様に近似する能力を持っているそうです。文献に倣い、4 つの関数 f(x) について近似を行った結果を以下に示します。入力層と隠れ層のユニット数は定数項を含めてそれぞれ 2 つと 4 つ、出力層のユニット数は一つで、隠れ層の活性化関数は双曲線正接関数 ( tanh )、出力層の活性化関数は恒等関数としています。定義域は [ -1, 1 ] で、等間隔に 50 点サンプリングした点の x が入力値、f(x) が教師信号となります。なお、H(x) はステップ関数で、x < 0 で f(x) = 0、x ≥ 0 で f(x) = 1 となります。
f(x) = x2 | f(x) = sin πx |
---|---|
f(x) = |x| | f(x) = H(x) |
グラフの黒点がサンプリング・データ、赤い実線が多層パーセプトロンによる近似データです。点線はそれぞれが隠れ層の出力データで、これらが重み付けされた和が赤い実線になります。
逆伝播 BackPropagation の引数について、学習率 rho は 0.1、収束判定のためのしきい値 threshold は 0.01 としました。50 個のサンプリング・データを学習させる処理を最大 10 万回繰り返しましたが、最大回数前に収束したのは f(x) = x2 のみでした。f(x) = sin πx と f(x) = |x| は文献に近いかたちで近似できたものの、f(x) = x2 と f(x) = H(x) はいまいち精度がよくありません。
逆伝播のサンプル・プログラム BackPropagation の中で、誤差 δj の値を出力値と教師信号の差分で計算しています。活性化関数が恒等関数の場合、「Widrow-Hoff の学習規則 ( Widrow-Hoff Learning Rule )」で示したように二乗和誤差を微分することで得られた結果ですが、これがロジスティック・シグモイド関数やソフトマックス関数を活性化関数に選んでも成り立つのかという問題が生じます。結論から言うとこれは成り立ちます。
まずは一つの従属変数 t について、平均値 μp = y( xp, w )、標準偏差 σ を持った正規分布に従うと仮定します。すなわち
となります。但し、y( xp, w ) は多層パーセプトロンの出力です。N 個の互いに独立な独立変数 X = { x1, x2, ... xN } と、それぞれに対応する従属変数 t = { t1, t2, ... tN } が与えられたとき、その尤度関数 L( w | X, t ) は
L( w | X, t ) | = | Πp{1→N}( N( y( xp, w ), σ2 ) ) |
= | [ 1 / ( 2πσ2 )N/2 ]exp( Σp{1→N}( -[ y( xp, w ) - tp ]2 ) / 2σ2 ) |
となって、これを最大化すればよいことになります。負の対数尤度をとれば、
となって、定数部分を無視すれば出力値と教師信号の二乗和誤差
を最小化する w を求めることになります。よって、出力層の活性化関数が恒等関数の場合、y( xp, w ) = akp より
となります。従属変数を多変数 t = ( t1, t2, ... tK ) にした場合、それらが互いに無相関で標準偏差も共通な値 σ であるとすれば
となります。但し、E は単位行列を表します。従属変数 T = { t1, t2, ... tN } として、尤度関数 L( w | X, T ) は
L( w | X, T ) | = | Πp{1→N}( [ 1 / ( 2π )K/2|σ2E|1/2 ]exp( -[ y( xp, w ) - tp ]t(σ2E)-1[ y( xp, w ) - tp ] / 2 ) |
= | [ 1 / ( 2πσ2 )NK/2 ]exp( -Σp{1→N}( || y( xp, w ) - tp ||2 ) / 2σ2 ) |
となります。但し、| σ2E | = σ2K, ( σ2E )-1 = E / σ2 であることを利用しています。負の対数尤度は
となって、やはり出力値と教師信号の誤差の二乗和を最小にすればよいことになります。
「ロジスティック回帰 ( Logistic Regression Model )」にて、二項分布を一般化した場合の対数尤度関数を以下のような式で表していました。
特に np = 1, tp = { 1, 0 } ならば、負の対数尤度は
-l( y | t ) | = | -Σp{1→N}( tpln ( yp / ( 1 - yp ) ) + ln ( 1 - yp ) ) |
= | -Σp{1→N}( tpln yp + ( 1 - tp )ln ( 1 - yp ) ) |
となります。この式を「交差エントロピー誤差関数 ( Cross-entropy Error Function )」といいます。yp による偏微分値は
であり、活性化関数ロジスティック・シグモイド関数により yp = 1 / ( 1 + e-ap ) が成り立つとすれば、
-∂l / ∂ap | = | -( ∂l / ∂yp )( ∂yp / ∂ap ) |
= | { -tp( 1 + e-ap ) + ( 1 - tp ) / [ 1 - 1 / ( 1 + e-ap ) ] }[ e-ap / ( 1 + e-ap )2 ] | |
= | [ -tp( 1 + e-ap ) + ( 1 - tp )( 1 + e-ap ) / e-ap ][ e-ap / ( 1 + e-ap )2 ] | |
= | -tpe-ap / ( 1 + e-ap ) + ( 1 - tp ) / ( 1 + e-ap ) | |
= | 1 / ( 1 + e-ap ) - tp( e-ap + 1 ) / ( 1 + e-ap ) | |
= | yp - tp |
という結果が得られます。
「名義ロジスティック回帰 ( Nomial Logistic Regression )」の場合、対数尤度の wkj による偏微分値は
という簡単な式で表すことができました。ロジスティック回帰の場合と同じように、np = 1, tpk = { 1, 0 } とすれば、
-∂l / ∂apk | = | -( ∂l / ∂wkj )( ∂wkj / ∂apk ) |
= | -( ∂l / ∂wkj )[ 1 / ( ∂apk / ∂wkj ) ] | |
= | -( tpk - ypk )xpj( 1 / xpj ) | |
= | ypk - tpk |
で、やはり出力値と教師信号との差分で表されます (補足 1)。
最後に、隠れ層を一つ持った最も単純な多層パーセプトロンを構築するための関数を紹介しておきます。
namespace NeuralNetwork { /* InitLayer : 層の初期化を行う layer : 層へのポインタ hasConstTerm : 定数項を持つなら true inCnt : 入力層(一つ前の層)のユニット数 func : 活性化関数へのポインタ */ void InitLayer( Layer* layer, bool hasConstTerm, size_t inCnt, const ActivationFunction* func ) { // 最初のユニットは定数項 if ( hasConstTerm ) { ( layer->front() ).a_j = 1; ( layer->front() ).z_j = 1; } size_t start = ( hasConstTerm ) ? 1 : 0; for ( Layer::size_type i = start ; i < layer->size() ; ++i ) { (*layer)[i].forLink.assign( inCnt, Unit::ForLink( 0, static_cast< double >( rand() ) / RAND_MAX ) ); (*layer)[i].f = func; } } /* CreateMLP : 隠れ層を 1 層持った多層パーセプトロンを構築する mlp : 構築する多層パーセプトロンへのポインタ inCnt : 入力層のユニット数(定数項を含む) midCnt : 隠れ層のユニット数(定数項を含む) outCnt : 出力層のユニット数 hiddenFunc : 隠れ層の活性化関数へのポインタ outFunc : 出力層の活性化関数へのポインタ */ void CreateMLP( vector< Layer >* mlp, size_t inCnt, size_t midCnt, size_t outCnt, const ActivationFunction* hiddenFunc, const ActivationFunction* outFunc ) { mlp->clear(); // 入力層の構築 mlp->push_back( Layer( inCnt ) ); InitLayer( &( mlp->back() ), true, 0, 0 ); // 中間層(隠れユニット)の構築 mlp->push_back( Layer( midCnt ) ); InitLayer( &( mlp->back() ), true, inCnt, hiddenFunc ); // 出力層の構築 mlp->push_back( Layer( outCnt ) ); InitLayer( &( mlp->back() ), false, midCnt, outFunc ); // 各ユニットをつなぐ Layer& in = mlp->front(); Layer& mid = (*mlp)[1]; Layer& out = mlp->back(); // 入力層・中間層の間の重みベクトル // 中間層の定数項は入力層へのリンクを持たないことに注意 for ( Layer::size_type i = 0 ; i < in.size() ; ++i ) { for ( Layer::size_type j = 1 ; j < mid.size() ; ++j ) { in[i].backLink.push_back( Unit::BackLink( 0, &( ( mid[j].forLink )[i].w_ji ) ) ); } } // 入力層から中間層への入力値 // 中間層の定数項は入力層へのリンクを持たないことに注意 for ( Layer::size_type j = 1 ; j < mid.size() ; ++j ) { for ( Layer::size_type i = 0 ; i < in.size() ; ++i ) { ( mid[j].forLink )[i].z_i = &( in[i].z_j ); } } // 中間層・出力層の間の重みベクトル // 出力層から中間層へのΔ for ( Layer::size_type j = 0 ; j < mid.size() ; ++j ) { for ( Layer::size_type k = 0 ; k < out.size() ; ++k ) { mid[j].backLink.push_back( Unit::BackLink( &( out[k].delta_j ), &( ( out[k].forLink )[j].w_ji ) ) ); } } // 中間層から出力層への入力値 for ( Layer::size_type k = 0 ; k < out.size() ; ++k ) { for ( Layer::size_type j = 0 ; j < mid.size() ; ++j ) { ( out[k].forLink )[j].z_i = &( mid[j].z_j ); } } } }
*2-1) 多変数関数の偏微分における公式については、証明を含め「(18) 一般化線形モデル ( Generalized Linear Model )」の「補足 2) 多変量のテイラー - マクローリン展開 ( Multivariate Taylor-Maclaurin Expansion )」で紹介しています。
「一般化線形モデル ( Generalized Linear Model )」において、平均値 E[t] ≡ y が 指数型分布族 ( Exponential Family of Distributions )
に従い、連結関数 g(・) によって g( yp ) = xpTw = ap と表されるとき、尤度関数の wj による偏微分値 ∂l / ∂wj は
で与えられることを証明しました。従って、もし
となるような連結関数を選べば、
-∂l / ∂wj = Σp{1→N}( ( yp - tp )xpj )
-∂l / ∂ap = yp - tp
と単純化することができます。このような連結関数は「正準連結関数 ( Canonical Link Function )」と呼ばれます。
例えば指数型分布族が正規分布 N( y, σ2 ) ならば、V[tp] = σ2 なので、g'(yp) = 1 / σ2 より g(yp) = yp / σ2 が正準連結関数となります。
「ロジスティック回帰 ( Logistic Regression Model )」の場合、二項分布の分散は、各試行回数 np = 1 ならば ( つまり二項分布はベルヌーイ分布 yptp( 1 - yp )1-tp を意味します ) yp( 1 - yp ) なので、g'(yp) = 1 / yp( 1 - yp ) となるような g(yp) が正準連結関数となります。ロジスティック・モデルに使うロジット関数 ln( y( 1 - y ) ) はこの導関数を持った連結関数ですが、残念ながらプロビット・モデルに使う N( 0, 1 ) の逆累積分布関数や、Complementary Log-log 関数はこの条件を満たしていません。
「名義ロジスティック回帰 ( Nomial Logistic Regression )」では、∂l / ∂wj の値がすでに上式のように単純化されていることを証明していました。各試行回数 npk = 1 ならば、多項分布の分散は ypk( 1 - ypk )、共分散は -ypkypj で表されます。活性化関数としてソフトマックス関数
を選択すると、
∂ypk / ∂apk | = | [ exp( apk )Σj( exp( apj ) ) - exp( apk )2 ] / [ Σj( exp( apj ) ) ]2 |
= | ypk - ypk2 = ypk( 1 - ypk ) | |
∂ypk / ∂apj | = | -exp( apk )exp( apj ) / [ Σj( exp( apj ) ) ]2 |
= | -ypkypj [ j ≠ k ] |
なので、逆関数の微分 ∂apj / ∂ypk はその逆数となり、分散や共分散と相殺されることになります。
前に戻る | タイトルに戻る |