Amazon Web Services ブログ

AWS DeepLens Lambda 関数と最新 Model Optimizer を深く知り尽くす

AWS DeepLens 向けに最新 Model Optimizer をリリースしました。これは皆さんのディープラーニングモデルを DeepLens GPU 上で効率的に実行できるよう最適化するもので、Python のコード一行のみで実行可能です。Model Optimizer は AWS DeepLens ソフトウェアバージョン 1.2.0 で利用できます。

AWS DeepLens は推論のために GPU にアクセスする際、Cl-DNN (Compute Library for Deep Neural Networks) を使用します。そのため、AWS DeepLens 上でモデルを実行するには、Cl-DNN 形式に変換しなくてはなりません。Model Optimizer はモデルのサイズにもよりますが、次のコード 1 行で、この変換を実行します。所要時間は 2-10 秒間です。

mo.optimize(model_name,input_width,input_height)

このコードを自身の Lambda 関数に含めることで、Model Optimizer にアクセスできるようになります。Lambda 推論関数を使用することにより、AWS DeepLens からデプロイしたばかりのモデルにアクセスできるようになります。この投稿では、Lambda 推論関数の作成方法について説明するとともに、皆さんの要件に合わせてカスタマイズできるテンプレートをご紹介します。

Lambda の推論はプリプロセス、推論、ポストプロセスの 3 つの関数を実行します。

Lambda 推論関数を作成するには、AWS Lambda コンソールを使用し、以下のステップに従ってください

  1. [Create function] を選択します。ディープラーニングモデル用に推論を実行するためにこの関数をカスタマイズします。
  2. [Blueprints] を選択します。
  3. 設計図「greengrass-hello-world」を検索します。
  4. Lambda 関数にモデルと同じ名前を付けます。
  5. 既存の IAM ロールを選択します:AWSDeepLensLambdaRole。登録プロセスの中で、このロールを作成したはずです。
  6. [Create function] を選択します。
  7. 関数コードでハンドラーが greengrassHelloWorld.function_handler であることを確認してください。
  8. GreengrassHello ファイルのコードをすべて削除してください。そして、このファイルに、Lambda 推論関数のコードを入力します。

まずは必要なパッケージをインポートするところから始めましょう。

import os
import greengrasssdk
import awscam
import mo
import cv2
from threading import Thread

os のインポートにより、AWS DeepLens のオペレーティングシステムにアクセスできるようになります。awscam をインポートすることで、AWS DeepLens 推論 API にアクセスできるようになります。 mo により Model Optimizer へアクセスできるようになります。 cv2 により CV のオープンライブラリーにアクセスできるようになります。これには画像のプリプロセスおよびインポート用の共通ツールが含まれています。 thread により Python のマルチスレッドライブラリーにアクセスできるようになります。推論の結果を mplayer に送信するには、別のスレッドを使用します。ここでは、モデルの結果を表示できます。

次に、MQTT を使用することで AWS IoT でクラウドにメッセージを送信できるようにする Greengrass Core SDK クライアントを作成します。Greengrass コア SDK は既にデバイスにロード済みであるため、改めてインポートする必要はありません。

client = greengrasssdk.client('iot-data')

続いて、Lambda 関数にメッセージの送信先となる AWS IoT トピックを作成します。このトピックには AWS IoT コンソールでアクセスできます。

iot_topic = '$aws/things/{}/infer'.format(os.environ['AWS_IOT_THING_NAME'])

mplayer を使用してローカルで結果を表示できるようにするには、FIFO ファイルに挿入する jpeg 画像を含むグローバル変数を宣言します。 results.mjpeg。 また、FIFO ファイルへのストリーミングをオフにするには、 Write_To_FIFO を False に変更します。この処理によってスレッドが削除されるため、mplayer から画像にアクセスして結果を見ることはできなくなります。

jpeg = None
Write_To_FIFO = True

次に、FIFO ファイルに出力画像を発行し、mplayer でそれを表示できるようにする単純なクラスを自分のスレッド上で実行します。以下は mplayer 上で結果を表示するための標準的なコードで、すべての Lambda 推論関数に共通です。ご自身のプロジェクト用に、このコードをコピーしてご自身の Lambda 推論関数に貼り付けて利用していただいて構いません。

