バックプロパゲーション

  以下,複数のファイル構成になっています.ファイル間の区切りを「---・・・」で示します.

------------------------制御データ----------------
誤差 0.1 出力 -2 出力ファイル kekka
順番 0 η 0.5 α 0.8 乱数 123
画面表示(円の大きさ,フォントサイズ,幅,高さ) 20 20 400 300

------------------------構造データ----------------
入力ユニット数 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

-----------------------プログラム------------------
/****************************/
/* back propagation model   */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import java.util.StringTokenizer;

class Test {
	/****************/
	/* main program */
	/****************/
	public static void main(String args[]) throws IOException, FileNotFoundException
	{
		int conv;   // 収束確認回数
		int m_tri;   // 最大学習回数
		int max = 0, no = 1, sw, tri;
		String f_name;
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
					// エラー
		if (args.length != 2) {
			System.out.print("***error   入力データファイル名を指定して下さい\n");
			System.exit(1);
		}

		else {
					// ネットワークの定義
			Backpr net = new Backpr (args[0], args[1]);
					// 学習パターン等の入力
			System.out.print("学習回数は? ");
			m_tri = Integer.parseInt(in.readLine());
			System.out.print("何回毎に収束を確認しますか? ");
			conv = Integer.parseInt(in.readLine());

			System.out.print("学習パターンのファイル名は? ");
			f_name = in.readLine();

			BackData 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);   // 学習対象の認識

				System.out.println("   回数 " + max + " 誤って認識したパターン数 " + no);
			}

			no = net.Recog(dt1, 1, max);   // 学習対象の認識と出力
					// 未学習パターンの認識
			System.out.print("未学習パターンの認識を行いますか?(=1:行う,=0:行わない) ");
			sw = Integer.parseInt(in.readLine());

			if (sw > 0) {

				System.out.print("未学習パターンのファイル名は? ");
				f_name = in.readLine();

				BackData dt2 = new BackData(f_name);

				no = net.Recog(dt2, 2, max);   // 未学習対象の認識と出力
			}
		}

		in.close();
	}
}

/******************************************************/
/* バックプロパゲーションの制御(クラス BackControl) */
/******************************************************/
class BackControl {
	protected double alpha, eata;   //重み及びバイアス修正パラメータ
	protected double eps;   // 許容誤差
	protected int seed;   // 乱数の初期値;
	protected int order;   // 入力パターンの与え方(=0:順番,=1:ランダム)
	protected int p_type;   // 出力先・方法の指定
                            //   =0 : 誤って認識した数だけ出力
                            //   =1 : 認識結果を出力
                            //   =2 : 認識結果と重みを出力
                            //        (負の時は,認識結果と重みをファイルへも出力)
	protected int c_size;   // 円の大きさ(直径,0のときは画面を表示しない)
	protected int f_size;   // フォントサイズ(0のときはユニット番号を表示しない)
	protected int width;   // 画面の幅
	protected int hight;   // 画面の高さ
	protected String o_file;   // 出力ファイル名

	/*************************************/
	/* クラスBackControlのコンストラクタ */
	/*      name : 入力データファイル名  */
	/*************************************/
	BackControl(String name) throws IOException, FileNotFoundException
	{
		StringTokenizer str;

		BufferedReader in = new BufferedReader(new FileReader(name));

		str = new StringTokenizer(in.readLine(), " ");
		str.nextToken();
		eps = Double.parseDouble(str.nextToken());
		str.nextToken();
		p_type = Integer.parseInt(str.nextToken());
		if (p_type < 0) {
			str.nextToken();
			o_file = str.nextToken();
		}

		str = new StringTokenizer(in.readLine(), " ");
		str.nextToken();
		order = Integer.parseInt(str.nextToken());
		str.nextToken();
		eata = Double.parseDouble(str.nextToken());
		str.nextToken();
		alpha = Double.parseDouble(str.nextToken());
		str.nextToken();
		seed = Integer.parseInt(str.nextToken());

		str = new StringTokenizer(in.readLine(), " ");
		str.nextToken();
		c_size = Integer.parseInt(str.nextToken());
		if (c_size > 0) {
			f_size = Integer.parseInt(str.nextToken());
			width = Integer.parseInt(str.nextToken());
			hight = Integer.parseInt(str.nextToken());
		}

		in.close();
	}
}

/*****************************************************/
/* バックプロパゲーションのデータ(クラス BackData) */
/*****************************************************/
class BackData {
	int noiu;   // 入力ユニットの数
	int noou;   // 出力ユニットの数
	int noip;   // 入力パターンの数
	double iptn[][];   // iptn[i][j] : (i+1)番目の入力パターンの(j+1)番目の
                       //              入力ユニットの入力値
                       //                i=0,noip-1  j=0,noiu-1
	double optn[][];   // optn[i][j] : (i+1)番目の入力パターンに対する(j+1)
                       //              番目の出力ユニットの目標出力値
                       //                i=0,noip-1  j=0,noou-1

