全コードまとめ|Excel VBAでIris分類問題

このページではこれまでに実装したモジュールやレイヤのコードをまとめておきます。
本ページ内のコードをコピペすることでIrisデータセットの学習を行うことができ、最終的には入力した4つの値から花の種類を識別することが可能になります。

 

モジュール作成

今回のIrisデータセットの学習に使用するモジュールは下記の通りです。
コードをコピペする前に各モジュールを作成しておきましょう。

 

実装コードまとめ

上記モジュールのコードは下記の通りです。

Functions (標準モジュール)

Option Explicit
Option Base 1
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
'************************************************************************************
'   関数名 :GetRandomRow
'   機能   :指定範囲内でランダムで生成した行数(自然数)を返す
'   引数   :DataCount      生成する値の数
'          :MaxDataCount  生成する値の上限値
'          :MinDataCount   生成する値の下限値(省略可/省略した場合は「1」となる)
'************************************************************************************

Public Function GetRandomRow(DataCount As Long, MaxDataCount As Long, Optional MinDataCount As Long) As Long()
    Dim i As Long, Num As Long
    Dim flag() As Boolean
    Dim RndRows() As Long
    
    ReDim flag(MaxDataCount)
    ReDim RndRows(MaxDataCount)

    Dim startDataCount As Long
    If MinDataCount < 1 Then
        startDataCount = 1
    Else
        startDataCount = MinDataCount
    End If
    
    For i = 1 To DataCount
    
        Randomize
        
        Do
            Num = Int((MaxDataCount - startDataCount + 1) * Rnd + startDataCount)
        Loop Until flag(Num) = False
        
        RndRows(i) = Num
        flag(Num) = True
    Next i
    
    ReDim Preserve flag(DataCount)
    ReDim Preserve RndRows(DataCount)
    
    GetRandomRow = RndRows
    
End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
'************************************************************************************
'   関数名 :RandomWeight
'   機能   :ランダム生成した数値の入った2次配列を返す
'   引数   :size1      2次配列のサイズ(1次元目)
'          :size2      2次配列のサイズ(2次元目)
'************************************************************************************

Public Function RandomWeight(ByRef size1 As Long, ByRef size2 As Long) As Double()

    Dim i As Long
    Dim j As Long
    Dim W As Double
    Dim List() As Double
    
    ReDim List(size1, size2)
    
    For i = 1 To size1
        For j = 1 To size2
    
        Randomize
        
        Do
            W = (Rnd * 2) - 1
        Loop Until W <> 0
    
        List(i, j) = W
        
        Next j
    Next i

    RandomWeight = List

End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
'************************************************************************************
'   関数名 :Zeros
'   機能   :0の入った1次配列を返す
'   引数   :size1      1次配列のサイズ
'************************************************************************************

Public Function Zeros(ByRef size1 As Long) As Double()

    Dim i As Long
    Dim List() As Double
    
    ReDim List(size1)

    For i = 1 To size1
        List(i) = 0
    Next i
    
    Zeros = List

End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
'************************************************************************************
'   関数名 :dot
'   機能   :2つの配列のドット積(内積)の計算結果が入った1次配列を返す
'   引数   :x()      1次元配列
'          :W()      2次元配列
'************************************************************************************

Public Function dot(ByRef x() As Double, ByRef W() As Double) As Double()
    
    Dim i As Long
    Dim j As Long
    Dim A() As Double
    
    ReDim A(UBound(W, 2))
    
    Dim sum As Double
    For i = 1 To UBound(W, 2)
    
        sum = 0
    
        For j = 1 To UBound(x)

            sum = sum + (x(j) * W(j, i))

        Next j

        A(i) = sum
        
    Next i
    
    dot = A

End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
'************************************************************************************
'   関数名 :dot2
'   機能   :2つの配列のドット積(内積)の計算結果が入った2次配列を返す
'   引数   :xT()      1次元配列
'          :dout()    1次元配列
'************************************************************************************

