SE_BOKUのまとめノート的ブログ

SE_BOKUが知ってること・勉強したこと・考えたことetc

学習済モデルをEXCELから使い、手書き数字画像から数字を読むデモを作る/Neural Network Console

Neural Network Console(ニューラルネットワークコンソール:以後NNC)で学習済のモデルをEXCELから使って、手書き画像ファイルの識別をやってみます。

実用性はほぼない、お遊びですが、「NNCの学習済モデルとパラメータを使って推論を外部プログラムからのキックで実行して、結果を受け取る」というAPI的な考え方で考えるのは、個人的に面白いかなと思ってます。

f:id:arakan_no_boku:20190326212937j:plain

 

学習済モデルの準備(はNNC組み込みのものをそのまま使います) 

 今回の主題はNNCのモデルでもデータでもありません。

だから、こだわらず、NNCに用意されているLeNet.sdcprj と MNISTを使います。

それを学習済にしてある状態から始めます。

 

自分の環境(Version1.20)だと、Accuracy 98%以上あったので十分かなと。

f:id:arakan_no_boku:20180708211401j:plain

 

作成したデモのイメージ

手軽なので、とりあえずEXCELで作りました。

 画面イメージです。

f:id:arakan_no_boku:20180708223611j:plain

操作手順とやれることは以下です。

  1. データファイルを指定する。
  2. ”バックでNnabla_cliを実行してチェックする”ボタンを押する。
  3. そうすると裏でNNCのモデルと学習済パラメータで推論を行う。
  4. 終了すると、結果をメッセージ欄に表示する。
  5. 対象になった画像をその下に拡大して表示する。

データファイルは、以下のようにして、検査対象の画像のパスを書くNNCのルールに沿ったものです。

x:image,y:label
./validation/7/v0.png,7

検査対象の画像は、自分が適当に手書きで数字を書いて、28✕28のグレースケールで保存したものを上記のパスにおいてます。

 

デモの結果イメージ

実行した結果です。

f:id:arakan_no_boku:20180708224856j:plain

 

データファイル名を書き換えて、もう一回実行します。

f:id:arakan_no_boku:20180708225106j:plain

これだけです。

 

VBAからnnabla_cliを実行してるだけ

裏でNNCのモデルと学習済パラメータで推論を行う部分は、Neural Network Libraries(以後、nnabla)に付属する、「nnabla_cli.exe」というツールを使います。

nnabla_cli.exeの使い方は以下の記事で説明しています。

arakan-pgm-ai.hatenablog.com

 

仮想環境にインストールしないように注意

仮想環境にnnablaをインストールしないように注意してください。

anaconda等では、「conda create」で仮想環境を作って、そこに各処理系をわけてインストールしてactivateして使うことができます。

pythonだけを使っている時には、全くそれで問題ありません。

でも、例えば今回のようにVBAなど外部プログラムから呼び出す場合は仮想環境での実行を保証できないから問題があります。

バッチファイル等で、activateしてから実行するように書いても仮想環境を有効化した時点で一回処理がきれてしまうのでうまくいきません。

普通にインストールして、nnabla_cli.exeにパスの通った状態にしてください。

 

結果は「output_result.csv」を取り込んで関数で表示

結果を表示するときにも注意点があります。

nnabla_cli.exe を使って推論を実行したときは、正解ラベルの判断は自分でやらないといけないことです。

結果は、-oオプションで指定したアウトプットフォルダに、output_result.csv というファイルで出力されます。

今回の場合だと、0から9までの数字に対する分類なので、y_0からy_9までの各々に対して確率値を各々出力します。

こんな感じです。 

x:image,y:label,y'__0,y'__1,y'__2,y'__3,y'__4,y'__5,y'__6,y'__7,y'__8,y'__9
C:\tmp\validation\6\v1.png,6,1.00743818621e-07,3.29592852576e-14,2.54965094176e-11,3.84634796236e-10,1.00817437954e-07,0.213651716709,0.785960197449,2.70906084024e-12,2.60029846686e-05,0.000361841026461

 

これをEXCELの「アウト」シートに読み込んでやります。

そうすると、C列からL列の2行目に、その確率値が収まっている形になります。

 

最大値の列インデックスを取得する関数

そのシートを読んで、以下の関数を使って、結果を取り出します。

=MATCH(MAX(アウト!C2:L2),アウト!C2:M2,0)-1

 

MAX関数で範囲内確率値の最大値を返してくれるので、MATCH関数でその値がある相対位置を得るわけです。

 

結果を表示する部分の関数

相対位置が1だとy_0・・数字は0・・なので、-1をして、答えの数字を得てます。

これだけでも十分ですが、ちょっと味気ないので、

="数字は " &MATCH(MAX(アウト!C2:L2),アウト!C2:M2,0)-1 & " です"

のようにメッセージ欄には表示してます。

 

実行するコマンドラインもセル参照で組み立てる

VBAから実行するには、Windows付属のコマンドプロンプト(CMD)を使います。

「CMD /C」オプションにコマンドライン文字列を指定して実行します。

今回は、フォルダ名とデータファイル名(データF)の入力セルの値で、コマンドライン文字列を整形して、B11セルに表示して、VBA内からそれを参照しています。

NNCの場合、モデルの「net..nntxt」、パラメータの「parameters.h5」はファイル名固定です。

なので、フォルダとデータファイル名だけで組み立てられるわけです。

結果、メインシートのB11セルには以下のような文字列が表示されることになります。

nnabla_cli forward -c "C:\tmp\net.nntxt" -p "C:\tmp\parameters.h5" -d "C:\tmp\mnist_test_01.csv" -o "C:\tmp"

なお。

