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

2020.06.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

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

Amazon SageMake(以下、SageMaker)の組み込みアルゴリズムであるオブジェクト検出は、デバイスにインストールしたMXNetフレームワークの上で利用することが可能です。

今回は、MacOSにセットアップしたMXNetでこれを利用してみました。

最初に、動作している様子です。 ResNet-50をベースネットワークとしたのもで5つの商品だけを検出する小さなモデルですが、比較的高速に動作していると思いました。(Macのファンは、少し唸ってますが・・・)

2 データセット

(1) Ground Truth

元となるデータは、ツールで自動的にアノテーションを付加した、Amazon SageMaker Ground Truth(以下、Ground Truth)形式のものです。


参考:[Amazon SageMaker] 撮影と同時にアノテーションを追加してAmazon SageMaker Ground Truth形式のデータを作成してみました

各商品(5種類)は、それぞれ約80枚撮影し、全部で401枚となっています。

(2) データ増幅

アノテーションがそのまま利用できる範囲で、画像を変換し、データは増幅されています。
参考:[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータをOpenCVで増幅してみました

変換に使用したリストは以下の通りで、9種類の変換を追加したことで、データ数は、401件から4011件となりました。

# 変換リスト
convertList = [
    {"function": saturation, "param": 0.6}, # 彩度
    {"function": saturation, "param": 0.8}, 
    {"function": brightness, "param": 0.8}, # 明度
    {"function": contrast,   "param": 0.8}, # コントラスト
    {"function": contrast,   "param": 1.6}, # コントラスト
    {"function": contrast,   "param": 2.0}, # コントラスト
    {"function": mosaic,     "param": 0.5},# モザイク
    {"function": gaussian,   "param": 10.0},# ガウスノイズ
    {"function": noise,      "param": 0.01}, # ごま塩ノイズ
]
全データ: 401件 
増幅後データ: 4011件

(3) RecordIO形式

効率的に学習に回せるように、上記のデータは、RecordIO形式に変換されています。
参考:[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータをオブジェクト検出で利用可能なRecordIO形式に変換してみました

学習用と検証用に8対2で分割しています。

全データ: 4010件 [0]ASPARA: 800件 [1]CRATZ: 800件 [2]PRETZEL: 800件 [3]PRIME: 800件 [4]OREO: 810件 
ASPARA => 640:160 残り:3210件
CRATZ => 640:160 残り:2410件
PRETZEL => 640:160 残り:1610件
PRIME => 640:160 残り:810件
OREO => 648:162 残り:0件
Train: 3208件

.
├── train.idx
├── train.lst
├── train.rec <= 657.8M
├── validation.idx
├── validation.lst
└── validation.rec <= 171.0M

出力された、train.recとvalidation.recは、S3にアップロードされます。

3 オブジェクト検出

学習は、組み込みアルゴリズムのオブジェクト検出(Object Detection)で行われています。

使用したインスタンスは、ml.p3.8xlarge ✕ 1(ボリュームサイズ 50GB)で、epochを50回して、トレーニング時間は、1622秒でした。

使用したハイパーパラメータは、以下のとおりです。

base_network    resnet-50
early_stopping  false
epochs  50
image_shape 512
label_width 350
learning_rate   0.001
lr_scheduler_factor 0.1
mini_batch_size 16
momentum    0.9
nms_threshold   0.45
num_classes 5
num_training_samples    3208
optimizer   sgd
overlap_threshold   0.5
use_pretrained_model    1
weight_decay    0.0005

学習の経過です。

epoch mAP smooth_l1 cross_entropy
-----------------------------------------
1       0.064   0.54    1.083
10      0.127   0.172   0.731
20      0.307   0.09    0.535
30      0.571   0.06    0.414
40      0.763   0.047   0.325
49      0.807   0.042   0.282

出力されたモデル(model.tar.gz)のサイズは、99.6MByteとなっていました。

4 MXNet用への変換

上記で作成したモデルを、MXNetのフレームワーク上で利用する場合、変換(損失層の削除とNMS層を追加)が必要になります。

変換は、https://github.com/zhreshold/mxnet-ssddeploy.pyを利用しました。

また、このツールは、Python2系での実行が必要ということで、作業は、SageMakerのノートブックインスタンス(JupyterNotebook)で行いました。

作業した内容は、以下のとおりです。

# 元モデル(model.tar.gz)を作業フォルダ(trained-model)に展開する
TMP_FOLDER='trained-model'
!tar -xvzf $TMP_FOLDER/model.tar.gz -C $TMP_FOLDER/

# ツールをダウンロードして、ヘルプを確認(動作確認)
!git clone https://github.com/zhreshold/mxnet-ssd.git
!python mxnet-ssd/deploy.py -h

# ハイパーパラメータを確認
!cat $TMP_FOLDER/hyperparams.json | jq

# ツールによる変換
!python mxnet-ssd/deploy.py --network resnet50 --num-class 5 --nms .45 --data-shape 512 --prefix $TMP_FOLDER/model_algo_1

上記により、作業フォルダには、deploy_model_algo_1-0000.params及び、deploy_model_algo_1-symbol.jsonが生成されます。

// 変換前
trained-model/model_algo_1-symbol.json
trained-model/hyperparams.json
trained-model/model.tar.gz
trained-model/model_algo_1-0000.params

// 変換後
trained-model/deploy_model_algo_1-0000.params
trained-model/deploy_model_algo_1-symbol.json
trained-model/model_algo_1-symbol.json
trained-model/hyperparams.json
trained-model/model.tar.gz
trained-model/model_algo_1-0000.params

5 コード

Mac上で実行したコードです。

変換で生成したモデルを、./modelに置き、Webカメラの画像を入力インターフェースに変換して推論にかけています。

こちらは、Python3で動作しています。

import mxnet as mx
import cv2
import numpy as np
from collections import namedtuple

# Webカメラ
DEVICE_ID = 0 
WIDTH = 800
HEIGHT = 600
FPS = 24

MODEL_PATH = './model/deploy_model_algo_1'
CLASSES = ['ASPARA','CRATZ','PRETZEL','PRIME','OREO']
COLORS = [(128, 0, 0),(0, 128, 0),(0, 0, 128),(128, 128, 0),(0, 128,128)]

def main():

    # Model Initialize
    SHAPE = 512
    input_shapes=[('data', (1, 3, SHAPE, SHAPE))]
    Batch = namedtuple('Batch', ['data'])
    sym, arg_params, aux_params = mx.model.load_checkpoint(MODEL_PATH, 0)
    mod = mx.mod.Module(symbol=sym, label_names=[], context=mx.cpu())
    mod.bind(for_training=False, data_shapes=input_shapes)
    mod.set_params(arg_params, aux_params)

    # Video Initialize
    cap = cv2.VideoCapture (DEVICE_ID)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT)
    cap.set(cv2.CAP_PROP_FPS, FPS)
    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))

    while True:

        # カメラ画像取得
        _, frame = cap.read()
        if(frame is None):
            continue

        # 入力インターフェースへの画像変換
        frame = frame[0 : int(height), 0 : int(height)] # 800*600 -> 600*600
        frame = cv2.resize(frame, (SHAPE, SHAPE)) # 600*600 -> 512*512
        img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # BGR -> RGB
        img = img.transpose((2, 0, 1)) # 512,512,3 -> 3,512,512
        img = img[np.newaxis, :] # 3,512,512 -> 1,3,512,512

        # 推論
        mod.forward(Batch([mx.nd.array(img)]))
        prob = mod.get_outputs()[0].asnumpy()
        prob = np.squeeze(prob)
        index = int(prob[:, 0][0])
        confidence = prob[0][1]
        x1 = int(prob[0][2] * SHAPE)
        y1 = int(prob[0][3] * SHAPE)
        x2 = int(prob[0][4] * SHAPE)
        y2 = int(prob[0][5] * SHAPE)

        # 表示
        print("[{}] {}, {}, {}, {}".format(CLASSES[index], x1,y1,x2,y2))
        if(confidence > 0.6): # 信頼度
            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

    cap.release()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

6 最後に

今回は、オブジェクト検出のモデルを、Mac上で利用してみました。 この要領を使用すると、RaspberryPiや、Jetson NanoもMxNetをインストールして利用可能かも知れません。

7 参考にさせて頂いたリンク


Training the Built-In Object Detection Model in Amazon SageMaker and running it on AWS IoT Greengrass