Public Function dot2(ByRef xT() As Double, ByRef dout() As Double) As Double()
    
    Dim i As Long
    Dim j As Long
    Dim dW() As Double
    
    ReDim dW(UBound(xT), UBound(dout))
    

    For i = 1 To UBound(dout)
        For j = 1 To UBound(xT)
    
            dW(j, i) = xT(j) * dout(i)

        Next j
    Next i
    
    dot2 = dW

End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
'************************************************************************************
'   関数名 :add
'   機能   :配列内の要素同士の和が入った2次配列を返す
'   引数   :xT()      1次元配列
'          :dout()    1次元配列
'************************************************************************************

Public Function add(ByRef x() As Double, ByRef Bias() As Double) As Double()

    Dim i As Long
    Dim A() As Double
    
    ReDim A(UBound(x))
    
    For i = 1 To UBound(x)
    
        A(i) = x(i) + Bias(i)

    Next i
    
    add = A
    
End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
'************************************************************************************
'   関数名 :Sigmoid
'   機能   :入力した1次元配列に対してSigmoid関数を適用する
'   引数   :A()      1次元配列
'************************************************************************************

Public Function Sigmoid(ByRef A() As Double) As Double()

    Dim i As Long
    Dim z() As Double
    
    ReDim z(UBound(A))
    
    For i = 1 To UBound(A)
    
        z(i) = 1 / (1 + Exp(A(i) * -1))

    Next i
    
    Sigmoid = z
    
End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
'************************************************************************************
'   関数名 :Softmax
'   機能   :入力した1次元配列に対してSoftmax関数を適用する
'   引数   :x()      1次元配列
'************************************************************************************

Public Function Softmax(ByRef x() As Double) As Double()

    Dim i As Long
    Dim max As Double
    Dim sum As Double
    Dim z() As Double
    
    ReDim z(UBound(x))

    max = x(1)
    For i = 1 To UBound(x)
        If x(i) > max Then
            max = x(i)
        End If
    Next i

    sum = 0
    For i = 1 To UBound(x)
        sum = sum + Exp(x(i) - max) 'オーバーフロー対策
    Next i
    
    For i = 1 To UBound(x)
        z(i) = Exp(x(i) - max) / sum
    Next i
    
    Softmax = z
    
End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
'************************************************************************************
'   関数名 :cross_entropy_error
'   機能   :交差エントロピー誤差より損失(Loss)を求める
'   引数   :y()    1次元配列(Softmax関数の出力値)
'          :t()    1次元配列(正解ラベル)
'************************************************************************************

Public Function cross_entropy_error(ByRef y() As Double, ByRef t() As Long)

    Dim delta As Double
    delta = 0.0000001 'infを発生させないため微小な数字を加算する
    
    Dim i As Long
    Dim sum As Double

    sum = 0
    For i = 1 To UBound(t)
        sum = sum + (t(i) * Log(y(i) + delta))
    Next

    cross_entropy_error = sum * -1

End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
'************************************************************************************
'   関数名 :one_hot_t
'   機能   :正解ラベルをone-hot表現にした1次元配列を返す
'   引数   :t_size      正解ラベルの総数(Iris分類の場合は3)
'          :label       正解ラベルのインデックス
'************************************************************************************

Public Function one_hot_t(ByRef t_size As Long, ByRef label As Integer) As Long()
    
    Dim labels() As Long
    ReDim labels(t_size)

    labels(label) = 1
    
    one_hot_t = labels
    
End Function

 

Main (標準モジュール)

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 Classification_iris()

    Dim Data() As Double
    ReDim Data(input_size)

