[Amazon SageMaker] オブジェクト検出のモデルをNeoで最適化して、Jetson Nano+OpenCV+Webカメラで使用してみました

2020.06.25

1 はじめに

CX事業本部の平内(SIN)です。

Amazon SageMager Neo(以下、Neo)を使用すると、既存のモデルを各種アーキテクチャのエッジデバイスで動作させる事ができます。 今回は、Amazon SageMager(以下、SageMaker)の組み込みアルゴリズムである「物体検出」で作成したモデルをNeoで最適化し、Jetson Nanoで利用してみました。

最初に動作している様子です。

Jetson Nanoに接続したWebカメラの画像を推論にかけ、検出結果を表示しています。推論は、約0.25秒で処理され、4FPS程度のフレームレートとなります。

2 データセット

データセットのアノテーションは、Microsoft VoTT Ver2.2.0 を利用させて頂きました。

アヒルとトマト羊の写真を20枚ずつ撮影し、バウンディングボックスの付与作業を行いました。

データとしては、40件です。

VoTTで作成された、データセットは、一旦、GroundTruth形式に変換し、最終的にRecordIO形式としています。


参考:[Amazon Rekognition] VoTTで作成したデータをCustom Labelsで利用可能なAmazon SageMaker Ground Truth形式に変換してみました


