<?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
*/
?>