バックプロパゲーション

# -*- coding: UTF-8 -*-
import sys
from math import *
from random import *
import numpy as np

######################################################
# バックプロパゲーションの制御(クラス BackControl)
######################################################

class BackControl :
	
	####################################
	# クラスBackControlのコンストラクタ
	#      name : 入力データファイル名
	####################################
	
	def __init__(self, name) :
	
		f           = open(name, "r")
		s           = f.readline().split()
		self.eps    = float(s[1])   # 許容誤差
		self.p_type = int(s[3])   # 出力先・方法の指定
	                  #   =0 : 誤って認識した数だけ出力
	                  #   =1 : 認識結果を出力
	                  #   =2 : 認識結果と重みを出力
	                  #        (負の時は,認識結果と重みをファイルへも出力)
		if self.p_type < 0 :
			self.o_file = s[5]   # 出力ファイル名
		s          = f.readline().split()
		self.order = int(s[1])   # 入力パターンの与え方(=0:順番,=1:ランダム)
		self.eata  = float(s[3])   # 重み及びバイアス修正パラメータ
		self.alpha = float(s[5])   # 重み及びバイアス修正パラメータ
		f.close()
	
######################################################
# バックプロパゲーションのデータ(クラス BackData)
######################################################

class BackData :
	
	####################################
	# クラスBackDataのコンストラクタ
	#      name : 入力データファイル名
	####################################
	
	def __init__(self, name) :
				# 入力パターン数等
		f         = open(name, "r")
		s         = f.readline().split()
		self.noip = int(s[1])   # 入力パターンの数
		self.noiu = int(s[3])   # 入力ユニットの数
		self.noou = int(s[5])   # 出力ユニットの数
				# 領域の確保
		self.iptn = np.empty((self.noip, self.noiu), np.float)
	                      # iptn[i][j] : (i+1)番目の入力パターンの(j+1)番目の
	                      #              入力ユニットの入力値
	                      #                i=0,noip-1  j=0,noiu-1
		self.optn = np.empty((self.noip, self.noou), np.float)
	                      # optn[i][j] : (i+1)番目の入力パターンに対する(j+1)
	                      #              番目の出力ユニットの目標出力値
	                      #                i=0,noip-1  j=0,noou-1
				# 入力パターン及び各入力パターンに対する出力パターンの入力
		for i1 in range(0, self.noip) :
			s = f.readline().split()
			for i2 in range(0, self.noiu) :
				self.iptn[i1][i2] = float(s[i2+1])
			s = f.readline().split()
			for i2 in range(0, self.noou) :
				self.optn[i1][i2] = float(s[i2+1])
		f.close()

###########################################
# バックプロパゲーション(クラス Backpr)
###########################################

