########################## # back propagation model # coded by Y.Suganuma ########################## ###################################################### # バックプロパゲーションの制御(クラス BackControl) ###################################################### class BackControl #################################### # クラスBackControlのコンストラクタ # name : 入力データファイル名 #################################### def initialize(name) f = open(name, "r") s = f.gets().split(" ") @_eps = Float(s[1]) # 許容誤差 @_p_type = Integer(s[3]) # 出力先・方法の指定 # =0 : 誤って認識した数だけ出力 # =1 : 認識結果を出力 # =2 : 認識結果と重みを出力 # (負の時は,認識結果と重みをファイルへも出力) if @_p_type < 0 @_o_file = s[5] # 出力ファイル名 end s = f.gets().split(" ") @_order = Integer(s[1]) # 入力パターンの与え方(=0:順番,=1:ランダム) @_eata = Float(s[3]) # 重み及びバイアス修正パラメータ @_alpha = Float(s[5]) # 重み及びバイアス修正パラメータ f.close() srand() end end ###################################################### # バックプロパゲーションのデータ(クラス BackData) ###################################################### class BackData #################################### # クラスBackDataのコンストラクタ # name : 入力データファイル名 #################################### def initialize(name) # 入力パターン数等 f = open(name, "r") s = f.gets().split(" ") @_noip = Integer(s[1]) # 入力パターンの数 @_noiu = Integer(s[3]) # 入力ユニットの数 @_noou = Integer(s[5]) # 出力ユニットの数 # 領域の確保 @_iptn = Array.new(@_noip) for i1 in 0 ... @_noip @_iptn[i1] = Array.new(@_noiu) end # iptn[i][j] : (i+1)番目の入力パターンの(j+1)番目の # 入力ユニットの入力値 # i=0,noip-1 j=0,noiu-1 @_optn = Array.new(@_noip) for i1 in 0 ... @_noip @_optn[i1] = Array.new(@_noou) end # optn[i][j] : (i+1)番目の入力パターンに対する(j+1) # 番目の出力ユニットの目標出力値 # i=0,noip-1 j=0,noou-1 # 入力パターン及び各入力パターンに対する出力パターンの入力 for i1 in 0 ... @_noip s = f.gets().split(" ") for i2 in 0 ... @_noiu @_iptn[i1][i2] = Float(s[i2+1]) end s = f.gets().split(" ") for i2 in 0 ... @_noou @_optn[i1][i2] = Float(s[i2+1]) end end f.close() end attr_accessor("_noip", "_noiu", "_noou", "_iptn", "_optn") end ########################################### # バックプロパゲーション(クラス Backpr) ########################################### class Backpr < BackControl ################################################ # クラスBackprのコンストラクタ # name_c : 制御データ用入力ファイル名 # name_s : ネットワーク記述入力ファイル名 ################################################ def initialize(name_c, name_s) super(name_c) # 親のコンストラクタ f = open(name_s, "r") # 入力ユニット,出力ユニットの数,関数タイプ s = f.gets().split(" ") @_noiu = Integer(s[1]) # 入力ユニットの数 @_noou = Integer(s[3]) # 出力ユニットの数 @_f_type = Integer(s[5]) # シグモイド関数のタイプ,0 : [0,1],1 : [-1,1] @_nou = @_noiu + @_noou # 入力ユニットと出力ユニットの和 # 各ユニットには最も上の出力ユニットから, # 隠れ層の各ユニット,及び,入力ユニットに至る # 一連のユニット番号が付けられる # 隠れユニットの階層数と各階層のユニット数 s = f.gets().split(" ") @_nolvl = Integer(s[1]) # 隠れユニットの階層数 @_nohu = Array.new(@_nolvl+1) # nohu[i] : レベル(i+1)の隠れ層のユニット数(隠れ層 # には下から順に番号が付けられ,出力層はレ # ベル(nolvl+1)の隠れ層とも見做される) # i=0,nolvl @_nohu[@_nolvl] = @_noou if @_nolvl > 0 for i1 in 0 ... @_nolvl @_nohu[i1] = Integer(s[i1+3]) @_nou += @_nohu[i1] end end # 領域の確保 @_con = Array.new(@_nou) for i1 in 0 ... @_nou @_con[i1] = Array.new(@_nou) end for i1 in 0 ... @_nou for i2 in 0 ... @_nou if i1 == i2 @_con[i1][i2] = -1 else @_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 end end end @_w = Array.new(@_nou) for i1 in 0 ... @_nou @_w[i1] = Array.new(@_nou) end for i1 in 0 ... @_nou for i2 in 0 ... @_nou @_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 end end @_dp = Array.new(@_nou) # ユニット(i+1)の誤差 i=0,nou-1 @_op = Array.new(@_nou) # ユニット(i+1)の出力 i=0,nou-1 @_theta = Array.new(@_nou) # ユニット(i+1)のバイアス i=0,nou for i1 in 0 ... @_nou @_theta[i1] = 0.2 * rand(0) - 0.1 end # 各ユニットのバイアスとユニット間の接続関係 # バイアス # バイアスデータの数 s = f.gets().split(" ") n = Integer(s[1]) f.gets() f.gets() f.gets() if n > 0 # バイアスデータの処理 for i0 in 0 ... n s = f.gets().split(" ") # ユニット番号 k1 = Integer(s[0]) # 不適当なユニット番号のチェック if k1 < 1 or k1 > (@_nou-@_noiu) print("***error ユニット番号 " + String(k1) + " が不適当" + "\n") end # バイアスの与え方 k1 -= 1 id = Integer(s[1]) @_con[k1][k1] = id # バイアスの初期設定 if @_con[k1][k1] == -1 x1 = Float(s[2]) x2 = Float(s[3]) @_theta[k1] = (x2 - x1) * rand(0) + x1 elsif @_con[k1][k1] == -2 or @_con[k1][k1] == -3 @_theta[k1] = Float(s[2]) else print("***error バイアスの与え方が不適当\n") end end end # 接続方法 # 接続データの数 s = f.gets().split(" ") n = Integer(s[1]) f.gets() f.gets() f.gets() if n > 0 # 接続データの処理 for i0 in 0 ... n s = f.gets().split(" ") # 接続情報 k1 = Integer(s[0]) k2 = Integer(s[1]) k3 = Integer(s[2]) k4 = Integer(s[3]) # 不適切な接続のチェック sw = 0 if k1 < 1 or k2 < 1 or k3 < 1 or k4 < 1 sw = 1 else if k1 > @_nou or k2 > @_nou or k3 > @_nou or k4 > @_nou sw = 1 else if k1 > k2 or k3 > k4 sw = 1 else if k4 >= k1 sw = 1 else l1 = -1 k = 0 i1 = @_nolvl while i1 > -1 k += @_nohu[i1] if k1 <= k l1 = i1 break end i1 -= 1 end l2 = -1 k = 0 i1 = @_nolvl while i1 > -1 k += @_nohu[i1] if k4 <= k l2 = i1 break end i1 -= 1 end if l2 <= l1 sw = 1 end end end end end if sw > 0 print("***error ユニット番号が不適当(" + String(k1) + " " + String(k2) + " " + String(k3) + " " + String(k4) + ")\n") end # 重みの初期値の与え方 k1 -= 1 k2 -= 1 k3 -= 1 k4 -= 1 id = Integer(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 接続方法が不適当\n") end end end # 重みの初期値の設定 for i1 in k3 ... k4+1 for i2 in k1 ... k2+1 @_con[i1][i2] = id if id == 0 @_w[i1][i2] = 0.0 elsif id == 1 @_w[i1][i2] = (x2 - x1) * rand(0) + x1 elsif id == 2 @_w[i1][i2] = x1 elsif id == 3 @_w[i1][i2] = x1 end end end end end f.close() end ########################################## # 誤差の計算,及び,重みとバイアスの修正 # ptn[i1] : 出力パターン ########################################## def Err_back(ptn) for i1 in 0 ... @_nou-@_noiu # 誤差の計算 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) end else x1 = 0.0 for i2 in 0 ... i1 if @_con[i2][i1] > 0 x1 += @_dp[i2] * @_w[i2][i1] end end 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 end end # 重みの修正 for i2 in i1+1 ... @_nou if @_con[i1][i2] == 1 or @_con[i1][i2] == 2 x1 = @_eata * @_dp[i1] * @_op[i2] + @_alpha * @_w[i2][i1] @_w[i2][i1] = x1 @_w[i1][i2] += x1 end end # バイアスの修正 if @_con[i1][i1] >= -2 x1 = @_eata * @_dp[i1] + @_alpha * @_w[i1][i1] @_w[i1][i1] = x1 @_theta[i1] += x1 end end end ######################################################## # 与えられた入力パターンに対する各ユニットの出力の計算 ######################################################## def Forward() i1 = @_nou - @_noiu - 1 while i1 > -1 sum = -@_theta[i1] for i2 in i1+1 ... @_nou if @_con[i1][i2] > 0 sum -= @_w[i1][i2] * @_op[i2] end end if @_f_type == 0 @_op[i1] = 1.0 / (1.0 + Math.exp(sum)) else @_op[i1] = 1.0 - 2.0 / (1.0 + Math.exp(sum)) end i1 -= 1 end end ############################# # 学習の実行 # p : 認識パターン # m_tri : 最大学習回数 ############################# def Learn(p, m_tri) k0 = -1 # エラーチェック if @_noiu != p._noiu or @_noou != p._noou print("***error 入力または出力ユニットの数が違います\n") end for i1 in 0 ... m_tri # パターンを与える順番の決定 if @_order == 0 # 順番 k0 += 1 if k0 >= p._noip k0 = 0 end else # ランダム k0 = Integer(rand(0) * p._noip) if k0 >= p._noip k0 = p._noip - 1 end end # 出力ユニットの結果を計算 k1 = @_nou - @_noiu for i2 in 0 ... @_noiu @_op[k1+i2] = p._iptn[k0][i2] end Forward() # 重みとバイアスの修正 Err_back(p._optn[k0]) end end ################################################ # 与えられた対象の認識と出力 # p : 認識パターン # pr : =0 : 出力を行わない # =1 : 出力を行う # =2 : 出力を行う(未学習パターン) # tri : 現在の学習回数 # return : 誤って認識したパターンの数 ################################################ def Recog(p, pr, tri) no = 0 # ファイルのオープン if @_p_type < 0 and pr > 0 if pr == 1 out = open(@_o_file, "w") out.print("***学習パターン***\n\n") else out = open(@_o_file, "a") out.print("\n***未学習パターン***\n\n") end end # 各パターンに対する出力 for i1 in 0 ... p._noip # 入力パターンの設定 k1 = @_nou - @_noiu for i2 in 0 ... @_noiu @_op[k1+i2] = p._iptn[i1][i2] end # 出力の計算 Forward() # 結果の表示 if @_p_type != 0 and pr > 0 printf("入力パターン%4d ", (i1+1)) for i2 in 0 ... @_noiu printf("%5.2f", @_op[k1+i2]) if i2 == @_noiu-1 print("\n") else if ((i2+1) % 10) == 0 print("\n ") end end end print("\n 出力パターン(理想) ") for i2 in 0 ... @_noou printf("%10.3f", p._optn[i1][i2]) if i2 == @_noou-1 print("\n") else if ((i2+1) % 5) == 0 print("\n ") end end end end sw = 0 if @_p_type != 0 and pr > 0 print(" (実際) ") end for i2 in 0 ... @_noou if @_p_type != 0 and pr > 0 printf("%10.3f", @_op[i2]) if i2 == @_noou-1 print("\n") else if ((i2+1) % 5) == 0 print("\n ") end end end if (@_op[i2]-p._optn[i1][i2]).abs() > @_eps sw = 1 end end if sw > 0 no += 1 end if @_p_type < 0 and pr > 0 out.printf("入力パターン%4d ", (i1+1)) for i2 in 0 ... @_noiu out.printf("%5.2f", @_op[k1+i2]) if i2 == @_noiu-1 out.print("\n") else if ((i2+1) % 10) == 0 out.print("\n ") end end end out.print("\n 出力パターン(理想) ") for i2 in 0 ... @_noou out.printf("%10.3f", p._optn[i1][i2]) if i2 == @_noou-1 out.print("\n") else if ((i2+1) % 5) == 0 out.print("\n ") end end end out.print(" (実際) ") for i2 in 0 ... @_noou out.printf("%10.3f", @_op[i2]) if i2 == @_noou-1 out.print("\n") else if ((i2+1) % 5) == 0 out.print("\n ") end end end end if @_p_type != 0 and pr > 0 $stdin.gets() end end # 重みの出力 if (@_p_type < -1 or @_p_type > 1) and pr == 1 print(" 重み") for i1 in 0 ... @_nou-@_noiu printf(" to%4d from ", (i1+1)) ln = -1 for i2 in 0 ... @_nou if @_con[i1][i2] > 0 if ln <= 0 if ln < 0 ln = 0 else print("\n ") end end printf("%4d%11.3f", i2+1, @_w[i1][i2]) ln += 1 if ln == 4 ln = 0 end end end print("\n") end print("\n バイアス ") ln = 0 for i1 in 0 ... @_nou-@_noiu printf("%4d%11.3f", i1+1, @_theta[i1]) ln += 1 if ln == 4 and i1 != @_nou-@_noiu-1 ln = 0 print("\n ") end end print("\n") $stdin.gets() end if @_p_type < 0 and pr == 1 out.print(" 重み\n") for i1 in 0 ... @_nou-@_noiu out.printf(" to%4d from ", (i1+1)) ln = -1 for i2 in 0 ... @_nou if @_con[i1][i2] > 0 if ln <= 0 if ln < 0 ln = 0 else out.print("\n ") end end out.printf("%4d%11.3f", i2+1, @_w[i1][i2]) ln += 1 if ln == 4 ln = 0 end end end out.print("\n") end out.print("\n バイアス ") ln = 0 for i1 in 0 ... @_nou-@_noiu out.printf("%4d%11.3f", i1+1, @_theta[i1]) ln += 1 if ln == 4 and i1 != @_nou-@_noiu-1 ln = 0 out.print("\n ") end end if ln != 0 out.print("\n") end end if @_p_type < 0 and pr > 0 out.close() end return no end end ct = 0 no = 1 # エラー if ARGV.length != 2 print("***error 入力データファイル名を指定して下さい\n") else # ネットワークの定義 net = Backpr.new(ARGV[0], ARGV[1]) # 学習パターン等の入力 print("学習回数は? ") m_tri = Integer($stdin.gets()) print("何回毎に収束を確認しますか? ") conv = Integer($stdin.gets()) print("学習パターンのファイル名は? ") f_name = $stdin.gets().strip() dt1 = BackData.new(f_name) # 学習 while ct < m_tri and no > 0 if (ct + conv) < m_tri tri = conv else tri = m_tri - ct end ct += tri net.Learn(dt1, tri) # 学習 no = net.Recog(dt1, 0, ct) # 学習対象の認識 print(" 回数 " + String(ct) + " 誤って認識したパターン数 " + String(no) + "\n") end no = net.Recog(dt1, 1, ct) # 学習対象の認識と出力 # 未学習パターンの認識 print("未学習パターンの認識を行いますか?(=1:行う,=0:行わない) ") sw = Integer($stdin.gets()) if sw > 0 print("未学習パターンのファイル名は? ") f_name = $stdin.gets().strip() dt2 = BackData.new(f_name) no = net.Recog(dt2, 2, ct) # 未学習対象の認識と出力 end end =begin ------------------------制御データ---------------- 誤差 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 =end