NNCクラウドの場合は、ダウンロードするファイルが「result.nnp」になります。

このファイルにモデル定義と学習済パラメータが含まれます。

そのため、クラウドで学習した場合のコマンド文字列は以下のようになります。

nnabla_cli forward -c "C:\tmp\return.nnp" -d "C:\tmp\mnist_test_03.csv" -o "C:\tmp"

パラメータの-pがいらないわけです。

 

nnabla_cliコマンドを実行するVBAソースです

全体をまず示します。

Sub forward()
    Dim WSH, wExec, sCmd As String, Result As String
    Dim buf, bufImg As String
    Dim line, col, tmpLine As Integer
    Dim tmp, tmp2 As Variant
    Dim i, j As Long
    Dim img, img2 As Shape
    Dim mainSheetName, outSheetName, dataFileName, imgFileName As String
    
    mainSheetName = "メイン"
    outSheetName = "アウト"
    
    '表示中の画像は削除する
    For Each img In Sheets(mainSheetName).Shapes
        If img.Type <> msoFormControl Then img.Delete
    Next img
    
    'データファイルから画像ファイル名を取得する
    dataFileName = Sheets(mainSheetName).Range("C3") & "\" & Sheets(mainSheetName).Range("C4")
    imgFileName = ""
    Open dataFileName For Input As #1
        tmpLine = 0
        Do Until EOF(1)
            Line Input #1, bufImg
            tmpLine = tmpLine + 1
            If tmpLine = 2 Then
                '/を\に置き換えて、WINDOWS絶対パスに変換する
                tmp2 = Split(bufImg, ",")
                imgFileName = Replace(tmp2(0), "./", Sheets(mainSheetName).Range("C3") & "\")
                imgFileName = Replace(imgFileName, "/", "\")
            End If
        Loop
    Close
         
    'B11セルのnnabla_cliコマンド文字列をCMDで実行する
    Set WSH = CreateObject("WScript.Shell")
    sCmd = Sheets(mainSheetName).Range("B11")
    Set wExec = WSH.Exec("%windir%\system32\cmd.exe /c " & sCmd)
    Do While wExec.Status = 0
        wExec.StdOut.ReadLine
        wExec.StdErr.ReadLine
    Loop
    Set wExec = Nothing
    Set WSH = Nothing
    
    '生成されたoutput_result.csv「アウト」シートに読み込む
    '結果判定はシートと関数で行うので、ここにか書かない
    line = 1
    col = 1
    Open Sheets(mainSheetName).Range("C3") & "\output_result.csv" For Input As #1
        Do Until EOF(1)
            Line Input #1, buf
            tmp = Split(buf, ",")
            For i = LBound(tmp) To UBound(tmp)
                Sheets(outSheetName).Cells(line, col) = tmp(i)
                col = col + 1
            Next i
            col = 1
            line = line + 1
        Loop
    Close #1
    
    '画像を開き、拡大する
    Sheets(mainSheetName).Range("C8").Select
     
    Sheets(mainSheetName).Pictures.Insert imgFileName
    For Each img2 In Sheets(mainSheetName).Shapes
        If img2.Type <> msoFormControl Then
            img2.Placement = xlMoveAndSize
      img2.LockAspectRatio = True img2.Width = 150 img2.Top = 147 img2.Left = 200 End If Next img2 End Sub

 

プログラムソースの補足

注意点のみ補足します。

 

実行待ちのループ

まず、処理の実行待ちをするループです。

Do While wExec.Status = 0
  wExec.StdOut.ReadLine
  wExec.StdErr.ReadLine
Loop 

残念ながら、現在のnnabla_cli.exeは、こんな風にアプリケーションAPIみたいな使われ方をするものではないので、毎回、起動と初期化のオーバーヘッドがかかります。

なので、当然かなり遅く、かつ、結果ファイル「output_result.csv」が保存されてから、後続処理を始めないとVBAがエラー終了してしまうので必須の処理です。

 

標準出力とエラー出力の情報はすてる

そのループの中で標準出力とエラー出力の情報を行読み込みして、ただ捨ててます。

wExec.StdOut.ReadLine
wExec.StdErr.ReadLine

無駄に見えますが、これをしないと、処理が正常に終了しなくなりますので、注意してください。

理由は単純で、nnabla_cli.exeで処理すると標準出力とエラー出力に大量のメッセージを出力するので、CMDのバッファ(4096バイトだったかな)を食いつぶして固まってしまうからです。

 

画像ファイルのパス名変換

もうひとつは画像ファイルを表示するときのパス名の変換です。

tmp2 = Split(bufImg, ",")
imgFileName = Replace(tmp2(0), "./", Sheets(mainSheetName).Range("C3") & "\")
imgFileName = Replace(imgFileName, "/", "\") 

単純に「./xxxxx/xxxxx.png」のようなLinux風パスの書き方のままだと、ファイルがみつからないと怒られるので、「C:\xxxxx\xxxxx.png」のWindowsパスに変換しているだけですが、ちょっとわかりづらいので、補足しておきます。

 

あとは、見ればわかる程度のソースじゃないかと思います。

 

最後に手書き数字画像を自分で作るときの注意

MNIST風の手書き数字画像は、ペイントとかを使えば自分でも簡単につくれます。

28ピクセル✕28ピクセルで、背景を黒にして、白ペンで数字を書いて保存すればよいだけなので。

ただ、ペイントで普通にpngで保存しただけだと、学習や推論時にエラーになります。

チャンネル数が1より大きいからです。

グレースケールになおしてやらないといけません。

グレースケールで保存できるツールを使うか、ペイントで保存したあとで、グレースケール変換をする必要があるので注意してください。 

ではでは。