	/************************************/
	/* クラスBackDataのコンストラクタ   */
	/*      name : 入力データファイル名 */
	/************************************/
	BackData(String name) throws IOException, FileNotFoundException
	{
		int i1, i2;
		StringTokenizer str;

		BufferedReader in = new BufferedReader(new FileReader(name));
	/*
	    入力パターン数等
	*/
		str = new StringTokenizer(in.readLine(), " ");
		str.nextToken();
		noip = Integer.parseInt(str.nextToken());
		str.nextToken();
		noiu = Integer.parseInt(str.nextToken());
		str.nextToken();
		noou = Integer.parseInt(str.nextToken());
	/*
	     領域の確保
	*/
		iptn = new double [noip][noiu];
		optn = new double [noip][noou];
	/*
	     入力パターン及び各入力パターンに対する出力パターンの入力
	*/
		for (i1 = 0; i1 < noip; i1++) {
			str = new StringTokenizer(in.readLine(), " ");
			str.nextToken();
			for (i2 = 0; i2 < noiu; i2++)
				iptn[i1][i2] = Double.parseDouble(str.nextToken());
			str = new StringTokenizer(in.readLine(), " ");
			str.nextToken();
			for (i2 = 0; i2 < noou; i2++)
				optn[i1][i2] = Double.parseDouble(str.nextToken());
		}

		in.close();
	}
}

/*******************************************/
/* バックプロパゲーション(クラス Backpr) */
/*******************************************/
class Backpr extends BackControl {
	private byte 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
	private int f_type;   // シグモイド関数のタイプ
                          //   0 : [0,1]
                          //   1 : [-1,1])
	private int noiu;   // 入力ユニットの数
	private int noou;   // 出力ユニットの数
	private int nohu[];   // nohu[i] : レベル(i+1)の隠れ層のユニット数(隠れ層
                          //           には下から順に番号が付けられ,出力層はレ
                          //           ベル(nolvl+1)の隠れ層とも見做される)
                          //             i=0,nolvl
	private int nolvl;   // 隠れユニットの階層数
	private int nou;   // 入力,出力,及び,隠れ層の各ユニットの数の和(各ユニ
                       // ットには最も上の出力ユニットから,隠れ層の各ユニット,
                       // 及び,入力ユニットに至る一連のユニット番号が付けられ
                       // る)
	private double dp[];   // dp[i] : ユニット(i+1)の誤差  i=0,nou-1
	private double op[];   // op[i] : ユニット(i+1)の出力  i=0,nou-1
	private double theta[];   //theta[i] : ユニット(i+1)のバイアス  i=0,nou
	private double 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
	private Random rn;   // 乱数