'*******************************************************
'       irisデータセット正規化(平均:0 / 分散:1)
'*******************************************************
    Dim DataCount As Long       'データ総数
    Dim DataSum As Double       'データ合計値
    Dim DataAve As Double       'データ平均値
    Dim DataDev As Double       'データ偏差値
    Dim DataDevSum As Double    'データ偏差の2乗合計値
    Dim DataDevSta As Double    'データ標準偏差
    Dim EndRow As Long
    Dim EndCol As Long
    Dim r As Integer
    Dim c As Integer

    With ws_iris_dataset
    
        EndRow = .Cells(rows.count, 1).End(xlUp).Row
        EndCol = .Cells(1, Columns.count).End(xlToLeft).Column
  
      'データの合計数
        DataCount = (EndRow - 1) * (EndCol - 1)
        
      'データの合計値
        For r = 2 To EndRow
            For c = 1 To (EndCol - 1)
                DataSum = DataSum + .Cells(r, c).Value
            Next c
        Next r
        
      'データの平均値
        DataAve = DataSum / DataCount
        
      'データの偏差2乗の合計値
        For r = 2 To EndRow
            For c = 1 To (EndCol - 1)
                DataDev = .Cells(r, c).Value - DataAve
                DataDevSum = DataDevSum + (DataDev ^ 2)
            Next c
        Next r
        
      'データの標準偏差
        DataDevSta = Sqr(DataDevSum / DataCount)
    
        For c = 1 To 4
            DataDev = ws_Classification_iris.Cells(2, c).Value - DataAve
            Data(c) = DataDev / DataDevSta
        Next c
        
    End With
    
    Dim ModelSheet As Worksheet
    Set ModelSheet = Worksheets.Item("Parameters")
    
    Dim network As TwoLayerNet
    Set network = New TwoLayerNet
    
    
    Classification_mode = True
    Call network.Initialize(input_size, hidden_size, output_size, ModelSheet)


    Dim answer As Long
    answer = network.answer(Data)
    
    Dim answer_species As String
    If answer = 1 Then: answer_species = "setosa"
    If answer = 2 Then: answer_species = "versicolor"
    If answer = 3 Then: answer_species = "virginica"
    
    ws_Classification_iris.Cells(2, 5).Value = answer_species

End Sub

 

preprocess (標準モジュール)

Sub preprocess_standardization()


    Dim ws As Worksheet
    Dim flg As Boolean
    Dim ws_iris_dataset As Worksheet
    Dim ws_iris_dataset_standardization As Worksheet


  'Irisデータセット定義
    flg = False
    For Each ws In Worksheets
        If ws.Name = "iris_dataset" Then flg = True
    Next ws

    If flg = True Then
        Set ws_iris_dataset = Worksheets.Item("iris_dataset")
    Else
        MsgBox "Irisデータセットが見つかりません。"
        Exit Sub
    End If


  '正規化データ出力用のシート作成
    flg = False
    For Each ws In Worksheets
        If ws.Name = "iris_dataset_standardization" Then flg = True
    Next ws

    If flg = True Then
        Set ws_iris_dataset_standardization = Worksheets.Item("iris_dataset_standardization")
        ws_iris_dataset_standardization.Cells.Clear
    Else
        Set ws_iris_dataset_standardization = Worksheets.add(After:=Worksheets(1))
        ws_iris_dataset_standardization.Name = "iris_dataset_standardization"
    End If


  'irisデータセット転記
    Dim r As Long
    Dim c As Long
    Dim EndRow As Long
    Dim EndCol As Long

    EndRow = ws_iris_dataset.Cells(rows.count, 1).End(xlUp).Row
    EndCol = ws_iris_dataset.Cells(1, Columns.count).End(xlToLeft).Column

    For r = 1 To EndRow
        For c = 1 To EndCol
            ws_iris_dataset_standardization.Cells(r, c).Value = ws_iris_dataset.Cells(r, c).Value
        Next c
    Next r


