TwoLayerNetクラスの実装|Irisデータセット分類問題

本ページは「ゼロから作る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の全コードです。
Option Explicit
Option Base 1
Dim Affine1 As Affine_Layer
Dim ReLU As ReLU_Layer
Dim Sigmoid As Sigmoid_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 Classification_mode = 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 Classification_mode = 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 Sigmoid = New Sigmoid_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)
If act_func = "ReLU" Then
z1 = ReLU.forward(a1)
ElseIf act_func = "Sigmoid" Then
z1 = Sigmoid.forward(a1)
End If
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 da1() As Double '活性化前重み付き入力値の総和(input→hidden)
Dim da2() As Double '活性化前重み付き入力値の総和(hidden→output)
Dim dz1() As Double 'a1の活性化後の値(ReLU関数)
da2 = Affine2.backward(dx)
If act_func = "ReLU" Then
dz1 = ReLU.backward(da2)
ElseIf act_func = "Sigmoid" Then
dz1 = Sigmoid.backward(da2)
End If
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 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 = 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
answer = ans
End Function
Sub Initialize
まずは関数「Initialize」の機能を解説していきます。
ここでは今まで作成した「Affineレイヤ」「ReLUレイヤ」「Sigmoidレイヤ」「Softmax-with-Lossレイヤ」のインスタンスを作成します。また、それと同時に重みパラメータとバイアスパラメータを初期化し、2つのAffineレイヤそれぞれに渡します。
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 Classification_mode = 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 Classification_mode = 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 Sigmoid = New Sigmoid_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つの処理を行います。
重みパラメータ/バイアスパラメータの初期化
重みパラメータとバイアスパラメータの初期化では「Classification_mode」というグローバル変数を使い処理を2つに分岐させています。1つの処理は「学習」の場合、もう1つの処理は「推論」の場合となっています。
「学習」の場合、重みパラメータの初期値は「-1~1」の間の少数をランダムで設定、バイアスパラメータの初期値はすべて「0」で設定します。
「Affine1」と「Affine2」の2つのレイヤ分、「Functions」モジュールで作成した関数「RandomWeight」と「zeros」を使って重みパラメータとバイアスパラメータの値をそれぞれ設定します。
「推論」の場合は引数の「Sheet」に書かれている値をすべて読み込み、重みパラメータとバイアスパラメータの初期値としてそのまま利用します。
レイヤの作成
これまでにクラスモジュールで実装してきた「Affineレイヤ」「ReLUレイヤ」「Sigmoidレイヤ」「Softmax-with-Lossレイヤ」のインスタンスを作成していきます。「Affineレイヤ」は2つ、その他のレイヤは1つずつ作成します。
また、これと同時に2つのAffineレイヤには(Affineレイヤの)関数「init」を使い、先ほど設定した重みパラメータとバイアスパラメータの初期値をそのまま受け渡します。これにより"Affineレイヤの中だけ"で重みパラメータとバイアスパラメータの値を更新させていくことができるようになります。
Function predict
関数「predict」はSoftmax関数で活性化する前までの重み付き入力値の総和を求める関数です。
簡単に言えば「Affine1」「ReLU(Sigmoid)」「Affine2」の3層での順伝播の最終出力値です。
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)
If act_func = "ReLU" Then
z1 = ReLU.forward(a1)
ElseIf act_func = "Sigmoid" Then
z1 = Sigmoid.forward(a1)
End If
a2 = Affine2.forward(z1)
predict = a2 'Softmax前までの入力値の総和
End Function
この関数の引数としては「x()」があります。
これはニューラルネットワークへの入力値、つまりはIrisデータ(4個の数値)が入った1次元配列です。
この関数は引数「x()」を入力値として「「Affine1」「ReLU(Sigmoid)」「Affine2」の順に順伝播(forward)を行い、その最終的な計算結果を1次元配列としてそのまま返します。
また、mainモジュール内で設定可能なグローバル変数「act_func」の中身によってReLUレイヤとSigmoidレイヤを切り替えることができるようになっています。(※今回のような学習データの少ないものではあまり違いを理解することはできませんが)
Function loss
関数「loss」は損失関数「交差エントロピー誤差」(Loss値)を求める関数です。
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()」はニューラルネットワークへの入力値、つまりは Irisデータ(4個の数値)が入った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値」と「学習精度」を表示するために使います。
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」はこのコードに包含されています)
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 da1() As Double '活性化前重み付き入力値の総和(input→hidden)
Dim da2() As Double '活性化前重み付き入力値の総和(hidden→output)
Dim dz1() As Double 'a1の活性化後の値(ReLU関数)
da2 = Affine2.backward(dx)
If act_func = "ReLU" Then
dz1 = ReLU.backward(da2)
ElseIf act_func = "Sigmoid" Then
dz1 = Sigmoid.backward(da2)
End If
da1 = Affine1.backward(dz1)
End Function
この関数の引数としては「x()」と「t()」があります。
「x()」はニューラルネットワークへの入力値、つまりはIrisデータ(4個の数値)が入った1次元配列で、「t()」はone-hot表現化した正解ラベルが入った1次元配列です。
この関数では今まで作成してきたレイヤ/関数を使って「順伝播(forward)」「逆伝播(backward)」を1回ずつ行います。メインモジュールではこの関数をループ内で呼び出すことで何百、何千回といった学習を行なっていきます。
「順伝播」は関数「loss」を呼び出すだけです。
順伝播では「交差エントロピー誤差(Loss値)」「ニューラルネットワークの最終出力値(導き出した答え)」「出力値が正解か不正解か」の3点を求める事ができます。
「逆伝播」はこれまでに作成したレイヤの関数「backward」を後ろのレイヤから順に行うだけです。
逆伝播により「Affine1」「Affine2」が持っている重みとバイアスパラメータの勾配を求めることができるため、そのパラメータの値を更新することができます。
ここでいう"更新"とは、順伝播でのLoss値が「0」に近づくように重みパラメータの値とバイアスパラメータの値を微小に変更することを意味します。つまり順伝播と逆伝播を繰り返せば繰り返すほどLoss値が「0」に近づいていき、結果としてどのような数値が入力されてもIrisの種類を当てることのできるパラメータ値にすることができるということです。
この関数では「Affine1」「Affine2」が持っている重みとバイアスパラメータの勾配を求めるだけで更新自体は行いません。(勾配の求め方は「Affineレイヤの実装」を参照下さい)
これらパラメータ別の勾配の値は各レイヤで保存されているため、Affineレイヤの関数「ParamsUpdate」を実行するだけでパラメータを更新することができます。Affineレイヤの関数「ParamsUpdate」は次項の関数「ParamsUpdate」でまとめて実行します。
Function ParamsUpdate
関数「ParamsUpdate」はその名の通り、パラメータの更新を行うための関数です。
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」では現在の重みパラメータの値とバイアスパラメータの値を指定のシートに全て書き出す関数です。
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」が持っている重みパラメータとバイアスパラメータを全て書き出します。この書き出されたパラメータの値は、学習済みモデルとしてニューラルネットワークの推論(簡単に言えば入力された画像にどの数字が書かれていうかを当てること)に使われます。
書き出される値は入力層、隠れ層、出力層のニューロンの数によって変化します。メインページの構成(入力層:4 ,隠れ層:3 ,出力層:3)の場合は計27個の値が書き出されます。
Function test_accuracy
関数「test_accuracy」は入力されたテストデータに対して、ニューラルネットワークが導き出した答えが正解「True」か不正解「False」かを返す関数です。
Public Function test_accuracy(ByRef x() As Double, ByRef t As Long) As Boolean
Dim i As Long
Dim ans 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 = t Then
test_accuracy = True
Else
test_accuracy = False
End If
End Function
引数としては「x()」と「t()」があります。
「x()」はニューラルネットワークへの入力値、つまりはIrisデータ(4個の数値)が入った1次元配列で、「t()」はone-hot表現化した正解ラベルが入った1次元配列です。
ここで注意する点としては入力する4つの数値と正解ラベルは"テスト用データ"の数値であるという点です。テスト用データはニューラルネットワークの学習に全く関与していない未知のデータです。
ニューラルネットワークは学習の量が多すぎると、学習に使ったデータのみに特化したニューラルネットワークが出来上がり、少しでも学習の画像データと違うものには全く対応できないという状態になってしまいます。(これを過学習といいます)
そのため学習の合間合間にテスト用データを読み込むことで過学習が起きていないかの確認を行う必要があります。
この関数では、この「学習の合間合間にテスト用データを読み込む」という処理を行っています。
入力されたテスト用データに対し正解か不正解かを求めて「True」「False」を返すだけで、パラメータの値を更新したり、何かの値を変更することは行いません。
この関数が途中から「False」しか返さなくなった時は過学習が起きている可能性があると言えます。
Function answer
関数「answer」はその名の通り、入力された画像データに対して、ニューラルネットワークが導き出した答え(つまりは「Irisの種類」)を返す関数です。
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
answer = ans
End Function
引数としては「x()」があります。
これはニューラルネットワークへの入力値、つまりはIrisデータ(4個の数値)が入った1次元配列です。これまでの関数と違い、ここでは実際に"自分で入力した4つの数値を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つにまとめています。
複数のモジュール間を行き来するので、初めは戸惑うかも知れませんが実際にやっていることは単純なので順を追ってゆっくり理解してください。
また、このクラスだけを見てもどのように扱うのかがわからないと思うので、ある程度の機能を理解したら次回の「メインモジュールの実装」に移ることをオススメします。
というのもこれまでのページと同様に、本ページだけを見てもどの関数をどのタイミングで呼び出せば学習ができるのか/推論ができるのかのイメージがつかないと思います。
メインモージュールの流れと合わせてみることでこのクラスの中身は深く理解できるので、このページだけをガッツリやるのは避けたほうがいいです。
むしろ、これまでに実装したレイヤ/関数のページや、この後で実装していくメインモージュールの実装ページを行き来しながら、ちょこちょこ理解していく方がイメージがつきやすいと思います。
次回はこれまでの全てを実行するためのmainモジュールの実装です。
“学習"についてのコードは次回で全て完了するのであと一息です。
【次回】メインモジュールの実装
【前回】Softmax -with-Lossレイヤの実装
メインページ