参考:[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータをオブジェクト検出で利用可能なRecordIO形式に変換してみました

変換が完了したデータは、学習用32件、検証用8件となっています。

全データ: 40件 [0]TOMATO: 20件 [1]AHIRU: 20件 
TOMATO => 16:4 残り:20件
AHIRU => 16:4 残り:0件
Train: 32件
AHIRU_20-TOMATO_20_S3
├── train.idx
├── train.lst
├── train.rec
├── validation.idx
├── validation.lst
└── validation.rec

train.rec及び、validation.recは、S3にアップロードしました。

3 学習

組み込みアルゴリズムである「物体検出」のトレーニングは、ml.p3.2xlarge(50G) で125秒でした。

指定したパラメータは、以下のとおりです。殆ど、デフォルト値のままですが、以下が変更されています。

  • base_network: resnet-50 (VGG-16では、Neoでのコンパイルに失敗しました)
  • mini_batch_size: 4 (検証データが、各4件しか無いため)
  • num_training_samples: 32(学習データは、32件となっているため)
base_network    resnet-50
early_stopping  false
early_stopping_min_epochs   10
early_stopping_patience 5
early_stopping_tolerance    0.0
epochs  50
freeze_layer_pattern    false
image_shape 512
label_width 350
learning_rate   0.001
lr_scheduler_factor 0.1
mini_batch_size 4
momentum    0.9
nms_threshold   0.45
num_classes 2
num_training_samples    32
optimizer   sgd
overlap_threshold   0.5
use_pretrained_model    1
weight_decay    0.0005

学習の経過です。epochが40を超えたあたりから、mAPは、1.0となっています。

epoch   mAP     smooth_l1       cross_entropy
-----------------------------------------
0       0.005   0.707   1.469
5       0.414   0.593   1.116
10      0.154   0.599   1.072
15      0.385   0.591   1.041
20      0.583   0.583   0.993
25      0.646   0.541   0.927
30      0.792   0.552   0.863
35      0.596   0.522   0.795
40      1.0     0.421   0.715
45      1.0     0.429   0.692
50      1.0     0.41    0.668

出来上がったモデルは、97.5Mbyteでした。

4 MXNet用への変換

「物体検出」で作成したモデルを、Neoで使用するためには、変換(損失層の削除とNMS層を追加)が必要になります。

変換は、SageMakerのノートブックインスタンス(JupyterNotebook)上で、https://github.com/zhreshold/mxnet-ssddeploy.pyを使用して行いました。


参考:[Amazon SageMaker] 組み込みアルゴリズムのオブジェクト検出(ResNet-50)をMac上のMXNetで利用してみました

変換で出力されるdeploy_model_algo_1-0000.params及び、deploy_model_algo_1-symbol.jsonをtar.gz で固めています。

% ls
deploy_model_algo_1-0000.params deploy_model_algo_1-symbol.json
% tar cvfz deploy_model.tar.gz *
a deploy_model_algo_1-0000.params
a deploy_model_algo_1-symbol.json
% ls *.tar.gz
deploy_model.tar.gz

5 Neo

下記の諸元で、上記のdeploy_model.tar.gzを最適化しています。

  • データ入力値: {"data":[1,3,512,512]}
  • 機械学習フレームワーク: MXNet
  • 対象デバイス: jetson_nano

最適化は、すぐに終わります。

出力された、deploy_model-jetson_nano.tar.gzの中は、下記の4つのファイルになっています。

6 Jetson Nano

Neoで最適化されたモデルを使用するためには、DLRのインストールと、スワップの追加(モデルのサイズが大きい場合)が必要です。

(1) DLRのインストール

DLRのインストールは、下記のドキュメント通り、cmake-3.17.2をインストールしたのち、DLRをcloneしてコンパイルしました。
https://neo-ai-dlr.readthedocs.io/en/latest/install.html

インストール完了を確認している様子です。

$ python3
Python 3.6.9 (default, Apr 18 2020, 01:56:04)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import dlr
>>> dlr.__version__
'1.2.0'
>>>

(2) SWAP

今回作成したモデルは、デフォルト(メモリ4G、スワップ2G)のままでは、動作できなかった為、スワップを増やしています。

作業した手順は、以下のとおりです。 デフォルトのSWAPを一旦解除し、6Gの /swapfile を作成して、アクティベートし直しました。

$ grep SwapTotal /proc/meminfo
SwapTotal:       2029696 kB
$sudo swapoff -a //swapプロセスを解除
$sudo dd if=/dev/zero of=/swapfile bs=1G count=6 //領域確保
6+0 records in
6+0 records out
6442450944 bytes (6.4 GB, 6.0 GiB) copied, 331.429 s, 19.4 MB/s
$sudo mkswap /swapfile // SWAPファイル作成
mkswap: /swapfile: insecure permissions 0644, 0600 suggested.
Setting up swapspace version 1, size = 6 GiB (6442446848 bytes)
no label, UUID=89e03939-ce9a-483d-a417-b9e7ef073ed5
$sudo swapon /swapfile //アクティベート
swapon: /swapfile: insecure permissions 0644, 0600 suggested.
$grep SwapTotal /proc/meminfo
SwapTotal:       6291452 kB

7 実行

Neoで作成したモデルを./modelに展開しています。

$ mkdir model
$ tar xvfz deploy_model-jetson_nano.tar.gz -C model
$ ls -la model
total 144084
drwxrwxr-x 2 nvidia nvidia      4096  6月 25 12:48 .
drwxrwxr-x 3 nvidia nvidia      4096  6月 25 12:47 ..
-rw-r--r-- 1 nvidia nvidia       154  6月 25 10:17 compiled.meta
-rw-r--r-- 1 nvidia nvidia      5917  6月 25 10:17 compiled_model.json
-rw-r--r-- 1 nvidia nvidia        32  6月 25 10:17 compiled.params
-rwxr-xr-x 1 nvidia nvidia 147516536  6月 25 10:17 compiled.so

実行しているコードは、以下のとおりです。

import cv2
import numpy as np
import dlr
import time

MODEL_PATH = './model'
CLASSES = ['TOMATO','AHIRU']
COLORS = [(128, 0, 0),(0, 128, 0)]
SHAPE = 512

DEVICE_ID = 0
WIDTH = 800
HEIGHT = 600
GST_STR = ('v4l2src device=/dev/video{} ! video/x-raw, width=(int){}, height=(int){} ! videoconvert ! appsink').format(DEVICE_ID, WIDTH, HEIGHT)

def main():

    print("Ver dlr:{} cv2:{}".format(dlr.__version__, cv2.__version__))

    # Video Initialize
    cap = cv2.VideoCapture(GST_STR, cv2.CAP_GSTREAMER)
    width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
    height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    fps = cap.get(cv2.CAP_PROP_FPS)
    print("fps:{} width:{} height:{}".format(fps, width, height))

    # Model Initialize
    print("DLRModel() start")
    model = dlr.DLRModel(MODEL_PATH, 'gpu')
    print("DLRModel() end")
    print("model.get_input_dtypes(): {}".format(model.get_input_dtypes()))
    print("model.get_input_names(): {}".format(model.get_input_names()))

    print("model OK")
    while(True):

        _, frame = cap.read()
        if(frame is None):
            continue

        frame = frame[0 : int(height), 0 : int(height)] # 横長の長方形 => 正方形
        frame = cv2.resize(frame, dsize=(SHAPE, SHAPE)) 
        img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        img = img.transpose((2, 0, 1))
        img = img[np.newaxis, :]

        print("img.shape: {}".format(img.shape))

        start = time.time()
        out = model.run({'data' : img})
        elapsed_time = time.time() - start
        print("{} [Sec]".format(elapsed_time))
        for det in out[0][0]:
            if(det[0]%1 == 0 and det[1] > 0):
                index = int(det[0])
                confidence = det[1]
                x1 = int(det[2] * SHAPE)
                y1 = int(det[3] * SHAPE)
                x2 = int(det[4] * SHAPE)
                y2 = int(det[5] * SHAPE)
                print("[{}] {:.1f} {}, {}, {}, {}".format(CLASSES[index], confidence, x1, y1, x2, y2))

                if(confidence > 0.5): # 信頼度
                    frame = cv2.rectangle(frame,(x1, y1), (x2, y2), COLORS[index],2)
                    frame = cv2.rectangle(frame,(x1, y1), (x1 + 150,y1-20), COLORS[index], -1)
                    label = "{} {:.2f}".format(CLASSES[index], confidence)
                    frame = cv2.putText(frame,label,(x1+2, y1-2), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 1, cv2.LINE_AA)

        # 画像表示
        cv2.imshow('frame', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
main()

8 最後に

今回は、SageMakerの組み込みアルゴリズムである「物体検出」で作成したモデルをNeoで最適化し、Jetson Nanoで利用してみました。

手順でも示していますが、「物体検出」で作成されたモデルは、そのままでは、Neoで利用できないので、注意が必要だと思いました。