'*******************************************************
'       irisデータセット正規化(平均:0 / 分散:1)
'*******************************************************
    Dim DataCount As Long       'データ総数
    Dim DataSum As Double       'データ合計値
    Dim DataAve As Double       'データ平均値
    Dim DataDev As Double       'データ偏差値
    Dim DataDevSum As Double    'データ偏差の2乗合計値
    Dim DataDevSta As Double    'データ標準偏差

  'データの合計数
    DataCount = (EndRow - 1) * (EndCol - 1)

  'データの合計値
    For r = 2 To EndRow
        For c = 1 To (EndCol - 1)
            DataSum = DataSum + ws_iris_dataset_standardization.Cells(r, c).Value
        Next c
    Next r

  'データの平均値
    DataAve = DataSum / DataCount

  'データの偏差2乗の合計値
    For r = 2 To EndRow
        For c = 1 To (EndCol - 1)
            DataDev = ws_iris_dataset_standardization.Cells(r, c).Value - DataAve
            DataDevSum = DataDevSum + (DataDev ^ 2)
        Next c
    Next r

  'データの標準偏差
    DataDevSta = Sqr(DataDevSum / DataCount)


  'データの正規化
    For r = 2 To EndRow
        For c = 1 To (EndCol - 1)
            DataDev = ws_iris_dataset_standardization.Cells(r, c).Value - DataAve
            ws_iris_dataset_standardization.Cells(r, c).Value = DataDev / DataDevSta
        Next c
    Next r


End Sub
Sub preprocess_split_data()

    Const train_size = 120              '学習用データ数
    Const test_size = 150 - train_size  'テスト用データ数
    

  '無作為にtrain_size個の行数を取得(学習用データに使用する行数を取得)
    Dim TrainRows() As Long
    ReDim TrainRows(train_size)
    TrainRows = Functions.GetRandomRow(train_size, 151, 2)


    Dim ws As Worksheet
    Dim flg As Boolean
    Dim ws_train_iris As Worksheet
    Dim ws_test_iris As Worksheet


  '学習用データ出力用のシート作成
    flg = False
    For Each ws In Worksheets
        If ws.Name = "train_iris" Then flg = True
    Next ws

    If flg = True Then
        Set ws_train_iris = Worksheets.Item("train_iris")
        ws_train_iris.Cells.Clear
    Else
        Set ws_train_iris = Worksheets.add(After:=Worksheets(1))
        ws_train_iris.Name = "train_iris"
    End If


  'テスト用データ出力用のシート作成
    flg = False
    For Each ws In Worksheets
        If ws.Name = "test_iris" Then flg = True
    Next ws

    If flg = True Then
        Set ws_test_iris = Worksheets.Item("test_iris")
        ws_test_iris.Cells.Clear
    Else
        Set ws_test_iris = Worksheets.add(After:=Worksheets(1))
        ws_test_iris.Name = "test_iris"
    End If


  'ベースデータ定義
    flg = False
    For Each ws In Worksheets
        If ws.Name = "test_iris" Then flg = True
    Next ws

    Dim ws_iris_dataset_stand As Worksheet
    Set ws_iris_dataset_stand = Worksheets.Item("iris_dataset_standardization")


  '学習用データ出力
    Dim i As Long
    Dim j As Integer
    Dim EndRow As Long

    For j = 1 To 5
        ws_train_iris.Cells(1, j).Value = ws_iris_dataset_stand.Cells(1, j).Value
    Next j

    For i = 1 To train_size
        EndRow = ws_train_iris.Cells(rows.count, 1).End(xlUp).Row
        For j = 1 To 5
            ws_train_iris.Cells(EndRow + 1, j).Value = ws_iris_dataset_stand.Cells(TrainRows(i), j).Value
        Next j
    Next i


  'テスト用データの行数取得(学習用データで使われない行数をすべて取得)
    Dim TestRows() As Long
    Dim k As Long
    Dim cnt As Long
    Dim chk As Integer: chk = 0

    For i = 2 To 151
        flg = False

      'TrainRowsの要素確認
        For j = 1 To UBound(TrainRows)
            If i = TrainRows(j) Then
                flg = True
            End If
        Next j

      'TrainRowsの要素以外の値を取得/格納
        If flg = False Then
            If chk = 0 Then
                ReDim Preserve TestRows(1)
            Else
                ReDim Preserve TestRows(UBound(TestRows) + 1)
            End If

            chk = chk + 1

            TestRows(UBound(TestRows)) = i
        End If
    Next i


  'テスト用データ出力
    For j = 1 To 5
        ws_test_iris.Cells(1, j).Value = ws_iris_dataset_stand.Cells(1, j).Value
    Next j

    For i = 1 To test_size
        EndRow = ws_test_iris.Cells(rows.count, 1).End(xlUp).Row
        For j = 1 To 5
            ws_test_iris.Cells(EndRow + 1, j).Value = ws_iris_dataset_stand.Cells(TestRows(i), j).Value
        Next j
    Next i