	/************************************************/
	/* クラスBackprのコンストラクタ                 */
	/*      name_c : 制御データ用入力ファイル名     */
	/*      name_s : ネットワーク記述入力ファイル名 */
	/************************************************/
	Backpr(String name_c, String name_s) throws IOException, FileNotFoundException
	{
		super(name_c);

		double x1, x2;
		int i0, i1, i2, id, k, k1, k2, k3, k4, l1, l2, n, sw;
		StringTokenizer str;

		BufferedReader in = new BufferedReader(new FileReader(name_s));
	/*
	     乱数の初期化
	*/
		rn  = new Random(seed);   // 乱数の初期設定
	/*
	     入力ユニット,出力ユニットの数,関数タイプ
	*/
		str = new StringTokenizer(in.readLine(), " ");
		str.nextToken();
		noiu = Integer.parseInt(str.nextToken());
		str.nextToken();
		noou = Integer.parseInt(str.nextToken());
		str.nextToken();
		f_type = Integer.parseInt(str.nextToken());
		nou = noiu + noou;   // 入力ユニットと出力ユニットの和
	/*
	     隠れユニットの階層数と各階層のユニット数
	*/
		str = new StringTokenizer(in.readLine(), " ");
		str.nextToken();
		nolvl = Integer.parseInt(str.nextToken());

		nohu        = new int [nolvl+1];
		nohu[nolvl] = noou;   // 出力ユニットの数

		if (nolvl > 0) {
			str.nextToken();
			for (i1 = 0; i1 < nolvl; i1++) {
				nohu[i1]  = Integer.parseInt(str.nextToken());
				nou      += nohu[i1];
			}
		}
	/*
	     領域の確保
	*/
		con = new byte [nou][nou];
		for (i1 = 0; i1 < nou; i1++) {
			for (i2 = 0; i2 < nou; i2++)
				con[i1][i2] =  (i1 == i2) ? (byte)-1 : (byte)0;
		}

		w = new double [nou][nou];
		for (i1 = 0; i1 < nou; i1++) {
			for (i2 = 0; i2 < nou; i2++)
				w[i1][i2] = 0.0;
		}

		dp    = new double [nou];
		op    = new double [nou];
		theta = new double [nou];

		for (i1 = 0; i1 < nou; i1++)
			theta[i1] = 0.2 * rn.nextDouble() - 0.1;
	/*
	     各ユニットのバイアスとユニット間の接続関係
	*/
					// バイアス
						// バイアスデータの数
		str = new StringTokenizer(in.readLine(), " ");
		str.nextToken();
		n = Integer.parseInt(str.nextToken());
		in.readLine();
		in.readLine();
		in.readLine();

		if (n > 0) {
						// バイアスデータの処理
			for (i0 = 0; i0 < n; i0++) {
							// ユニット番号
				str = new StringTokenizer(in.readLine(), " ");
				k1 = Integer.parseInt(str.nextToken());
							// 不適当なユニット番号のチェック
				if (k1 < 1 || k1 > (nou-noiu)) {
					System.out.print("***error  ユニット番号 " + k1 + " が不適当\n");
					System.exit(1);
				}
							// バイアスの与え方
				k1--;
				id = Integer.parseInt(str.nextToken());
				con[k1][k1] = (byte)id;
							// バイアスの初期設定
				switch (con[k1][k1]) {
					case -1:
						x1 = Double.parseDouble(str.nextToken());
						x2 = Double.parseDouble(str.nextToken());
						theta[k1] = (x2 - x1) * rn.nextDouble() + x1;
						break;
					case -2:
						theta[k1] = Double.parseDouble(str.nextToken());
						break;
					case -3:
						theta[k1] = Double.parseDouble(str.nextToken());
						break;
					default:
						System.out.print("***error  バイアスの与え方が不適当\n");
						System.exit(1);
				}
			}
		}
					// 接続方法
						// 接続データの数
		str = new StringTokenizer(in.readLine(), " ");
		str.nextToken();
		n = Integer.parseInt(str.nextToken());
		in.readLine();
		in.readLine();
		in.readLine();

		if (n > 0) {
						// 接続データの処理
			for (i0 = 0; i0 < n; i0++) {
							// 接続情報
				str = new StringTokenizer(in.readLine(), " ");
				k1 = Integer.parseInt(str.nextToken());
				k2 = Integer.parseInt(str.nextToken());
				k3 = Integer.parseInt(str.nextToken());
				k4 = Integer.parseInt(str.nextToken());
							// 不適切な接続のチェック
				sw = 0;
				if (k1 < 1 || k2 < 1 || k3 < 1 || k4 < 1)
					sw = 1;
				else {
					if (k1 > nou || k2 > nou || k3 > nou || k4 > nou)
						sw = 1;
					else {
						if (k1 > k2 || k3 > k4)
							sw = 1;
						else {
							if (k4 >= k1)
								sw = 1;
							else {
								l1 = -1;
								k  = 0;
								for (i1 = nolvl; i1 >= 0 && l1 < 0; i1--) {
									k += nohu[i1];
									if (k1 <= k)
										l1 = i1;
								}
								l2 = -1;
								k  = 0;
								for (i1 = nolvl; i1 >= 0 && l2 < 0; i1--) {
									k += nohu[i1];
									if (k4 <= k)
										l2 = i1;
								}
								if (l2 <= l1)
									sw = 1;
							}
						}
					}
				}

				if (sw > 0) {
					System.out.print("***error  ユニット番号が不適当("
                                       + k1 + " " + k2 + " " + k3 + " " + k4 + ")\n");
					System.exit(1);
				}
							// 重みの初期値の与え方
				k1--;
				k2--;
				k3--;
				k4--;

				id = Integer.parseInt(str.nextToken());
				x1 = 0.0;
				x2 = 0.0;

				if (id == 1) {
					x1 = Double.parseDouble(str.nextToken());
					x2 = Double.parseDouble(str.nextToken());
				}
				else {
					if (id > 1)
						x1 = Double.parseDouble(str.nextToken());
					else {
						if (id != 0) {
							System.out.print("***error  接続方法が不適当\n");
							System.exit(1);
						}
					}
				}
							// 重みの初期値の設定
				for (i1 = k3; i1 <= k4; i1++) {
					for (i2 = k1; i2 <= k2; i2++) {
						con[i1][i2] = (byte)id;
						switch (id) {
							case 0:
								w[i1][i2] = 0.0;
							case 1:
								w[i1][i2] = (x2 - x1) * rn.nextDouble() + x1;
								break;
							case 2:
								w[i1][i2] = x1;
								break;
							case 3:
								w[i1][i2] = x1;
								break;
						}
					}
				}
			}
		}

		in.close();
	/*
	     画面表示
	*/
		Win win;
		if (c_size > 0)
			win = new Win("Network Structure", c_size, f_size, width, hight,
                          noiu, nohu, nolvl, con);
	}

