ONNX Chainer  + OpenCV メモ

# ONNX Chainer  + OpenCV メモ
Chainerで学習したモデルをVCで実行したいというのがきっかけ．
Chainerで自作NNモデルをONNX形式ではき出してOpenCV dnnで読み込んでみたので忘れないようにメモ．

#### 概要
[0] 環境:Win10x64 + VisualStudio2015 + Opencv3.4.5+Anaconda3-2019(Python3.7.1 -- >3.6.7)
[1] 回帰モデルをNNで学習．
[2] ONXX形式で学習済みモデルを保存．
[3] OpenCV3.4.5(C++)で学習済みモデルを読み込み予測する．

## [1] 回帰モデルの学習
#### (1) NNモデル
NNモデルは，入力1，中間層x2，出力2層モデルとした．
モデルは色々変えてみるとおもしろいかもしない．

```
class MyChain(Chain):
    def __init__(self, n_units=10):
        super(MyChain, self).__init__(
             l1=L.Linear(1, n_units),
             l2=L.Linear(n_units, n_units),
             l3=L.Linear(n_units, 1))
        print("n_units={}".format(n_units) )
# ONXXでは，__call__が呼ばれる
    def __call__(self, x):
        return self.predict(x)
# forward方向の計算
    def predict(self, x):
        h1 = F.sigmoid(self.l1(x))
        h2 = F.relu(self.l2(h1))
        h3 = self.l3(h2)
        return h3
```

#### (2) サンプルデータ
サンプルデータを-π～πの範囲で，2.2sin(x)で作成．
振幅の2.2とか別に意味はない．
データ数は，256．

```
# データ数
N=256
# A sin( w*x )
A=2.2
w=1
# 標本データの生成
#   真の関数としてsin関数を
x, y = [], []
for i in np.linspace(-np.pi , np.pi , N ):
    x.append([i])
    y.append([A*math.sin(w*i)])  # 真の関数

```
#### (3) 学習
学習の最適化アルゴリズムにはAdamを使用． 
他にもSGDやAdaGradなど，Chainerは色々な最適化法を実装しているので，もっと勉強しよう．
学習過程は，以下のようになったので，最適化はうまくいっているみたい．

||
|:---:|
|![errorFunction.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/238679/8cb54f30-6e5c-af00-e703-6bb9fba6eb74.png)|
|学習過程の誤差関数|

で，学習結果をプロットした．ユニットの数や最適化手法，層数をいじると
もっと複雑な回帰ができると思う．

||
|:---:|
|![prediction.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/238679/ed6d978d-9615-d770-f023-2ef5acd14008.png)|
|学習結果|

まぁ，近似できていることが確認できた．


## [2] ONXX形式で学習済みモデルを保存

onnx形式での保存は，onnx_chainerをインポートしてexportするだけ．  

```
import onnx_chainer
 :
onnx_model = onnx_chainer.export(model, x, filename='MyChain.onnx')

```
##### 【onnx-chainerインストメモ】
onnx-chainerのインストールは，onnxとonnx-chainerをインストする必要があるみたい．
githubからonnxをcloneして，python setup.py install でもコンパイル時にエラー
pip install onnxでもonnx-chainerとバージョンがあわないみたい．．．
色々やってみたが，20190430時点では，conda-forgeにあるbinaryしかだめみたい．

```
conda install -c conda-forge onnx
```
でonnxを入れないと，onnx-chainerは走らないらしい．

その後，

```
pip install onnx-chainer
````
とした．onnx-chainerでpythonが3.6までの対応のようだ．

```
The following packages will be DOWNGRADED:
  _ipyw_jlab_nb_ext~                           0.1.0-py37_0 --> 0.1.0-py36_0
  anaconda-navigator                           1.9.7-py37_0 --> 1.9.7-py36_0
  certifi                                   2019.3.9-py37_0 --> 2019.3.9-py36_0
  conda                                       4.6.14-py37_0 --> 4.6.14-py36_0
  navigator-updater                            0.2.1-py37_0 --> 0.2.1-py36_0
  numba                               0.43.1-py37hf9181ef_0 --> 0.43.1-py36hf9181ef_0
  protobuf                             3.7.1-py37he025d50_0 --> 3.5.1-py36_vc14_3
  py-lief                              0.9.0-py37ha925a31_2 --> 0.9.0-py36ha925a31_2
  pycurl                            7.43.0.2-py37h7a1dbc1_0 --> 7.43.0.2-py36h7a1dbc1_0
  pyqt                                 5.9.2-py37h6538335_2 --> 5.9.2-py36h6538335_2
  scipy                                1.2.1-py37h29ff71c_0 --> 1.2.1-py36h29ff71c_0
  sphinxcontrib                                  1.0-py37_1 --> 1.0-py36_1
```
とか，Anaconda3(python3.7)を入れていてもダウングレードしてまう...けど，まぁいいか．
全コードはこんな感じ．

```
#-*- coding: utf-8 -*-
# 曲線回帰


# 数値計算関連
import math
import random
import numpy as np
import matplotlib.pyplot as plt

# chainer
from chainer import Chain, Variable
import chainer.functions as F
import chainer.links as L
from chainer import optimizers

# chainer onnx
import onnx_chainer

