MNISTの読み込み(F#)

機械学習のデータとして、手書き数字の画像がまとめられた下記のサイトを利用することがあります。
MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

訓練データとして60000画像、テストデータとして10000画像用意されています。

今回はこのデータをF#で利用できるようパースしてみたいと思います。(フォーマットは上記サイトに記載されています。)

読み込み関数

データはbig endianで保存されています。

let readInt32BigEndian (reader : BinaryReader) : int =
    BitConverter.ToInt32(reader.ReadBytes(4) |> Array.rev, 0)

let loadLabels (path : string) : byte[] =
    use reader = new BinaryReader(File.OpenRead(path))
    assert (readInt32BigEndian reader = 2049)
    let count = readInt32BigEndian reader
    reader.ReadBytes(count)

let loadImages (path : string) : byte[][] =
    use reader = new BinaryReader(File.OpenRead(path))
    assert (readInt32BigEndian reader = 2051)
    let count  = readInt32BigEndian reader
    let height = readInt32BigEndian reader
    let width  = readInt32BigEndian reader
    [| 1..count |] |> Array.map (fun _ -> reader.ReadBytes(width * height))


動作確認

実際にpngで保存してみて中身を確認してみます。

let savePng8 (width : int) (height : int) (pixels : byte[]) (path : string) : unit =
   use stream = new FileStream(path, FileMode.Create)
   let encoder = PngBitmapEncoder()
   let bmp = BitmapSource.Create(width, height, 96.0, 96.0, PixelFormats.Gray8, null, pixels, width)
   encoder.Frames.Add(BitmapFrame.Create(bmp))
   encoder.Save(stream)

let main argv = 
    let images = loadImages "train-images.idx3-ubyte"
    let labels = loadLabels "train-labels.idx1-ubyte"

    Array.iteri2
        (fun i image label ->
            let path = sprintf "image%d(%d).png" i label
            File1.savePng8 28 28 image path
        )
        (images |> Array.take 3)
        (labels |> Array.take 3)
f:id:any-programming:20180219125849p:plain f:id:any-programming:20180219125852p:plain f:id:any-programming:20180219125854p:plain
5 0 4


学習用に変形

実際にデータを利用する際には、数学ライブラリのデータで取得したほうが便利です。

今回はMath.NETのVector形式に変換してみます。
f:id:any-programming:20180219131050p:plain

また、画像データを255で割って正規化し、ラベルデータを10要素のVectorに変換します。(例:3 → [0, 0, 0, 1, 0, 0, 0, 0, 0, 0])

open MathNet.Numerics.LinearAlgebra

let loadLabelVectors (path : string) : Vector<double>[] =
    loadLabels path
    |> Array.map 
        (fun label -> 
            [| 0uy..9uy |] 
            |> Array.map (fun x -> if x = label then 1.0 else 0.0) 
            |> Vector.Build.Dense
        )

let loadImageVectors (path : string) : Vector<double>[] =
    loadImages path
    |> Array.map (Array.map (fun x -> (double x) / 255.0) >> Vector.Build.Dense)