	/******************************************/
	/* 誤差の計算,及び,重みとバイアスの修正 */
	/*      ptn[i1] : 出力パターン            */
	/******************************************/
	void Err_back(double ptn[])
	{
		double x1;
		int i1, i2;

		for (i1 = 0; i1 < nou-noiu; i1++) {
					// 誤差の計算
			if (i1 < noou) {
				if (f_type == 0)
					dp[i1] = (ptn[i1] - op[i1]) * op[i1] * (1.0 - op[i1]);
				else
					dp[i1] = 0.5 * (ptn[i1] - op[i1]) * 
                             (op[i1] - 1.0) * (op[i1] + 1.0);
			}
			else {
				x1 = 0.0;
				for (i2 = 0; i2 < i1; i2++) {
					if (con[i2][i1] > 0)
						x1 += dp[i2] * w[i2][i1];
				}
				if (f_type == 0)
					dp[i1] = op[i1] * (1.0 - op[i1]) * x1;
				else
					dp[i1] = 0.5 * (op[i1] - 1.0) * (op[i1] + 1.0) * x1;
			}
					// 重みの修正
			for (i2 = i1+1; i2 < nou; i2++) {
				if (con[i1][i2] == 1 || con[i1][i2] == 2) {
					x1         = eata * dp[i1] * op[i2] + alpha * w[i2][i1];
					w[i2][i1]  = x1;
					w[i1][i2] += x1;
				}
			}
					// バイアスの修正
			if (con[i1][i1] >= -2) {
				x1         = eata * dp[i1] + alpha * w[i1][i1];
				w[i1][i1]  = x1;
				theta[i1] += x1;
			}
		}
	}

	/********************************************************/
	/* 与えられた入力パターンに対する各ユニットの出力の計算 */
	/********************************************************/
	void Forward()
	{
		double sum;
		int i1, i2;

		for (i1 = nou-noiu-1; i1 >= 0; i1--) {

			sum = -theta[i1];

			for (i2 = i1+1; i2 < nou; i2++) {
				if (con[i1][i2] > 0)
					sum -= w[i1][i2] * op[i2];
			}

			op[i1] = (f_type == 0) ? 1.0 / (1.0 + Math.exp(sum)) :
                                     1.0 - 2.0 / (1.0 + Math.exp(sum));
		}
	}

	/*****************************/
	/* 学習の実行                */
	/*      p : 認識パターン     */
	/*      m_tri : 最大学習回数 */
	/*****************************/
	void Learn(BackData p, int m_tri)
	{
		int i1, i2, k0 = -1, k1;
	/*
	     エラーチェック
	*/
		if (noiu != p.noiu || noou != p.noou) {
			System.out.print("***error  入力または出力ユニットの数が違います\n");
			System.exit(1);
		}

		for (i1 = 0; i1 < m_tri; i1++) {
	/*
	     パターンを与える順番の決定
	*/
			if (order == 0) {       // 順番
				k0++;
				if (k0 >= p.noip)
					k0 = 0;
			}
			else {                  // ランダム
				k0 = (int)(rn.nextDouble() * p.noip);
				if (k0 >= p.noip)
					k0 = p.noip - 1;
			}
	/*
	     出力ユニットの結果を計算
	*/
			k1 = nou - noiu;
			for (i2 = 0; i2 < noiu; i2++)
				op[k1+i2] = p.iptn[k0][i2];

			Forward();
	/*
	     重みとバイアスの修正
	*/
			Err_back(p.optn[k0]);
		}
	}