#################################################
# 3層ニューラルネットワークモデル
# 色々試してもおもしろい
class MyChain(Chain):
    def __init__(self, n_units=10):
        super(MyChain, self).__init__(
             l1=L.Linear(1, n_units),
             l2=L.Linear(n_units, n_units),
             l3=L.Linear(n_units, 1))
        print("n_units={}".format(n_units) )
# ONXXでは，__call__が呼ばれる
    def __call__(self, x):
        return self.predict(x)
# forward方向の計算
    def predict(self, x):
        h1 = F.sigmoid(self.l1(x))
        h2 = F.relu(self.l2(h1))
        h3 = self.l3(h2)
        return h3
#################################################


# データ数
N=256
# A sin( w*x )
A=2.2
w=1
# 標本データの生成
#   真の関数としてsin関数を
x, y = [], []
for i in np.linspace(-np.pi , np.pi , N ):
    x.append([i])
    y.append([A*math.sin(w*i)])  # 真の関数



# chainerの変数として再度宣言
x = Variable(np.array(x, dtype=np.float32))
y = Variable(np.array(y, dtype=np.float32))


# NNモデルを宣言
model = MyChain( 128 )

# 損失関数の計算
#   損失関数には自乗誤差(MSE)を使用
def errorFunction(x, y, model):
    t = model.predict(x)
    loss = F.mean_squared_error(t, y)
    return loss


# chainerのoptimizer
#   最適化のアルゴリズムには Adam を使用
optimizer = optimizers.Adam()
#optimizer = optimizers.SGD() #SGDの場合

# modelのパラメータをoptimizerに渡す
optimizer.setup(model)


# 学習回数
epoch=500
ef=np.zeros((epoch,2),dtype=np.float32)

# パラメータの学習
for i in range(0,epoch):
    model.zerograds()
    loss = errorFunction(x, y, model)
    loss.backward()
    ef[i,0]=i;
    ef[i,1]=loss.data;
    print("{}/{} loss={}".format( i, epoch,loss.data))  # 現状のMSEを表示
    optimizer.update(errorFunction, x, y, model)


# プロット
plt.plot(ef[:,0], ef[:,1], color='blue', label='error function')
plt.legend()
plt.savefig('errorFunction.png')
plt.show()

t = model.predict(x)
plt.plot(x.data, y.data, color='blue', label='sample')
plt.plot(x.data, t.data, color='red' , label='prediction')
plt.grid(which='major',color='gray',linestyle='-')
plt.xlim(-np.pi, np.pi)
plt.legend()
plt.savefig('prediction.png')
plt.show()


# ONNX形式に学習モデルを出力
onnx_model = onnx_chainer.export(model, x, filename='MyChain.onnx')

```
## [3] OpenCVで予測
OpenCV3.4.5(C++)でONXX形式の学習モデルを読み込むには，dnnモジュールを利用した．  
サンプルデータの作成は，NNモデルを学習した際の教師データにあわせている．

結果は，以下．  
青がサンプルデータ（答え）で，赤が予測結果．  
onnxモデルを読み込んで予測ができている．ちょっと感動．


||
|:---:|
|![opencvresult.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/238679/a1bc6e59-9319-9e40-a254-4e0ffdd6de19.png)|
|OpenCV予測結果|

コードは，以下．

```
#pragma comment(lib,"C:\\OpenCV\\lib\\opencv_core345.lib")
#pragma comment(lib,"C:\\OpenCV\\lib\\opencv_dnn345.lib")

#define _USE_MATH_DEFINES 

#include <math.h>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>


int main(int argc, char* argv[])
{
	// ネットワークインスタンス
	cv::dnn::Net net;
	
	// ONNXのネットワーク読み込み
	net = cv::dnn::readNetFromONNX ("MyChain.onnx");
	
	net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
	net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);


	int N=256;
	
	//サンプルデータ作成
	//chainerに合わせて，floatで作成
	cv::Mat matX(N,1,CV_32F);
	cv::Mat matY(N,1,CV_32F);
	
	for( int i=0; i<N; i++){
		float x=2.0*M_PI/N * i - M_PI;
		matX.ptr<float>(i)[0]=x;
		matY.ptr<float>(i)[0]=2.2*sin(x);
	}
	
	net.setInput( matX );
	cv::Mat matYpredict=net.forward();
	
	for( int i=0; i<N; i++){
		float x=matX.ptr<float>(i)[0];
		printf(" %d\t%f\t%f\t%f\n", i, 
			matX.ptr<float>(i)[0] ,        //定義域
			matYpredict.ptr<float>(i)[0] , //予測
			matY.ptr<float>(i)[0]  );      //答え
	}
	
	
	return 0;
}

```
#### 参考にしたページ
以下のページを参考にしました．ありがとうございました．  
1)Deep Learning 入門（２） - Chainerで自作の非線形回帰を試そう -
https://qiita.com/yoshizaki_kkgk/items/bfe559d1bdd434be03ed  
2)OpenCV Manual(cv::dnn::Net Class Reference)
https://docs.opencv.org/3.4.5/db/d30/classcv_1_1dnn_1_1Net.html#a5e74adacffd6aa53d56046581de7fcbd  
3)run Keras model on opencv
https://www.slideshare.net/takmin/run-keras-model-on-opencv