機械学習に挑戦(4)
NNCの学習結果をC/C++で使ってみる
(NNPの場合)

どもです。
前回のエントリで、NNCで学習結果のモデルを「NNB(NNabla C Runtime file format)」形式でエクスポートし、そのコードを使用してC/C++のアプリケーションを実装してみました。
ところでNNCが提供する「学習結果のモデルのエクスポート」の形式にはもう1つ、「NNP(Neural Network Libraries file format)」形式があります。
今回のエントリでは、この「NNP(Neural Network Libraries file format)」形式でエクスポートした学習モデルを、C/C++で実装したアプリケーションで使用してみます。

1. 作業環境

今回のエントリでは、以下の環境で作業を実施します。

項目 内容
OS Windows10 Pro 64bit
(21H1)
CPU i7-8700
メモリ 16GB
IDE Visual Studio Community 2019
Version 16.11.8
OpenCV Ver. 4.5.5
vc15/x64向けビルド
Neural Network Libraries Ver. 1.24.0
Chocolately (バージョン記載が見つけれませんでした)

今回使用する環境の中で、「Neural Network Libaries」と「Chocolately」は初出です。

「Neural Network Libaries」は、NNP形式の学習結果を使用する際に使用するライブラリ群です。
これは、Gitから取得、ビルドします。
ビルドの手順については、本エントリ内で説明します。

「Chocolately」は、Windows用のパッケージ管理を行うツールです。
Linuxの「apt-get」のようなツールだと思っておけばよいと、理解しています。
詳しいことは、本エントリの趣旨とはずれるので、割愛します。

2. モデルのエクスポート

まず、学習結果のモデルをエクスポートします。
NNCで作成済みのモデルは、以下の手順でエクスポートできます。

  1. NNCを起動する
  2. エクスポートしたい学習結果を含むプロジェクトを開く
  3. 「学習結果リスト」の任意の学習結果上で右クリックし、
    [エクスポート]-[NNP(Neural Network Libraries file format)]
    を選択する



この手順を実施することで、学習結果のモデル(.nnp)が出力されます。



なお、「NNB形式のエクスポート」の場合と異なり、「NNP形式のエクスポート」では「.nnp」ファイルのみが出力されます。
C言語のソースコードは出力されません。

3. Neural Network Libraries

エクスポートした学習結果のモデルとソースコードを使用するためには、対応する環境が必要です。
この環境が「Neural Network Libraries」と呼ばれるライブラリ群であり、ネットで公開されています。
ここでは、このライブラリ群のビルドについて記載します。

3.1. ソースコードの取得

Neural Network Librariesは、GitHubで公開されています。
GitHubのリポジトリをクローンする、あるいは環境一式をダウンロードして取得します。

3.2. ソースコード(ライブラリ群)のビルド

ソースコードが取得できたら、それらをビルドします。
GitHubでは、様々なプラットフォームに対するビルド手順が提供されています。
今回は、Windows環境でビルド、C/C++で使用するので、コチラの手順でビルドを実施します。

ビルドそのものは、リンク先に記載された手順を実行すれば、基本的にはOKです。
ただし、今回は「VisualStudioCommunity2019」で使用するライブラリをビルドします。
実行(ビルド)する必要があるのは、以下の2つのみです。

cmd /c nnabla\build-tools\msvc\build_cpplib.bat 2019
cmd /c nnabla\build-tools\msvc\test_nbla.bat 2019

もちろん、その前の2つのコマンドを実行しても問題は無いと思います。
しかしながら、環境によってはエラーが発生する可能性もありますので、注意が必要です。

3.3. 成果物

ビルドが完了すると、成果物は以下の場所に格納されます。

nnabla\build_vs2019\bin\Release

以上で、ライブラリ群のビルドは完了です。

4. 推定処理の実装

次は、モデルを使用した推定処理の実装です。
推定処理は、コチラに記載されたコードがともてよい参考になります。
加えてコードの詳細な説明が、ココに記載されています。
これらの情報を元にして、推論を行う処理を実装します。

4.1. コード

実際のコードは、以下のようになります。

#include "nbla_utils/nnp.hpp"

#include <cassert>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <string>