	/***********************************************/
	/* 与えられた対象の認識と出力                  */
	/*      p : 認識パターン                       */
	/*      pr : =0 : 出力を行わない               */
	/*           =1 : 出力を行う                   */
	/*           =2 : 出力を行う(未学習パターン) */
	/*      tri : 現在の学習回数                   */
	/*      return : 誤って認識したパターンの数    */
	/***********************************************/
	int Recog(BackData p, int pr, int tri) throws IOException, FileNotFoundException
	{
		int i1, i2, k1, No = 0, ln, sw;
		String next;

		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	/*
	     ファイルのオープン
	*/
		PrintStream out = null;
		if (p_type < 0 && pr > 0) {
			if (pr == 1) {
				out = new PrintStream(new FileOutputStream(o_file));
				out.print("***学習パターン***\n\n");
			}
			else {
				out = new PrintStream(new FileOutputStream(o_file, true));
				out.print("\n***未学習パターン***\n\n");
			}
		}
	/*
	     各パターンに対する出力
	*/
		for (i1 = 0; i1 < p.noip; i1++) {
					// 入力パターンの設定
			k1 = nou - noiu;
			for (i2 = 0; i2 < noiu; i2++)
				op[k1+i2] = p.iptn[i1][i2];
					// 出力の計算
			Forward();
					// 結果の表示
			if (p_type != 0 && pr > 0) {

				System.out.print("入力パターン" + (i1+1) + "   ");
				for (i2 = 0; i2 < noiu; i2++) {
					System.out.print(" " + op[k1+i2]);
					if (i2 == noiu-1)
						System.out.print("\n");
					else {
						if(((i2+1) % 10) == 0)
							System.out.print("\n                   ");
					}
				}

				System.out.print("\n    出力パターン(理想)  ");
				for (i2 = 0; i2 < noou; i2++) {
					System.out.print(" " + p.optn[i1][i2]);
					if (i2 == noou-1)
						System.out.print("\n");
					else {
						if(((i2+1) % 5) == 0)
							System.out.print("\n                        ");
					}
				}
			}

			sw = 0;
			if (p_type != 0 && pr > 0)
				System.out.print("                (実際)  ");
			for (i2 = 0; i2 < noou; i2++) {
				if (p_type != 0 && pr > 0) {
					System.out.print(" " + op[i2]);
					if (i2 == noou-1)
						System.out.print("\n");
					else {
						if(((i2+1) % 5) == 0)
							System.out.print("\n                        ");
					}
				}
				if (Math.abs(op[i2]-p.optn[i1][i2]) > eps)
					sw = 1;
			}

			if (sw > 0)
				No++;

			if (p_type < 0 && pr > 0) {

				out.print("入力パターン" + (i1+1) + "   ");
				for (i2 = 0; i2 < noiu; i2++) {
					out.print(" " + op[k1+i2]);
					if (i2 == noiu-1)
						out.print("\n");
					else {
						if(((i2+1) % 10) == 0)
							out.print("\n                   ");
					}
				}

				out.print("\n    出力パターン(理想)  ");
				for (i2 = 0; i2 < noou; i2++) {
					out.print(" " + p.optn[i1][i2]);
					if (i2 == noou-1)
						out.print("\n");
					else {
						if(((i2+1) % 5) == 0)
							out.print("\n                        ");
					}
				}

				out.print("                (実際)  ");
				for (i2 = 0; i2 < noou; i2++) {
					out.print(" " + op[i2]);
					if (i2 == noou-1)
						out.print("\n");
					else {
						if(((i2+1) % 5) == 0)
							out.print("\n                        ");
					}
				}
			}

			if (p_type != 0 && pr > 0)
				next = in.readLine();
		}
	/*
	     重みの出力
	*/
		if ((p_type < -1 || p_type > 1) && pr == 1) {

			System.out.print("    重み\n");
			for (i1 = 0; i1 < nou-noiu; i1++) {
				System.out.print("      to " + (i1+1) + " from  ");
				ln = -1;
				for (i2 = 0; i2 < nou; i2++) {
					if (con[i1][i2] > 0) {
						if (ln <= 0) {
							if (ln < 0)
								ln = 0;
							else
								System.out.print("\n                   ");
						}
						System.out.print(" " + (i2+1) + " " + w[i1][i2]);
						ln += 1;
						if (ln == 4)
							ln = 0;
					}
				}

				System.out.print("\n");
			}

			System.out.print("\n    バイアス  ");
			ln = 0;
			for (i1 = 0; i1 < nou-noiu; i1++) {
				System.out.print(" " + (i1+1) + " " + theta[i1]);
				ln += 1;
				if (ln == 4 && i1 != nou-noiu-1) {
					ln = 0;
					System.out.print("\n              ");
				}
			}

			if (ln != 0)
				System.out.print("\n");

			next = in.readLine();
		}

		if (p_type < 0 && pr == 1) {

			out.print("    重み\n");
			for (i1 = 0; i1 < nou-noiu; i1++) {
				out.print("      to " + (i1+1) + " from  ");
				ln = -1;
				for (i2 = 0; i2 < nou; i2++) {
					if (con[i1][i2] > 0) {
						if (ln <= 0) {
							if (ln < 0)
								ln = 0;
							else
								out.print("\n                   ");
						}
						out.print(" " + (i2+1) + " " + w[i1][i2]);
						ln += 1;
						if (ln == 4)
							ln = 0;
					}
				}

				out.print("\n");
			}

			out.print("\n    バイアス  ");
			ln = 0;
			for (i1 = 0; i1 < nou-noiu; i1++) {
				out.print(" " + (i1+1) + " " + theta[i1]);
				ln += 1;
				if (ln == 4 && i1 != nou-noiu-1) {
					ln = 0;
					out.print("\n              ");
				}
			}

			if (ln != 0)
				out.print("\n");
		}

		if (p_type < 0 && pr > 0)
			out.close();

		return No;
	}
}

