【VBA編】(順伝播)隠れ層の作成
レイヤークラス
前回はユニットクラスを作成しました。今回からはユニットを管理するレイヤークラスを作成していきます。
入力層は以前こちらで作成しました。
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】に実装します。
なので、以下のように実装しています。
標準モジュール【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」を複数インスタンス化すれば、複数の隠れ層をつなげていくことができます。
次回は出力層の実装をしていく予定です。