class Backpr(BackControl) :
	
	################################################
	# クラスBackprのコンストラクタ
	#      name_c : 制御データ用入力ファイル名
	#      name_s : ネットワーク記述入力ファイル名
	################################################
	
	def __init__(self, name_c, name_s) :
	
		BackControl.__init__(self, name_c)   # 親のコンストラクタ
		f = open(name_s, "r")
				# 入力ユニット,出力ユニットの数,関数タイプ
		s           = f.readline().split()
		self.noiu   = int(s[1])   # 入力ユニットの数
		self.noou   = int(s[3])   # 出力ユニットの数
		self.f_type = int(s[5])   # シグモイド関数のタイプ,0 : [0,1],1 : [-1,1]
		self.nou    = self.noiu + self.noou   # 入力ユニットと出力ユニットの和
	                  # 各ユニットには最も上の出力ユニットから,
	                  # 隠れ層の各ユニット,及び,入力ユニットに至る
	                  # 一連のユニット番号が付けられる
				# 隠れユニットの階層数と各階層のユニット数
		s          = f.readline().split()
		self.nolvl = int(s[1])   # 隠れユニットの階層数
		self.nohu  = np.empty(self.nolvl+1, np.int)
	                 # nohu[i] : レベル(i+1)の隠れ層のユニット数(隠れ層
	                 #           には下から順に番号が付けられ,出力層はレ
	                 #           ベル(nolvl+1)の隠れ層とも見做される)
	                 #             i=0,nolvl
		self.nohu[self.nolvl] = self.noou
	
		if self.nolvl > 0 :
			for i1 in range(0, self.nolvl) :
				self.nohu[i1]  = int(s[i1+3])
				self.nou      += self.nohu[i1]
				# 領域の確保
		self.con = np.empty((self.nou, self.nou), np.int)
		for i1 in range(0, self.nou) :
			for i2 in range(0, self.nou) :
				if i1 == i2 :
					self.con[i1][i2] = -1
				else :
					self.con[i1][i2] = 0
	              # 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
	
		self.w = np.empty((self.nou, self.nou), np.float)
		for i1 in range(0, self.nou) :
			for i2 in range(0, self.nou) :
				self.w[i1][i2] = 0.0
	              # 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
	
		self.dp    = np.empty(self.nou, np.float)   # ユニット(i+1)の誤差  i=0,nou-1
		self.op    = np.empty(self.nou, np.float)   # ユニット(i+1)の出力  i=0,nou-1
		self.theta = np.empty(self.nou, np.float)   # ユニット(i+1)のバイアス  i=0,nou
		for i1 in range(0, self.nou) :
			self.theta[i1] = 0.2 * random() - 0.1
				# 各ユニットのバイアスとユニット間の接続関係
						# バイアス
							# バイアスデータの数
		s = f.readline().split()
		n = int(s[1])
		f.readline()
		f.readline()
		f.readline()
	
		if n > 0 :
							# バイアスデータの処理
			for i0 in range(0, n) :
				s = f.readline().split()
								# ユニット番号
				k1 = int(s[0])
								# 不適当なユニット番号のチェック
				if k1 < 1 or k1 > (self.nou-self.noiu) :
					print("***error  ユニット番号 " + str(k1) + " が不適当")
								# バイアスの与え方
				k1 -= 1
				id  = int(s[1])
				self.con[k1][k1] = id
								# バイアスの初期設定
				if self.con[k1][k1] == -1 :
					x1  = float(s[2])
					x2  = float(s[3])
					self.theta[k1] = (x2 - x1) * random() + x1
				elif self.con[k1][k1] == -2 or self.con[k1][k1] == -3 :
					self.theta[k1] = float(s[2])
				else :
					print("***error  バイアスの与え方が不適当")
						# 接続方法
							# 接続データの数
		s = f.readline().split()
		n = int(s[1])
		f.readline()
		f.readline()
		f.readline()
	
		if n > 0 :
							# 接続データの処理
			for i0 in range(0, n) :
				s  = f.readline().split()
								# 接続情報
				k1 = int(s[0])
				k2 = int(s[1])
				k3 = int(s[2])
				k4 = int(s[3])
								# 不適切な接続のチェック
				sw = 0
				if k1 < 1 or k2 < 1 or k3 < 1 or k4 < 1 :
					sw = 1
				else :
					if k1 > self.nou or k2 > self.nou or k3 > self.nou or k4 > self.nou :
						sw = 1
					else :
						if k1 > k2 or k3 > k4 :
							sw = 1
						else :
							if k4 >= k1 :
								sw = 1
							else :
								l1 = -1
								k  = 0
								for i1 in range(self.nolvl, -1, -1) :
									k += self.nohu[i1]
									if k1 <= k :
										l1 = i1
										break
								l2 = -1
								k  = 0
								for i1 in range(self.nolvl, -1, -1) :
									k += self.nohu[i1]
									if k4 <= k :
										l2 = i1
										break
								if l2 <= l1 :
									sw = 1
	
				if sw > 0 :
					print("***error  ユニット番号が不適当(" + str(k1) + " " + str(k2) + " " + str(k3) + " " + str(k4) + ")")
								# 重みの初期値の与え方
				k1 -= 1
				k2 -= 1
				k3 -= 1
				k4 -= 1
	
				id = int(s[4])
	
				if id == 1 :
					x1  = float(s[5])
					x2  = float(s[6])
				else :
					if id > 1 :
						x1  = float(s[5])
					else :
						if id != 0 :
							print("***error  接続方法が不適当")
								# 重みの初期値の設定
				for i1 in range(k3, k4+1) :
					for i2 in range(k1, k2+1) :
						self.con[i1][i2] = id
						if id == 0 :
							self.w[i1][i2] = 0.0
						elif id == 1 :
							self.w[i1][i2] = (x2 - x1) * random() + x1
						elif id == 2 :
							self.w[i1][i2] = x1
						elif id == 3 :
							self.w[i1][i2] = x1
	
		f.close()
	
	##########################################
	# 誤差の計算,及び,重みとバイアスの修正
	#      ptn[i1] : 出力パターン
	##########################################
	
	def Err_back(self, ptn) :
	
		for i1 in range(0, self.nou-self.noiu) :
						# 誤差の計算
			if i1 < self.noou :
				if self.f_type == 0 :
					self.dp[i1] = (ptn[i1] - self.op[i1]) * self.op[i1] * (1.0 - self.op[i1])
				else :
					self.dp[i1] = 0.5 * (ptn[i1] - self.op[i1]) * (self.op[i1] - 1.0) * (self.op[i1] + 1.0)
			else :
				x1 = 0.0
				for i2 in range(0, i1) :
					if self.con[i2][i1] > 0 :
						x1 += self.dp[i2] * self.w[i2][i1]
				if self.f_type == 0 :
					self.dp[i1] = self.op[i1] * (1.0 - self.op[i1]) * x1
				else :
					self.dp[i1] = 0.5 * (self.op[i1] - 1.0) * (self.op[i1] + 1.0) * x1
						# 重みの修正
			for i2 in range(i1+1, self.nou) :
				if self.con[i1][i2] == 1 or self.con[i1][i2] == 2 :
					x1 = self.eata * self.dp[i1] * self.op[i2] + self.alpha * self.w[i2][i1]
					self.w[i2][i1]  = x1
					self.w[i1][i2] += x1
						# バイアスの修正
			if self.con[i1][i1] >= -2 :
				x1 = self.eata * self.dp[i1] + self.alpha * self.w[i1][i1]
				self.w[i1][i1]  = x1
				self.theta[i1] += x1
	
	########################################################
	# 与えられた入力パターンに対する各ユニットの出力の計算
	########################################################
	
	def Forward(self) :
	
		for i1 in range(self.nou-self.noiu-1, -1, -1) :
	
			sum = -self.theta[i1]
	
			for i2 in range(i1+1, self.nou) :
				if self.con[i1][i2] > 0 :
					sum -= self.w[i1][i2] * self.op[i2]
	
			if self.f_type == 0 :
				self.op[i1] = 1.0 / (1.0 + exp(sum))
			else :
				self.op[i1] = 1.0 - 2.0 / (1.0 + exp(sum))
	
	#############################
	# 学習の実行
	#      p : 認識パターン
	#      m_tri : 最大学習回数
	#############################
	
	def Learn(self, p, m_tri) :
	
		k0 = -1
				# エラーチェック
		if self.noiu != p.noiu or self.noou != p.noou :
			print("***error  入力または出力ユニットの数が違います")
	
		for i1 in range(0, m_tri) :
				# パターンを与える順番の決定
			if self.order == 0 :   # 順番
				k0 += 1
				if k0 >= p.noip :
					k0 = 0
			else :   # ランダム
				k0 = int(random() * p.noip)
				if k0 >= p.noip :
					k0 = p.noip - 1
				# 出力ユニットの結果を計算
			k1 = self.nou - self.noiu
			for i2 in range(0, self.noiu) :
				self.op[k1+i2] = p.iptn[k0][i2]
	
			self.Forward()
				# 重みとバイアスの修正
			self.Err_back(p.optn[k0])
	
	################################################
	# 与えられた対象の認識と出力
	#      p : 認識パターン
	#      pr : =0 : 出力を行わない
	#           =1 : 出力を行う
	#           =2 : 出力を行う(未学習パターン)
	#      tri : 現在の学習回数
	#      return : 誤って認識したパターンの数
	################################################
	
	def Recog(self, p, pr, tri) :
	
		No = 0
				# ファイルのオープン
		if self.p_type < 0 and pr > 0 :
			if pr == 1 :
				out = open(self.o_file, "w")
				out.write("***学習パターン***\n\n")
			else :
				out = open(self.o_file, "a")
				out.write("\n***未学習パターン***\n\n")
				# 各パターンに対する出力
		for i1 in range(0, p.noip) :
						# 入力パターンの設定
			k1 = self.nou - self.noiu
			for i2 in range(0, self.noiu) :
				self.op[k1+i2] = p.iptn[i1][i2]
						# 出力の計算
			self.Forward()
						# 結果の表示
			if self.p_type != 0 and pr > 0 :
	
				print("入力パターン{0:4d}    ".format(i1+1), end="")
				for i2 in range(0, self.noiu) :
					print("{0:5.2f}".format(self.op[k1+i2]), end="")
					if i2 == self.noiu-1 :
						print("")
					else :
						if ((i2+1) % 10) == 0 :
							print("\n                    ", end="")
	
				print("\n    出力パターン(理想)   ", end="")
				for i2 in range(0, self.noou) :
					print("{0:10.3f}".format(p.optn[i1][i2]), end="")
					if i2 == self.noou-1 :
						print("")
					else :
						if ((i2+1) % 5) == 0 :
							print("\n                         ", end="")
	
			sw = 0
			if self.p_type != 0 and pr > 0 :
				print("                (実際)   ", end="")
			for i2 in range(0, self.noou) :
				if self.p_type != 0 and pr > 0 :
					print("{0:10.3f}".format(self.op[i2]), end="")
					if i2 == self.noou-1 :
						print("\n")
					else :
						if ((i2+1) % 5) == 0 :
							print("\n                         ", end="")
				if abs(self.op[i2]-p.optn[i1][i2]) > self.eps :
					sw = 1
	
			if sw > 0 :
				No += 1
	
			if self.p_type < 0 and pr > 0 :
	
				out.write("入力パターン{0:4d}    ".format(i1+1))
				for i2 in range(0, self.noiu) :
					out.write("{0:5.2f}".format(self.op[k1+i2]))
					if i2 == self.noiu-1 :
						out.write("\n")
					else :
						if ((i2+1) % 10) == 0 :
							out.write("\n                    ")
	
				out.write("\n    出力パターン(理想)   ")
				for i2 in range(0, self.noou) :
					out.write("{0:10.3f}".format(p.optn[i1][i2]))
					if i2 == self.noou-1 :
						out.write("\n")
					else :
						if ((i2+1) % 5) == 0 :
							out.write("\n                         ")
	
				out.write("                (実際)   ")
				for i2 in range(0, self.noou) :
					out.write("{0:10.3f}".format(self.op[i2]))
					if i2 == self.noou-1 :
						out.write("\n")
					else :
						if ((i2+1) % 5) == 0 :
							out.write("\n                         ")
	
			if self.p_type != 0 and pr > 0 :
				input("")
				# 重みの出力
		if (self.p_type < -1 or self.p_type > 1) and pr == 1 :
	
			print("    重み")
			for i1 in range(0, self.nou-self.noiu) :
				print("      to{0:4d} from   ".format(i1+1), end="")
				ln = -1
				for i2 in range(0, self.nou) :
					if self.con[i1][i2] > 0 :
						if ln <= 0 :
							if ln < 0 :
								ln = 0
							else :
								print("\n                    ", end="")
						print("{0:4d}{1:11.3f}".format(i2+1, self.w[i1][i2]), end="")
						ln += 1
						if ln == 4 :
							ln = 0
	
				print("")
	
			print("\n    バイアス   ", end="")
			ln = 0
			for i1 in range(0, self.nou-self.noiu) :
				print("{0:4d}{1:11.3f}".format(i1+1, self.theta[i1]), end="")
				ln += 1
				if ln == 4 and i1 != self.nou-self.noiu-1 :
					ln = 0
					print("\n               ", end="")
			print("")
	
			input("")
	
		if self.p_type < 0 and pr == 1 :
	
			out.write("    重み\n")
			for i1 in range(0, self.nou-self.noiu) :
				out.write("      to{0:4d} from   ".format(i1+1))
				ln = -1
				for i2 in range(0, self.nou) :
					if self.con[i1][i2] > 0 :
						if ln <= 0 :
							if ln < 0 :
								ln = 0
							else :
								out.write("\n                    ")
						out.write("{0:4d}{1:11.3f}".format(i2+1, self.w[i1][i2]))
						ln += 1
						if ln == 4 :
							ln = 0
	
				out.write("\n")
	
			out.write("\n    バイアス   ")
			ln = 0
			for i1 in range(0, self.nou-self.noiu) :
				out.write("{0:4d}{1:11.3f}".format(i1+1, self.theta[i1]))
				ln += 1
				if ln == 4 and i1 != self.nou-self.noiu-1 :
					ln = 0
					out.write("\n               ")
	
			if ln != 0 :
				out.write("\n")
	
		if self.p_type < 0 and pr > 0 :
			out.close()
	
		return No
	
