2011年4月10日日曜日

パーセプトロンの学習アルゴリズム

 前回からだいぶ時間が過ぎてしまい申し訳ないです。ようやく、プログラムを書き自分なりに学習アルゴリズムを改めて理解したと言うことで、ようやく、パーセプトロンの学習についての記事を書かせて頂くことができます。

テキストを参考にJavaでプログラムを書いてみました。学習の部分だけですが公表します(プログラム1)。そういうわけ今回はプログラムの説明が主な部分となり、Javaプログラミングの専門用語も出てきているので、やや難しいかもしれないです。その点については予めお詫び申し上げます。

それでは、全体をまず簡単に説明しておくと、初期化した(つまり全て0にした)重みベクトルとバイアス値(構造体splaneのメンバの配列xと変数b)をトレーニングデータ(構造体tdata)を使って少しずつ変化をさせて、トレーニングデータを作った時の超平面ベクトルとバイアス値と同じにさせます。

プログラムの全体的な流れを見ると、一番外側のdo ~while()構文のループで学習結果にミスが無くなるまで学習を行うと言うことを表わし、その一つ内側のfor文、

for(i=0;i<PARAMS.TRANING_DATA_NUMBER;i++){

(註)クラスPARAMSは定数を記憶させるための構造体クラスとして作成しており、そのメンバのTRAINING_DATA_NUMBRはトレーニングデータの数を定義している定数です。

が全ての学習データを使って学習するループに相当します。


次にどういうときに変化させるかは、トレーニングデータについている教師信号(構造体tdataのメンバ、配列t)とトレーニングデータを学習中のパーセプトロンの超平面関数から求まる超平面の内側か外側かという信号が一致しないときに変化させます。プログラムでは、


flag=checkInputData.check(splane,tdata.x[i]);
if (flag!=tdata.t[i]) {
(註)クラスchckeInputDataは基本的に超平面の関数の計算を行うクラスで、メソッドcheckは入力(配列x) が超平面のパラメータ(構造体splane)を引数として、入力がその超平面の内か外かをtureかfalseで返すサブルーチンです。

の部分ががそれに相当しています。

一致しない場合、次のような処理を行います。

まず、トレーニングデータの入力が本当は外側であると言うことが正解の場合は重みベクトルとバイアスの値を小さくします。逆の場合は大きくします。その判定は、

//修正値の符号を判定
if(!flag)  //flag=false(内側)と判定したけれど本当は外側
//重みベクトルやバイアスの修正をするときには値を増やす
sgn=1.0;
else  //本当は内側
//修正のとき値を減らす
sgn=-1.0;


の部分で行っています。

残りの部分は、実際に修正を行っていますが、重みベクトルは、入力に比例した形で、また、バイアスは入力の最大値の二乗に比例した形で修正をしていきます。プログラム1では、

//重みベクトルの修正(for(j=0...for文で)
absx_max = 0.0;// 入力の最大値を求めるための変数の初期化
for(j=0;j<PARAMS.INPUT_NODE_NUMBER;j++) {
//重みベクトルを修正
splane.w[j]=splane.w[j]+sgn*PARAMS.MARGIN_H*tdata.x[i][j];
//ついでにバイアス修正用に絶対値の最大のものを探す
absx_max=Math.max(absx_max, Math.abs(tdata.x[i][j]));
}//end for(...
// バイアスデータの修正:重みデータと同じく
splane.b=splane.b+sgn*PARAMS.MARGIN_H*absx_max*absx_max;


のような形ですね。ここで、PARAMS.MARGN_Hはどれくらいの割合で値を修正するかのパラメーターです。大きくすると大きく変わります。

以上、プログラムを説明する形で、パーセプトロンの学習アルゴリズムを説明してきました。

学習結果の一例を最後に付け加えておきます。

 トレーニングデータの超平面パラメータ
 w0=0.24367554436321281
 w1=0.5915102902018888
 w2=0.8350026087000493
 w3=0.010795002661586062
 w4=0.993060164842189
 b=-1.9140424373031704

学習結果
 学習後のパーセプトロンの重みベクトルとバイアス
 w0=1.2074118471297801
   学習後データ/トレーニングデータ=4.9549980499071395
 w1=3.216837052250113
   学習後データ/トレーニングデータ=5.438345039022351
 w2=4.441724716869855
   学習後データ/トレーニングデータ=5.3194141797770325
 w3=0.05523700915026458
   学習後データ/トレーニングデータ=5.116905561017143
 w4=5.243881601727755
   学習後データ/トレーニングデータ=5.280527592767836
 b=-10.132584835061216
   学習後データ/トレーニングデータ=5.293814096064521

    図2 学習結果の例

入力ノードが5で、トレーニングデータの数は1000の時の一例です。

全く同じにはなりませんが、学習後のデータとトレーニングデータの重みベクトルとバイアス値の比がほぼ同じになることが分かります。これは超平面の方程式が、

 wx+b=0

を解くことで得られるため単純に、
  
  a(wx+b)=0  (aは任意の定数)

としても、基本的に同じ超平面の方程式だと言うことと同等です。

もう一つ、重みベクトルの桁数が極端に小さいか極端に大きいとき、つまり、正規化したときの絶対値がほかのと比べて桁違いに0に小さいか1に近いときにはこの比率が一定になりにくいというのもあります。そこの入力ノードだけ重みの影響がなさ過ぎるか、ありすぎるということで、結果に占める他の重みベクトルとの比率が異なってくるためだと思われます。

例えば、下の図3のような例です。
トレーニングデータの超平面パラメータ
 w0=0.7613366224093473
 w1=0.010079129478856919
 w2=0.10925859566898377
 w3=0.5491526223821906
 w4=0.4920507254118396
 b=-0.6605344241740716

学習結果
 学習後のパーセプトロンの重みベクトルとバイアス
 w0=0.4299458055244819 
   学習後データ/トレーニングデータ=0.5647249756144179
 w1=0.010003528032325834
   学習後データ/トレーニングデータ=0.9924992087174122
 w2=0.057596521028957
   学習後データ/トレーニングデータ=0.5271578009610776
 w3=0.3101608910393426
   学習後データ/トレーニングデータ=0.5647990711468945
 w4=0.27440011809371634
   学習後データ/トレーニングデータ=0.557666321625778
 b=-0.3736947791698093
   学習後データ/トレーニングデータ=0.5657461072329047

図3 必ずしも比率が一致しない例

プログラム1 パーセプトロンの学習プログラム(Java)
/**
 * パーセプトロンのトレーニングの為のクラス
 * 
 * @author hondaetsurou
 */

/** 
 * メンバ
 *         フィールド splane メソッド training
 * 
 *       フィールド splane パーセプトロンが学習した後の超平面のパラメータ構造体 super_plane型の構造体変数
 * 
 *       メソッドtraining super_plane型 返値 splane パーセプトロンのトレーニングを行う
 * 
 */

public class trainingPerceptron {
// 重みベクトルとバイアスを持つ構造体クラスsuper_planeをパーセプトロンの学習にも使う
static super_plane splane = new super_plane();

/*  学習  */
static super_plane training(training_data tdata) {

// パーセプトロンの出す答が間違っているときのトレーニングデータの数
int missed = 0;
// ループカウンタ
int i, j;
// 修正データの符号
double sgn = 0.0;
//  あるトレーニングデータの組の中での絶対値が最大の値を記憶する変数(バイアスベクトル修正用)
double absx_max;
// トレーニングデータを入力したときのパーセプトロンの出力
boolean flag;


// トレーニングデータによる学習
do {

// 変数の初期化
missed = 0;

for (i = 0; i < PARAMS.TRANING_DATA_NUMBER; i++) {
// パーセプトロンの超平面の内か外かを判定
flag = checkInputData.check(splane, tdata.x[i]);
if (flag != tdata.t[i]) {
// トレーニングデータと答えが違うとき
missed++; // 失敗の数の変数をインクリメント
// 超平面の重みベクトルととバイアスを変える

// 修正値の符号を判定
if (!flag) // flag=false(内側)と判定したけれど本当は外側
// 重みベクトルやバイアスの修正をするときには値を増やす
sgn = 1.0;
else
// 本当は内側
// 修正のとき値を減らす
sgn = -1.0;

// 重みベクトルの修正(for(j=0...のfor文で)
absx_max = 0.0;// 入力の最大値を求めるための変数の初期化
for (j = 0; j < PARAMS.INPUT_NODE_NUMBER; j++) {
// 重みベクトルを修正
splane.w[j] = splane.w[j] + sgn * PARAMS.MARGIN_H
* tdata.x[i][j];
// ついでにバイアス修正用に絶対値の最大のものを探す
absx_max = Math.max(absx_max, Math.abs(tdata.x[i][j]));
}// end for(j...

//  バイアスデータの修正:重みデータと同じく
splane.b = splane.b + sgn * PARAMS.MARGIN_H * absx_max
* absx_max;

}// end if
}// end for(i...
// System.out.println("missed"+missed);
} while (missed > 0);
return splane;
}

}


0 件のコメント:

コメントを投稿