<?php /****************************/ /* back propagation model */ /* coded by Y.Suganuma */ /****************************/ /******************************************************/ /* バックプロパゲーションの制御(クラス BackControl) */ /******************************************************/ class BackControl { protected $alpha; //重み及びバイアス修正パラメータ protected $eata; protected $eps; // 許容誤差 protected $order; // 入力パターンの与え方(=0:順番,=1:ランダム) protected $p_type; // 出力先・方法の指定 // =0 : 誤って認識した数だけ出力 // =1 : 認識結果を出力 // =2 : 認識結果と重みを出力 // (負の時は,認識結果と重みをファイルへも出力) protected $o_file; // 出力ファイル名 /*************************************/ /* クラスBackControlのコンストラクタ */ /* name : 入力データファイル名 */ /*************************************/ function BackControl($name) { $in = fopen($name, "rb"); $str = trim(fgets($in)); strtok($str, " "); $this->eps = floatval(strtok(" ")); strtok(" "); $this->p_type = intval(strtok(" ")); if ($this->p_type < 0) { strtok(" "); $this->o_file = strtok(" "); } fscanf($in, "%*s %d %*s %lf %*s %lf", $this->order, $this->eata, $this->alpha); fclose($in); } } /*****************************************************/ /* バックプロパゲーションのデータ(クラス BackData) */ /*****************************************************/ class BackData { public $noiu; // 入力ユニットの数 public $noou; // 出力ユニットの数 public $noip; // 入力パターンの数 public $iptn; // iptn[i][j] : (i+1)番目の入力パターンの(j+1)番目の // 入力ユニットの入力値 // i=0,noip-1 j=0,noiu-1 public $optn; // optn[i][j] : (i+1)番目の入力パターンに対する(j+1) // 番目の出力ユニットの目標出力値 // i=0,noip-1 j=0,noou-1 /************************************/ /* クラスBackDataのコンストラクタ */ /* name : 入力データファイル名 */ /************************************/ function BackData($name) { $in = fopen($name, "rb"); /* 入力パターン数等 */ fscanf($in, "%*s %d %*s %d %*s %d", $this->noip, $this->noiu, $this->noou); /* 領域の確保 */ $this->iptn = array($this->noip); for ($i1 = 0; $i1 < $this->noip; $i1++) $this->iptn[$i1] = array($this->noiu); $this->optn = array($this->noip); for ($i1 = 0; $i1 < $this->noip; $i1++) $this->optn[$i1] = array($this->noou); /* 入力パターン及び各入力パターンに対する出力パターンの入力 */ for ($i1 = 0; $i1 < $this->noip; $i1++) { $str = trim(fgets($in)); strtok($str, " "); for ($i2 = 0; $i2 < $this->noiu; $i2++) $this->iptn[$i1][$i2] = intval(strtok(" ")); $str = trim(fgets($in)); strtok($str, " "); for ($i2 = 0; $i2 < $this->noou; $i2++) $this->optn[$i1][$i2] = intval(strtok(" ")); } fclose($in); } } /*******************************************/ /* バックプロパゲーション(クラス Backpr) */ /*******************************************/ class Backpr extends BackControl { public $con; // con[i][j] : 各ユニットに対するバイアスの与え方,及び, // 接続方法 // [i][i] : ユニット(i+1)のバイアスの与え方 // =-3 : 入力値で固定 // =-2 : 入力値を初期値として,学習により変更 // =-1 : 乱数で初期値を設定し,学習により変更 // [i][j] : ユニット(i+1)と(j+1)の接続方法(j>i) // =0 : 接続しない // =1 : 接続する(重みの初期値を乱数で設定し,学習) // =2 : 接続する(重みの初期値を入力で与え,学習) // =3 : 接続する(重みを入力値に固定) // i=0,nou-1 j=0,nou-1 public $f_type; // シグモイド関数のタイプ // 0 : [0,1] // 1 : [-1,1]) public $noiu; // 入力ユニットの数 public $noou; // 出力ユニットの数 public $nohu; // nohu[i] : レベル(i+1)の隠れ層のユニット数(隠れ層 // には下から順に番号が付けられ,出力層はレ // ベル(nolvl+1)の隠れ層とも見做される) // i=0,nolvl public $nolvl; // 隠れユニットの階層数 public $nou; // 入力,出力,及び,隠れ層の各ユニットの数の和(各ユニ // ットには最も上の出力ユニットから,隠れ層の各ユニット, // 及び,入力ユニットに至る一連のユニット番号が付けられ // る) public $dp; // dp[i] : ユニット(i+1)の誤差 i=0,nou-1 public $op; // op[i] : ユニット(i+1)の出力 i=0,nou-1 public $theta; //theta[i] : ユニット(i+1)のバイアス i=0,nou public $w; // w[i][j] : ユニット(i+1)から(j+1)への重み(j>i) // w[j][i] : (i+1)から(j+1)への重みの前回修正量(j>i) // w[i][i] : ユニット(i+1)のバイアスの前回修正量 // i=0,nou-1 j=0,nou-1 /************************************************/ /* クラスBackprのコンストラクタ */ /* name_c : 制御データ用入力ファイル名 */ /* name_s : ネットワーク記述入力ファイル名 */ /************************************************/ function Backpr($name_c, $name_s) { parent::BackControl($name_c); $in = fopen($name_s, "rb"); /* 乱数の初期化 */ mt_srand(); /* 入力ユニット,出力ユニットの数,関数タイプ */ fscanf($in, "%*s %d %*s %d %*s %d", $this->noiu, $this->noou, $this->f_type); $this->nou = $this->noiu + $this->noou; // 入力ユニットと出力ユニットの和 /* 隠れユニットの階層数と各階層のユニット数 */ $str = trim(fgets($in)); strtok($str, " "); $this->nolvl = intval(strtok(" ")); $this->nohu = array($this->nolvl+1); $this->nohu[$this->nolvl] = $this->noou; // 出力ユニットの数 if ($this->nolvl > 0) { strtok(" "); for ($i1 = 0; $i1 < $this->nolvl; $i1++) { $this->nohu[$i1] = intval(strtok(" ")); $this->nou += $this->nohu[$i1]; } } /* 領域の確保 */ $this->con = array($this->nou); for ($i1 = 0; $i1 < $this->nou; $i1++) { $this->con[$i1] = array($this->nou); for ($i2 = 0; $i2 < $this->nou; $i2++) $this->con[$i1][$i2] = ($i1 == $i2) ? -1 : 0; } $this->w = array($this->nou); for ($i1 = 0; $i1 < $this->nou; $i1++) { $this->w[$i1] = array($this->nou); for ($i2 = 0; $i2 < $this->nou; $i2++) $this->w[$i1][$i2] = 0.0; } $this->dp = array($this->nou); $this->op = array($this->nou); $this->theta = array($this->nou); for ($i1 = 0; $i1 < $this->nou; $i1++) $this->theta[$i1] = 0.2 * mt_rand() / mt_getrandmax() - 0.1; /* 各ユニットのバイアスとユニット間の接続関係 */ // バイアス // バイアスデータの数 fscanf($in, "%*s %d", $n); fgets($in); fgets($in); fgets($in); if ($n > 0) { // バイアスデータの処理 for ($i0 = 0; $i0 < $n; $i0++) { $str = trim(fgets($in)); // ユニット番号 $k1 = intval(strtok($str, " ")); // 不適当なユニット番号のチェック if ($k1 < 1 || $k1 > ($this->nou-$this->noiu)) exit("***error ユニット番号 ".$k1." が不適当\n"); // バイアスの与え方 $k1--; $id = intval(strtok(" ")); $this->con[$k1][$k1] = $id; // バイアスの初期設定 switch ($this->con[$k1][$k1]) { case -1: $x1 = floatval(strtok(" ")); $x2 = floatval(strtok(" ")); $this->theta[$k1] = ($x2 - $x1) * mt_rand() / mt_getrandmax() + $x1; break; case -2: $this->theta[$k1] = floatval(strtok(" ")); break; case -3: $this->theta[$k1] = floatval(strtok(" ")); break; default: exit("***error バイアスの与え方が不適当\n"); } } } // 接続方法 // 接続データの数 fscanf($in, "%*s %d", $n); fgets($in); fgets($in); fgets($in); if ($n > 0) { // 接続データの処理 for ($i0 = 0; $i0 < $n; $i0++) { $str = trim(fgets($in)); // 接続情報 $k1 = intval(strtok($str, " ")); $k2 = intval(strtok(" ")); $k3 = intval(strtok(" ")); $k4 = intval(strtok(" ")); // 不適切な接続のチェック $sw = 0; if ($k1 < 1 || $k2 < 1 || $k3 < 1 || $k4 < 1) $sw = 1; else { if ($k1 > $this->nou || $k2 > $this->nou || $k3 > $this->nou || $k4 > $this->nou) $sw = 1; else { if ($k1 > $k2 || $k3 > $k4) $sw = 1; else { if ($k4 >= $k1) $sw = 1; else { $l1 = -1; $k = 0; for ($i1 = $this->nolvl; $i1 >= 0 && $l1 < 0; $i1--) { $k += $this->nohu[$i1]; if ($k1 <= $k) $l1 = $i1; } $l2 = -1; $k = 0; for ($i1 = $this->nolvl; $i1 >= 0 && $l2 < 0; $i1--) { $k += $this->nohu[$i1]; if ($k4 <= $k) $l2 = $i1; } if ($l2 <= $l1) $sw = 1; } } } } if ($sw > 0) exit("***error ユニット番号が不適当(".$k1." ".$k2." ".$k3." ".$k4."\n"); // 重みの初期値の与え方 $k1--; $k2--; $k3--; $k4--; $id = intval(strtok(" ")); if ($id == 1) { $x1 = floatval(strtok(" ")); $x2 = floatval(strtok(" ")); } else { if ($id > 1) $x1 = floatval(strtok(" ")); else { if ($id != 0) exit("***error 接続方法が不適当\n"); } } // 重みの初期値の設定 for ($i1 = $k3; $i1 <= $k4; $i1++) { for ($i2 = $k1; $i2 <= $k2; $i2++) { $this->con[$i1][$i2] = $id; switch ($id) { case 0: $this->w[$i1][$i2] = 0.0; break; case 1: $this->w[$i1][$i2] = ($x2 - $x1) * mt_rand() / mt_getrandmax() + $x1; break; case 2: $this->w[$i1][$i2] = $x1; break; case 3: $this->w[$i1][$i2] = $x1; break; } } } } } fclose($in); } /******************************************/ /* 誤差の計算,及び,重みとバイアスの修正 */ /* ptn[$i1] : 出力パターン */ /******************************************/ function Err_back($k0, $ptn) { for ($i1 = 0; $i1 < $this->nou-$this->noiu; $i1++) { // 誤差の計算 if ($i1 < $this->noou) { if ($this->f_type == 0) $this->dp[$i1] = ($ptn[$i1] - $this->op[$i1]) * $this->op[$i1] * (1.0 - $this->op[$i1]); else $this->dp[$i1] = 0.5 * ($ptn[$i1] - $this->op[$i1]) * ($this->op[$i1] - 1.0) * ($this->op[$i1] + 1.0); } else { $x1 = 0.0; for ($i2 = 0; $i2 < $i1; $i2++) { if ($this->con[$i2][$i1] > 0) $x1 += $this->dp[$i2] * $this->w[$i2][$i1]; } if ($this->f_type == 0) $this->dp[$i1] = $this->op[$i1] * (1.0 - $this->op[$i1]) * $x1; else $this->dp[$i1] = 0.5 * ($this->op[$i1] - 1.0) * ($this->op[$i1] + 1.0) * $x1; } // 重みの修正 for ($i2 = $i1+1; $i2 < $this->nou; $i2++) { if ($this->con[$i1][$i2] == 1 || $this->con[$i1][$i2] == 2) { $x1 = $this->eata * $this->dp[$i1] * $this->op[$i2] + $this->alpha * $this->w[$i2][$i1]; $this->w[$i2][$i1] = $x1; $this->w[$i1][$i2] += $x1; } } // バイアスの修正 if ($this->con[$i1][$i1] >= -2) { $x1 = $this->eata * $this->dp[$i1] + $this->alpha * $this->w[$i1][$i1]; $this->w[$i1][$i1] = $x1; $this->theta[$i1] += $x1; } } } /********************************************************/ /* 与えられた入力パターンに対する各ユニットの出力の計算 */ /********************************************************/ function Forward() { for ($i1 = $this->nou-$this->noiu-1; $i1 >= 0; $i1--) { $sum = -$this->theta[$i1]; for ($i2 = $i1+1; $i2 < $this->nou; $i2++) { if ($this->con[$i1][$i2] > 0) $sum -= $this->w[$i1][$i2] * $this->op[$i2]; } $this->op[$i1] = ($this->f_type == 0) ? 1.0 / (1.0 + exp($sum)) : 1.0 - 2.0 / (1.0 + exp($sum)); } } /*****************************/ /* 学習の実行 */ /* p : 認識パターン */ /* m_tri : 最大学習回数 */ /*****************************/ function Learn($p, $m_tri) { $k0 = -1; /* エラーチェック */ if ($this->noiu != $p->noiu || $this->noou != $p->noou) exit("***error 入力または出力ユニットの数が違います\n"); for ($i1 = 0; $i1 < $m_tri; $i1++) { /* パターンを与える順番の決定 */ if ($this->order == 0) { // 順番 $k0 += 1; if ($k0 >= $p->noip) $k0 = 0; } else { // ランダム $k0 = intval(mt_rand() / mt_getrandmax() * $p->noip); if ($k0 >= $p->noip) $k0 = $p->noip - 1; } /* 出力ユニットの結果を計算 */ $k1 = $this->nou - $this->noiu; for ($i2 = 0; $i2 < $this->noiu; $i2++) $this->op[$k1+$i2] = $p->iptn[$k0][$i2]; $this->Forward(); /* 重みとバイアスの修正 */ $this->Err_back($k0, $p->optn[$k0]); } } /***********************************************/ /* 与えられた対象の認識と出力 */ /* p : 認識パターン */ /* pr : =0 : 出力を行わない */ /* =1 : 出力を行う */ /* =2 : 出力を行う(未学習パターン) */ /* tri : 現在の学習回数 */ /* return : 誤って認識したパターンの数 */ /***********************************************/ function Recog($p, $pr, $tri) { $No = 0; /* ファイルのオープン */ if ($this->p_type < 0 && $pr > 0) { if ($pr == 1) { $out = fopen($this->o_file, "wb"); fwrite($out, "***学習パターン***\n\n"); } else { $out = fopen($this->o_file, "ab"); fwrite($out, "\n***未学習パターン***\n\n"); } } /* 各パターンに対する出力 */ for ($i1 = 0; $i1 < $p->noip; $i1++) { // 入力パターンの設定 $k1 = $this->nou - $this->noiu; for ($i2 = 0; $i2 < $this->noiu; $i2++) $this->op[$k1+$i2] = $p->iptn[$i1][$i2]; // 出力の計算 $this->Forward(); // 結果の表示 if ($this->p_type != 0 && $pr > 0) { printf("入力パターン%4d ", $i1+1); for ($i2 = 0; $i2 < $this->noiu; $i2++) { printf("%5.2f", $this->op[$k1+$i2]); if ($i2 == $this->noiu-1) printf("\n"); else { if((($i2+1) % 10) == 0) printf("\n "); } } printf("\n 出力パターン(理想) "); for ($i2 = 0; $i2 < $this->noou; $i2++) { printf("%10.3f", $p->optn[$i1][$i2]); if ($i2 == $this->noou-1) printf("\n"); else { if((($i2+1) % 5) == 0) printf("\n "); } } } $sw = 0; if ($this->p_type != 0 && $pr > 0) printf(" (実際) "); for ($i2 = 0; $i2 < $this->noou; $i2++) { if ($this->p_type != 0 && $pr > 0) { printf("%10.3f", $this->op[$i2]); if ($i2 == $this->noou-1) printf("\n"); else { if((($i2+1) % 5) == 0) printf("\n "); } } if (abs($this->op[$i2]-$p->optn[$i1][$i2]) > $this->eps) $sw = 1; } if ($sw > 0) $No++; if ($this->p_type < 0 && $pr > 0) { $str = "入力パターン".($i1+1)." "; for ($i2 = 0; $i2 < $this->noiu; $i2++) { $str = $str." ".$this->op[$k1+$i2]; if ($i2 == $this->noiu-1) $str = $str."\n"; else { if((($i2+1) % 10) == 0) $str = $str."\n "; } } fwrite($out, $str); $str = "\n 出力パターン(理想) "; for ($i2 = 0; $i2 < $this->noou; $i2++) { $str = $str." ".$p->optn[$i1][$i2]; if ($i2 == $this->noou-1) $str = $str."\n"; else { if((($i2+1) % 5) == 0) $str = $str."\n "; } } fwrite($out, $str); $str = " (実際) "; for ($i2 = 0; $i2 < $this->noou; $i2++) { $str = $str." ".$this->op[$i2]; if ($i2 == $this->noou-1) $str = $str."\n"; else { if((($i2+1) % 5) == 0) $str = $str."\n "; } } fwrite($out, $str); } if ($this->p_type != 0 && $pr > 0) { fgets(STDIN); if ($i1 == 0) fgets(STDIN); } } /* 重みの出力 */ if (($this->p_type < -1 || $this->p_type > 1) && $pr == 1) { printf(" 重み\n"); for ($i1 = 0; $i1 < $this->nou-$this->noiu; $i1++) { printf(" to%4d from ", $i1+1); $ln = -1; for ($i2 = 0; $i2 < $this->nou; $i2++) { if ($this->con[$i1][$i2] > 0) { if ($ln <= 0) { if ($ln < 0) $ln = 0; else printf("\n "); } printf("%4d%11.3f", $i2+1, $this->w[$i1][$i2]); $ln += 1; if ($ln == 4) $ln = 0; } } printf("\n"); } printf("\n バイアス "); $ln = 0; for ($i1 = 0; $i1 < $this->nou-$this->noiu; $i1++) { printf("%4d%11.3f", $i1+1, $this->theta[$i1]); $ln += 1; if ($ln == 4 && $i1 != $this->nou-$this->noiu-1) { $ln = 0; printf("\n "); } } if ($ln != 0) printf("\n"); fgets(STDIN); } if ($this->p_type < 0 && $pr == 1) { $str = " 重み\n"; for ($i1 = 0; $i1 < $this->nou-$this->noiu; $i1++) { $str = $str." to ".( $i1+1)." from "; $ln = -1; for ($i2 = 0; $i2 < $this->nou; $i2++) { if ($this->con[$i1][$i2] > 0) { if ($ln <= 0) { if ($ln < 0) $ln = 0; else $str = $str."\n "; } $str = $str.($i2+1)." ".$this->w[$i1][$i2]; $ln += 1; if ($ln == 4) $ln = 0; } } $str = $str."\n"; } fwrite($out, $str); $str = "\n バイアス "; $ln = 0; for ($i1 = 0; $i1 < $this->nou-$this->noiu; $i1++) { $str = $str.($i1+1)." ".$this->theta[$i1]; $ln += 1; if ($ln == 4 && $i1 != $this->nou-$this->noiu-1) { $ln = 0; $str = $str."\n "; } } if ($ln != 0) $str = $str."\n"; fwrite($out, $str); } if ($this->p_type < 0 && $pr > 0) fclose($out); return $No; } } /****************/ /* main program */ /****************/ $max = 0; $no = 1; // エラー if (count($argv) != 3) exit("***error 入力データファイル名を指定して下さい\n"); else { // ネットワークの定義 $net = new Backpr($argv[1], $argv[2]); // 学習パターン等の入力 printf("学習回数は? "); fscanf(STDIN, "%d", $m_tri); // 最大学習回数 printf("何回毎に収束を確認しますか? "); fscanf(STDIN, "%d", $conv); // 収束確認回数 printf("学習パターンのファイル名は? "); fscanf(STDIN, "%s", $f_name); $dt1 = new BackData($f_name); // 学習 while ($max < $m_tri && $no > 0) { $tri = (($max + $conv) < $m_tri) ? $conv : $m_tri - $max; $max += $tri; $net->Learn($dt1, $tri); // 学習 $no = $net->Recog($dt1, 0, $max); // 学習対象の認識 printf(" 回数 %d 誤って認識したパターン数 %d\n", $max, $no); } $no = $net->Recog($dt1, 1, $max); // 学習対象の認識と出力 // 未学習パターンの認識 printf("未学習パターンの認識を行いますか?(=1:行う,=0:行わない) "); fscanf(STDIN, "%d", $sw); if ($sw > 0) { printf("未学習パターンのファイル名は? "); fscanf(STDIN, "%s", $f_name); $dt2 = new BackData($f_name); $no = $net->Recog($dt2, 2, $max); // 未学習対象の認識と出力 } } /* ------------------------制御データ---------------- 誤差 0.1 出力 -2 出力ファイル kekka 順番 0 η 0.5 α 0.8 ------------------------構造データ---------------- 入力ユニット数 2 出力ユニット数 1 関数タイプ 0 隠れ層の数 1 各隠れ層のユニット数(下から) 1 バイアス入力ユニット数 1 ユニット番号:出力ユニットから順に番号付け 入力方法:=-3:固定,=-2:入力後学習,=-1:乱数(default,[-0.1,0.1])) 値:バイアス値(ー2またはー3の時)または一様乱数の範囲(下限,上限) 1 -1 -0.05 0.05 接続方法の数 2 ユニット番号:ユニットk1からk2を,k3からk4に接続 接続方法:=0:接続なし,=1:乱数,=2:重み入力後学習,=3:重み固定 値:重み(2または3の時)または一様乱数の範囲(1の時:下限,上限) 3 4 1 2 1 -0.1 0.1 2 2 1 1 1 -0.1 0.1 ------------------------学習データ---------------- パターンの数 4 入力ユニット数 2 出力ユニット数 1 入力1 0 0 出力1 0 入力2 0 1 出力2 1 入力3 1 0 出力3 1 入力4 1 1 出力4 0 ------------------------認識データ---------------- パターンの数 4 入力ユニット数 2 出力ユニット数 1 入力1 0 0 出力1 0 入力2 0 1 出力2 1 入力3 1 0 出力3 1 入力4 1 1 出力4 0 */ ?>