End Sub

'************************************************************************************
'   関数名 :GetRandomRow
'   機能   :指定範囲内でランダムで生成した行数(自然数)を返す
'   引数   :DataCount      生成する値の数
'          :MAX_DATA      生成する値の上限値
'          :minDataCount   生成する値の下限値(省略可/省略した場合は「1」となる)
'************************************************************************************

Public Function GetRandomRow(DataCount As Long, MAX_DATA As Long, Optional MinDataCount As Long) As Long()
    Dim i As Long, Num As Long
    Dim flag() As Boolean
    Dim RndRows() As Long
    
    ReDim flag(MAX_DATA)
    ReDim RndRows(MAX_DATA)

    Dim startDataCount As Long
    If MinDataCount < 1 Then
        startDataCount = 1
    Else
        startDataCount = MinDataCount
    End If
    
    For i = 1 To DataCount
    
        Randomize
        
        Do
            Num = Int((MAX_DATA - startDataCount + 1) * Rnd + startDataCount)
        Loop Until flag(Num) = False
        
        RndRows(i) = Num
        flag(Num) = True
    Next i
    
    ReDim Preserve flag(DataCount)
    ReDim Preserve RndRows(DataCount)
    
    GetRandomRow = RndRows
    
End Function

 

Affine_Layer (クラスモジュール)

Option Explicit
Option Base 1

Dim W() As Double
Dim b() As Double
Dim x() As Double
Dim dW() As Double
Dim db() As Double
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Function init(ByRef input_W() As Double, ByRef input_b() As Double)

    Dim i As Long
    Dim j As Long
    
    ReDim W(UBound(input_W, 1), UBound(input_W, 2))
    For i = 1 To UBound(input_W, 1)
        For j = 1 To UBound(input_W, 2)
            W(i, j) = input_W(i, j)
        Next j
    Next i

    ReDim b(UBound(input_b))
    For i = 1 To UBound(input_b)
        b(i) = input_b(i)
    Next i
    
End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Function forward(ByRef input_x() As Double) As Double()

    Dim i As Long

    Dim out() As Double
    ReDim x(UBound(input_x))
    ReDim out(UBound(input_x))
    
    For i = 1 To UBound(input_x)
        x(i) = input_x(i)
    Next i
    
    out = Functions.dot(x, W)    '重み付き入力値の総和
    out = Functions.add(out, b)  'バイアス加算
    
    forward = out

End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Function backward(ByRef dout() As Double)

    Dim i As Long
    Dim j As Long
    Dim WT() As Double
    Dim dx() As Double
    
    ReDim WT(UBound(W, 2), UBound(W, 1))
    For i = 1 To UBound(W, 1)
        For j = 1 To UBound(W, 2)
            WT(j, i) = W(i, j)
        Next j
    Next i
    
    ReDim dx(UBound(W, 1))
    
    dx = Functions.dot(dout, WT)
    
    
    ReDim dW(UBound(W, 1), UBound(W, 2))
    dW = Functions.dot2(x, dout)
    
    
    ReDim db(UBound(W, 2))
    db = dout
    
    backward = dx

