F#で逆誤差伝播法
今回はニューラルネットワークで利用される、逆誤差伝播法をF#で実装してみたいと思います。
実装をするに際し、Math.NETライブラリを利用しています。
レイヤーの定義
今回は、全結合のAffine層、ReLU活性化層、Softmax最終活性化層を定義しました。
その他の層が欲しい場合は、ここに定義を追加していく形となります。
またAffine層の初期化として、He初期値を利用する関数も定義しました。
type Layer = | Affine of weight : Matrix<double> * bias : Vector<double> | ReLU type LastLayer = | SoftmaxCrossEntropy type Network = { Layers : Layer[] LastLayer : LastLayer } let createAffineHe (inputCount : int) (outputCount : int) : Layer = let weight = Matrix<double>.Build.Random(inputCount, outputCount) * (sqrt (2.0 / double inputCount)) let bias = Vector<double>.Build.Dense(outputCount) Affine(weight, bias)
伝播&逆伝播関数
純粋な伝播を定義するforward関数と、伝播しながら逆伝播関数を生成するforwardAndCreateBackward関数を定義します。
let forward (input : Vector<double>) (layer : Layer) : Vector<double> = match layer with | Affine(weight, bias) -> input * weight + bias | ReLU -> input |> Vector.map (max 0.0) let forwardAndCreateBackward (rate : double) (input : Vector<double>) (layer : Layer) : (Vector<double> -> Layer * Vector<double>) * Vector<double> = let output = forward input layer let backward = match layer with | Affine(weight, bias) -> (fun (dy : Vector<double>) -> let dx = dy * weight.Transpose() let dw = input.ToColumnMatrix() * dy.ToRowMatrix() Affine(weight - rate * dw, bias - rate * dy), dx ) | ReLU -> (fun dy -> let dx = Vector.map2 (fun y dy -> if y = 0.0 then 0.0 else dy) output dy layer, dx ) backward, output
学習関数
順伝播しながら逆伝播関数を生成し、最終層から逆伝播させ、更新された新しいNetworkを生成しています。
let softmax (x : Vector<double>) : Vector<double> = let c = Vector.max x let e = x |> Vector.map (fun x -> exp (x - c)) e / (Vector.sum e) let learn (rate : double) (network : Network) (input : Vector<double>) (teacher : Vector<double>) : Network = let backwards, y = network.Layers |> Array.mapFold (forwardAndCreateBackward rate) input let dy = match network.LastLayer with | SoftmaxCrossEntropy -> (softmax y) - teacher let layers, _ = backwards |> Array.rev |> Array.mapFold (|>) dy { network with Layers = layers |> Array.rev }
評価関数
入力と教師データから正答率を算出しています。(教師データは、どれか一つの値が活性化するものと想定しています。)
let predict (network : Network) (input : Vector<double>) : Vector<double> = let y = network.Layers |> Array.fold forward input match network.LastLayer with | SoftmaxCrossEntropy -> softmax y let accuracy (network : Network) (inputs : Vector<double>[]) (teachers : Vector<double>[]) : double = let outputs = inputs |> Array.map (predict network) Seq.map2 (=) (outputs |> Seq.map Vector.maxIndex) (teachers |> Seq.map Vector.maxIndex) |> Seq.averageBy (fun x -> if x then 1.0 else 0.0)
MNISTを学習してみる
MNISTの読み込みに関しては、下記記事を参照してみてください。
MNISTの読み込み(F#) - 何でもプログラミング
10000データ学習ごとの正答率は下記のように推移しました。
[ 0.0931, 0.913, 0.9321, 0.9297, 0.9441, 0.9471, 0.9488 ]
let trainImages = Mnist.loadImageVectors "train-images.idx3-ubyte" let trainLabels = Mnist.loadLabelVectors "train-labels.idx1-ubyte" let testImages = Mnist.loadImageVectors "t10k-images.idx3-ubyte" let testLabels = Mnist.loadLabelVectors "t10k-labels.idx1-ubyte" let initialNetwork = { Layers = [| createAffineHe 784 50 ReLU createAffineHe 50 10 |] LastLayer = SoftmaxCrossEntropy } Seq.zip trainImages trainLabels |> Seq.scan (fun network (image, label) -> learn 0.01 network image label) initialNetwork |> Seq.indexed |> Seq.iter (fun (i, network) -> if i % 10000 = 0 then printf "accuracy %f\n" (accuracy network testImages testLabels) )