メインモジュールの実装|Irisデータセット分類問題

本ページは「ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装」を参考にニューラルネットワークを作成していきます。参考書籍では「Affineレイヤ」「ReLUレイヤ」「Softmax-with-Lossレイヤ」という3つのレイヤを作成し、それらを組み合わせることでニューラルネットワークを構築していきます。本サイトでも同じように3つのレイヤをVBAで作成し実装していきます。(実際はSigmoidレイヤを含む4レイヤ)
これまでに「Affineレイヤ」「ReLUレイヤ」「Sigmoidレイヤ」「Softmax-with-Lossレイヤ」、それらを組み合わせたニューラルネットワーク本体である「TwoLayerNetクラス」をVBAで実装しました。
本ページではニューラルネットワークを呼び出し、学習を行うための機能をメインモジュールを実装していきます。また、別ページではこのメインモジュール内に推論を行う機能も追加します。つまり、マクロとして実行するのは今回実装するメインモジュールの関数のみとなっています。
メインモジュールの機能
メインモジュールでは「学習」と「推論」の2つの機能を持ちます。
(本ページでは「学習」の機能のみを実装します)
ニューラルネットワークの学習
「学習」とは、順伝播と逆伝播を繰り返し、重みパラメータとバイアスパラメータの値を徐々に更新していき『適正な値』を見つけ出すことを意味しています。
例えば「ガクの長さ = 6.7」「ガクの幅 = 3」「花弁の長さ = 5.2」「花弁の幅 = 2.3」と4項目それぞれに数値を入力したときに「Virginica」と正しく種類を判別できるようなパラメータ値を見つけます。この入力された数値とその正解となる種類は学習データに依存します。
適切なパラメータの値はこれまでに実装したクラス/レイヤの関数を順番に実行していくことで導き出すことができます。導き出す方法はいくつかありますが、今回実装している方法は「誤差逆伝播法」という1番シンプルで簡単な仕組みです。
とても極端な話をすれば「TwoLayerNetクラス」の関数「gradient」と関数「ParamsUpdate」をループ処理に入れるだけで学習が可能になっています。(引数を準備する必要がありますが)
ニューラルネットワークの推論
「推論」とは、入力された4つの数字から花の種類が「Setosa」「Versicolor」「Virginica」のどれなのかをニューラルネットが自ら考え導き出すことです。
処理としては非常にシンプルで「学習」で求めた適切なパラメータの値をそのまま使い、順伝播を1回行うだけです。この順伝播によって出力された値がニューラルネットワークの最終的な答えとなります。
1度学習済みのパラメータさえ求めることができれば同じニューラルネットワークに入れることで何度でも使い回すことができます。つまり今回の場合、1度学習を終えパラメータ値が取得できれば入力された4つの値を変更してもそれに対応した花の種類を何回も導き出すことができます。
ただ注意しないといけないのは、正解できる確率(精度)は学習の内容によるという点です。
正しく十分な学習ができていれば正解をうまく導き出すことができますし、逆に学習が不十分の場合はうまく導き出すことができません。
今回のIris分類問題はニューラルネットワークを使うほどでもないかなり簡単な問題のため、学習は数秒で終わり精度もほぼ100%となります。
メインモジュールの実装
まずは標準モジュールで「Main」というモジュールを作成します。
これまでに実装したクラス/レイヤと揃えるため、本モジュールでも「Option Base 1」を使い配列を「1」スタートにします。
以下はメインモジュール(学習のみ)の全コードです。
Option Explicit
Option Base 1
'*********************************************************************
' ユーザ-設定(ハイパーパラメータ設定)
'*********************************************************************
'ユニット数の設定-----------------------------------------------------
Const input_size = 4 '入力層のユニット数 = 4 (ガクの長さ / ガクの幅 / 花弁の長さ / 花弁の幅)
Const hidden_size = 3 '隠れ層のユニット数設定(任意で変更可)
Const output_size = 3 '出力層のユニット数 = 3 (setosa / versicolor / virginica)
'学習終了条件---------------------------------------------------------
Const MinLoss = 0.03 '目標Loss値
Const MaxEpoc = 1000 '最大エポック数(学習回数)
'学習設定-------------------------------------------------------------
Const train_size = 120 '学習用データ数
Const test_size = 30 'テスト用データ数
Const LearningRate = 0.01 '学習率(デフォルト「0.01」)
Const batch_size = 100 'ミニバッチサイズ(※batch_size < train_size)
Public Const act_func = "Sigmoid" '活性化関数の種類(「ReLU」 or 「Sigmoid」)
Public ParamsSheet As Worksheet 'パラメータ書き出し用シート
Public Classification_mode As Boolean '分類モード(学習時は「True」、推論時は「False」)
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Sub Train()
Classification_mode = False
'パラメータ出力用シート定義
Dim ws As Worksheet
Dim flg As Boolean
Dim res As String
Dim ParamsSheet As Worksheet
For Each ws In Worksheets
If ws.Name = "Parameters" Then flg = True
Next ws
If flg = True Then
Set ParamsSheet = Worksheets.Item("Parameters")
ParamsSheet.Cells.Clear
Else
Set ParamsSheet = Worksheets.add(After:=Worksheets(1))
ParamsSheet.Name = "Parameters"
End If
'学習用データ定義
Dim ws_train_iris As Worksheet
Set ws_train_iris = Worksheets.Item("train_iris")
'テスト用データ定義
Dim ws_test_iris As Worksheet
Set ws_test_iris = Worksheets.Item("test_iris")
'ニューラルネットワークのインスタンス作成
Dim network As TwoLayerNet
Set network = New TwoLayerNet
'ニューラルネットワーク初期化(各レイヤのインスタンス作成)
Call network.Initialize(input_size, hidden_size, output_size, ParamsSheet)
Dim eRow As Long
Dim eCol As Long
Dim Datas() As Double
Dim c As Long
Dim r As Long
Dim i As Long
Dim epoc As Long
Dim sumE As Double
Dim sumAcc As Long
Dim cnt As Long
Dim label As Integer
Dim label_species As String
Dim t() As Long
Dim DataRows() As Long
'ニューラルネットワーク学習
With ws_train_iris
eRow = .Cells(rows.count, 1).End(xlUp).Row
eCol = .Cells(1, Columns.count).End(xlToLeft).Column
ReDim Datas(eCol - 1)
Do
'最大エポック数に到達したら学習終了
If epoc >= MaxEpoc Then Exit Do
sumAcc = 0
sumE = 0
cnt = 0
'学習データ行をランダム取得
ReDim DataRows(batch_size)
DataRows = Functions.GetRandomRow(batch_size, train_size, 2)
For r = 1 To UBound(DataRows)
cnt = cnt + 1
'入力データ取得
For c = 1 To 4
Datas(c) = .Cells(DataRows(r), c).Value
Next c
'正解ラベル取得
label_species = .Cells(DataRows(r), 5).Value
If label_species = "setosa" Then: label = 1
If label_species = "versicolor" Then: label = 2
If label_species = "virginica" Then: label = 3
'labelをone-hot化
t = Functions.one_hot_t(output_size, label)
'勾配を求める
Call network.gradient(Datas, t)
'Loss値を求める
sumE = sumE + network.E
'学習用データでの正解数カウント
If network.accuracy = True Then
sumAcc = sumAcc + 1
End If
'Affinレイヤの重み/バイアスパラメータを更新
Call network.ParamsUpdate(LearningRate)
Next r
epoc = epoc + 1
'テストデータ(未学習データ)で精度確認************************************************
Dim testData() As Double
Dim testLabel As Long
Dim testLabel_species As String
Dim testDataRows() As Long
Dim test_acc() As Double
Dim test_acc_size As Long
Dim test_cnt As Long
test_acc_size = 10
ReDim testData(input_size)
ReDim testDataRows(test_acc_size)
testDataRows = Functions.GetRandomRow(test_acc_size, test_size, 2)
test_cnt = 0
For r = 1 To UBound(testDataRows)
For c = 1 To 4
testData(c) = ws_test_iris.Cells(testDataRows(r), c).Value
Next c
testLabel_species = ws_test_iris.Cells(testDataRows(r), 5).Value
If testLabel_species = "setosa" Then: testLabel = 1
If testLabel_species = "versicolor" Then: testLabel = 2
If testLabel_species = "virginica" Then: testLabel = 3
If network.test_accuracy(testData, testLabel) = True Then
test_cnt = test_cnt + 1
End If
Next r
'************************************************************************************
'学習状況をイミディエイトウィンドウに出力
Debug.Print epoc & "回目: " & sumE / cnt & " 正解率: " & (sumAcc / cnt) * 100 & "% テスト正解率" & (test_cnt / test_acc_size) * 100 & "% "
Loop Until sumE / cnt < MinLoss
End With
Call network.ExportParameters(ParamsSheet)
MsgBox "学習終了" & vbLf & "学習結果はシート(Parameters)に書き出しました。"
End Sub
以下では上記のコードでどのような処理を行っているかを解説していきます。
※メインモジュールではこれまでに実装したクラス/レイヤ/関数を全て呼び出します。
適宜、各実装ページに戻り本モージュールとの繋がりやレイヤごとの繋がりを確認しましょう。
Sub Train
関数「Train」ではこれまでに実装したレイヤや関数を呼び出してニューラルネットワークの学習を行う関数です。ここでは「この関数がどのような処理を行なっているのか」を部分ごとに分けて上から順に説明していきます。
ハイパーパラメータ設定
はじめに、学習の初期設定ともいえるハイパーパラメータを設定します。
ハイパーパラメータとは各層のニューロン数や学習率などのニューラルネットワークの根源となるパラメータのことをいいます。このハイパーパラメータの値は学習前に変更することが可能で、学習結果に応じて変更したり自分の好きな設定で学習を行うことができます。
今回作成したニューラルネットワークではハイパーパラメータの値を変更できるものは少ないですが、コードを書き足したり、レイヤを増やすことで隠れ層の数を増やしたり、活性化関数を変更したりすることも可能になります。(ニューラルネットワークについて理解が深まったらぜひコードを書き換えてみてください)
Option Explicit
Option Base 1
'*********************************************************************
' ユーザ-設定(ハイパーパラメータ設定)
'*********************************************************************
'ユニット数の設定-----------------------------------------------------
Const input_size = 4 '入力層のユニット数 = 4 (ガクの長さ / ガクの幅 / 花弁の長さ / 花弁の幅)
Const hidden_size = 3 '隠れ層のユニット数設定(任意で変更可)
Const output_size = 3 '出力層のユニット数 = 3 (setosa / versicolor / virginica)
'学習終了条件---------------------------------------------------------
Const MinLoss = 0.03 '目標Loss値
Const MaxEpoc = 1000 '最大エポック数(学習回数)
'学習設定-------------------------------------------------------------
Const train_size = 120 '学習用データ数
Const test_size = 30 'テスト用データ数
Const LearningRate = 0.01 '学習率(デフォルト「0.01」)
Const batch_size = 100 'ミニバッチサイズ(※batch_size < train_size)
Public Const act_func = "Sigmoid" '活性化関数の種類(「ReLU」or「Sigmoid」)
Public ParamsSheet As Worksheet 'パラメータ書き出し用シート
Public Classification_mode As Boolean '分類モード(学習時は「True」、推論時は「False」)
基本的に今回変更できるのは以下のハイパーパラメータだけです。
hidden_size:隠れ層のニューロン数
MinLoss:最少Loss値(Loss値がこの値になったら学習を終了する)
MaxEpoc:最大エポック数(batch_size×MaxEpoc個のデータを学習したら学習を終了する)
train_size:学習用データのデータ数
test_size:テスト用データのデータ数
LearningRate:学習率(この値が小さいほど学習時間は長くなるが精度の高い学習ができる)
batch_size:バッチサイズ(まとめて何個のデータを学習するかを決める)
act_func:隠れ層の活性化関数(ReLU関数とSigmoid関数の切り替えが可能)
batch_size、MaxEpocについては本来の意味とは少し違うので注意してください。
基本的にIris分類問題は簡単な問題なのでハイパーパラメータを少しいじったところで大きな変化はありませんし、よほどおかしな値を入力しない限りほとんどの確率でうまく分類することができます。
シートの設定
ここからは「Sub Train()」プロシージャ内のコードです。
まずは「学習した結果を出力するためのシート」を用意します。
Classification_mode = False
'パラメータ出力用シート定義
Dim ws As Worksheet
Dim flg As Boolean
Dim res As String
Dim ParamsSheet As Worksheet
For Each ws In Worksheets
If ws.Name = "Parameters" Then flg = True
Next ws
If flg = True Then
Set ParamsSheet = Worksheets.Item("Parameters")
ParamsSheet.Cells.Clear
Else
Set ParamsSheet = Worksheets.add(After:=Worksheets(1))
ParamsSheet.Name = "Parameters"
End If
ここで作成したシートに順伝播と逆伝播によって求められた"最適"な重みパラメータとバイアスパラメータを出力します。今回は簡単な問題で学習時間も数秒で済むため、学習毎に学習の結果が上書きされていきます。
学習に何時間もかかる場合は、学習途中の場合でもところどころでパラメータを出力します。
こうすることで、その地点から学習を再開することが可能になるためです。
Iris分類問題の次のレベル『Excel VBAでニューラルネットワークを再現|MNIST学習で文字認識』ではこの機能を実装し、学習を途中で終了し、再開させることが可能になっています。
学習
次にTwoLayerNetのインスタンスを作成し、ループ分を使って学習を行なっていきます。
'学習用データ定義
Dim ws_train_iris As Worksheet
Set ws_train_iris = Worksheets.Item("train_iris")
'テスト用データ定義
Dim ws_test_iris As Worksheet
Set ws_test_iris = Worksheets.Item("test_iris")
'ニューラルネットワークのインスタンス作成
Dim network As TwoLayerNet
Set network = New TwoLayerNet
'ニューラルネットワーク初期化(各レイヤのインスタンス作成)
Call network.Initialize(input_size, hidden_size, output_size, ParamsSheet)
Dim eRow As Long
Dim eCol As Long
Dim Datas() As Double
Dim c As Long
Dim r As Long
Dim i As Long
Dim epoc As Long
Dim sumE As Double
Dim sumAcc As Long
Dim cnt As Long
Dim label As Integer
Dim label_species As String
Dim t() As Long
Dim DataRows() As Long
'ニューラルネットワーク学習
With ws_train_iris
eRow = .Cells(rows.count, 1).End(xlUp).Row
eCol = .Cells(1, Columns.count).End(xlToLeft).Column
ReDim Datas(eCol - 1)
Do
'最大エポック数に到達したら学習終了
If epoc >= MaxEpoc Then Exit Do
sumAcc = 0
sumE = 0
cnt = 0
'学習データ行をランダム取得
ReDim DataRows(batch_size)
DataRows = Functions.GetRandomRow(batch_size, train_size, 2)
For r = 1 To UBound(DataRows)
cnt = cnt + 1
'入力データ取得
For c = 1 To 4
Datas(c) = .Cells(DataRows(r), c).Value
Next c
'正解ラベル取得
label_species = .Cells(DataRows(r), 5).Value
If label_species = "setosa" Then: label = 1
If label_species = "versicolor" Then: label = 2
If label_species = "virginica" Then: label = 3
'labelをone-hot化
t = Functions.one_hot_t(output_size, label)
'勾配を求める
Call network.gradient(Datas, t)
'Loss値を求める
sumE = sumE + network.E
'学習用データでの正解数カウント
If network.accuracy = True Then
sumAcc = sumAcc + 1
End If
'Affinレイヤの重み/バイアスパラメータを更新
Call network.ParamsUpdate(LearningRate)
Next r
epoc = epoc + 1
'テストデータ(未学習データ)で精度確認************************************************
Dim testData() As Double
Dim testLabel As Long
Dim testLabel_species As String
Dim testDataRows() As Long
Dim test_acc() As Double
Dim test_acc_size As Long
Dim test_cnt As Long
test_acc_size = 10
ReDim testData(input_size)
ReDim testDataRows(test_acc_size)
testDataRows = Functions.GetRandomRow(test_acc_size, test_size, 2)
test_cnt = 0
For r = 1 To UBound(testDataRows)
For c = 1 To 4
testData(c) = ws_test_iris.Cells(testDataRows(r), c).Value
Next c
testLabel_species = ws_test_iris.Cells(testDataRows(r), 5).Value
If testLabel_species = "setosa" Then: testLabel = 1
If testLabel_species = "versicolor" Then: testLabel = 2
If testLabel_species = "virginica" Then: testLabel = 3
If network.test_accuracy(testData, testLabel) = True Then
test_cnt = test_cnt + 1
End If
Next r
'************************************************************************************
'学習状況をイミディエイトウィンドウに出力
Debug.Print epoc & "回目: " & sumE / cnt & " 正解率: " & (sumAcc / cnt) * 100 & "% テスト正解率" & (test_cnt / test_acc_size) * 100 & "% "
Loop Until sumE / cnt < MinLoss
End With
複雑なように見えますが処理の流れを大雑把にみると以下の通りです。
① ランダムで学習データを取得
② ①のデータの正解ラベルを取得しone-hot表現に変換する
③ ①と②を引数としてTwoLayerNetの関数「gradient」を実行
④ ③で取得した勾配を使い、各Affineレイヤのパラメータを更新
⑤ ①〜④の処理を「batch_size」回行う(「batch_size」回の学習結果の平均の精度を出力する)
⑥ ⑤の処理を学習終了条件を満たすまでループする
この処理の流れはミニバッチ学習から来ています。
本来のミニバッチ学習では③の処理の時点で複数の画像データをまとめてTwoLayerNetに渡し、まとめて計算を行います。(通常これを1エポックという)
しかしVBAでミニバッチ学習を行う場合、ニューラルネットに入力するデータが増えることは配列の次元が増えることを意味します。ここでは機能の理解をメインとしているためあまり複雑な計算は避けています。そのため本来のミニバッチ学習とは少し変えた処理で、ミニバッチ学習に似せた処理をしています。(つまりミニバッチ学習自体は実装できていません)
上記の処理には書いていませんが、ループの中ではテストデータの精度確認も併せて行います。
処理の内容自体は上記の内容とほとんど同じなので割愛しますが、最終的にはこのテストデータの精度で学習の良し悪しを判断します。(詳しくは実際に学習をする際に解説します)
ここの処理は説明を聞くよりも1行1行コードを見ながら何をしているかを確認していった方が理解しやすいと思います。おそらくニューラルネットワークを学ぶ上で1番つまづきやすいのが「学習」の部分なのでここはじっくりとやっていきましょう。
学習終了処理
最後に学習終了条件を満たした際の処理を書いておきます。
ここではTwoLayerNetの関数「ExportParameters」を呼び出すだけです。
引数となる「ParamsSheet」はコードのはじめの方で作成した「学習した結果を出力するためのシート」です。
Call network.ExportParameters(ParamsSheet)
MsgBox "学習終了" & vbLf & "学習結果はシート(Parameters)に書き出しました。"
まとめ
ここでは「ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装」を参考に、ニューラルネットワークで学習を行うためのメインモジュールを作成しました。
今回実装したメインモジュールの機能をまとめると以下の通りです。
Sub Train:学習を行う
長いコードにはなっていますが機能としては「学習を行う」の1つだけです。
これまでに実装してきたレイヤやクラスの関数を呼び出して学習していくので、初めのうちは少しややこしく感じるかもしれません。ただ、コードが理解できればやっている内容が思ったよりはシンプルだということがわかります。
このシンプルな内容を理解することで、ニューラルネットワークの仕組みも理解できるのでじっくり理解していってください。
今回のメインモジュールで「学習」に関するコードはすべてです。
次回はこれらのコードを使いIrisの分類問題を行う方法を解説していきます。
【次回】Irisデータセットを学習させてみよう
【前回】TwoLayerNetクラスの実装
メインページ










