TwoLayerNetクラスの実装|Excel VBAでMNIST機械学習
本ページは「ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装」を参考にニューラルネットワークを作成していきます。参考書籍では「Affineレイヤ」「ReLUレイヤ」「Softmax-with-Lossレイヤ」という3つのレイヤを作成し、それらを組み合わせることでニューラルネットワークを構築していきます。本サイトでも同じように3つのレイヤをVBAで作成し実装していきます。
これまでに「Affineレイヤ」「ReLUレイヤ」「Softmax-with-Lossレイヤ」は実装しました。
ここでは書籍同様にそれらを組み合わせた「TwoLayerNetクラス」をVBAで実装していきます。
「TwoLayerNetクラス」と難しく言っていますが、簡単に言えばこのクラスこそがニューラルネットワークです。このクラスにはいくつかの関数がありますが、関数同士が繋がっているものあるので実際に呼び出して実行する関数は限られています。
かなり長い内容になっていますが、ニューラルネットワークそのものを実装していくのでじっくりと理解しながら見ていきましょう。
TwoLayerNetクラスの機能
TwoLayerNetクラスでは今までに実装した3層(レイヤ)を呼び出し、「順伝播」「逆伝播」を行っていきます。これまでに実装したレイヤでは「forward」(順伝播)と「backward」(逆伝播)の関数を作成しているので、ここではそれらを呼び出して実行するだけです。
最終的にはメインのモジュールのループの中にこのTwoLayerNetを呼び出し学習を繰り返し行う流れになっています。また「順伝播」「逆伝播」以外にも、精度や答えを返す関数や学習したパラメータをExcelシートに書き出す関数など様々なものがあるので各機能で何を行っているのか理解しておきましょう。
機械学習の流れ
これまでに何回も出てきましたが、今回作成していくニューラルネットワークをレイヤで表すと下図の通りです。この流れを取り仕切っているのが今回実装していく「TwoLayerNet」です。
再三になりますが「TwoLayerNet」の流れは以下のようになっています。(以下では上図の各レイヤを左から「Affine1」「ReLU」「Affine2」「Softmax-with-Loss」として説明していきます)
1. 入力値を「Affine1」に入れ、入力値と重みの総和を計算する。
2.「Affine1」の出力を「ReLU」に入れて活性化させる。
3.「ReLU」の出力を「Affine2」に入れ、入力値と重みの総和を計算する。
4.「Affine2」の出力を「Softmax-with-Loss」に入れ、活性化の結果/損失関数を求める。
5.「Softmax-with-Loss」から「Affine2」に逆伝播(Affine2のパラメータを更新)
6.「Affine2」から「ReLU」に逆伝播
7.「ReLU」から「Affine1」に逆伝播(Affine1のパラメータを更新)
上記の1~7を何百、何千回とループすることで「Affine1」と「Affine2」のパラメータ(重み/バイアス)が正解に近づくように徐々に更新されていきます。あとは損失関数をもとにループを止めれば学習終了といった流れになってます。
TwoLayerNetクラスの実装
まずはクラスモジュールで「TwoLayerNet」というモジュールを作成します。
「TwoLayerNet」では「Option Base 1」を使い配列を「1」スタートにします。
これは要素数とインデックスを合わせ配列同士の計算をわかりやすくするためです。
以下はTwoLayerNetの全コードです。
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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
'VBA TwoLayerNet(class) Option Explicit Option Base 1 Dim Affine1 As Affine_Layer Dim ReLU As ReLU_Layer Dim Affine2 As Affine_Layer Dim SoftmaxWithLoss As SoftmaxWithLoss_Layer Dim class_loss As Double Dim class_accuracy As Boolean Dim class_input_size As Long '入力層ニューロン数 Dim class_hidden_size As Long '隠れ層ニューロン数 Dim class_output_size As Long '出力層ニューロン数 '―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Public Sub Initialize(ByRef input_size As Long, ByRef hidden_size As Long, ByRef output_size As Long, ByRef Sheet As Worksheet) Dim WeightList1() As Double Dim WeightList2() As Double Dim BiasList1() As Double Dim BiasList2() As Double ReDim WeightList1(input_size, hidden_size) 'W1 ReDim WeightList2(hidden_size, output_size) 'W2 ReDim BiasList1(hidden_size) 'b1 ReDim BiasList2(output_size) 'b2 class_input_size = input_size class_hidden_size = hidden_size class_output_size = output_size Dim i As Long Dim j As Long '************************************************** ' W1,b1の作成と初期化(読み込み) '************************************************** If ParamsCheck = False Then WeightList1 = Functions.RandomWeight(input_size, hidden_size) '引数の数だけランダムの重みを作成してリストに格納 BiasList1 = Functions.zeros(hidden_size) Else For i = 1 To UBound(WeightList1, 1) For j = 1 To UBound(WeightList1, 2) WeightList1(i, j) = Sheet.Cells(i, j) Next j Next i For i = 1 To UBound(BiasList1) BiasList1(i) = Sheet.Cells(UBound(WeightList1, 1) + 1, i) Next i End If '************************************************** ' W2,b2の作成と初期化(読み込み) '************************************************** If ParamsCheck = False Then WeightList2 = Functions.RandomWeight(hidden_size, output_size) '引数の数だけランダムの重みを作成してリストに格納 BiasList2 = Functions.zeros(output_size) Else For i = 1 To UBound(WeightList2, 1) For j = 1 To UBound(WeightList2, 2) WeightList2(i, j) = Sheet.Cells(UBound(WeightList1, 1) + 1 + i, j) Next j Next i For i = 1 To UBound(BiasList2) BiasList2(i) = Sheet.Cells(UBound(WeightList1, 1) + 1 + UBound(WeightList2, 1) + 1, i) Next i End If '************************************************** ' レイヤの生成 '************************************************** Set Affine1 = New Affine_Layer Call Affine1.init(WeightList1, BiasList1) Set ReLU = New ReLU_Layer Set Affine2 = New Affine_Layer Call Affine2.init(WeightList2, BiasList2) Set SoftmaxWithLoss = New SoftmaxWithLoss_Layer End Sub '―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Public Function predict(ByRef x() As Double) As Double() Dim a1() As Double '活性化前重み付き入力値の総和(input→hidden) Dim a2() As Double '活性化前重み付き入力値の総和(hidden→output) Dim z1() As Double 'a1の活性化後の値(ReLU関数) a1 = Affine1.forward(x) z1 = ReLU.forward(a1) a2 = Affine2.forward(z1) predict = a2 'Softmax前までの入力値の総和 End Function '―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Public Function loss(ByRef x() As Double, ByRef t() As Long) Dim i As Integer Dim max_t_index As Integer Dim max_y As Double Dim max_y_index As Integer Dim y() As Double ReDim y(class_output_size) y = predict(x) class_loss = SoftmaxWithLoss.forward(y, t) loss = class_loss 't(正解ラベル)が「1」のインデックスを取得 For i = 1 To UBound(t) If t(i) = 1 Then max_t_index = i End If Next i 'y(softmax出力値)が最大値のインデックスを取得 max_y = y(1) max_y_index = 1 For i = 1 To UBound(y) If y(i) > max_y Then max_y = y(i) max_y_index = i End If Next i If max_t_index = max_y_index Then class_accuracy = True Else class_accuracy = False End If End Function '―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Public Property Get E() E = class_loss End Property '―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Public Property Get accuracy() As Boolean accuracy = class_accuracy End Property '―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Public Function gradient(ByRef x() As Double, ByRef t() As Long) 'forward Call loss(x, t) 'backward Dim dout As Double Dim dx() As Double dout = 1 ReDim dx(UBound(t)) dx = SoftmaxWithLoss.backward(dout) Dim da2() As Double 'output→hiddenの逆伝播の値 Dim dz1() As Double 'ReLU関数の逆伝播の値 Dim da1() As Double 'hidden→inputの逆伝播の値 da2 = Affine2.backward(dx) dz1 = ReLU.backward(da2) da1 = Affine1.backward(dz1) End Function '―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Public Function ParamsUpdate(ByRef LearningRate As Double) Call Affine1.ParamsUpdate(LearningRate) Call Affine2.ParamsUpdate(LearningRate) End Function '―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Public Function ExportParameters(ByRef ExportSheet As Worksheet) Application.ScreenUpdating = False Dim i As Long Dim j As Long Dim W1() As Double Dim b1() As Double Dim W2() As Double Dim b2() As Double ReDim W1(class_input_size, class_hidden_size) ReDim b1(class_hidden_size) ReDim W2(class_hidden_size, class_output_size) ReDim b2(class_output_size) W1 = Affine1.Weight b1 = Affine1.Bias W2 = Affine2.Weight b2 = Affine2.Bias With ExportSheet .Cells.ClearContents For i = 1 To UBound(W1, 1) '784 For j = 1 To UBound(W1, 2) .Cells(i, j).Value = W1(i, j) Next j Next i For i = 1 To UBound(b1) .Cells(UBound(W1, 1) + 1, i).Value = b1(i) Next i For i = 1 To UBound(W2, 1) For j = 1 To UBound(W2, 2) .Cells(UBound(W1, 1) + 1 + i, j).Value = W2(i, j) Next j Next i For i = 1 To UBound(b2) .Cells(UBound(W1, 1) + 1 + UBound(W2, 1) + 1, i).Value = b2(i) Next i End With Application.ScreenUpdating = True End Function '―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Public Function test_accuracy(ByRef x() As Double, ByRef t As Long) As Boolean Dim i As Long Dim ans As Long Dim ans2 As Long Dim tmp_a As Double Dim tmp_t As Long Dim A() As Double ReDim A(10) A = predict(x) tmp_a = A(1) ans = 1 For i = 1 To UBound(A) If tmp_a < A(i) Then tmp_a = A(i) ans = i End If Next i If ans = 10 Then ans = 0 End If If ans = t Then test_accuracy = True Else test_accuracy = False End If End Function '―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Public Function answer(ByRef x() As Double) As Long Dim i As Long Dim ans As Long Dim tmp_a As Double Dim A() As Double ReDim A(class_output_size) A = predict(x) tmp_a = A(1) ans = 1 For i = 1 To UBound(A) If tmp_a < A(i) Then tmp_a = A(i) ans = i End If Next i If ans = 10 Then ans = 0 End If answer = ans End Function |
いままで通りPythonのコード(書籍のコード)を記載するとかなり長くなるなるので「TwoLayerNet」については割愛します。(Pythonコードは書籍を参照下さい)
※このクラスでは標準モジュールで作成した「Functions」の関数をいくつか呼び出して使用するので、「Functions」も合わせて読み進めて下さい。
Sub Initialize
まずは関数「Initialize」の機能を解説していきます。
ここでは今まで作成した「Affineレイヤ」「ReLUレイヤ」「Softmax-with-Lossレイヤ」のインスタンスを作成します。また、それと同時に重みパラメータとバイアスパラメータを初期化し、2つのAffineレイヤそれぞれに渡します。
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 |
'VBA TwoLayerNet(class) Public Sub Initialize(ByRef input_size As Long, ByRef hidden_size As Long, ByRef output_size As Long, ByRef Sheet As Worksheet) Dim WeightList1() As Double Dim WeightList2() As Double Dim BiasList1() As Double Dim BiasList2() As Double ReDim WeightList1(input_size, hidden_size) 'W1 ReDim WeightList2(hidden_size, output_size) 'W2 ReDim BiasList1(hidden_size) 'b1 ReDim BiasList2(output_size) 'b2 class_input_size = input_size class_hidden_size = hidden_size class_output_size = output_size Dim i As Long Dim j As Long '************************************************** ' W1,b1の作成と初期化(読み込み) '************************************************** If ParamsCheck = False Then WeightList1 = Functions.RandomWeight(input_size, hidden_size) '引数の数だけランダムの重みを作成してリストに格納 BiasList1 = Functions.zeros(hidden_size) Else For i = 1 To UBound(WeightList1, 1) For j = 1 To UBound(WeightList1, 2) WeightList1(i, j) = Sheet.Cells(i, j) Next j Next i For i = 1 To UBound(BiasList1) BiasList1(i) = Sheet.Cells(UBound(WeightList1, 1) + 1, i) Next i End If '************************************************** ' W2,b2の作成と初期化(読み込み) '************************************************** If ParamsCheck = False Then WeightList2 = Functions.RandomWeight(hidden_size, output_size) '引数の数だけランダムの重みを作成してリストに格納 BiasList2 = Functions.zeros(output_size) Else For i = 1 To UBound(WeightList2, 1) For j = 1 To UBound(WeightList2, 2) WeightList2(i, j) = Sheet.Cells(UBound(WeightList1, 1) + 1 + i, j) Next j Next i For i = 1 To UBound(BiasList2) BiasList2(i) = Sheet.Cells(UBound(WeightList1, 1) + 1 + UBound(WeightList2, 1) + 1, i) Next i End If '************************************************** ' レイヤの生成 '************************************************** Set Affine1 = New Affine_Layer Call Affine1.init(WeightList1, BiasList1) Set ReLU = New ReLU_Layer Set Affine2 = New Affine_Layer Call Affine2.init(WeightList2, BiasList2) Set SoftmaxWithLoss = New SoftmaxWithLoss_Layer End Sub |
引数としては「input_size」「hidden_size」「output_size」「Sheet」の4つがあります。
「input_size」「hidden_size」「output_size」はそれぞれ入力層、隠れ層、出力層のニューロン数です。この数によって重みパラメータの数とバイアスパラメータの数が決まります。
「Sheet」は学習済みの重み/バイアスパラメータが出力されているシートです。
このマクロでは学習を終えるたびに学習結果である重みとバイアスパラメータの値をシートに書き出します。加えてこのマクロは、その出力した値を再度ニューラルネットワークに取り込み続きから学習を始めることもできるようになっています。
つまり「Sheet」は以前出力したパラメータを再度ニューラルネットワークに取り込むための引数だということです。
関数「Initialize」では「重みパラメータの初期化」「バイアスパラメータの初期化」「レイヤの作成」の3つの処理を行います。
重みパラメータ/バイアスパラメータの初期化
重みパラメータとバイアスパラメータの初期化では「ParamsCheck」というグローバル変数を使い処理を2つに分岐させています。1つの処理は「はじめからの学習」の場合、もう1つの処理は「続きからの学習の場合」となっています。
「はじめからの学習」の場合、重みパラメータの初期値は「-1~1」の間の少数をランダムで設定、バイアスパラメータの初期値はすべて「0」で設定します。
「Affine1」と「Affine2」の2つのレイヤ分、「Functions」モジュールで作成した関数「RandomWeight」と「zeros」を使って重みパラメータとバイアスパラメータの値をそれぞれ設定します。
「続きからの学習」の場合は引数の「Sheet」に書かれている値をすべて読み込み、重みパラメータとバイアスパラメータの初期値としてそのまま利用します。
レイヤの作成
これまでにクラスモジュールで実装してきた「Affineレイヤ」「ReLUレイヤ」「Softmax-with-Lossレイヤ」のインスタンスを作成していきます。「Affineレイヤ」は2つ、その他のレイヤは1つずつ作成します。
また、これと同時に2つのAffineレイヤには(Affineレイヤの)関数「init」を使い、先ほど設定した重みパラメータとバイアスパラメータの初期値をそのまま受け渡します。これにより”Affineレイヤの中だけ”で重みパラメータとバイアスパラメータの値を更新させていくことができるようになります。
Function predict
関数「predict」はSoftmax関数で活性化する前までの重み付き入力値の総和を求める関数です。
簡単に言えば「Affine1」「ReLU」「Affine2」の3層での順伝播の最終出力値です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
'VBA TwoLayerNet(class) Public Function predict(ByRef x() As Double) As Double() Dim a1() As Double '活性化前重み付き入力値の総和(input→hidden) Dim a2() As Double '活性化前重み付き入力値の総和(hidden→output) Dim z1() As Double 'a1の活性化後の値(ReLU関数) a1 = Affine1.forward(x) z1 = ReLU.forward(a1) a2 = Affine2.forward(z1) predict = a2 'Softmax前までの入力値の総和 End Function |
この関数の引数としては「x()」があります。
これはニューラルネットワークへの入力値、つまりはMNIST画像データ(784個の数値)が入った1次元配列です。
この関数は引数「x()」を入力値として「「Affine1」「ReLU」「Affine2」の順に順伝播(forward)を行い、その最終的な計算結果を1次元配列としてそのまま返します。
Function loss
関数「loss」は損失関数「交差エントロピー誤差」(Loss値)を求める関数です。
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 |
'VBA TwoLayerNet(class) Public Function loss(ByRef x() As Double, ByRef t() As Long) Dim i As Integer Dim max_t_index As Integer Dim max_y As Double Dim max_y_index As Integer Dim y() As Double ReDim y(class_output_size) y = predict(x) class_loss = SoftmaxWithLoss.forward(y, t) loss = class_loss 't(正解ラベル)が「1」のインデックスを取得 For i = 1 To UBound(t) If t(i) = 1 Then max_t_index = i End If Next i 'y(softmax出力値)が最大値のインデックスを取得 max_y = y(1) max_y_index = 1 For i = 1 To UBound(y) If y(i) > max_y Then max_y = y(i) max_y_index = i End If Next i If max_t_index = max_y_index Then class_accuracy = True Else class_accuracy = False End If End Function |
この関数の引数としては「x()」と「t()」があります。
「x()」はニューラルネットワークへの入力値、つまりはMNIST画像データ(784個の数値)が入った1次元配列で、「t()」はone-hot表現化した正解ラベルが入った1次元配列です。
ここでの処理は非常にシンプルで前項の関数「predict」と「Softmax-with-Lossレイヤ」の関数「forward」を実行しているだけです。実行すると交差エントロピー誤差の値(Loss値)を求めることができます。
また、引数として入力された正解ラベルと関数「predict」の出力値から、ニューラルネットワークの導き出した答えが合っているかを確認します。正解の場合は「True」を、不正解の場合は「False」を変数「class_accuracy」に格納します。
これを次々項のプロパティ「accuracy」を使ってどこからでも呼び出せるようにします。これはメインモジュールで学習の回数に対する正解数を求め、ニューラルネットワークの精度を測るためです。
Property Get E/accuracy
プロパティの「E」と「accuracy」はメインモジュールから前項の関数「loss」で求めた値を取得するためのものです。学習中に現在の「Loss値」と「学習精度」を表示するために使います。
1 2 3 4 5 6 7 8 9 10 11 12 |
'VBA TwoLayerNet(class) Public Property Get E() E = class_loss End Property '―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― Public Property Get accuracy() As Boolean accuracy = class_accuracy End Property |
Function gradient
関数「gradient」は今までに実装してきた関数を使い「順電波」「逆伝播」を行う関数です。
実際に学習の処理としてメインモジュールから実行する関数はこの「gradient」です。(コードを見ればわかりますが上記までに出てきた関数「predict」「loss」はこのコードに包含されています)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
'VBA TwoLayerNet(class) Public Function gradient(ByRef x() As Double, ByRef t() As Long) 'forward Call loss(x, t) 'backward Dim dout As Double Dim dx() As Double dout = 1 ReDim dx(UBound(t)) dx = SoftmaxWithLoss.backward(dout) Dim da2() As Double 'output→hiddenの逆伝播の値 Dim dz1() As Double 'ReLU関数の逆伝播の値 Dim da1() As Double 'hidden→inputの逆伝播の値 da2 = Affine2.backward(dx) dz1 = ReLU.backward(da2) da1 = Affine1.backward(dz1) End Function |
この関数の引数としては「x()」と「t()」があります。
「x()」はニューラルネットワークへの入力値、つまりはMNIST画像データ(784個の数値)が入った1次元配列で、「t()」はone-hot表現化した正解ラベルが入った1次元配列です。
この関数では今まで作成してきたレイヤ/関数を使って「順伝播(forward)」「逆伝播(backward)」を1回ずつ行います。メインモジュールではこの関数をループ内で呼び出すことで何百、何千回といった学習を行なっていきます。
「順伝播」は関数「loss」を呼び出すだけです。
順伝播では「交差エントロピー誤差(Loss値)」「ニューラルネットワークの最終出力値(導き出した答え)」「出力値が正解か不正解か」の3点を求める事ができます。
「逆伝播」はこれまでに作成したレイヤの関数「backward」を後ろのレイヤから順に行うだけです。
逆伝播により「Affine1」「Affine2」が持っている重みとバイアスパラメータの勾配を求めることができます。各パラメータの勾配を求める事でそのパラメータの値を更新することができます。
ここでいう「更新」とは、順伝播でのLoss値が「0」に近づくように重みパラメータの値とバイアスパラメータの値を微小に変更することを意味します。つまり順伝播と逆伝播を繰り返せば繰り返すほどLoss値が「0」に近づいていき、結果としてどのような画像が入力されても描かれている数字を当てることのできるパラメータ値にすることができるということです。
この関数では「Affine1」「Affine2」が持っている重みとバイアスパラメータの勾配を求めるだけで更新自体は行いません。(勾配の求め方は「Affineレイヤの実装」を参照下さい)
これらパラメータ別の勾配の値は各レイヤで保存されているため、Affineレイヤの関数「ParamsUpdate」を実行するだけでパラメータを更新することができます。Affineレイヤの関数「ParamsUpdate」は次項の関数「ParamsUpdate」でまとめて実行します。
Function ParamsUpdate
関数「ParamsUpdate」はその名の通り、パラメータの更新を行うための関数です。
1 2 3 4 5 6 7 |
'VBA TwoLayerNet(class) Public Function ParamsUpdate(ByRef LearningRate As Double) Call Affine1.ParamsUpdate(LearningRate) Call Affine2.ParamsUpdate(LearningRate) End Function |
引数としては学習率を表す「LearningRate」があります。
この関数では「Affineレイヤ」で作成した関数「ParamsUpdate」を実行するだけです。
「Affine1」「Affine2」はそれぞれの重みパラメータとバイアスパラメータを持っているので、どちらも関数「ParamsUpdate」でパラメータを更新します。この時、引数との「LearningRate」をそのまま関数「ParamsUpdate」の引数として使います。
Function ExportParameters
関数「ExportParameters」では現在の重みパラメータの値とバイアスパラメータの値を指定のシートに全て書き出す関数です。
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 |
'VBA TwoLayerNet(class) Public Function ExportParameters(ByRef ExportSheet As Worksheet) Application.ScreenUpdating = False Dim i As Long Dim j As Long Dim W1() As Double Dim b1() As Double Dim W2() As Double Dim b2() As Double ReDim W1(class_input_size, class_hidden_size) ReDim b1(class_hidden_size) ReDim W2(class_hidden_size, class_output_size) ReDim b2(class_output_size) W1 = Affine1.Weight b1 = Affine1.Bias W2 = Affine2.Weight b2 = Affine2.Bias With ExportSheet .Cells.ClearContents For i = 1 To UBound(W1, 1) '784 For j = 1 To UBound(W1, 2) .Cells(i, j).Value = W1(i, j) Next j Next i For i = 1 To UBound(b1) .Cells(UBound(W1, 1) + 1, i).Value = b1(i) Next i For i = 1 To UBound(W2, 1) For j = 1 To UBound(W2, 2) .Cells(UBound(W1, 1) + 1 + i, j).Value = W2(i, j) Next j Next i For i = 1 To UBound(b2) .Cells(UBound(W1, 1) + 1 + UBound(W2, 1) + 1, i).Value = b2(i) Next i End With Application.ScreenUpdating = True End Function |
引数としては「ExportSheet」があります。
これは現在の重み/バイアスパラメータの値を書き出すシートです。
この関数では引数「ExportSheet」の1枚のシートに、現在「Affiner1」と「Affine2」が持っている重みパラメータとバイアスパラメータを全て書き出します。この書き出されたパラメータの値は、学習済みモデルとしてニューラルネットワークの推論(簡単に言えば入力された画像にどの数字が書かれていうかを当てること)に使われます。
また、本ページの初めに出てきた関数「Initialize」でも軽く触れた通り、この書き出された値をそのままニューラルネットワークに取り込み学習を続きから始めることも可能です。
書き出される値は入力層、隠れ層、出力層のニューロンの数によって変化します。メインページの構成(入力層:784 ,隠れ層:64 ,出力層:10)の場合は計50890個の値が書き出されます。
Function test_accuracy
関数「test_accuracy」は入力されたテストデータに対して、ニューラルネットワークが導き出した答えが正解「True」か不正解「False」かを返す関数です。
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 |
'VBA TwoLayerNet(class) Public Function test_accuracy(ByRef x() As Double, ByRef t As Long) As Boolean Dim i As Long Dim ans As Long Dim ans2 As Long Dim tmp_a As Double Dim tmp_t As Long Dim A() As Double ReDim A(10) A = predict(x) tmp_a = A(1) ans = 1 For i = 1 To UBound(A) If tmp_a < A(i) Then tmp_a = A(i) ans = i End If Next i If ans = 10 Then ans = 0 End If If ans = t Then test_accuracy = True Else test_accuracy = False End If End Function |
引数としては「x()」と「t()」があります。
「x()」はニューラルネットワークへの入力値、つまりはMNIST画像データ(784個の数値)が入った1次元配列で、「t()」はone-hot表現化した正解ラベルが入った1次元配列です。
ここで注意する点としては入力する画像データと正解ラベルはテスト用データの画像データであるという点です。テスト用データはニューラルネットワークの学習に全く関与していない未知のデータです。
ニューラルネットワークは学習の量が多すぎると、学習に使ったデータのみに特化したニューラルネットワークが出来上がり、少しでも学習の画像データと違うものには全く対応できないという状態になってしまいます。(これを過学習といいます)
そのため学習の合間合間にテスト用データを読み込むことで過学習が起きていないかの確認を行う必要があります。
この関数では、この「学習の合間合間にテスト用データを読み込む」という処理を行っています。
入力されたテスト用データに対し正解か不正解かを求めて「True」「False」を返すだけで、パラメータの値を更新したり、何かの値を変更することは行いません。
Function answer
関数「answer」はその名の通り、入力された画像データに対して、ニューラルネットワークが導き出した答え(つまりは「何の数字が描かれているか」)を返す関数です。
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 |
'VBA TwoLayerNet(class) Public Function answer(ByRef x() As Double) As Long Dim i As Long Dim ans As Long Dim tmp_a As Double Dim A() As Double ReDim A(class_output_size) A = predict(x) tmp_a = A(1) ans = 1 For i = 1 To UBound(A) If tmp_a < A(i) Then tmp_a = A(i) ans = i End If Next i If ans = 10 Then ans = 0 End If answer = ans End Function |
引数としては「x()」があります。
これはニューラルネットワークへの入力値、つまりはMNIST画像データ(784個の数値)が入った1次元配列です。これまでの関数と違い、ここでは実際に”自分で描いた28×28(px)の画像データを1次元配列にしたもの”を入力します。(自分で描いた画像を1次元配列に変換するのは別のモジュールで行います)
ニューラルネットワークはSoftmax関数で出力された値をパーセンテージと考え、1番確率の高いもの(正確にいうとそれに紐づく正解ラベル)を最終的な答えとします。
ここで考えるべきは、Softmax関数は全ての入力値の割合から合計が「1」になるような値に変換する関数であるということです。これはSoftmax関数適用前の値の大小関係と、適用後の大小関係は同じであるという考えもできます。
つまりはSoftmax関数を適用しなくても答えを導き出すことができるということです。
よって、学習を行わないこの関数では「Softmax-with-Lossレイヤ」は使わずに、関数「predict」の出力値のうち1番値の大きい出力の正解ラベルを「最終的に導き出した答え」として返します。(Softmax関数を適用しても問題はありませんが、その分 計算時間が増えます)
まとめ
ここでは「ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装」を参考にVBAでTwoLayerNetクラスの実装を行いました。
今回実装したTwoLayerNetクラスの機能をまとめると以下の通りです。
Sub Initialize:重み/バイアスパラメータの初期設定 & レイヤの作成
Function predict:順伝播(重み付き入力値の総和にバイアスを加算したものを取得)
Function loss:損失関数とニューラルネットワークが導き出した答えを求める
Property Get E/accuracy:Loss値/学習精度の取得
Function gradient:逆伝播(Affineレイヤの重みとバイアスの勾配を求める)
Function ParamsUpdate:勾配を使って重みとバイアスを更新する
Function ExportParameters:学習終了時に重みとバイアスをExcelシートに書き出す
Function test_accuracy:入力したテスト用データが正解不正解を確認する
Function answer:入力値に対するニューラルネットワークの導き出した答えを返す
このクラスではこれまでに実装したレイヤや関数を1つにまとめています。
複数のモジュール間を行き来するので、初めは戸惑うかも知れませんが実際にやっていることは単純なので順を追ってゆっくり理解してください。
また、このクラスだけを見てもどのように扱うのかがわからないと思うので、ある程度の機能を理解したら「メインモジュールの実装」に移ることをオススメします。
というのもこれまでのページと同様に、本ページだけを見てもどの関数をどのタイミングで呼び出せば学習ができるのか/推論ができるのかのイメージがつかないと思います。
メインモージュールの流れと合わせてみることでこのクラスの中身は深く理解できるので、このページだけをガッツリやるのは避けたほうがいいです。
むしろ、これまでに実装したレイヤ/関数のページや、この後で実装していくメインモージュールの実装ページを行き来しながら、ちょこちょこ理解していく方がイメージがつきやすいと思います。