/*******************/
/* クラスWinの定義 */
/*******************/
class Win extends Frame {

	byte con[][];   // 接続関係
	int nohu[];   // 各層のユニット数(入力層から出力層)
	int nolvl;   // 層の数
	int nou;   // ユニット数
	int c_size;   // 円の大きさ(直径)
	int f_size;   // フォントサイズ
	int width;   // 画面の幅
	int height;   // 画面の高さ
	int x[], y[];   // ユニットの位置

	/**************************************************/
	/* コンストラクタ                                 */
	/*      name : Windowのタイトル                   */
	/*      c_size_i : 円の大きさ(直径)             */
	/*      f_size_i : フォントサイズ                 */
	/*      width_i : 画面の幅                        */
	/*      height_i : 画面の高さ                     */
	/*      noiu_i : 入力層のユニット数               */
	/*      nohu_i : 各層のユニット数(入力層を除く) */
	/*      nolvl_i : 層の数(入力層を除く)          */
	/*      con_i : 接続関係                          */
	/**************************************************/
	Win (String name, int c_size_i, int f_size_i, int width_i, int height_i,
         int noiu_i, int nohu_i[], int nolvl_i, byte con_i[][])
	{
					// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
		super(name);
					// データの設定
		int i1, i2;

		c_size = c_size_i;
		f_size = f_size_i;
		width  = width_i;
		height = height_i;

		nou = noiu_i;
		for (i1 = 0; i1 <= nolvl_i; i1++)
			nou += nohu_i[i1];

		nolvl = nolvl_i + 2;
		nohu  = new int [nolvl];
		for (i1 = 0; i1 <= nolvl_i; i1++)
			nohu[i1+1] = nohu_i[i1];
		nohu[0] = noiu_i;

		con = new byte [nou][nou];
		for (i1 = 0; i1 < nou; i1++) {
			for (i2 = 0; i2 < nou; i2++)
				con[i1][i2] = con_i[i1][i2];
		}

		x = new int [nou];
		y = new int [nou];
					// Windowの大きさ
		setSize(width, height);
					// ウィンドウを表示
		setVisible(true);
					// イベントアダプタ
		addWindowListener(new WinEnd());
	}

	/********/
	/* 描画 */
	/********/
	public void paint (Graphics g)
	{
						// 初期設定
		int y_s = (f_size > 25) ? 25 + f_size : 50;
		int y_e = (c_size > 20 ) ? height - c_size - 10 : height - 30;
		int c_x, c_y = y_s;
		int i1, i2, k = 0;
		int x_step, y_step = (y_e - y_s) / (nolvl - 1);
		Font f;
						// フォントサイズ
		if (f_size > 0) {
			f = new Font("TimesRoman", Font.PLAIN, f_size);
			g.setFont(f);
		}
						// 各ユニットを描く
		for (i1 = nolvl-1; i1 >= 0; i1--) {
			x_step = width / nohu[i1];
			c_x    = x_step / 2;
			for (i2 = 0; i2 < nohu[i1]; i2++) {
				x[k] = c_x;
				y[k] = c_y;
				g.fillOval(x[k], y[k], c_size, c_size);
				if (f_size > 0)
					g.drawString(Integer.toString(k+1), x[k]-f_size/2, y[k]);
				c_x += x_step;
				k++;
			}
			c_y += y_step;
		}
						// 接続関係を描く
		k = c_size / 2;
		for (i1 = 0; i1 < nou-nohu[0]; i1++) {
			for (i2 = i1+1; i2 < nou; i2++ ) {
				if (con[i1][i2] != 0)
					g.drawLine(x[i1]+k, y[i1]+k, x[i2]+k, y[i2]+k);
			}
		}
	}

	/************/
	/* 終了処理 */
	/************/
	class WinEnd extends WindowAdapter
	{
		public void windowClosing(WindowEvent e) {
			System.exit(0);
		}
	}
}
		

  コンパイルした後,

java Test 制御データファイル名 構造データファイル名

と入力してやれば実行が開始されます.

  制御データファイルは,バックプロパゲーションの全体の実行を制御するためのものであり,たとえば以下のような形式で作成します.なお,以下,右図に示すようなネットワークにより,排他的論理和を学習させる場合を例として説明を行っていきます.
誤差 0.1 出力 -2 出力ファイル kekka
順番 0 η 0.5 α 0.8 乱数 123
画面表示(円の大きさ,フォントサイズ,幅,高さ) 20 20 400 300		
  日本語で記述した部分(「誤差」,「出力」等)は,次に続くデータの説明ですのでどのように修正しても構いませんが,削除したり,または,複数の文(間に半角のスペースを入れる)にするようなことはしないでください(以下の説明においても同様).各データの意味は以下に示す通りです.

