無限不可能性ドライブ

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

【VBA編】データの標準化

前回はそれぞれのシートにCSVデータを読み込む処理を作成しました。
今回は読み込んだデータを標準化する処理を書いていきます。

標準化

「標準化」とは、データを「平均が0、分散が1になるように変換する」ことです。
標準化を行うことで、学習の効率がよくなるそうなので、今回使用するデータについても標準化することにします。
グラフで見るとこんな感じ。
f:id:celaeno42:20181118000955p:plain

がくの長さを x 軸、がくの幅を y 軸にとってデータをプロットしています。
青い点が標準化前のデータ(元のデータ)、オレンジの点が標準化後のデータです。
標準化後のデータは原点 O の周辺に集まっているのがわかると思います。

標準化の計算

標準化は個々のデータについておこなっていくのですが、Excelの関数では、

  =STANDARDIZE(x, 平均, 標準偏差)

を使って求めることができます。
なお、引数の x は 元の値ですが、

  平均は「=AVERAGE(データ)」
  標準偏差は「=STDEV.P(データ)」

でそれぞれ求めることができます。

標準化後の値を z とすると、数式では以下のようにあらわされます。


\displaystyle z = \frac{x - \mu}{\sigma} = \frac{標準化前の値 - 平均}{標準偏差}


(今回のコードではワークシート関数を使うのでこの式は利用しません。)

では、標準化のコードを書いていきましょう。

必要なシートの追加

訓練データとテストデータそれぞれの標準化後のデータを格納するシートを追加します。
標準化後のデータを入力データとして利用するので、それぞれのシート名には「(入力)」とつけています。
オブジェクト名はそれぞれ「ws_Train_Data_Input」、「ws_Test_Data_Input」としました。
f:id:celaeno42:20181130204519p:plain


定数の宣言

標準モジュール「G」に以下の列挙型の定数を宣言しておきます。
「ラベル列」というのは、データの正解(値)が格納されている列です。
今回であれば「アヤメの種類」が格納されている列です。

'[G - 標準モジュール]

Public Enum RW
    DATA_TITLE = 1             'データのタイトル行
    DATA_START = 2             'データの開始行
End Enum

Public Enum CL
    DATA_LABEL = 1             'データのラベル列
    DATA_START = 2             'データの開始列
End Enum

標準化のコード

「mdlReadData」に以下のコードを書きましょう。
引数として、元データのあるシート、書き込み先のシートを取っています。
 元データのあるシートは、「ws_Train_Data」もしくは「ws_Test_Data」
 書き込み先のシートは、「ws_Train_Data_Input」もしくは「ws_Test_Data_Input」
が呼び出し元でセットされます。

'[mdlReadData - 標準モジュール]

'ワークシートのデータを標準化して書き込み先のワークシートに書き込む
'[引数] <- aWsData : Worksheet / 元データがあるシート, aWsStandardized : Worksheet / 標準化したデータを書き込む先のシート
'[戻り値] -> なし
Private Sub standardizeData(ByRef aWsData As Worksheet, ByRef aWsStandardized As Worksheet)
    Dim r As Long
    Dim c As Long
    Dim i As Long
    Dim eRow As Long
    Dim eCol As Long
    Dim mean As Double                           '平均値
    Dim std As Double                               '標準偏差
    
    aWsStandardized.Cells.Clear
    
    With aWsData
    
        '最終行と最終列を取得する
        eRow = .Cells(Rows.Count, 1).End(xlUp).Row
        eCol = .Cells(1, Columns.Count).End(xlToLeft).Column
        
        '列ごとに処理
        For c = CL.DATA_START To eCol
            
            '平均値と標準偏差を求める
            mean = WorksheetFunction.Average(.Range(.Cells(RW.DATA_START, c), .Cells(eRow, c)).Value)
            std = WorksheetFunction.StDev_P(.Range(.Cells(RW.DATA_START, c), .Cells(eRow, c)).Value)
            
            '標準化を行う
            For r = RW.DATA_START To eRow
                aWsStandardized.Cells(r, c).Value = WorksheetFunction.Standardize(CDbl(.Cells(r, c).Value), mean, std)
            Next
            
        Next
        
        'タイトル行とラベル列をコピー貼り付け
        .Rows(RW.DATA_TITLE).Copy Destination:=aWsStandardized.Rows(RW.DATA_TITLE)
        .Columns(CL.DATA_LABEL).Copy Destination:=aWsStandardized.Columns(CL.DATA_LABEL)
        
    End With
    
End Sub

1列目はラベルなので、2列目から処理を開始します。(CL.DATA_START は 2です。)
列ごとにワークシート関数で平均「mean」と標準偏差「std」を求めています。
平均と標準偏差が求まったら、元データの値を「=STANDARDIZE(x, 平均, 標準偏差)」のワークシート関数を使って標準化し、標準化後の値を書き込み先のシートに書き込みます。
すべての列について上記処理を行い、最後にタイトル行とラベル列を書き込み先シートにコピーして終了です。

標準化処理の呼び出し

では、上記の標準化処理を呼び出してデータを標準化しましょう。
前回作成した「Click_データ読み込み()」プロシージャにコードを追加します。
追加するのは、
 [ Call standardizeData(ws_Train_Data, ws_Train_Data_Input) ]
 [ Call standardizeData(ws_Test_Data, ws_Test_Data_Input) ]
の2行です。
それ以外の変更点はありません(コメントを除く)。

'[mdlReadData - 標準モジュール]

'データの読み込み
Public Sub Click_データ読み込み()
    Dim filePath As String
    
    Application.ScreenUpdating = False
    
    '訓練データの読み込み
    filePath = Range(G.RNG_TRAIN_DATA_PATH).Value
    Call readData(ws_Train_Data, filePath)
    '訓練データを標準化する
    Call standardizeData(ws_Train_Data, ws_Train_Data_Input)

    
    'テストデータの読み込み
    filePath = Range(G.RNG_TEST_DATA_PATH).Value
    Call readData(ws_Test_Data, filePath)
    'テストデータを標準化する
    Call standardizeData(ws_Test_Data, ws_Test_Data_Input)

    Application.ScreenUpdating = True
    
    MsgBox "データを読み込みました。", vbOKOnly + vbInformation
    
End Sub

処理の確認

では、メインシートの「データ読み込み」ボタンをクリックしてデータを読み込んでみましょう。
「訓練データ(入力)」シート、「テストデータ(入力)」シートにそれぞれ標準化後の値が書き込まれていると思います。

【訓練データ(入力)】シート(ws_Train_Data_Input)
f:id:celaeno42:20181130213440p:plain

【テストデータ(入力)】シート(ws_Test_Data_Input)
f:id:celaeno42:20181130213643p:plain