[Amazon SageMaker] JumpStartのファインチューニングで作成したResNet18のモデルをSageMaker Neoで最適化して、Jetson NanoのWebカメラで使用してみました

2021.02.14

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

1 はじめに

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

Amazon SageMaker JumpStart(以下、JumpStart)は、TensorFlow Hub や PyTorch Hub に公開されているモデルをGUIで簡単にデプロイして利用できます。 以下は、PyTorch HubのResNet50でファイチューニングしてみた例です。

今回は、上記と同じ要領でResNet18から学習したモデルをSageMaker Neo(以下、Neo)でJetson Nano用に最適化して使用してみました。

前回、同じモデルをPyTorch上で使用した際の処理時間が、0.04sec 〜 0.07sec程度だったのに対し、今回は、0.02secとなっているので、倍以上の速度が出ていることになります。

2 コンパイル

JumpStartのファインチューニングで作成されたモデルは、PyTorchのモデルそのものですので、Neoでコンパイルが可能です。

(1) コンパイルジョブの作成

下記の設定で、コンパイルを開始します。

  • アーティファクトの場所 : ファインチューニングで出力されたモデル
  • データ入力設定 : {"data":[1,3,224,224]}
  • 機械学習フレームワーク : PyTorch
  • 対象デバイス : jetson_nano
  • S3出力先 : モデルを出力するバケット

(2) No pth file found for PyTorch model.

しかし、先の要領でコンパイルすると、エラーとなってしまいます。

ClientError: InputConfiguration: No pth file found for PyTorch model. Please make sure the framework you select is correct.

ドキュメント上は、PyTorchのモデルは、「拡張子がptもしくはpthのものをtar.gzで圧縮したもの」となっているので、出力された圧縮ファイルがそのまま利用できるはずなのですが、何故か、拡張は、pthでないエラーとなってしまいました。

下記が、ファインチューニングで出力された圧縮ファイルです。

一旦、解凍し、拡張子をpthに変えて、tar.gzを作成し直しました。

% tree .
.
└── model
    └── model.pth

$ tar cfvz model.tar.gz model
a model
a model/model.pth

$ ls -la *.tar.gz
-rw-r--r--  1 hirauchi.shinichi  staff  41569044  2  4 06:09 model.tar.gz

(3) コンパイル完了

拡張子を変更すると、コンパイルは成功します。

3 JetPack 4.4

使用したJetPackは、4.4です。

DLRのコンパイル済みモジュールは、最新の4.5用がまだ無いため、一つ前の4.4をアーカイブから取得して利用しています。

https://developer.nvidia.com/embedded/jetpack-archive

https://developer.nvidia.com/jetpack-sdk-44-archive

4 DLR(Deep Learning Runtime)

DLRは、数日前に、バージョンが1.8となっていました。コンパイル済みのモジュールもJetPack4.4までは、公開されています。


https://github.com/neo-ai/neo-ai-dlr/releases

途中、numpyをsetupできるように、予めCythonを入れてから、dlrをインストールしています。
参考:Installing DLR

$ pip3 install Cython
Successfully installed Cython-0.29.21
$ pip3 install https://neo-ai-dlr-release.s3-us-west-2.amazonaws.com/v1.8.0/jetpack4.4/dlr-1.8.0-py3-none-any.whl

以下は、インストール状況を確認しています。

$ python3
Python 3.6.9 (default, Oct  8 2020, 12:12:24)
>>> import dlr
>>> dlr.__version__
'1.8.0'
>>> import numpy
>>> numpy.__version__
'1.19.5'

簡単なコードで、推論にエラーが出ないかどうかを確認できます。

import numpy as np
import time
import os
import dlr
from dlr.counter.phone_home import PhoneHome
PhoneHome.disable_feature()

os.environ['TVM_TENSORRT_CACHE_DIR'] = '.'

model = dlr.DLRModel('model/', dev_type='gpu', use_default_dlr=True)
print("DLRModel() end")
print("model.get_input_dtypes(): {}".format(model.get_input_dtypes()))
print("model.get_input_names(): {}".format(model.get_input_names()))

img = np.random.rand(1, 3, 224, 224)
print(img.shape)
print("start")

for _ in range(10):
    start = time.time()
    model.run({'data': img})
    processing_time = time.time() - start
    print("{:.2f} sec".format(processing_time))

5 コード

以下が、Jetson Nano上で、DLRを使用して推論しているコードです。

入力画像の変換や、出力値からSoftmax関数で信頼度を出力する処理は、ファインチューニンの学習時に使用されていたコードをそのまま利用させて頂いています。

# -*- coding: utf-8 -*-
import cv2
import numpy as np
import time
import os
import dlr
from dlr.counter.phone_home import PhoneHome
from torch.nn import functional as F
from torchvision import transforms
import torch

# Webカメラ
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)

# 入力画像の変換
RANDOM_RESIZED_CROP = 224
NORMALIZE_MEAN = [0.485, 0.456, 0.406]
NORMALIZE_STD = [0.229, 0.224, 0.225]
transform = transforms.Compose(
	[
		transforms.ToPILImage(), # Numpy -> PIL
		transforms.Resize(RANDOM_RESIZED_CROP),
		transforms.CenterCrop(RANDOM_RESIZED_CROP),
		transforms.ToTensor(),
		transforms.Normalize(NORMALIZE_MEAN, NORMALIZE_STD),
	]
)

class_names = ["ASPARAGUS","CRATZ","NOIR","OREO","PRETZEL"]

def main():
	cap = cv2.VideoCapture(GST_STR, cv2.CAP_GSTREAMER)

	# フォーマット・解像度・FPSの取得
	width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
	height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
	print("width:{} height:{}".format(width, height))

	# SHARE = 224
	PhoneHome.disable_feature()
	os.environ['TVM_TENSORRT_CACHE_DIR'] = '.'
	model = dlr.DLRModel('model/', 'gpu', use_default_dlr=True)
	print("DLRModel() end")
	print("model.get_input_dtypes(): {}".format(model.get_input_dtypes()))
	print("model.get_input_names(): {}".format(model.get_input_names()))

	while True:

		# カメラ画像取得
		_, frame = cap.read()
		if(frame is None):
			continue
		image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
		image = transform(image) # 入力形式への変換
		image = image.unsqueeze(0) # バッチ次元の追加 (3,224,224) => (1,3,224,244)

		start = time.time()
		outputs = model.run({'data': image})
		processing_time = time.time() - start
		
		batch_probs = F.softmax(torch.from_numpy(outputs[0]), dim=1)
		batch_probs, batch_indices = batch_probs.sort(dim=1, descending=True)
		
		for probs, indices in zip(batch_probs, batch_indices):
			name = class_names[indices[0].int()]
			confidence = probs[0]
			str = "{} {:.1f}% {:.2f} sec".format(name, confidence, processing_time)
			print(str)
			cv2.putText(frame, str, (10, HEIGHT - 100), cv2.FONT_HERSHEY_PLAIN, 4, (255, 255, 255), 5, cv2.LINE_AA)

		cv2.imshow('frame', frame)

		if cv2.waitKey(1) & 0xFF == ord('q'):
			break

	# VideoCaptureオブジェクト破棄
	cap.release()
	cv2.destroyAllWindows()

if __name__ == '__main__':
	main()

6 最後に

今回は、JumpStartのファインチューニングで作成されたPytorchモデルをNeoで最適化してJetson Nanoで使用してみました。 コードなどは、Pytorchのものと殆ど変わりなく利用でき、倍以上の速度がでてしまうNeoは、強力だと思います。