誤差

  収束の判定を行うための値です.各出力ユニットの実際の出力値と目標出力値の差がこの値以下になると収束と見なします.

出力

  出力先とその方法を指定します.以下に示すいずれかの値を入力します.
  • =0:誤って認識した数だけ出力
  • =1:認識結果を出力
  • =2:認識結果と重みを出力
なお,負の値を使用すると,結果をファイルへも出力します.この例の場合,-2 となっていますので,認識結果と重みが,ディスプレイとファイルに出力されます.

出力ファイル

  上の「出力」の項目に対して負の値を入力したときに必要となり,出力ファイル名を入力します.なお,ファイルへ出力をしないときは,「出力ファイル kekka 」の項を削除しておいてください.

順番

  学習例をどのような順番で与えるかを指定します.0 を入力すると学習例が入力された順番通り,また,1 を入力するとランダムに学習例が選ばれます.

η,および,α

  重みを修正する際の係数です

乱数

  乱数の初期値です

3 行目

  これらは,ネットワークの接続状況を図示するためのデータです.最初のデータは,円の大きさ(直径,各ユニットを円で描きます)を意味しています.この値を 0 にすると,接続状況の図示を行いません. 2 番目のデータは,ユニット番号を表示するフォントの大きさです.0 を入力すると,ユニット番号の表示を行いません. 3,および,4 番目のデータは,Windows の大きさです.Windows の幅と高さを入力します.

  構造データファイルは,ネットワークの構造(ユニットの数,接続関係等)を記述するためのものであり,たとえば以下のような形式で作成します.
入力ユニット数 2 出力ユニット数 1 関数タイプ 0
隠れ層の数 1 各隠れ層のユニット数(下から) 1
バイアス入力ユニット数 1
 ユニット番号:出力ユニットから順に番号付け
 入力方法:=-3:固定,=-2:入力後学習,=-1:乱数(default,[-0.1,0.1]))
 値:バイアス値(ー2またはー3の時)または一様乱数の範囲(下限,上限)
1 -1 -0.1 0.1
接続方法の数 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		
  各データの意味は以下に示す通りです.

入力ユニット数

  入力ユニット数を入力します

出力ユニット数

  出力ユニット数を入力します

関数タイプ

  各ユニットの出力値を計算するシグモイド関数の形を指定します.0 を入力すると各ユニットの出力値の範囲が [0, 1] となり,また,1 を入力すると [-1, 1] となります.

隠れ層の数

  隠れ層(入力層,出力層は含まれません)の数を入力します.

各隠れ層のユニット数(下から)

  各隠れ層に含まれるユニット数を入力します.たとえば,隠れ層の数が 2 であり(当然,「隠れ層の数」に対応する値も 2 にしなければなりません),1 番目の隠れ層(入力層のすぐ上)に含まれるユニット数が 5,かつ,2 番目の隠れ層に含まれるユニット数が 3 であるときは,この部分は,上で述べた項目も含め,

隠れ層の数 2 各隠れ層のユニット数(下から) 5 3

となります.なお,隠れ層の数が 0 の場合は,「各隠れ層のユニット数」以下を削除してください.

バイアス入力ユニット数

  何も指定しなければ,各ユニットのバイアスの初期値は,[-0.1 0.1] 区間の一様乱数によって設定されます.バイアスの初期値を変更したり,または,特定の値に固定したいような場合にこの項を使用します.ここで指定するのは,特別な指定を行いたいユニットの数です.なお,この項に続く 3 行は単なるコメントですが,この項に 0 を入力した場合でも削除しないでください.

1 -1 -0.1 0.1

  バイアス指定データです.上の項で指定した数だけこのようなデータが必要になります.このデータの意味するところは以下の通りです.まず,最初の数字(この例では 1 )は,ユニット番号を表しています.ネットワークの各ユニットは出力ユニットから順番に図に示すような番号付けが行われます.その番号を指定してください.

  次の数字(この例では -1 )は,バイアスの設定方法を示します.-1 の場合は,一様乱数を使用します.この例のように,次に続く 2 つのデータによって一様乱数の区間(この例の場合,[-0.1 0.1] )を指定します.

  -2 や -3 を指定した場合は,一様乱数を使用せず,次に続くデータがそのままバイアスの初期値となります.たとえば,ユニット 1 のバイアスの初期値を 0.1 にしたい場合は以下のようにします.

1 -2 0.1

なお,-3 の場合は,与えられた初期値を学習によって変更せず,そのまま固定されます.

接続方法の数

  接続方法を示すデータ群の数を入力します.接続方法を入力しないと,各ユニットは全く接続されない状態となります.従って,この項には必ず 0 より大きい値が設定されるはずです.なお,この項に続く 3 行は単なるコメントですが,絶対に削除しないでください.