#include "opencv2/opencv.hpp"

#define INPUT_DATA_SIZE     (28 * 28)
using namespace std;

void read_input_data(const string& file_name, float* data)
{
	cv::String file_name_for_opencv(file_name);
	cv::Mat colorImage;
	colorImage = cv::imread(file_name_for_opencv);
	
	cv::Mat grayImage;
	cv::cvtColor(colorImage, grayImage, cv::COLOR_BGR2GRAY);

	uint8_t* input_data = (uint8_t*)grayImage.data;
	for (int index = 0; index < INPUT_DATA_SIZE; index++) {
		uint8_t data_value = *(input_data + index);
		float data_to_set = (float)(data_value) / (float)255.0;
		*(data + index) = data_to_set;
	}
}

int main(int argc, char* argv[])
{
	if (3 != argc) {
		return -1;
	}

	const string nnp_file(argv[1]);
	const string data_file(argv[2]);
	string executor_name("Executor");

	//Create a context
	nbla::Context ctx{
		{"cpu:float"}, "CpuCachedArray", "0"
	};
	nbla::utils::nnp::Nnp nnp(ctx);

	//Set NNP file no Nnp object.
	nnp.add(nnp_file);

	//Get an executor instance.
	auto executor = nnp.get_executor(executor_name);
	executor->set_batch_size(1);
	nbla::CgVariablePtr x = executor->get_data_variables().at(0).variable;
	float* data = x->variable()->cast_data_and_get_pointer<float>(ctx);

	//Read and set input data to the buffer.
	read_input_data(data_file, data);

	//Execute prediction.
	executor->execute();

	//Get output buffer
	nbla::CgVariablePtr y = executor->get_output_variables().at(0).variable;
	const float* y_data = y->variable()->get_data_pointer<float<(ctx);

	for (int index = 0; index < 10; index++) {
		printf("%3d : %.4f\n", index, y_data[index]);
	}
	return 0;
}

実施している処理は、先述のリンク先で紹介したコードと殆ど同じです。
異なるのは、画像を読み込んで推論で使用するデータを作成する部分が主です。
そしてこの処理も、前回のエントリで紹介した内容と同じです。
特筆すべき処理は無い、と考えます。

5. やってみよう

実装したアプリケーションを動作させる際に、モデルには機械学習に挑戦(3) NNCで0~9までの数字を判別してみたで作成した学習結果のモデルを使用します。
画像データには、前回のエントリでの試行と比較しやすくするために、「9」が書かれた画像を使用します。

モデルはコマンドラインの第1引数に、画像は第2引数に、それぞれファイルのパスを設定します。

で。
実行結果が、下図のようになります。



細かい数値が前回の結果と異なる点が気になりますが、とりあえず「『9』である可能性が最も高い」と推定されていることが分かります。

6. まとめ

前回に引き続き、今回もNNCからエクスポートした学習モデルをC/C++で使用して、推定を行いました。
今回は、「Neural Network Libraries」に対応した学習結果のモデル(.nnp)を使用しています。
結果として、Neural Network Libraries/.nnp形式のモデルを使用した場合でも、推定ができると分かりました。
また、「Neural Network Libraries」とモデルを使用した推定の基本的な手順も分かりました。

2回にわたって、NNCからエクスポートしたモデルをC/C++で使用する方法を書きました。
2つの方法を比較した場合、(個人的な感想ですが)「Neural Network Libraries」の方が使いやすいと感じています。

気になる点は、同じモデルをエクスポートし、かつ同じデータを使用したにも関わらず、推定結果を示す値が異なっている点です。
これについては、ライブラリの実装、内容を調査してみないと分かりません。
そのため、ここでこれ以上の追及はできません…(スミマセン)。

えー…。
ではっ!

ex. 注意点

今回のプログラムですが、VisualStudioの[Debug]モードで実行した場合、以下のコードで例外が発生します。

nbla::Context ctx{
	{"cpu:float"}, "CpuCachedArray", "0"
};

この行を実行したタイミングで、「bad_allocation」例外が発生します。
調べてみると、これはコチラで既に報告されている不具合の様子です。
また、現在対応を検討しているようなので、修正を待つのが良いかと思います。