class FIFO_Thread(Thread):
    def __init__(self):
        ''' Constructor. '''
        Thread.__init__(self)
 
    def run(self):
        fifo_path = "/tmp/results.mjpeg"
        if not os.path.exists(fifo_path):
            os.mkfifo(fifo_path)
        f = open(fifo_path,'w')
        client.publish(topic=iot_topic, payload="Opened Pipe")
        while Write_To_FIFO:
            try:
                f.write(jpeg.tobytes())
            except IOError as e:
                continue  

次に Lambda 関数で推論クラスを定義します。  Greengrass 推論関数を定義するところから始めましょう。

def greengrass_infinite_infer_run():
	input_width = 224
	input_height = 224

input_width および input_height パラメータはこのモデルの入力パラメータを定義します。モデルは推論の実行に、同じサイズのフレームを想定します。AWS DeepLens にデプロイしようとしているモデル用にこれらのパラメータをカスタマイズできます。

次の行にはモデル名を定義します。この名前はトレーニングしたモデルの params と json ファイルのプレフィックスに一致している必要があります。たとえば、params と json ファイルが squeezenet_v1.1-0000.params と squeezenet_v1.1-symbol.json であった場合、モデル名にはそれぞれ、squeezenet_v1.1 のプレフィックスを付けなければなりません。これは重要です。モデル名がこれら 2 つのファイルのプレフィックスに一致していないと、推量は機能せず、エラーになります。

model_name = 'squeezenet_v1.1'

次に、Model Optimizer を開始します。これにより、デプロイ済みのモデルが Cl-DNN 形式に変換され、AWS DeepLens GPU でアクセスできるようになります。これは処理後に最適化されたアーティファクトへのパスを返します。

error, model_path = mo.optimize(model_name,input_width,input_height)

推量エンジンにモデルをロードします。ここに GPU へアクセスするためのコードを入力できます。CPU にアクセスするには、次のようにコードを入力します: “GPU” = 0。ただし、ディープラーニングモデルの場合は、効率性と速度の観点から、GPU へアクセスするようお勧めします。

model = awscam.Model(model_path, {"GPU": 1})

モデルがロードされたことを AWS IoT に伝達するメッセージを送信できます。

client.publish(topic=iot_topic, payload="Model loaded")

次に、実行しようとしているモデルの型を定義します。たとえば、ニューラル式の転送であれば、モデルの型は segmentation で、そのオブジェクトローカライゼーションは ssd (single shot detector) のようになります。画像の分類は次のとおりです: classification。画像を分類する squeezenet モデルをデプロイしようとしているため、モデルの型を次のように定義します: classification

model_type = "classification"

squeezenet_v1.1 には分類が 1,000 件あるので、数字のラベルを人が認識できるラベルに定義するため python のリストを書くのは現実的ではありません。代わりに、インデックスとラベルが順番に表示されたテキストファイルを追加します。この例のテキストファイルはここで参照できます。

Lambda 関数にテキストファイルを追加する:

  1. 関数のコードブロックで、File を選択します。次に、New File を選びます。
  2. テキストファイルのデータを先程作成したばかりの新しいファイルにコピーします。
  3. [File]、[Save] の順に選択します。
  4. sysnet.txt というファイル名を付けて、[Save] を選択します

関連するインデックスとラベルの付いたファイルが、Lambda 関数パッケージに追加されます。

次に左側のメニューから greengrassHelloWorld.py を選択して、Python コードに戻ります。次のステップでは追加したばかりの sysnet.txt ファイルを開きます。

with open('sysnet.txt', 'r') as f:
	labels = [l.rstrip() for l in f]

結果に表示する分類の数を定義します。ここで 5 を指定することで、出力結果に上位 5 位の確率値が降順で戻されます。この値には、このモデルでサポートされている任意の数を指定できます。

topk = 5

次に、結果を mplayer で表示するための FIFO スレッドを開始します。

results_thread = FIFO_Thread()
results_thread.start()

次の Inference is starting メッセージを AWS IoT コンソールに発行します。次の数行で推論ループのコードを入力します。

client.publish(topic=iot_topic, payload="Inference is starting")

次のコードで、AWS DeepLens は mjpeg ストリーム上の最新のフレームにアクセスできるようになります。以下は AWS DeepLens カメラの入力ストリームです。 awscam.getLastFrame は推量を解析し実行するために、ストリームから最後または最新のフレームを取得します。 raise Exception は関数がストリームからフレームを取得できなかった場合に例外を生成します。

while True:
     ret, frame = awscam.getLastFrame()
     if ret == False: 
            raise Exception("Failed to get frame from the stream")

     frame_resize = cv2.resize(frame, (input_width, input_height))

