/* このプログラムは,以下に紹介する逐次修正法と慣性項を用いて ExORの計算を学習するプログラムである. 逐次修正法とは, 1. 入力パターンを提示し,それに対する出力結果を求める. 2. 出力結果と教師信号との誤差を逆伝播しながら,各層の各ユニットにおける誤差を求める. 3. その誤差をもとに結合荷重の修正量を計算し,結合荷重の修正を行う. 以上の処理を全入力パターンに対して行い,さらに,何度も同じことを繰り返す。 慣性項 上記の逐次修正法では,「二乗誤差Eの微分値」に比例した値だけによって 結合荷重の修正量が決定される.一般に,このような勾配降下法では, 修正量は微少な方が良いとされている.しかしながら,修正量を小さくすると, 学習速度は遅くなってしまう.そこで,探索点の動きが振動しない程度に 学習速度を上げる一つの方法として,慣性項を導入して結合荷重の修正を行う. */ #include<stdio.h> #include<math.h> #include<time.h> #include<stdlib.h> #define LAYER 3 //階層数 #define INPUT 2 //入力数 #define HIDDEN 3 //Hidden Layerのニューロン数/中間層のユニット数 #define OUTPUT 1 //出力数 #define CTG 4 //カテゴリ数 #define ITERATIONS 100000 //繰り返し数 #define ETA 1.50 //イータ #define EPSILON 1.00 //イプシロン #define ALPHA 0.90 //慣性の係数アルファ #define WD 2.00 #define MIN_ERR 0.0001 //最小エラー #define ON 0.9 //活性状態 #define OFF 0.1 //活性状態じゃない #define SMAX 15 #define SMIN -15 #define MAX 0.99995 #define MIN 0.00005 #ifdef _Cdecl #define drand() ((double)(rand()+1)/RAND_MAX-0.5) #else #define drand() ((double)(rand()+1)/RAND_MAX-0.5) #endif #define sq(x) (x)*(x) double i_lay[CTG][INPUT+1], //Input Layerの配列(バイアス+1) h_lay[HIDDEN+1], //Hidden Layerの配列(バイアス+1) o_lay[OUTPUT], //Output Layerの配列 teach[CTG][OUTPUT]; //教師信号の配列 double ih_w[INPUT+1][HIDDEN], //Input-Hidden間の重みの配列 ho_w[HIDDEN+1][OUTPUT]; //Hidden-Output間の重みの配列 double h_del[HIDDEN+1], //Hidden Layerのデルタ配列 o_del[OUTPUT]; //Output Layerのデルタ配列 double ih_dw[INPUT+1][HIDDEN], ho_dw[HIDDEN+1][OUTPUT]; extern double sigmoid(); //sigmoid 関数 main(argc,argv) int argc; char **argv; { int i,j,ite,ctg; double err,sum; srand((int)time((long *)0)); /* ExOR Problem 入力パターン及び教師信号の指示 i_lay[*] [0]は入力1 i_lay[*] [1]は入力2 i_lay[*] [2]は入力バイアス */ i_lay[0][0]=OFF; i_lay[0][1]=OFF; i_lay[0][2]=ON; teach[0][0]=OFF; i_lay[1][0]=ON; i_lay[1][1]=OFF; i_lay[1][2]=ON; teach[1][0]=ON; i_lay[2][0]=OFF; i_lay[2][1]=ON; i_lay[2][2]=ON; teach[2][0]=ON; i_lay[3][0]=ON; i_lay[3][1]=ON; i_lay[3][2]=ON; teach[3][0]=OFF; h_lay[HIDDEN]=ON; //Hidden Layerのバイアス //重みの初期値の設定 for(j=0;j<HIDDEN;j++) for(i=0;i<=INPUT;i++){ ih_w[i][j]=WD*drand(); ih_dw[i][j]=0.; } for(j=0;j<OUTPUT;j++) for(i=0;i<=HIDDEN;i++){ ho_w[i][j]=WD*drand(); ho_dw[i][j]=0.; } for(ite=0;ite<=ITERATIONS; ite++){ for(ctg=0,err=0.;ctg<CTG;ctg++){ //Hidden Layerの出力の計算 for(j=0;j<HIDDEN;j++){ for(i=0,sum=0.;i<=INPUT;i++) sum += i_lay[ctg][i]*ih_w[i][j]; h_lay[j]=sigmoid(sum); } //Output Layerの出力の計算 for(j=0;j<OUTPUT;j++){ for(i=0,sum=0.;i<=HIDDEN;i++) sum+=h_lay[i]*ho_w[i][j]; o_lay[j]=sigmoid(sum); } //デルタの計算 for(i=0;i<OUTPUT;i++){ err+=sq(teach[ctg][i]-o_lay[i])/2./(double)CTG; o_del[i]=EPSILON*(teach[ctg][i]-o_lay[i])*o_lay[i]*(1.-o_lay[i]); } for(j=0;j<=HIDDEN;j++){ h_del[j]=0.; for(i=0;i<OUTPUT;i++) h_del[j]+=o_del[i]*ho_w[j][i]; h_del[j]*=EPSILON*h_lay[j]*(1.-h_lay[j]); } //結合荷重の修正量 for(j=0;j<=HIDDEN;j++) for(i=0;i<OUTPUT;i++){ ho_dw[j][i]=ETA*o_del[i]*h_lay[j]+ALPHA*ho_dw[j][i]; ho_w[j][i]+=ho_dw[j][i]; } for(j=0;j<=INPUT;j++) for(i=0;i<HIDDEN;i++){ ih_dw[j][i]=ETA*h_del[i]*i_lay[ctg][j]+ALPHA*ih_dw[j][i]; ih_w[j][i]+=ih_dw[j][i]; } } fprintf(stderr,"iteration = %4d, error = %1.5f\n",ite,err); //収束判定 if((err<MIN_ERR)||(ite==ITERATIONS)){ for(ctg=0;ctg<CTG;ctg++){ fprintf(stderr,"ctg[%d] : ",ctg); for(i=0;i<INPUT;i++) fprintf(stderr,"i[%d] = %1.1f ",i,i_lay[ctg][i]); //Hidden Layerの出力の計算 for(j=0;j<HIDDEN;j++){ for(i=0,sum=0.;i<=INPUT;i++) sum+=i_lay[ctg][i]*ih_w[i][j]; h_lay[j]=sigmoid(sum); } //Output Layerの出力の計算 for(j=0;j<OUTPUT;j++){ for(i=0,sum=0.;i<=HIDDEN;i++) sum+=h_lay[i]*ho_w[i][j]; o_lay[j]=sigmoid(sum); fprintf(stderr,"o[%d] = %1.5f, t{%d] = %1.1f\n",j,o_lay[j],j,teach[ctg][j]); } } fprintf(stderr,"iteration = %4d, error = %1.5f\n",ite,err); break; } } } double sigmoid(s) double s; { double sm; sm=EPSILON*s; if(sm>SMAX) return(MAX); else if(sm<SMIN) return(MIN); else return(1./(1.+exp(-sm))); } |
実行結果 ex_bp_oは ctg[0] : i[0] = 0.1 i[1] = 0.1 o[0] = 0.32447, t{0] = 0.1 ctg[1] : i[0] = 0.9 i[1] = 0.1 o[0] = 0.50603, t{0] = 0.9 ctg[2] : i[0] = 0.1 i[1] = 0.9 o[0] = 0.89247, t{0] = 0.9 ctg[3] : i[0] = 0.9 i[1] = 0.9 o[0] = 0.32333, t{0] = 0.1 iteration = 10000, error = 0.03426 または ctg[0] : i[0] = 0.1 i[1] = 0.1 o[0] = 0.10116, t{0] = 0.1 ctg[1] : i[0] = 0.9 i[1] = 0.1 o[0] = 0.89931, t{0] = 0.9 ctg[2] : i[0] = 0.1 i[1] = 0.9 o[0] = 0.46290, t{0] = 0.9 ctg[3] : i[0] = 0.9 i[1] = 0.9 o[0] = 0.46339, t{0] = 0.1 iteration = 10000, error = 0.04775 のような値を出す。 結果に近づいているとは言い難い。 学ぶ回数を増やしても以下の様に近づくことはなかった。 tg[0] : i[0] = 0.1 i[1] = 0.1 o[0] = 0.10007, t{0] = 0.1 ctg[1] : i[0] = 0.9 i[1] = 0.1 o[0] = 0.89967, t{0] = 0.9 ctg[2] : i[0] = 0.1 i[1] = 0.9 o[0] = 0.48426, t{0] = 0.9 ctg[3] : i[0] = 0.9 i[1] = 0.9 o[0] = 0.48432, t{0] = 0.1 iteration = 100000, error = 0.04322 ex_bp_moは、 ctg[0] : i[0] = 0.1 i[1] = 0.1 o[0] = 0.10619, t{0] = 0.1 ctg[1] : i[0] = 0.9 i[1] = 0.1 o[0] = 0.88907, t{0] = 0.9 ctg[2] : i[0] = 0.1 i[1] = 0.9 o[0] = 0.88908, t{0] = 0.9 ctg[3] : i[0] = 0.9 i[1] = 0.9 o[0] = 0.12099, t{0] = 0.1 iteration = 146, error = 0.00010 このように近い値を少ない回数で出す。 が、何度も行うと、 ctg[0] : i[0] = 0.1 i[1] = 0.1 o[0] = 0.09998, t{0] = 0.1 ctg[1] : i[0] = 0.9 i[1] = 0.1 o[0] = 0.90288, t{0] = 0.9 ctg[2] : i[0] = 0.1 i[1] = 0.9 o[0] = 0.50323, t{0] = 0.9 ctg[3] : i[0] = 0.9 i[1] = 0.9 o[0] = 0.50324, t{0] = 0.1 iteration = 100000, error = 0.04250 のように、近づかない例もあることが分かった。 ex_bp_lは、 ctg[0] : i[0] = 0.1 i[1] = 0.1 o[0] = 0.11644, t{0] = 0.1 ctg[1] : i[0] = 0.9 i[1] = 0.1 o[0] = 0.87184, t{0] = 0.9 ctg[2] : i[0] = 0.1 i[1] = 0.9 o[0] = 0.89970, t{0] = 0.9 ctg[3] : i[0] = 0.9 i[1] = 0.9 o[0] = 0.11456, t{0] = 0.1 iteration = 30000, error = 0.00016 ctg[0] : i[0] = 0.1 i[1] = 0.1 o[0] = 0.10058, t{0] = 0.1 ctg[1] : i[0] = 0.9 i[1] = 0.1 o[0] = 0.89920, t{0] = 0.9 ctg[2] : i[0] = 0.1 i[1] = 0.9 o[0] = 0.89996, t{0] = 0.9 ctg[3] : i[0] = 0.9 i[1] = 0.9 o[0] = 0.10026, t{0] = 0.1 iteration = 30000, error = 0.00000 このようにとても近い値を出すことが確認されたが、 ctg[0] : i[0] = 0.1 i[1] = 0.1 o[0] = 0.10038, t{0] = 0.1 ctg[1] : i[0] = 0.9 i[1] = 0.1 o[0] = 0.89963, t{0] = 0.9 ctg[2] : i[0] = 0.1 i[1] = 0.9 o[0] = 0.49989, t{0] = 0.9 ctg[3] : i[0] = 0.9 i[1] = 0.9 o[0] = 0.50010, t{0] = 0.1 iteration = 30000, error = 0.04002 のように、ごく希に結果が上手く行かないことが確認された。 結果がうまく行かないことがあるのは、 極値が見つけられ、学習方法の特性上、 その周辺から学習が脱出せず、 範囲内での最も適切な値が出なかったと考えられる。 |
サンプルソースの1の中間層のユニット数を変更してみた。 #define HIDDEN 2 //Hidden Layerのニューロン数 この数を3にしてみた結果、以下のような結果が得られた。 % ./ex_bp_o ctg[0] : i[0] = 0.1 i[1] = 0.1 o[0] = 0.10321, t{0] = 0.1 ctg[1] : i[0] = 0.9 i[1] = 0.1 o[0] = 0.89486, t{0] = 0.9 ctg[2] : i[0] = 0.1 i[1] = 0.9 o[0] = 0.89584, t{0] = 0.9 ctg[3] : i[0] = 0.9 i[1] = 0.9 o[0] = 0.10469, t{0] = 0.1 iteration = 1089, error = 0.00001 % ./ex_bp_l ctg[0] : i[0] = 0.1 i[1] = 0.1 o[0] = 0.10004, t{0] = 0.1 ctg[1] : i[0] = 0.9 i[1] = 0.1 o[0] = 0.89999, t{0] = 0.9 ctg[2] : i[0] = 0.1 i[1] = 0.9 o[0] = 0.89994, t{0] = 0.9 ctg[3] : i[0] = 0.9 i[1] = 0.9 o[0] = 0.10005, t{0] = 0.1 iteration = 3913, error = 0.00000 % ./ex_bp_o ctg[0] : i[0] = 0.1 i[1] = 0.1 o[0] = 0.10649, t{0] = 0.1 ctg[1] : i[0] = 0.9 i[1] = 0.1 o[0] = 0.88734, t{0] = 0.9 ctg[2] : i[0] = 0.1 i[1] = 0.9 o[0] = 0.88844, t{0] = 0.9 ctg[3] : i[0] = 0.9 i[1] = 0.9 o[0] = 0.12032, t{0] = 0.1 iteration = 143, error = 0.00010 元々収束が早かったex_bp_moは、変化はあまり見られなかったが、 ex_bp_l,ex_bp_oは、HIDDENの値を3に変えることにより、 早く収束する様になり、とても近い値が得られた。 また、収束しないということが格段と減った。 しかし、精度は低下した。 HIDDENの値が2から3に増えることにより、 変形の大きなパターンに対応する認識能力が備わるためと考えられる。 今回のプログラムは3以上に増やしても 2から3に増やしたときのような大きな変化は見られなかった。 5以上になるとほぼ変化しないように見受けられた。 また、むやみやたらにHIDDENの値を増やすということは、 学習の過程が増えるため、計算量が増えるので、 ある程度の値でとどめておくことが有効だと考えられる。 (今回は、それが3であった。) |
学習用の数字パターンファイルをそれぞれ+1個ずつ用意した。 計8個の学習用の数字パターンファイルができ、 それぞれを認識したが、精度は低かった。 未知のデータに対する認識率は、 2,3カ所の0と1が違う、という程度だと認識率は高く、 下の例ように全体的にずれたものなどは認識率は悪かった。 これは、学習方法の問題である。 学習ファイル 000100 000100 000100 000100 000100 認識率の比較的高い未知のデータ 000100 000100 000000 000100 000010 認識率の低い未知のデータ 010000 010000 010000 010000 010000 |