packages feed

menoh-0.3.0: app/mnist_example.hs

{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
module Main (main) where

import qualified Codec.Picture as Picture
import qualified Codec.Picture.Types as Picture
import Control.Applicative
import Control.Monad
import Data.Monoid
import qualified Data.Vector.Generic as VG
import qualified Data.Vector.Storable as VS
import Data.Version
import Options.Applicative
import Menoh
import System.FilePath
import Text.Printf

import Paths_menoh (getDataDir)

main :: IO ()
main = do
  putStrLn "mnist example"
  dataDir <- getDataDir
  opt <- execParser (parserInfo (dataDir </> "data"))

  let input_dir = optInputPath opt

  images <- forM [(0::Int)..9] $ \i -> do
    let fname :: String
        fname = printf "%d.png" i
    ret <- Picture.readImage $ input_dir </> fname
    case ret of
      Left e -> error e
      Right img -> return (Picture.extractLumaPlane $ Picture.convertRGB8 img, i, fname)

  let batch_size  = length images
      channel_num = 1
      height = 28
      width  = 28
      category_num = 10

      input_dims, output_dims :: Dims
      input_dims  = [batch_size, channel_num, height, width]
      output_dims = [batch_size, category_num]

      -- Aliases to onnx's node input and output tensor name
      mnist_in_name  = "139900320569040"
      mnist_out_name = "139898462888656"

  -- Load ONNX model data
  model_data <- makeModelDataFromONNXFile (optModelPath opt)

  -- Specify inputs and outputs
  vpt <- makeVariableProfileTable
           [(mnist_in_name, DTypeFloat, input_dims)]
           [mnist_out_name]
           model_data
  optimizeModelData model_data vpt

  -- Construct computation primitive list and memories
  model <- makeModel vpt model_data "mkldnn"

  -- Copy input image data to model's input array
  writeBuffer model mnist_in_name [VG.map fromIntegral (Picture.imageData img) :: VS.Vector Float | (img,_,_) <- images]

  -- Run inference
  run model

  -- Get output
  (vs :: [VS.Vector Float]) <- readBuffer model mnist_out_name

  -- Examine the results
  forM_ (zip images vs) $ \((_img,expected,fname), scores) -> do
    let guessed = VG.maxIndex scores
    putStrLn fname
    printf "Expected: %d Guessed: %d\n" expected guessed
    putStrLn $ "Scores: " ++ show (zip [(0::Int)..] (VG.toList scores))
    putStrLn $ "Probabilities: " ++ show (zip [(0::Int)..] (VG.toList (softmax scores)))
    putStrLn ""

-- -------------------------------------------------------------------------

data Options
  = Options
  { optInputPath :: FilePath
  , optModelPath :: FilePath
  }

optionsParser :: FilePath -> Parser Options
optionsParser dataDir = Options
  <$> inputPathOption
  <*> modelPathOption
  where
    inputPathOption = strOption
      $  long "input"
      <> short 'i'
      <> metavar "DIR"
      <> help "input image path"
      <> value dataDir
      <> showDefault
    modelPathOption = strOption
      $  long "model"
      <> short 'm'
      <> metavar "PATH"
      <> help "onnx model path"
      <> value (dataDir </> "mnist.onnx")
      <> showDefault

parserInfo :: FilePath -> ParserInfo Options
parserInfo dir = info (helper <*> versionOption <*> optionsParser dir)
  $  fullDesc
  <> header "mnist_example - an example program of Menoh haskell binding"
  where
    versionOption :: Parser (a -> a)
    versionOption = infoOption (showVersion version)
      $  hidden
      <> long "version"
      <> help "Show version"

-- -------------------------------------------------------------------------

softmax :: (Real a, Floating a, VG.Vector v a) => v a -> v a
softmax v | VG.null v = VG.empty
softmax v = VG.map (/ s) v'
  where
    m = VG.maximum v
    v' = VG.map (\x -> exp (x - m)) v
    s = VG.sum v'

-- -------------------------------------------------------------------------