End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Function ParamsUpdate(ByRef LearningRate As Double)

  'Wの更新
    Dim i As Long
    Dim j As Long
    
    For i = 1 To UBound(W, 1)
        For j = 1 To UBound(W, 2)
            W(i, j) = W(i, j) - (LearningRate * dW(i, j))
        Next j
    Next i


  'bの更新
    For i = 1 To UBound(b)
        b(i) = b(i) - (LearningRate * db(i))
    Next i

End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Property Get Weight()

    Weight = W

End Property
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Property Get Bias()

    Bias = b

End Property

 

ReLU_Layer (クラスモジュール)

Option Explicit
Option Base 1

Dim mask() As Boolean
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Function forward(ByRef x() As Double) As Double()

    Dim i As Long
    Dim out() As Double
    
    ReDim out(UBound(x))
    ReDim mask(UBound(x))
    
    For i = 1 To UBound(x)
        
        If x(i) <= 0 Then
            out(i) = 0
            mask(i) = True
        Else
            out(i) = x(i)
            mask(i) = False
        End If
        
    Next i
    
    forward = out

End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Function backward(ByRef dout() As Double) As Double()

    Dim i As Long
    Dim dx() As Double
    
    ReDim dx(UBound(dout))
    
    For i = 1 To UBound(dout)
        
        If mask(i) = True Then
            dx(i) = 0
        Else
            dx(i) = dout(i)
        End If
        
    Next i
    
    backward = dx

End Function

 

Sigmoid_Layer (クラスモジュール)

Option Explicit
Option Base 1

Dim out() As Double
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Function forward(ByRef x() As Double) As Double()

    Dim i As Long
    
    ReDim out(UBound(x))
    
    For i = 1 To UBound(x)
        
        out(i) = 1 / (1 + Exp(x(i) * -1))
    
    Next i
    
    forward = out

End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Function backward(ByRef dout() As Double) As Double()

    Dim i As Long
    Dim dx() As Double
    
    ReDim dx(UBound(dout))
    
    For i = 1 To UBound(dout)
        
        dx(i) = dout(i) * (1 - out(i)) * out(i)
        
    Next i
    
    backward = dx

End Function

 

SoftmaxWithLoss_Layer (クラスモジュール)

Option Explicit
Option Base 1

Dim loss As Double
Dim y() As Double
Dim t() As Long
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Function forward(ByRef x() As Double, ByRef input_t() As Long) As Double
    
    Dim i As Long
    
    ReDim t(UBound(input_t))
    For i = 1 To UBound(input_t)
        t(i) = input_t(i)
    Next i
    
    ReDim y(UBound(x))
    y = Functions.Softmax(x)
    
    loss = Functions.cross_entropy_error(y, t)
    
    forward = loss

End Function
'――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
Public Function backward(ByRef dout As Double) As Double()

    Dim dx() As Double
    Dim i As Long
    
    ReDim dx(UBound(t))
    
    For i = 1 To UBound(t)
        dx(i) = (y(i) - t(i))
    Next
    
    backward = dx
    
End Function

 

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

 

Irisデータセットの学習

上記コードをコピペしたら下記の手順を行うことで手書き文字が認識できるようになります。

1. Irisデータセットを学習させてみよう                 ニューラルネットワークの学習
2. 入力された値からIrisの種類を分類してみよう   ニューラルネットワークによる推論

 

まとめ

このページでは「Excel VBAでニューラルネットワークを再現|Irisデータセット分類問題」で実装したモジュールのVBAコードをまとめて紹介しました。コードで何を行なっているのか詳しく知りたい方は下記リンクを参照してください。

標準モジュール

Functionsモジュールの実装
メインモジュールの実装 
Irisデータの前処理 (preprocessモジュール)

クラスモジュール

Affineレイヤの実装
ReLUレイヤの実装
Sigmoidレイヤの実装
Softmax-with-Lossレイヤの実装
TwoLayerNetクラスの実装

 

 icon-book 参考書籍

AI,Deep Learning,Excel,VBA