F#で逆誤差伝播法(ミニバッチ対応版)

下記記事にて逆誤差伝播法をF#で実装してみました。
F#で逆誤差伝播法 - 何でもプログラミング

1データ/教師データ毎にネットワークを更新していましたが、今回はある程度の数学習してその変位の平均でネットワークを更新する、ミニバッチ法に対応してみたいと思います。

前回のものを流用して、ネットワーク更新の時にAffine層の平均を計算するのでもよいのですが、今回はそもそも入力でMatrix(列方向に複数データが入る)を受け取れるよう実装してみます。

特に記載のないものは、上記記事を参照してみてください。

伝播&逆伝播関数

let forward (input : Matrix<double>) (layer : Layer) : Matrix<double> =
    match layer with
    | Affine(weight, bias) -> 
        input * weight |> Matrix.mapRows (fun _ x -> x + bias)            
    | ReLU ->
        input |> Matrix.map (max 0.0)

let forwardAndCreateBackward 
    (rate : double) (input : Matrix<double>) (layer : Layer) 
    : (Matrix<double> -> Layer * Matrix<double>) * Matrix<double> =
    let output = forward input layer
    let backward =
        match layer with
        | Affine(weight, bias) ->
            (fun (dy : Matrix<double>) ->
                let dx = dy * weight.Transpose()
                let dw = input.Transpose() * dy                    
                Affine(weight - rate * dw, bias - rate * (Matrix.sumCols dy)), dx
            )
        | ReLU ->
            (fun (dy : Matrix<double>) ->
                let dx = dy |> Matrix.mapi (fun i j dy -> if output.[i, j] = 0.0 then 0.0 else dy)
                layer, dx
            )
    backward, output


学習関数

let softmaxRows (x : Matrix<double>) : Matrix<double> =            
    x |> Matrix.mapRows (fun _ x -> softmax x)

let learn (rate : double) (network : Network) (input : Matrix<double>) (teacher : Matrix<double>) : Network =
    let backwards, y = network.Layers |> Array.mapFold (forwardAndCreateBackward rate) input
    let dy = 
        match network.LastLayer with
        | SoftmaxCrossEntropy -> 
            ((softmaxRows y) - teacher) / (double y.RowCount)
    let layers, _ = backwards |> Array.rev |> Array.mapFold (|>) dy
    { network with Layers = layers |> Array.rev }


評価関数

let predict (network : Network) (input : Matrix<double>) : Matrix<double> =
    let y = network.Layers |> Array.fold forward input
    match network.LastLayer with
    | SoftmaxCrossEntropy -> softmaxRows y        

let accuracy (network : Network) (input : Matrix<double>) (teacher : Matrix<double>) : double =
    let output = predict network input
    Seq.map2 
        (=) 
        (output  |> Matrix.toRowSeq |> Seq.map Vector.maxIndex)
        (teacher |> Matrix.toRowSeq |> Seq.map Vector.maxIndex)
    |> Seq.averageBy (fun x -> if x then 1.0 else 0.0)


MNISTを学習

10000データ学習ごとの正答率は下記のように推移しました。
[ 0.0947, 0.8723, 0.8893, 0.9092, 0.9154, 0.9154, 0.9183 ]

let shuffle (ary : 'a[]) : 'a[] =
    let random = System.Random()
    ary |> Array.sortBy (fun _ -> random.Next())

let trainImages = Mnist.loadImageVectors "train-images.idx3-ubyte"
let trainLabels = Mnist.loadLabelVectors "train-labels.idx1-ubyte"
let testImages  = Mnist.loadImageVectors "t10k-images.idx3-ubyte" |> Matrix.Build.DenseOfRowVectors
let testLabels  = Mnist.loadLabelVectors "t10k-labels.idx1-ubyte" |> Matrix.Build.DenseOfRowVectors

let initialNetwork =
    { Layers =
        [| createAffineHe 784 50
            ReLU
            createAffineHe 50 10
        |]
        LastLayer = SoftmaxCrossEntropy
    }

let batchSize = 100
seq { 1..trainImages.Length / batchSize }
|> Seq.scan
    (fun net i -> 
        let indices = [| 0..trainImages.Length - 1 |] |> shuffle |> Array.take batchSize
        let images = indices |> Array.map (fun i -> trainImages.[i]) |> Matrix.Build.DenseOfRowVectors
        let labels = indices |> Array.map (fun i -> trainLabels.[i]) |> Matrix.Build.DenseOfRowVectors
        learn 0.1 net images labels 
    )
    initialNetwork
|> Seq.iter (fun network -> printf "accuracy %f\n" (accuracy network testImages testLabels))