ncnnのクラス分類の学習モデル(restnet)を作成する


## 概要 
M5StickV2 で使える深層学習フレームワークのncnnの学習モデル作リ方について説明します。ncnnの学習データトレーニングは、Pytorchなどの深層学習フレームワークで学習したモデルを変換することで作成します。

ここで説明するプロセスでは、
Pytorchトレーニング→(変換)→onnxモデル→(変換)→ncnnモデル
という流れとなります。


dianjixz氏がGitHubで公開しているv831_restnet18のコードを使います。
v831_restnet18は、restnet18の深層学習のモデルを元にして、ncnnのクラス分類の学習モデルを作成することができます。


## PC環境
構築手順に入る前に、まずPCに以下のバージョンのUbuntuをインストールした環境で、構築しました。

[M5UnitV2のクロスコンパイル環境を構築する手順](https://qiita.com/nnn112358/pbulic/9e15450265b689300216)

```
Ubuntu 22.04.3 LTS(x64)
OpenCV ver 0.4.4
ncnn ver 231027
```

## Minicondaのインストール

まず最初に、Minicondaをインストールして、Pythonの環境を作ります。
Minicondaのインストーラは、Minicondaのウェブサイトからダウンロードして取得します。


```
% mkdir -p ~/miniconda3
% wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh
% bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3
% rm -rf ~/miniconda3/miniconda.sh
```

Miniconda上で、pythonの仮想環境を作成します。

```
$ conda create -n unitv2
```

condaをアクティブ化します。condaのアクティブ化する作業は、シェル立ち上げるごとに必要です。
次に、Pytorchをインストールします。

```
$ conda activate unitv2
$ conda install pytorch torchvision torchaudio cpuonly -c pytorch
$ conda install opencv onnx
$ pip install torchsummary
```

https://pytorch.org/get-started/locally/


## v831_restnet18のダウンロード

GitHubからv831_restnet18のコードをダウンロードします。
v831_restnet18は、M5UnitV2のために作成されたリポジトリではなく、Allwinner　v831というCPUを搭載しているSipeed社のMAixⅡのための学習モデルさくせいのためのリポジトリです。
MAixⅡは、MaixPy3という開発環境が用意されているのですが、MaixPy3はM5UnitV2と同じく、ncnnの学習モデルを使った画像処理を行うことができます。今回は、v831_restnet18を使って、ncnnの学習モデルを作成します。

```
% git clone https://github.com/dianjixz/v831_restnet18 --recursive
% cd v831_restnet18/
```



## クラス分類の学習モデル作成 

画像分類に使用される主なモデルはresnet18で、pytorchで学習モデルを作成して、onnxモデル変換後にncnnに変換します。
プロセスとしては、pytorchトレーニング→onnxモデル→ncnnモデルという流れになります。


フォルダ構成は以下のようになります。
画像サイズは224x224で、フォーマットはdataのデータセット例を参照してください。

```
├── classes_label.py                            #クラス分類のラベル
├── classifier_resnet_test.py                   #テストのpythonプログラム
├── classifier_resnet_train.py                  #学習のpythonプログラム
├── convert.py                                  #モデル変換のpythonプログラム
├── convs_data                                  #フォルダのアップロード
├── convs_data.zip                              #アップロードされたzipファイル
├── data                                        #トレーニングデータフォルダ
│   ├── mouse                                  #分類フォルダ
│   └── sipeed_logo
├── out                                         #トレーニングモデル出力フォルダ、一定のトレーニング期間ごとにモデルパラメータを出力する
│   ├── classifier_19.pth
│   ├── classifier_39.pth
│   └── classifier_59.pth
├── restnet_img.jpeg
└── test                                        #テストデータセットフォルダ（分類されていない）
7 directories
```
## データセットの準備

v831_resnet18プロジェクトのclassifier_resnet_train.pyファイルを開きます。 ここでは、学習時に注意すべきパラメータを示します。
classes_label.pyファイルのlabelsの値をデータフォルダのディレクトリに対応するように変更します。


```
dataset_path = "data"               #トレーニングセットへのパス（デフォルトのまま）
val_split_from_data = 0.1 # 10%		#学習率（デフォルトのまま）
batch_size = 4						#トレーニングバッチ、変更不要（デフォルトのまま）
learn_rate = 0.001	                #学習率、変更不要（デフォルトのまま）
total_epoch = 100					#トレーニングサイクル、合計100サイクルをトレーニングする（デフォルトのまま）
eval_every_epoch = 5				#1サイクルあたりのトレーニングセッション数（デフォルトのまま）
save_every_epoch = 20				#何サイクル保存するか（デフォルトのまま）
dataload_num_workers = 2			#(デフォルトのまま）
input_shape = (3, 224, 224)			#入力サイズ（デフォルトのまま）
cards_id = [0]					#使用するトレーニングカード（デフォルトのまま）
param_save_path = './out/classifier_{}.pth'	#パラメータ保存パス（デフォルトのまま）
```

データファイルがトレーニングデータセット、テストファイルがテストデータです。
 両方のデータセットに画像が重複しないように注意してください。

トレーニングデータセット：カテゴリーごとに1つのフォルダがあり、フォルダ名はカテゴリー名と一致するようにします。

```
データセットのフォルダ構造
── mouse
│   ├── 20026.jpg
...
├── sipeed_logo
│   ├── 19418.jpg
...
...
```

## トレーニング

次のコマンドを実行すると、トレーニングが開始されます。

```
python classifier_resnet_train.py  
```



例:
```
.
├── classifier_99.pth             #トレーニング中に保存されたパラメータ
├── classifier_final.pth          #トレーニング終了後に保存されるパラメータ

└── classifier.onnx               #生成されたonnxディープラーニングネットワークファイル
```

### ZeroDivisionErrorが発生した場合

ZeroDivisionErrorが発生した場合,もともとのサンプルに入っている画像が少なすぎるために発生します。トレーニングの画像ファイルを増やすことで対処します。

```
Traceback (most recent call last):
  File "/home/nnn/Desktop/train2/v831_restnet18/classifier_resnet_train.py", line 140, in <module>
    print("val loss: {:.5f}".format(running_loss / len(valloader)))
                                    ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~
ZeroDivisionError: division by zero
```



## テスト
データセットの画像と同じサイズになるように注意して、テスト画像を準備します。 新しいtestディレクトリを作成し、その中に配置します。

このコマンドはユーザーの環境でncnnツールを呼び出すので、それがインストールされ、環境変数に追加されていることを確認してください。

モデルテストを行い、ncnnモデルファイルをテストします。

```
python classifier_resnet_test.py ./test ./out/classifier_final.pth
```

テスト実行後、Pythorchの学習データ、onnxの学習モデルと、ncnnの学習モデルが生成されます。

```
$ ls
classifier_2.pth  classifier.bin  
classifier_1.pth  classifier.onnx  
classifier_3.pth  classifier.param  
```

classifier.binとclassifier.paramが生成されたncnnモデルになります。

## onnxからncnnへのモデル変換

onnxからncnnへのモデル変換は、以下のコマンドで実行することも可能です。
onnx2ncnnは、ncnnのインストール時に生成される実行ファイルです。

```
onnx2ncnn classifier.onnx classifier.param classifier.bin
```

また、別の手段として、オンラインでonnxからncnnへのモデル変換を行うことができるサイトがあります。

 https://convertmodel.com/


## 参考資料
[dianjixz/v831_restnet18](https://github.com/dianjixz/v831_restnet18 )
https://github.com/dianjixz/v831_restnet18 

[maix_train](https://github.com/sipeed/maix_train)
https://github.com/sipeed/maix_train