無限不可能性ドライブ

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

【VBA編】(順伝播)ユニットの作成

f:id:celaeno42:20181201115558p:plain

ユニット

ニューラルネットワークの基本となる単位です。隠れ層、出力層のユニットは基本的には同じ機能を持つので、共通して使える「classUnit(ユニットクラス)」として定義します。まずは順伝播の機能を作ります。
各ユニットは次のような機能を持つように設計します。
 【順伝播】
  - 入力値に重みを掛けて足し合わせる
  - 上記の値にバイアス値を足す
  - (上記の値に活性化関数を適用する)←これはレイヤークラスに実装します
  - 上記の値を出力する
 【逆伝播】
  - 重みとバイアスを更新する

重みとバイアス

各ユニットはそれぞれに重みとバイアスのパラメータを持ちます。重みは入力データの数と同じだけ必要です。また、学習が正しく行えるように、それぞれの重みは 0 以外のランダムな数値で初期化されている必要があります。(ただし、バイアスの初期値は 0 でかまいません。)

実装

以上を踏まえユニットクラスを実装しましょう。クラスとして定義することで、インスタンス化で必要なだけパラメータの異なる複製が作れるようになります。

重みの初期値に使うランダム値の生成

重みは 0 以外のランダムな数値で初期化する必要があるので、ランダム値を生成するプロシージャを作成します。ランダム値の範囲としては -1より大きく、1より小さい範囲としています。
コードは「ML」モジュールに記述しています。

標準モジュール【ML】

'[ML - マシンラーニング演算用モジュール]
Option Explicit

'-1 < num < 1 で0以外のランダム値を返す
'[戻り値] -> getRandom() : Double / -1 より大きく 1 より小さいランダム値(0以外)
Public Function getRandom() As Double
    Dim i As Long
    Dim num As Double
    
    Randomize
        
    Do
        num = (Rnd * 2) - 1
    Loop Until num <> 0
    
    getRandom = num

End Function


ユニットの初期化

ユニットの持つパラメータ(重みとバイアス)を初期化します。ユニットを管理するレイヤークラス(classHiddenLayer, classOutputLayer)からそのユニットが持つ重みの個数を引数として受け取ります。そして、先ほど作成した「getRandom()」プロシージャを呼び出して、各重みをランダム値で初期化しています。

ここで、ユニットクラスで必要となるモジュールレベル変数も宣言しておきましょう。
「Option Base 1」の宣言も忘れずにしておいてください。

クラスモジュール【classUnit】

'[classUnit - ユニットクラス]
Option Explicit
Option Base 1

Dim mWeightList() As Double         '重みのリスト
Dim mBias As Double                 'バイアス
Dim mU As Double                    '活性化関数適用前の合計値
Dim mZ As Double                    '出力値

'パラメータ(重みとバイアス)を初期化する
'[引数] <- aWeightCount : Long / 重みの数
Public Sub Initialize(ByRef aWeightCount As Long)
    Dim i As Long
    
    ReDim mWeightList(aWeightCount)
    '各重みをランダム値で初期化
    For i = 1 To aWeightCount
        mWeightList(i) = ML.getRandom()
    Next
    
    'バイアスを0で初期化
    mBias = 0
    
End Sub


合計値の計算

入力データとパラメータをもとに計算した合計値(活性化関数適用前の値)を求めます。上の図ではユニットの「U」となっている値です。
引数としてレイヤークラスから入力値のリストをもらい、リストの各データに対応する重みを掛けて足し合わせています。
(いままでの)説明では、それで計算した値にバイアス値を加えるとしています(していました)が、合計値に後で足すのと結果は変わらないので、今回は「mU」の初期値としてバイアス値をセットしています。
また、レイヤークラスで計算結果を取得できるように Getプロパティ で計算結果「mU」を返すようにしています。

クラスモジュール【classUnit】

'[classUnit - ユニットクラス]

'活性化関数適用前の合計値を計算する
'入力値にそれぞれの重みを掛けてバイアス値を加える
'[引数] <- aInputDataList() : Double / 入力値のリスト
Public Sub CalcU(ByRef aInputDataList() As Double)
    Dim i As Long

    mU = mBias
    For i = 1 To UBound(aInputDataList)
        mU = mU + (mWeightList(i) * aInputDataList(i))
    Next
    
End Sub

'活性化関数適用前の合計値を返す
'[戻り値] -> U : Double / 活性化関数適用前の合計値
Public Property Get U() As Double
    U = mU
End Property


出力値の計算

重みとパラメータから計算された値がわかったので、これに活性化関数を適用してユニットの出力値とします。上の図では「Z」となっている部分です。
ただし、(今回の設計では)活性化関数の適用計算はレイヤークラスが受け持つため、計算自体はレイヤークラスで行います。
ここでは、レイヤークラスから活性化関数適用後の値を受け取って次の層に渡すために、Let / Get プロパティとして実装します。

クラスモジュール【classUnit】

'[classUnit - ユニットクラス]

'出力値を格納する
'[引数] <- aActivatedU : Double / 活性化関数適用後の合計値
Public Property Let Z(ByRef aActivatedU As Double)
    mZ = aActivatedU
End Property

'出力値を返す
'[戻り値] -> Z : Double / 出力値
Public Property Get Z() As Double
    Z = mZ
End Property


ユニットクラスの実装はいったんここまでにして次はレイヤークラス(隠れ層クラス)を実装していきます。
逆伝播の処理については、逆伝播の実装で行っていきます。

f:id:celaeno42:20181212233850p:plain