2011年4月20日水曜日

番外編 ベクトルとか内積とかユークリッド距離とか正規化とか


単純パーセプトロンがn個の入力を持つとき、入力と重みベクトルにはn個のパラメータがあり、その内積を云々とか平気で書いていますけど、もう少しだけ詳しく説明しましょう。
まず、ベクトル。これは、長さと方向を持つという説明をしました。n個のパラメータを持つということは言い換えればn個の座標軸を持つベクトル空間によって表現されるわけです。
3以上のn個の座標軸を持つ空間のことを普通n次元空間という言い方をしますね。0次元が広がりを持たない点、1次元が線、2次元を面とかあるいは2次元から、2次元空間ともいったりもしますが、元々は3次元を空間ということの拡張(つまり縦横高さでどれか一つが0(通常高さ))と考えられます。我々の基本的な認識能力は3次元の空間であり、それ以下もそれ以上も空間の延長という形でしか我々は表現できないということかも知れません。
ところで、ベクトルはn次元空間においてもやっぱりベクトルと言いますよね。これは、ベクトルは2次元の時は二つの座標で原点からの距離と向きを決定することができる。これを3次元、そしてn次元へと拡張しても全く同様に考えられるということからベクトルは同様の概念でベクトルだと考えられる訳ですよね。
さて、超平面はどうして超平面に垂直な法線ベクトルと入力の内積で考えられるのでしょうか。内積とはなんなのか。
少しずつ考えていきましょう。まず、n次元空間のベクトルはn個の座標で原点からの距離と向きを表現できるのでした。たとえば、2次元で考えればx座標とy座標で表現できるのですね。これは、我々が平面を俯瞰できるからそのように考えるのであって、例えば、ある港から船でどこか魚釣りのポイントを目指しているなら方角と距離を指示してもらわないとたどり着けないでしょう。

つまり、ベクトルには地図を見るように座標を示す示し方と、船に乗って海のどこかへたどり着こうとするとき場合のような基準から角度と長さを示す示し方と二通りあるわけです。(え?今時ならGPSあるだろって?そ、それは………)
しかし、3次元をこえたn次元空間で角度と距離なんて我々には想像もつかないからn個の座標で言うわけですが、言い方を変えれば、長さと角度(方向)のある一本の棒はn個の基本的なベクトル(単位ベクトルという)、例えば2次元ベクトルx(x1,x2)だったら(10)と(01)で表せる二つの単位ベクトルのそれぞれ、x1倍、x2倍と考えることもできます。あるベクトルxは基本ベクトルの合成されたものして考える訳です。

逆に、ベクトルxを単位ベクトルに分解することもできる。それが内積というものです。つまり、ベクトルx(x1,x2)x座標のみの単位ベクトル(10)の内積を取れば(x1,0)というベクトルが得られます。見方を変えれば、ベクトルx(x1,x2)をx軸に写像したものが内積という見方もできるでしょう。

では、どうして、超平面の方程式は入力ベクトルと法線ベクトルとの内積で表せるのでしょう。

まず、超平面上にある点があると言うことは、例えば、

w1x1+w2x2+………+wnxn+b=0  
という方程式が成り立つと言うことですよね。つまりは0になると言うことが大事なわけです。ところで、超平面に垂直な線は超平面に写像したとき大きさはないでしょう?大きさ0。実際はバイアスbがあるので少し話はややこしいですが、例えば、原点を通るような超平面は、

  w1x1+w2x2+………+wnxn=0  
で、これが原点からbだけ平行移動したものが求める超空間の方程式となるというわけです。従って、上記のような方程式がベクトルの内積を使うとすごく簡単にwx+b=0と表せるというわけなのです。
このような内積を定義できる空間のことをユークリッド空間と言います。難しい言い方ですが、単に直交座標系が成り立つ空間、と言って良いでしょう。

ユークリッド空間では、2つのn次元ベクトルa,bの距離(ユークリッド距離)は点aから点bへ向かうベクトルの大きさに等しく次のように定義されます。




最後に、正規化を説明しましょう。正規化とは、簡単に言えば、どんな長さも基準に対して相対的であるので、基準になるものが一番大きければ、全ては0から1の実数で表せるでしょう、と言うことです。例えば、直角三角形の斜辺の長さはほかの2辺より必ず大きいので、直角三角形の斜辺を基準とすれば、ほかの2辺は0から1の間の大きさで表せる、そういうことになりますよね。



2011年4月19日火曜日

パーセプトロンが線形分離可能であることの証明 その1


いよいよ、本題であるパーセプトロンが線形分離可能であるということの証明に入ります。証明の大きな流れとしては次のようになります。

  1. たとえば、本来y=wx+bの外側にあるデータが学習途中のパーセプトロンが行う分類では内側にあると判定されたとしましょう。そのときパーセプトロンがy=wx+b0と答えます。つまり、本来と符号が逆ということです。このように間違ったときに負の値を返すような関数をマージンといい、基本的には超平面とトレーニングデータの集合の各要素との距離を表わす関数として定義します。距離ならば負の時は間違いと分かりますよね。
  2. 最終的には、パーセプトロンの学習した超平面ともともとの教師用の超平面は一致せずにある距離が生まれるでしょう?これを最大マージンと言ったりもしますが、この間に入るようなデータだけがうまく分類できない可能性があります。その可能性が極めて小さければ、パーセプトロンはトレーニングデータの学習によって線形分離可能だ、と言うことができるでしょう。


さて、本格的に証明する前に、少しだけ準備をしましょう。すでにいくつかは出てきていますが、用語やその他の概念を定義して、説明する必要があるからです。

ここからは、テキスト「サポートベクターマシン入門」のp.16にある定義2.2を以下参照して説明します。

まず、マージンについてです。超平面が法線ベクトルwとバイアスbで表わされ、あるi番目の入力(xi,yi)のマージンγiを以下のように定義します。

  γi=yi (<wxi>+b)
ちなみにですが、少し話がそれますが、前回のプログラムにPARAMS.MARGIN_Hというものがあり、学習するときの値の変化に大きく関わっていました。こちらの方は学習率といいます。こちらも覚えといて下さいね。



さて、マージンγiに話を戻しますが、図5に幾何マージン関数マージンを簡単に図示しました。簡単に言うと2点間の距離が幾何マージンで2つの平行する超平面間の距離が関数マージンです。この関数マージン負の時に学習がやり直される、というのが学習のアルゴリズムでの条件でもあります。

さて、トレーニングデータの集合Sがあるとします。トレーニングデータSの各要素ごとに、教師である超平面の方程式と平行な超平面が作ることができ、教師である平面とのそれぞれの距離は幾何マージンと同じになりますよね。

さて、教師である超平面と最も距離の小さい(最も幾何マージンの少ない)トレーニングデータの集合Sの点までの距離(数学的に正確に言うとユークリッド距離)が、学習の結果、これ以上教師である超平面と近づくことのできない関数マージンと考えられるでしょう。これを、最大マージンと言います。また、これを関数マージンで考えればその超平面は最大マージン超平面と言うことができるでしょう。そして、その最大マージンは線形分離可能であれば0以上であることは誰の目にも明らかですよね。

注)ところで、前回の記事でプログラムの結果をお見せしたときに、教師の超平面と学習結果の超平面のだす答えには任意の定数倍の関係があると言いました。今回、そのことはわかりやすさを重要視して説明しておりませんが、基本的に双方が正規化されているという前提でお話しさせて頂いております。これ以降の記事でもその辺りは暗黙の了解であるとして説明致しますのでどうぞご了承下さい。

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;
}

}