----------------------------------

# -*- coding: UTF-8 -*-
import numpy as np
import sys
from math import *
from random import *
from function import Backpr, BackData, BackControl

##########################
# back propagation model
#      coded by Y.Suganuma
##########################

ct = 0
no = 1
				# エラー
if len(sys.argv) != 3 :
	print("***error   入力データファイル名を指定して下さい")

else :
				# ネットワークの定義
	net = Backpr(sys.argv[1], sys.argv[2])
				# 学習パターン等の入力
	m_tri  = int(input("学習回数は? "))
	conv   = int(input("何回毎に収束を確認しますか? "))
	f_name = input("学習パターンのファイル名は? ")
	dt1    = BackData(f_name)
				# 学習
	while ct < m_tri and no > 0 :

		if (ct + conv) < m_tri :
			tri = conv
		else :
			tri = m_tri - ct
		ct += tri

		net.Learn(dt1, tri)   # 学習

		no = net.Recog(dt1, 0, ct)   # 学習対象の認識

		print("   回数 " + str(ct) + " 誤って認識したパターン数 " + str(no))

	no = net.Recog(dt1, 1, ct)   # 学習対象の認識と出力
				# 未学習パターンの認識
	sw = int(input("未学習パターンの認識を行いますか?(=1:行う,=0:行わない) "))

	if sw > 0 :

		f_name = input("未学習パターンのファイル名は? ")
		dt2    = BackData(f_name)
		no     = net.Recog(dt2, 2, ct)   # 未学習対象の認識と出力

------------------------制御データ----------------
誤差 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