今回は、Novikoffの定理の証明を参考にしたパーセプトロンの学習アルゴリズムを説明します。
パーセプトロンの学習アルゴリズムの回と同様にテキストを参考にJavaでプログラムを書いてみました。同じく学習の部分だけですが公表します(プログラム2)。今回のプログラムとパーセプトロンの学習アルゴリズムの回に示したプログラム1との相違点は、まず、Rつまり、原点から最も遠いトレーニングデータとの距離を変数absx_maxにする部分と、もう一つ、学習中のパーセプトロンの超平面のパラメータの修正をマージンの絶対値を使って修正するところですので、そこを中心に説明します。
まず、Rを求める部分は、 変数absx_maxの宣言、初期化したすぐ後に、計算されるようになりました。下にその部分を抜き出しました。
// あるトレーニングデータの組の中で距離の絶対値が最大の値を記憶する変数(バイアスベクトル修正用)
double absx_max=0;
double absx; //入力のベクトルの距離の絶対値の計算用
for (i = 0; i < PARAMS.TRANING_DATA_NUMBER; i++) {
//i番目のトレーニング用入力データの原点からの絶対値を計算する
absx=0;
for (j = 0; j < PARAMS.INPUT_NODE_NUMBER; j++) {
absx=tdata.x[i][j]*tdata.x[i][j];
}
//大きい方をabsx_maxとする
absx_max=Math.max(absx,absx_max);
}
absx_max=Math.sqrt(absx_max); //最後に一番大きいものだけの平方根を取ると距離が得られる
あるトレーニングデータの入力ベクトルxiに対しての原点からの距離を計算され変数absxに入力されます。そのあと、数学用ライブラリクラスのメソッドMath.maxを利用して、これまで最も大きかった値を記憶している変数absx_maxと今回計算された変数absxの大きい方が、入力ベクトル大きさの最大値として変数absx_maxに入力されるということになります。
次に、yの絶対値を学習中のパーセプトロンの超平面パラメータの修正に使用するのでその部分をこれまでの入力ベクトルが内側か外側かという判定用の変数flagの宣言の後に変数absyを宣言しています。下に該当部分を抜き出しています。
// トレーニングデータを入力したときのパーセプトロンの出力
boolean flag;
// 入力ベクトルと計算中のパーセプトロンの超平面との距離の絶対値
double absy;
// 修正のための符号を付けた入力ベクトルと計算中のパーセプトロンの超平面との距離
double y;
ここでは同時に、後で変数absyの値に、どちらの向きに修正するかということの符号を付けた値を記憶する変数yも宣言しています。一方で、変数yに、どちらの方向に修正するかということも記憶することにしたため、以前あった変数sgnは変数yに吸収された形で消滅しています。
これら変数の宣言の後、学習結果にミスが無くなるまで学習を行うというための一番外側のdo ~while()構文がはじまります。さらに、その一つ内側の全ての学習用データ使うためのfor文があるところも以前のプログラム1と同様です。
ただし、その後、入力が学習中のパーセプトロンの内側か外側かを記憶する変数flagに値が入るところまでは同じですが、その下に距離(yの絶対値)を変数absyに入力する部分が挿入されています。(該当部を下に抜き出しています)
for (i = 0; i < PARAMS.TRANING_DATA_NUMBER; i++) {
// パーセプトロンの超平面の内か外かを判定
flag = checkInputData.check(splane, tdata.x[i]);
//距離yを得る(上記のflagの計算の時に計算されているものを持ってくる)
absy=Math.abs(checkInputData.getY(splane, tdata.x[i]));
ここでは、同じクラスの別のメソッドをgetYを使っています。実は、その上の行で使われているcheckというメソッド、その処理の中でこのgetYメソッドを判定に用いています。
ところで、このメソッドgetYで得られた値が負であることと、教師信号と学習中のパーセプトロンで超平面の内か外かということが一致しないこととは同一ではありません。そこで、メソッドgetYで得られた値は絶対値のみを使うことにして、符号は、学習が成功失敗かというチェックの後、失敗の処理をするときに下のような形で付けることにしています。
if(!flag)
// 重みベクトルやバイアスの修正をするときにはyの絶対値分値を増やす
y = absy;
else
// 本当は内側
// 修正のときyの絶対値分、値を減らす
y = -absy;
ここまでが主なプログラムの修正の部分で、あとは、次のように、学習中パーセプトロンの超平面のパラーメータの補正を変数yを利用した形にしてしまえば、終了となります。
// 重みベクトルの修正(for(j=0...のfor文で)
for (j = 0; j < PARAMS.INPUT_NODE_NUMBER; j++) {
// 重みベクトルを修正
splane.w[j] = splane.w[j] + y*tdata.x[i][j]* PARAMS.MARGIN_H;
}// end for(j...
// バイアスデータの修正:重みデータと同じく
splane.b = splane.b + y*PARAMS.MARGIN_H * absx_max
* absx_max;
では、以前と同じように、学習結果の一例を挙げてみましょう。ここで、学習率ηを表わす変数PARAMS.MARGIN_H=1としています。
トレーニングデータの超平面パラメータ
w0=0.47749051088214955
w1=0.4836372397113169
w2=0.8900003656198667
w3=0.359925073478623
w4=0.9232138025880994
b=-1.197057368073004
学習結果
学習後のパーセプトロンの重みベクトルとバイアス
w0=1.3009984557359189
学習後データ/トレーニングデータ=2.724658241547801
w1=1.215038025678252
学習後データ/トレーニングデータ=2.512292118786197
w2=2.3719841357120437
学習後データ/トレーニングデータ=2.6651496194161743
w3=0.9192196644433451
学習後データ/トレーニングデータ=2.5539195020764236
w4=2.403287382704314
学習後データ/トレーニングデータ=2.6031753164511167
b=-3.1585261511612073
学習後データ/トレーニングデータ=2.6385754228686062
収束は、以前のプログラムよりもかなり早くなったと思います。もっともこれはこのプログラムの学習率の大きさにもよりますけどね。
最後に、この学習用クラスを利用される場合に一つだけ注意点を書いておきます。この学習プログラムでは、学習中のパーセプトロンの超平面パラメータを初期化するときに全て0にするのは避けて下さい。入力ベクトルに何を入れてもcheckInputData.getYメソッドで0という結果が帰ってくるため、y=0即ちパラメータの修正値も0となってしまい、無限ループに陥ることになってしまいます。老婆心ながらご忠告申し上げる次第。私は、バイアスbの初期値を0.1としています。この値に特に根拠は無くy=0となる無限ループに陥らないような初期値なら何でも良いと思われます。ご参考まで。
プログラム2
/**
* パーセプトロンのトレーニング(Novikoffの定理証明型)の為のクラス
*
* @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 absx_max=0;
double absx; //入力のベクトルの距離の絶対値の計算用
for (i = 0; i < PARAMS.TRANING_DATA_NUMBER; i++) {
//i番目のトレーニング用入力データの原点からの絶対値を計算する
absx=0;
for (j = 0; j < PARAMS.INPUT_NODE_NUMBER; j++) {
absx=tdata.x[i][j]*tdata.x[i][j];
}
//大きい方をabsx_maxとする
absx_max=Math.max(absx,absx_max);
}
absx_max=Math.sqrt(absx_max); //最後に一番大きいものだけの平方根を取ると距離が得られる
// トレーニングデータを入力したときのパーセプトロンの出力
boolean flag;
// 入力ベクトルと計算中のパーセプトロンの超平面との距離の絶対値
double absy;
// 修正のための符号を付けた入力ベクトルと計算中のパーセプトロンの超平面との距離
double y;
// トレーニングデータによる学習
do {
// 変数の初期化
missed = 0;
for (i = 0; i < PARAMS.TRANING_DATA_NUMBER; i++) {
// パーセプトロンの超平面の内か外かを判定
flag = checkInputData.check(splane, tdata.x[i]);
//距離yを得る(上記のflagの計算の時に計算されているものを持ってくる)
absy=Math.abs(checkInputData.getY(splane, tdata.x[i]));
if (flag!=tdata.t[i]) {
// トレーニングデータと答えが違うとき
missed++; // 失敗の数の変数をインクリメント
// 超平面の重みベクトルととバイアスを変える
if(!flag)
// 重みベクトルやバイアスの修正をするときにはyの絶対値分値を増やす
y = absy;
else
// 本当は内側
// 修正のときyの絶対値分、値を減らす
y = -absy;
// 重みベクトルの修正(for(j=0...のfor文で)
for (j = 0; j < PARAMS.INPUT_NODE_NUMBER; j++) {
// 重みベクトルを修正
splane.w[j] = splane.w[j] + y*tdata.x[i][j]* PARAMS.MARGIN_H;
}// end for(j...
// バイアスデータの修正:重みデータと同じく
splane.b = splane.b + y*PARAMS.MARGIN_H * absx_max
* absx_max;
}// end if
}// end for(i...
System.out.println("missed"+missed);
} while (missed > 0);
return splane;
}
}