3 4 1 2 1 -0.1 0.1
2 2 1 1 1 -0.1 0.1

  各々,接続方法を示すデータ群です.この例の場合,上の項で 2 を入力しているため,これら 2 つのデータ群を必要とします.各データの意味するところは以下の通りです.

  最初の 4 つのデータ( k1, k2, k3, k4 とする)は接続するユニット番号を表しています.ユニット番号が k1 から k2 (その間のユニットも含む)であるユニットを,ユニット番号が k3 から k4 (その間のユニットも含む)であるユニットに接続することを意味しています.たとえば,「3 5 1 2」の入力により,「3-1, 3-2, 4-1, 4-2, 5-1, 5-2」という 6 つの接続が生成されます.この例の場合,どのようになるかは,図と照らし合わせてみれば明らかだと思います.

  5 つ目の数字は,接続方法を表し,0,1,2,および,3 のいずれかの値を設定します.0 を選択すると最初の 4 つのデータで指定されたユニット間の接続が削除されます.それ以外の値を入力した場合は,指定されたユニット間が接続されます.ただし,接続方法として指定した値によって異なってきます.その意味は,ユニットのバイアスの項で説明した -1,-2,および,-3 の場合を,重みの初期値に読み替えてみれば明らかだと思います.

  上で説明したデータの元で実行すると,以下に示すようなメッセージが出力されますので,下線部を入力してください.

学習回数は? 5000
何回毎に収束を確認しますか? 500
学習パターンのファイル名は? learn.dat

  与えられた 1 つの学習例(訓練例,学習パターン)のもとで,重みを修正する手続きを 1 回の学習とします.従って,上の例では,この動作を 5000 回行うことになります.

  このプログラムでは,1 回の学習毎に収束したか否か(すべての例を正しく分類したか否か)の判定を行いません.上の例の 2 行目で与えられた回数毎にその判定を行います.そのとき,収束していれば,学習手続きを終了します.

  3 番目に与えているのが,学習例を記述したファイル名です. この例の場合,ファイル learn.dat は以下のように記述されています.
パターンの数 4 入力ユニット数 2 出力ユニット数 1
入力1 0 0
 出力1 0
入力2 0 1
 出力2 1
入力3 1 0
 出力3 1
入力4 1 1
 出力4 0		
  各データの意味するところは以下の通りです.

パターンの数

  学習例の数を入力します.この例では,2 ビットの排他的論理和を 4 つの学習例を使用して学習を行うことになります.なお,入力ユニット数,および,出力ユニット数に関しては,構造データファイルで指定した値と同じ値に設定してください.

入力1

  各学習例において,各入力ユニットに入力される値を設定します.この例の場合,入力ユニットの数が 2 ですので,2 つの値を必要とします.

出力1

  上の入力が与えられたときの目標出力値を入力します.上の項と同様,出力ユニットの数だけ数値が並ぶことになります.

  以上のデータを与えると,以下のような出力が得られます.
回数 500 誤って認識したパターン数 4
回数 1000 誤って認識したパターン数 4
回数 1500 誤って認識したパターン数 4
回数 2000 誤って認識したパターン数 4
回数 2500 誤って認識したパターン数 4
回数 3000 誤って認識したパターン数 3
回数 3500 誤って認識したパターン数 0		
  収束,または,規定の学習回数を終了すると,以下のような結果がディスプレイ(および,ファイル)に出力されます.なお,ディスプレイ出力は,適宜停止しますが,return キーを押せば次の出力が得られます.
入力パターン   1     0.00 0.00
    出力パターン(理想)        0.000
                (実際)        0.060
入力パターン   2     0.00 1.00
    出力パターン(理想)        1.000
                (実際)        0.911
       ・・・・・・
    重み
      to   1 from      2    -10.700   3     -4.675   4     -4.675
      to   2 from      3     -6.825   4     -6.827

    バイアス      1      7.139   2      2.501		
  出力パターンの理想の右側に書かれた値が目標出力値であり,その下が,実際の出力値を表しています.また,重みの項は,ユニット 2 から 1 への重みが -10.700,3 から 1 への重みが -4.675 等のように読んでください.

  学習が終了すると,

未学習パターンの認識を行いますか?(=1:行う,=0:行わない)

というメッセージが出力されます.未学習パターンの認識を行う場合は,1 を入力した後,その後で出力されるメッセージに従って,未学習パターンを記述したファイル名(認識データ)を入力してください.なお,未学習パターンファイルの記述方法は,学習例ファイル(学習データ)と全く同じです.添付したプログラム内では,学習データと全く同じデータが与えてありますので,認識データに対しても同じ結果が得られるはずです.