無限不可能性ドライブ

『ニューラルネットワーク自作入門』に刺激されてExcelVBAでニューラルネットワークを作ってみたものの、やっぱり数学やらなきゃと思い少しずつやってきたのもあって、自分の知識の整理とかそういった感じです。

【VBA編】(順伝播)隠れ層の作成

f:id:celaeno42:20181201115558p:plain

レイヤークラス

前回はユニットクラスを作成しました。今回からはユニットを管理するレイヤークラスを作成していきます。
入力層は以前こちらで作成しました。
celaeno42.hatenablog.com

隠れ層と出力層がありますが、若干機能が異なるので今回は別々に実装します。

隠れ層

今回のニューラルネットワークでは隠れ層は2層ありますが、機能は同じです。
今回は隠れ層を実装していきましょう。

隠れ層の初期化

まずは初期化処理を作っていきます。コードは「classHiddenLayer」に記述します。
レイヤークラスは自分の層のユニットを管理するので、ユニット数、ユニットへの入力データ数、活性化関数の種類を知っておく必要があります。
例えば、隠れ層1層めでは、ユニット数 = 3, 入力データ数 = 4, 活性化関数 = ReLU とします。
これらは引数として呼び出し元から渡せるようにしておきます。

クラスモジュール【classHiddenLayer】

'[classHiddenLayer - 隠れ層クラス]
Option Explicit
Option Base 1

Dim mUnitList() As classUnit        'ユニット格納用リスト
Dim mUnitCount As Long              '自レイヤーのユニット数
Dim mAct As Long                    '活性化関数の種類

'自レイヤーのユニットを作成する
'[引数] <- aUnitCount : Long / ユニットの数, aInputCount : Long / 入力データの数, aActivationFunction : Long / 活性化関数の種類
Public Sub Initialize(ByRef aUnitCount As Long, ByRef aInputCount As Long, ByRef aActivationFunction As Long)
    Dim i As Long
    
    ReDim mUnitList(aUnitCount)
    
    '必要な数だけユニットを作成しユニット格納用リストに格納する
    For i = 1 To aUnitCount
        Set mUnitList(i) = New classUnit
        'Newしたユニットを初期化する
        Call mUnitList(i).Initialize(aInputCount)
    Next
    
    mUnitCount = aUnitCount
    mAct = aActivationFunction
    
End Sub

「classUnit」型の配列「mUnitList()」にNewしたユニット(インスタンス化したユニット)を格納していきます。
Newしたユニットは初期化したいので「Initialize()」を呼び出して初期化処理をします。ユニットは各入力データに対応する重みを持つ必要があるため、引数として入力データの数を渡しています。
最後に、ユニット数と活性化関数の種類をモジュールレベル変数に格納してこのモジュールの他のプロシージャでも使えるようにしています。

順伝播

自レイヤーの管理するユニットに入力データを渡してユニットの出力値を計算します。入力データは引数として受け取るようにします。
ユニットで u を計算したら、活性化関数を適用して z を計算します。「activateU()」はこの後で実装します。

クラスモジュール【classHiddenLayer】

'順伝播:各ユニットのuとzを求める
'[引数] <- aInputDataList() : Double / 入力データのリスト
Public Sub Forward(ByRef aInputDataList() As Double)
    Dim i As Long
    
    'ユニットに入力値を渡して u を計算する
    For i = 1 To mUnitCount
        Call mUnitList(i).CalcU(aInputDataList)
    Next
    
    '活性化関数を適用して z を計算する
    Call activateU
    
End Sub


活性化関数

標準モジュール【G】に活性化関数の種類を列挙型で宣言しておきましょう。こうすることで、他の活性化関数を実装する際の拡張性を確保します。

標準モジュール【G】

Public Enum ACT
    ReLU = 1
    Softmax = 2
End Enum


活性化関数の計算はマシンラーニング演算用モジュール【ML】に実装します。

\displaystyle
ReLU(x) = max(x, 0) =
\left \{ \begin{array}{ll}
 x & (x \geq 0) \\
 0 & (x \lt 0)
 \end{array} \right.

なので、以下のように実装しています。

標準モジュール【ML】

'活性化関数 ReLU
'[引数] <- aU : Double / ユニットの u
'[戻り値] -> actReLU : Double / ReLU適用後の値
Public Function actReLU(ByRef aU As Double) As Double

    actReLU = WorksheetFunction.Max(aU, 0)

End Function


【classHiddenLayer】に戻って、いま実装した「actReLU()」を呼び出す処理を書きましょう。

クラスモジュール【classHiddenLayer】

'ユニットの u に活性化関数を適用する
Private Sub activateU()

    If mAct = ACT.ReLU Then
        Call activationReLU
    End If

End Sub

'ユニットの u にReLUを適用する
Private Sub activationReLU()
    Dim i As Long
    
    For i = 1 To mUnitCount
        mUnitList(i).Z = ML.actReLU(mUnitList(i).U)
    Next
    
End Sub

「activateU()」プロシージャでいったん受けたのち、その層の活性化関数によって処理を分けるようにしています。今回は ReLU だけなのでありがたみはありませんが、例えばシグモイド関数を実装したい場合「activationSigmoid()」のようなプロシージャを作成し「activateU()」に [ ElseIf mACT = ACT.Sigmoid Then ] を追加して呼び出すようにすることで、活性化関数を適用したい呼び出し元(今回は「Forward()」プロシージャ)が、活性化関数によって呼び出すプロシージャを変える必要がなくなります。

「activationReLU()」では、ユニットごとに先ほど実装した【ML】モジュールの「actReLU()」を呼び出して、ユニットの z の値(活性化関数適用後の値)を計算しています。

出力値リスト

各ユニットの z (最終出力値 = 活性化関数適用後の出力値)が求まったので、次の層に渡すためにリスト化(配列化)します。なお、次の層ではこの出力値のリストを入力値として受け取ります。
コードは各ユニットの z を戻り値用の配列に格納しているだけです。

'次の層に渡すための出力リスト(配列)を準備する
'[戻り値] -> OutputDataList() : Double / 自層の各ユニットの出力値の配列
Public Function OutputDataList() As Double()
    Dim i As Long
    Dim outputDatas() As Double
    
    ReDim outputDatas(mUnitCount)
    
    For i = 1 To mUnitCount
        outputDatas(i) = mUnitList(i).z
    Next
    
    OutputDataList = outputDatas()
    
End Function


さて、これで入力から出力までを求めることができました。
自層の出力値が次の層の入力値になるので「classHiddenLayer」を複数インスタンス化すれば、複数の隠れ層をつなげていくことができます。
次回は出力層の実装をしていく予定です。


f:id:celaeno42:20181212233850p:plain