先日はニューラルネットワーク学習方法の一つ勾配下降法について調べました。
今回は先日の内容を元に、勾配下降法による学習をPythonによって実装してみました。
勾配下降法
ニューラルネットワークの学習法として勾配下降法があります。
勾配下降法では 損失関数L の重みパラメータwによる微分値を計算し、 損失関数L の値が最小になるように重みwをを更新していきます。
今回は損失関数として、交差エントロピー誤差関数を使用します。
ニューラルネットワークモデル
今回使用するニューラルネットワークのモデルを示します。
- 今回使用するニューラルネットワークは「入力 – 中間」・「中間 – 出力(ソフトマック)」の二層ニューラルネットワークです。
- 使用する活性化関数には シグモイド関数 を使用しました。
- 今回学習する関数は二値入力の「AND」・「XOR」関数を学習します。
- ニューラルネットワークの出力として、入力が「1に分類される確率」と「0に分類される確率」を出力します。
またニューラルネットワークのパラメータを次のように設定しました。
- 入力層 : 2
- 中間層 : 4
- 出力層 : 2
- 学習率 : 0.1
- 学習回数 : 30000回
コード
今回使用するコードを示します。実装にはPython 3.xを用いました。またコードの実装には下記参考文献を参考にさせていただきました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
import numpy as np import matplotlib.pyplot as plt import pickle #入力 input_array = np.array([[0,0],[1,0],[0,1],[1,1]]) #正解データ #training_data = np.array([[0,1],[1,0],[1,0],[0,1]]) #XOR教師データ #training_data = np.array([[0,1],[1,0],[1,0],[1,0]]) #OR教師データ #training_data = np.array([[1,0],[1,0],[1,0],[0,1]]) #NAND教師データ training_data = np.array([[0,1],[0,1],[0,1],[1,0]]) #AND教師データ def numerical_gradient(f, x): h = 0.01 grad = np.zeros_like(x) i1 = np.nditer(x, flags=['multi_index'], op_flags=['readwrite']) while not i1.finished: i = i1.multi_index temp = x[i] x[i] = float(temp) + h fxh1 = f(x) x[i] = temp - h fxh2 = f(x) grad[i] = (fxh1 - fxh2) / (2*h) x[i] = temp i1.iternext() return grad def sigmoid(x): return 1 / (1 + np.exp(-x)) def softmax(x): if x.ndim == 2: x = x.T #転置行列 x = x - np.max(x, axis=0) y = np.exp(x) / np.sum(np.exp(x), axis=0) return y.T x = x - np.max(x) return (np.exp(x) / np.sum(np.exp(x))) def cross_entropy_error(y, t): h = 1e-7 size = y.shape[0] return (-1 * np.sum(t * np.log(y + h)) / size) #ニューラルネットワーククラスの定義 class two_net: def __init__(self,input_size, output_size, intermediate_size, weight_init_cons=0.1,weight_inport=False,weight_export=False): self.parameters = {} if(weight_inport == False): self.parameters['W1'] = weight_init_cons * np.random.randn(input_size + 1,intermediate_size) self.parameters['W2'] = weight_init_cons * np.random.randn(intermediate_size + 1,output_size) else: with open('init_weight.bf','rb') as inport_file: self.parameters = pickle.load(inport_file) print("重みを読み込みました") if(weight_export == True): with open('init_weight.bf','wb') as save_file: pickle.dump(self.parameters,save_file) print("重みを保存しました") def predict(self,x): W1,W2 = self.parameters['W1'],self.parameters['W2'] #x1 = np.insert(x,0,1) x1 = np.insert(x,0,1,axis=1) a1 = np.dot(x1,W1) z = sigmoid(a1) #z1 = np.insert(z,0,1) z1 = np.insert(z,0,1,axis=1) a2 = np.dot(z1,W2) y = softmax(a2) return y def loss(self,x,t): y = self.predict(x) return cross_entropy_error(y,t) def numeric_gradient(self, x, t): loss_W = lambda W: self.loss(x, t) grads = {} grads['W1'] = numerical_gradient(loss_W, self.parameters['W1']) grads['W2'] = numerical_gradient(loss_W, self.parameters['W2']) return grads loss_array = [] net = two_net(input_size = 2,output_size = 2, intermediate_size = 4 ,weight_inport = True) #初期重み表示 print("W1 : ") print(net.parameters['W1']) print("\nW2 : ") print(net.parameters['W2']) #学習パラメータ times = 30000 z = times learning_rate = 0.1 temp1 = net.predict(input_array) print("\n学習前推論\nx = [0,0], y = {0[0]} : x = [1,0], y = {0[1]} \nx = [0,1], y = {0[2]} :x = [1,1], y = {0[3]}".format(temp1)) print("\n学習開始") #学習ループ while z: temp = net.predict(input_array) grad = net.numeric_gradient(input_array,training_data) for key in ('W1', 'W2'): net.parameters[key] -= learning_rate * grad[key] loss_array.append(net.loss(input_array,training_data)) if ((z % 5000) == 0): print("z = {0}".format(z)) z -= 1 print("z = {0}\n学習終了\n ".format(z)) #推論実行結果 temp1 = net.predict(input_array) print("学習後推論\nx = [0,0], y = {0[0]} : x = [1,0], y = {0[1]} \nx = [0,1], y = {0[2]} :x = [1,1], y = {0[3]}".format(temp1)) #重みデータの保存 with open('weight.bf','wb') as save_file: pickle.dump(net.parameters,save_file) print("重みを保存しました") #損失推移のグラフ表示 x = np.arange(0, times, 1.0) plt.plot(x,loss_array) plt.show() |
学習の実行
AND関数の学習
関数の学習として、初めにAND関数を学習させました。今回学習するAND関数の入力と教師データを次に示します。
入力 | 教師 | ||
x1 | x2 | t1 | t2 |
0 | 0 | 0 | 1 |
1 | 0 | 0 | 1 |
0 | 1 | 0 | 1 |
1 | 1 | 1 | 0 |
上記のプログラムを実行した結果を次に示します。
1 2 3 4 5 6 7 |
学習前推論 x = [0,0], y = [0.46544817 0.53455183] : x = [1,0], y = [0.46849933 0.53150067] x = [0,1], y = [0.4656259 0.5343741] :x = [1,1], y = [0.46867525 0.53132475] 学習後推論 x = [0,0], y = [6.73711662e-07 9.99999326e-01] : x = [1,0], y = [4.86922951e-04 9.99513077e-01] x = [0,1], y = [5.03634679e-04 9.99496365e-01] :x = [1,1], y = [0.99878442 0.00121558] |
学習前はどの入力でも正しい結果を出力することができませんでしたが、学習によって正しく分類できるようになりました。
次に学習回数と損失関数の値の関係を示します。
グラフと横軸が学習回数、縦軸が損失関数の値を示しています。
AND関数の学習では、損失関数の値は学習を重ねることで順調に減少していきました。
XOR関数の学習
続いてXOR関数の学習を行いました。学習するXOR関数の入力と教師データを次に示します。
入力 | 教師 | ||
x1 | x2 | t1 | t2 |
0 | 0 | 0 | 1 |
1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 |
1 | 1 | 0 | 1 |
上記のプログラムを実行した結果を次に示します。
1 2 3 4 5 6 7 |
学習前推論 x = [0,0], y = [0.46544817 0.53455183] : x = [1,0], y = [0.46849933 0.53150067] x = [0,1], y = [0.4656259 0.5343741] :x = [1,1], y = [0.46867525 0.53132475] 学習後推論 x = [0,0], y = [0.0037655 0.9962345] : x = [1,0], y = [0.99576818 0.00423182] x = [0,1], y = [0.99585897 0.00414103] :x = [1,1], y = [0.00649744 0.99350256] |
AND関数の学習と同様、学習前はも正しい結果を出力することができませんでしたが、学習によって正しく分類できるようになりました。
一方で損失関数の値はAND関数の場合とは異なりました。次に学習回数と損失関数の値の関係を示します。
グラフと横軸が学習回数、縦軸が損失関数の値を示しています。
AND関数の場合とは異なり、学習回数15000回までは損失関数の値は減少せず、20000回ほど学習しないと正しく分類することができませんでした。
OR関数、NAND関数も同様に学習させたとこと、損失関数の値はAND関数の場合と同様になりました。
この原因はわかりませんが、線形分離可能な関数のほうが線形分離不可能なXOR関数よりも学習回数が少なかかったことから、線形分離可能かどうかが学習回数に影響するようです。
まとめ
- 勾配下降法によってニューラルネットワークの学習を行った。
- ニューラルネットワークの層は二層に固定し、中間層を4層とした。
- ニューラルネットワークの出力部にソフトマックス関数を使用した。
- AND・XOR関数の学習を行い、線形分離可能な関数のほうが学習回数が少なかった。
参考文献
- 馬場則夫, 小島史男, 小澤誠一 : ニューラルネットワークの基礎と応用, p. 10, 1995, 共立出版
- 麻生英樹 : ニューラルネットワーク情報処理, 1989, 産業図書
- 麻生英樹ほか:深層学習 Deep Learning, 2016, 近代科学社
- 斎藤康毅 : ゼロから作るDeep Learning, 2018, オライリージャパン
関連記事