top > ニューラルネット Report1



課題1. サンプルソース1にコメントをつける.
ソース(ex_bp_mo)
/*
このプログラムは,以下に紹介する逐次修正法と慣性項を用いて
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)));
}


課題2 サンプルソース1を動かしてExORの学習がうまくいくことを確認する
実行結果

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

のように、ごく希に結果が上手く行かないことが確認された。

結果がうまく行かないことがあるのは、
極値が見つけられ、学習方法の特性上、
その周辺から学習が脱出せず、
範囲内での最も適切な値が出なかったと考えられる。

課題3 サンプルソース1で,中間層のユニット数を変更した場合に どのように挙動が変化するか確認する.
サンプルソースの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であった。)

課題4 サンプルソース2は“0,1,2,3”の数字文字認識のためのNNである.
学習用の数字パターンファイルを作成し,認識力の高いNNを学習により完成させよ.
評価データはレポート提出日に提示するので,
未知のデータに対する認識率ができるかどうかを確認する.
学習用の数字パターンファイルをそれぞれ+1個ずつ用意した。
計8個の学習用の数字パターンファイルができ、
それぞれを認識したが、精度は低かった。

未知のデータに対する認識率は、
2,3カ所の0と1が違う、という程度だと認識率は高く、
下の例ように全体的にずれたものなどは認識率は悪かった。
これは、学習方法の問題である。

学習ファイル
000100
000100
000100
000100
000100

認識率の比較的高い未知のデータ
000100
000100
000000
000100
000010

認識率の低い未知のデータ
010000
010000
010000
010000
010000



Copyright since-2006 j06008 All rights reserved.