【VBA編】(順伝播)出力層の作成
出力層
前回は隠れ層のクラスを作成しました。今回は出力層用のクラスを作成します。なお、今回のコードは隠れ層とほとんど同じです。
celaeno42.hatenablog.com
出力層の初期化
隠れ層同様、初期化処理を作ります。コードは「classOutputLayer」に記述します。なお、内容は「classHiddenLayer」と同じです。
クラスモジュール【classOutputLayer】
'[classOutputLayer - 出力層クラス] 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
順伝播
順伝播の処理も隠れ層と同様です。
クラスモジュール【classOutputLayer】
'順伝播:各ユニットの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
活性化関数
今回の出力層では活性化関数として Softmax を適用します。
例えば出力層1つめのユニットの出力値を求める Softmax の計算式は以下の通りです。
これをもとに【ML】モジュールに実装します。
なお、忘れずに [ Option Base 1 ] を宣言しておいてください。
標準モジュール【ML】
Option Explicit Option Base 1 '活性化関数 Softmax '[引数] <- aUList() : Double / 出力層のすべての出力値のリスト, aIndex : Long / 対象のユニットのインデックス '[戻り値] -> actSoftmax : Double / Softmax適用後の値 Public Function actSoftmax(ByRef aUList() As Double, ByRef aIndex As Long) As Double Dim i As Long Dim max As Double Dim sum As Double 'オーバーフロー対策ですべての出力値から最大値を引く必要があるため最大値を求めておく max = WorksheetFunction.max(aUList) '分母部分の計算 sum = 0 For i = 1 To UBound(aUList) sum = sum + Exp(aUList(i) - max) Next '該当ユニットの出力値を全出力値で割る actSoftmax = Exp(aUList(aIndex) - max) / sum End Function
計算式では、すべてのユニットの出力(を したもの)の合計を求めて、該当のユニットの出力(の)をその合計で割っていますが、実装の際にはオーバーフローに気をつける必要があります。
は で e は約 2.71828… のため、u が大きいとすぐにオーバーフローしてしまいます。そこで、実装時には出力層のユニットの u の最大値をそれぞれの出力値から引いて値を小さくすることで、オーバーフローを防ぐようにします。
ちなみに、活性化関数の適用をレイヤークラスで行うようにしているのは、Softmax の計算でその層の全ユニットの出力値が必要になるためです。自ユニットは他のユニットの出力値を知る必要がないため、あえてユニットクラスでは実装しないようにしました。ReLUなどの自ユニットの出力値だけが引数になっているものであれば、ユニットクラスに実装してもよいかと思います。
では、【classOutputLayer】に戻って、いま実装した「actSoftmax()」を呼び出す処理を書きましょう。
クラスモジュール【classOutputLayer】
'ユニットの u に活性化関数を適用する Private Sub activateU() If mAct = ACT.Softmax Then Call activationSoftmax End If End Sub 'ユニットの u にSoftmaxを適用する Private Sub activationSoftmax() Dim i As Long Dim uList() As Double 'Softmaxの計算には全出力値が必要となるため、配列に格納 ReDim uList(mUnitCount) For i = 1 To mUnitCount uList(i) = mUnitList(i).u Next For i = 1 To mUnitCount mUnitList(i).z = ML.actSoftmax(uList, i) Next End Sub
「actSoftmax()」 の引数として渡すため、出力層のすべてのユニットの出力値をいったん配列に格納しています。
ニューラルネットワークの推測
出力層の各ユニットの z(最終出力値)が求まったので、出力値が最も大きいユニット(のインデックス)をニューラルネットワークが推測した答えとします。
今回のニューラルネットワークでは、最も出力値の大きいユニットが1番目のユニットであれば「Irsi-Setosa」、2番目のユニットであれば「Iris-Versicolor」、3番目のユニットであれば「Iris-Virginica」と推測したとします。最終的な判断は「mdlSupervisor」モジュールで行うため、ここではインデックスを返す処理のみを実装しています。
'最も大きい出力値のユニットのインデックスを返す Public Function GetAnswerIndex() As Long Dim i As Long Dim ans As Long ans = 1 For i = 2 To mUnitCount If mUnitList(i).z > mUnitList(ans).z Then ans = i End If Next GetAnswerIndex = ans End Function
出力値リスト
各ユニットの 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
さて、これでニューラルネットワークの入力から推測までの実装ができました。ただ、パラメータを更新する機能は未実装なので、まだ学習はできません。この後は、いったん動作確認した後で、バックプロパゲーションによる学習機能を実装していく予定です。