Neural Network Console(ニューラルネットワークコンソール:以後NNC)で学習済のモデルをEXCELから使って、手書き画像ファイルの識別をやってみます。
実用性はほぼない、お遊びですが、「NNCの学習済モデルとパラメータを使って推論を外部プログラムからのキックで実行して、結果を受け取る」というAPI的な考え方で考えるのは、個人的に面白いかなと思ってます。
学習済モデルの準備(はNNC組み込みのものをそのまま使います)
今回の主題はNNCのモデルでもデータでもありません。
だから、こだわらず、NNCに用意されているLeNet.sdcprj と MNISTを使います。
それを学習済にしてある状態から始めます。
自分の環境(Version1.20)だと、Accuracy 98%以上あったので十分かなと。
作成したデモのイメージ
手軽なので、とりあえずEXCELで作りました。
画面イメージです。
操作手順とやれることは以下です。
- データファイルを指定する。
- ”バックでNnabla_cliを実行してチェックする”ボタンを押する。
- そうすると裏でNNCのモデルと学習済パラメータで推論を行う。
- 終了すると、結果をメッセージ欄に表示する。
- 対象になった画像をその下に拡大して表示する。
データファイルは、以下のようにして、検査対象の画像のパスを書くNNCのルールに沿ったものです。
x:image,y:label
./validation/7/v0.png,7
検査対象の画像は、自分が適当に手書きで数字を書いて、28✕28のグレースケールで保存したものを上記のパスにおいてます。
デモの結果イメージ
実行した結果です。
データファイル名を書き換えて、もう一回実行します。
これだけです。
VBAからnnabla_cliを実行してるだけ
裏でNNCのモデルと学習済パラメータで推論を行う部分は、Neural Network Libraries(以後、nnabla)に付属する、「nnabla_cli.exe」というツールを使います。
nnabla_cli.exeの使い方は以下の記事で説明しています。
仮想環境にインストールしないように注意
仮想環境に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より大きいからです。
グレースケールになおしてやらないといけません。
グレースケールで保存できるツールを使うか、ペイントで保存したあとで、グレースケール変換をする必要があるので注意してください。
ではでは。