次は画像のプリプロセスです。カメラからの入力フレームはモデルがトレーニングされた入力ディメンションと同じではありません。このモデルの入力パラメータは先程のステップで定義しています。このステップではカメラの入力フレームのサイズを、このモデルの入力ディメンションと同じサイズに変更します。この例では、モデルの入力パラメータに合わせるため、フレームに低解像度処理を施すだけです。トレーニングしたモデルによっては、画像の標準化など、他のプリプロセスステップを実行しなければならない場合があります。

infer_output = model.doInference(frame_resize)

これでプリプロセスステップのフレームで推論の準備ができました。前述のコードは最後のレイヤーの名称とともに、ディクショナリとしてモデルの結果を出力します。出力データは人が認識できる形式には解析されません。この出力をそのまま使用するには、独自に解析アルゴリズムを実装する必要があります。AWS DeepLens はサポートされているモデルの型に対し、ネットワークの結果を解析するための簡単な推論を提供しています。次のメソッドでは model_type 付きのディクショナリをそのキーとして、2 つ目のディクショナリをその値として返します。2 つ目のディクショナリには label および probability キーがあり、これにより人の認識できないラベルとそれに関連付けられた確率を取得できるようになります。

parsed_results = model.parseResult(model_type, infer_output)

出力から表示する結果の件数を「n」件とするようにコードをカスタマイズできます。この例では、上位 5 件を表示するようにカスタマイズしました。結果は降順で表示され、先頭には確率の最も高い値が来ます。

top_k = parsed_results[model_type][0:topk]

結果が出たところで、クラウドにこの結果を送信しましょう。ここでは結果に json 形式を使用している点に注意してください。そうすることで、クラウドにある他の Lambda 関数もこの IoT トピックにサブスクライブし、個々に関心のあるイベントが発生したときにアクションを起こせるようになっています。たとえば、re:Invent ワークショップではホットドッグが検出されるたびに、SMS メッセージを送信する Lambda 関数をクラウドにデプロイしました。また、ネットワークで出力されるラベルを人の理解できるラベルへ変換するためのループに入る前に、作成したラベルリストの使用方法にも注意してください。

msg = "{"
prob_num = 0 
for obj in top_k:
    if prob_num == topk-1: 
         msg += '"{}": {:.2f}'.format(labels[obj["label"]], obj["prob"])
    else:
         msg += '"{}": {:.2f},'.format(labels[obj["label"]], obj["prob"])
    prob_num += 1
msg += "}"

ここではメッセージが json 形式になっているので、AWS IoT でクラウドにこれを送信します。

client.publish(topic=iot_topic, payload=msg)

これでデータがクラウドに格納されたので、mplayer でそれを表示するために画像のポストプロセスを行います。この事例では、単純なポストプロセスを実行し、次を使用してオリジナルの 4 メガピクセルの画像にラベルを追加します: openCV。このステップは要件に合わせてカスタマイズできます。

cv2.putText(frame, labels[top_k[0]["label"]], (0,22), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 165, 20), 4)

ポストプロセスが完了したら global jpeg 変数を更新します。そうすることで、mplayer を使用して結果を表示できるようになります。

global jpeg
ret,jpeg = cv2.imencode('.jpg', frame)

さらに、何らかの問題が発生したときに例外を検出し、クラウドに例外を送信するコードを追加することもできます。これには次のコードを使用します。

except Exception as e:
	msg = "Lambda failed: " + str(e)
	client.publish(topic=iot_topic, payload=msg)

早速関数を実行して、結果を見てみましょう。

greengrass_infinite_infer_run()

コードを保存し、発行するのを忘れないでください。関数を発行しないと、AWS DeepLens コンソールで Lambda 推論関数を表示できません。

おめでとうございます! squeezenet モデルの推論に使用できる Lambda 関数が完成しました。そらに、Model Optimizer を使用しました。

squeezenet モデルにはここからアクセスできます。

不明な点があるとき、またはヘルプが必要な場合は、AWS DeepLens フォーラムを参照してください。


今回のブログの投稿者について

Jyothi Nookula は AWS DeepLens のシニアプロダクトマネージャです。彼女はお客様を喜ばせる製品の制作に情熱を傾けています。余暇には絵を描き、自身の作品展示会に慈善関係者を招いています。

Eddie Calleja は AWS ディープラーニングのソフトウェア開発エンジニアです。また、DeepLens デバイスの開発にも携わっています。元々物理学者であった彼は、余暇には現代の物理問題に AI 技法を適用する方法について考えています。