Amazon SageMakerとAWS RoboMakerでAWS DeepRacerを走らせて学習させるノートブックを試してみた

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

こんにちは、大阪DI部の大澤です。

強化学習を学べるミニ自動運転車、AWS DeepRacerをAWS RoboMakerのシミュレーション上で動かし、DeepRacerの行動選択時に使用するモデルをAmazon SageMakerで学習させるサンプルノートブックを試してみました。今回はその内容をお伝えします。 実機が無くてもノートブックに沿って進めていくことで、DeepRacerをシミュレーション上で走らせられます!

概要

SageMakerとRoboMakerを使うことでDeepRacerをシミュレーション上で動かし、モデルを学習させる事ができます。 RoboMakerのシミュレーションジョブでDeepRacerを走らせ、その結果どうなったかをSageMakerで回しているモデルの学習ジョブにフィードバックします。 そのフィードバックに基づいてモデルは学習し、更新されます。RoboMakerのシミュレーションジョブでDeepRacerが更新されたモデルを用いて再び走ります。これを繰り返すことで徐々にDeepRacerはコースに沿って走れるようになっていきます。

amazon-sagemaker-examples/rl_deepracer_coach_robomaker.ipynb at master · awslabs/amazon-sagemaker-examples

関係図に載っている用語はざっくり次のような感じです。

  • 状態(State): 車の前方に付いているカメラからの画像です。
  • 行動(Action): 車の操作。ステアリング角度(-1〜1)とスロットル(0〜1)の2つの値です。
  • 状態(画像)に基づいてエージェントが行動を決めます。
  • 報酬(Reward): 現在の位置などの情報と行動に対する評価みたいなものです。
  • どういう状態の時はどういう行動が良いのかをエージェントに学習させるために主に使います。
  • 学習しないときやシミュレーション環境ではなく現実で動かす場合には報酬は不要です。(なので、シミュレーション上でしか取ることが出来ないデータを使って報酬を決められます。)
  • エージェント(Agent): 現在の状態(入力された画像)に基づいてどういう行動をとるかを決めます。DeepRacer自体とも言えます。
  • SageMakerで学習されるモデルはエージェントを構成する1要素として考えられます。

やってみた

やってみた内容は主に次の通りです。

  • SageMakerのノートブックインスタンスの作成&起動
  • IAMロールやVPCなどの環境設定
  • SageMakerの学習ジョブの作成
  • RoboMakerのシミュレーションアプリケーションとシミュレーションジョブの作成
  • RoboMakerのシミュレーションジョブから学習しているDeepRacerの確認
  • RoboMakerの評価用シミュレーションジョブの作成と確認

※ RoboMakerは東京リージョンで使えないので、バージニア北部リージョンなどのRoboMakerが対応しているリージョンに移動する必要があります。今回はバージニア北部リージョンでやってみました。

ノートブックインスタンス起動からサンプルノートブックの準備

SageMakerのマネジメントコンソールを開き、バージニア北部リージョンへ移動後、ノートブック一覧ページからノートブックインスタンスを作成します。作成&起動が完了後、ノートブックを開きます。(JupyterLabでも問題ありません。)

ノートブックを開いたら、SageMaker Examplesタブからサンプルノートブック一覧を開きます。reinforcement_learningからrl_deepracer_robomaker_coach_gazeboを選択し、コピーします。

rl_deepracer_robomaker_coach_gazeboは以下のようなファイルで構成されています。

  • rl_deepracer_coach_robomaker.ipynb: SageMakerとRoboMakerの設定やDeepRacerの強化学習を進めていくノートブックです。
  • src/: SageMakerとRoboMakerを連携してDeepRacerの学習を進める中で実行するスクリプトファイルを含んでいます。
  • training_worker.py : 強化学習実行時のエントリポイントです。
  • robomaker/presets/deepracer.py: DeepRacerの強化学習を行う際のプリセット(設定)です。
  • エピソード数や学習率から割引率といった学習時のパラメータが設定されています。
  • robomaker/environments/deepracer_env.py: DeepRacerの強化学習用の環境を定義するスクリプトです。
  • 行動や報酬などもここで定義されています。
  • robomaker/environments/init.pyで読み込む環境定義クラスが指定されています。環境定義を変更する場合はこちらも確認が必要です。

  • markov/: 主にS3とのやり取りを楽にするためのユーティリティスクリプトファイルを含んでいます。

  • common/: ノートブックや学習を進めるにあたり便利なユーティリティスクリプトファイルを含んでいます。

環境作成

ここからは順次ノートブック上のスクリプトを実行していきます。(セルを選択した上でshift + enterによって、セルを1つずつ実行できます。)

まずは使用するパッケージ/モジュールの読み込みを行います。

import sagemaker
import boto3
import sys
import os
import glob
import re
import subprocess
from IPython.display import Markdown
from time import gmtime, strftime
sys.path.append("common")
from misc import get_execution_role, wait_for_s3_object
from sagemaker.rl import RLEstimator, RLToolkit, RLFramework
from markdown_helper import *

次に学習時に使用するS3バケットとパスを指定します。スクリプトのままだとデフォルトバケットを使用しますが、必要に応じて別のバケット名を指定しても問題ありません。

# S3 bucket
sage_session = sagemaker.session.Session()
s3_bucket = sage_session.default_bucket()
s3_output_path = 's3://{}/'.format(s3_bucket) # SDKが学習時にジョブ名と出力フォルダ名を自動的に追加します

ジョブ名とリージョン名を確認します。RoboMakerは東京リージョンでは使えないので、対応しているバージニア北部リージョンなどで実行する必要があるためです。

job_name_prefix = 'rl-deepracer'

# ユニークなジョブ名を作成します
tm = gmtime()
job_name = s3_prefix = job_name_prefix + "-sagemaker-" + strftime("%y%m%d-%H%M%S", tm) #Ensure S3 prefix contains SageMaker
s3_prefix_robomaker = job_name_prefix + "-robomaker-" + strftime("%y%m%d-%H%M%S", tm) #Ensure that the S3 prefix contains the keyword 'robomaker'


# 学習時間を指定します。
job_duration_in_seconds = 3600 * 5

aws_region = sage_session.boto_region_name

# RoboMakerは使えるリージョンが限られているので、使えるリージョンか確認する
if aws_region not in ["us-west-2", "us-east-1", "eu-west-1"]:
    raise Exception("This notebook uses RoboMaker which is available only in US East (N. Virginia), US West (Oregon) and EU (Ireland). Please switch to one of these regions.")
print("Model checkpoints and other metadata will be stored at: {}{}".format(s3_output_path, job_name))

IAMロールの設定

学習やシミュレーションなどを実行する際に使用するIAMロールです。ノートブック起動時に関連づけたIAMロールが使われます。

try:
    role = sagemaker.get_execution_role()
except:
    role = get_execution_role('sagemaker')

print("Using IAM role arn: {}".format(role))

display(Markdown(generate_help_for_robomaker_trust_relationship(role)))

今回はRoboMakerのシミュレーションを作成する必要があるので、表示される説明にしたがって信頼関係にRoboMakerを追加します。説明の中にあるIAMロール設定画面へのリンクを開き、信頼関係タブの信頼関係の編集から信頼関係に関するポリシーを編集することができます。

VPCの設定

学習ジョブを実行するSageMakerとシミュレーションジョブを実行するRoboMakerはお互いに通信を行う必要があります。なので、通信が出来るように学習ジョブとシミュレーションジョブを実行するためのVPCとサブネット、セキュリティグループを取得します。 今回はデフォルト設定のものを使います。

ec2 = boto3.client('ec2')
default_vpc = [vpc['VpcId'] for vpc in ec2.describe_vpcs()['Vpcs'] if vpc["IsDefault"] == True][0]

default_security_groups = [group["GroupId"] for group in ec2.describe_security_groups()['SecurityGroups'] \
                   if group["GroupName"] == "default" and group["VpcId"] == default_vpc]

default_subnets = [subnet["SubnetId"] for subnet in ec2.describe_subnets()["Subnets"] \
                  if subnet["VpcId"] == default_vpc and subnet['DefaultForAz']==True]

print("Using default VPC:", default_vpc)
print("Using default security group:", default_security_groups)
print("Using default subnets:", default_subnets)

先ほど取得したVPCにS3エンドポイントを作成します。

try:
    route_tables = [route_table["RouteTableId"] for route_table in ec2.describe_route_tables()['RouteTables']\
                if route_table['VpcId'] == default_vpc]
except Exception as e:
    if "UnauthorizedOperation" in str(e):
        # 権限が足りないので、IAMロールに権限を追加するガイドを表示する
        display(Markdown(generate_help_for_s3_endpoint_permissions(role)))
    else:
        # 権限以外の理由で作成に失敗したので、手動でのS3エンドポイントの作成ガイドを表示する
        display(Markdown(create_s3_endpoint_manually(aws_region, default_vpc)))
    raise e

print("Trying to attach S3 endpoints to the following route tables:", route_tables)

assert len(route_tables) >= 1, "No route tables were found. Please follow the VPC S3 endpoint creation "\
                              "guide by clicking the above link."

try:
    ec2.create_vpc_endpoint(DryRun=False,
                           VpcEndpointType="Gateway",
                           VpcId=default_vpc,
                           ServiceName="com.amazonaws.{}.s3".format(aws_region),
                           RouteTableIds=route_tables)
    print("S3 endpoint created successfully!")
except Exception as e:
    if "RouteAlreadyExists" in str(e):
        print("S3 endpoint already exists.")
    elif "UnauthorizedOperation" in str(e):
        # 権限が足りないので、IAMロールに権限を追加するガイドを表示する
        display(Markdown(generate_help_for_s3_endpoint_permissions(role)))
        raise e
    else:
        # 権限以外の理由で作成に失敗したので、手動でのS3エンドポイントの作成ガイドを表示する
        display(Markdown(create_s3_endpoint_manually(aws_region, default_vpc)))

SageMakerの学習ジョブを回す

一通りの環境設定が終わったらSageMakerで学習ジョブを実行します。

まずは強化学習の環境を定義したスクリプトとプリセットを定義したスクリプトをS3にアップロードします。

s3_location = "s3://%s/%s" % (s3_bucket, s3_prefix)

# 指定した保存先に何もない状態にする
!aws s3 rm --recursive {s3_location}

# 環境定義スクリプトとプリセットスクリプトをS3にアップロードする
!aws s3 cp src/robomaker/environments/ {s3_location}/environments/ --recursive --exclude ".ipynb_checkpoints*" --exclude "*.pyc"
!aws s3 cp src/robomaker/presets/ {s3_location}/presets/ --recursive --exclude ".ipynb_checkpoints*" --exclude "*.pyc"

学習途中に獲得報酬や損失の値を確認できるようにメトリクスとして定義します。

metric_definitions = [
    # Training> Name=main_level/agent, Worker=0, Episode=19, Total reward=-102.88, Steps=19019, Training iteration=1
    {'Name': 'reward-training',
     'Regex': '^Training>.*Total reward=(.*?),'},

    # Policy training> Surrogate loss=-0.32664725184440613, KL divergence=7.255815035023261e-06, Entropy=2.83156156539917, training epoch=0, learning_rate=0.00025
    {'Name': 'ppo-surrogate-loss',
     'Regex': '^Policy training>.*Surrogate loss=(.*?),'},
     {'Name': 'ppo-entropy',
     'Regex': '^Policy training>.*Entropy=(.*?),'},

    # Testing> Name=main_level/agent, Worker=0, Episode=19, Total reward=1359.12, Steps=20015, Training iteration=2
    {'Name': 'reward-testing',
     'Regex': '^Testing>.*Total reward=(.*?),'},
]

強化学習用のEstimatorであるRLEstimatorを作成し、学習ジョブを実行します。

RLCOACH_PRESET = "deepracer"

instance_type = "ml.c4.2xlarge"

estimator = RLEstimator(entry_point="training_worker.py", # 学習開始時に実行されるスクリプト
                        source_dir='src', # スクリプトがどこにあるか
                        dependencies=["common/sagemaker_rl"], # ユーティリティスクリプトのパス
                        toolkit=RLToolkit.COACH, # ツールキットの種類
                        toolkit_version='0.11.0', # ツールキットのバージョン
                        framework=RLFramework.TENSORFLOW, # NNを学習させるときに使用する深層学習フレームワーク
                        role=role, # 学習を実行する際に使用するロール
                        train_instance_type=instance_type, # 学習ジョブで使用するインスタンスタイプ
                        train_instance_count=1, # 学習ジョブで使用するインスタンス数
                        output_path=s3_output_path, # モデルアーティファクト(学習結果)の出力先
                        base_job_name=job_name_prefix, # ジョブ名の接頭辞
                        train_max_run=job_duration_in_seconds, # 学習ジョブの最大実行時間
                        hyperparameters={"s3_bucket": s3_bucket, # 学習中のモデルデータなどをやり取りするためのS3バケット
                                         "s3_prefix": s3_prefix, # 学習中のモデルデータなどをやり取りするためのS3オブジェクトキーの接頭辞
                                         "aws_region": aws_region, # ↑で指定したS3バケットがあるリージョン
                                         "RLCOACH_PRESET": RLCOACH_PRESET, # 使用するパラメータプリセットの名前
                                      },
                        metric_definitions = metric_definitions, # メトリクス
                        subnets=default_subnets, # どのサブネットで学習させるか
                        security_group_ids=default_security_groups, # どのセキュリティグループを学習用インスタンスに付けるか
                    )

# 学習ジョブを実行する
estimator.fit(job_name=job_name, wait=False)

RoboMakerのシミュレーション環境の作成

これでSageMakerで学習ジョブが作成されました。しかし、これだけではモデルの学習は行われません。学習を進めるためにはRoboMakerのシミュレーションジョブを動かす必要があります。

まずはシミュレーションを行う際に必要となるアプリケーションバンドルをダウンロードし、S3にアップロードします。

from botocore.exceptions import UnknownServiceError

robomaker = boto3.client("robomaker")

# アプリケーションバンドルの保存先
bundle_s3_key = 'deepracer/simulation_ws.tar.gz'
bundle_source = {'s3Bucket': s3_bucket,
                 's3Key': bundle_s3_key,
                 'architecture': "X86_64"}

# シミュレーション実行時に使用するソフトウェアの情報
simulation_software_suite={'name': 'Gazebo',
                           'version': '7'}
robot_software_suite={'name': 'ROS',
                      'version': 'Kinetic'}
rendering_engine={'name': 'OGRE', 'version': '1.x'}

# アプリケーションバンドルがある場所
simulation_application_bundle_location = "https://s3-us-west-2.amazonaws.com/robomaker-applications-us-west-2-11d8d0439f6a/deep-racer/deep-racer-1.0.74.0.1.0.82.0/simulation_ws.tar.gz"

# ダウンロードし、今回使う場所にアップロードする
!wget {simulation_application_bundle_location}
!aws s3 cp simulation_ws.tar.gz s3://{s3_bucket}/{bundle_s3_key}
!rm simulation_ws.tar.gz

RoboMakerのシミュレーションアプリケーションを作成します。 シミュレーションアプリケーションはどういうシミュレーションを行うかの設定プリセット的なものです。

app_name = "deepracer-sample-application" + strftime("%y%m%d-%H%M%S", gmtime())

try:
    response = robomaker.create_simulation_application(name=app_name,
                                                   sources=[bundle_source],  # アプリケーションバンドルの場所
                                                   simulationSoftwareSuite=simulation_software_suite, # シミュレーション用ソフトウェア(Gazebo)
                                                   robotSoftwareSuite=robot_software_suite, # ロボットをハンドルするソフトウェア(ROS)
                                                   renderingEngine=rendering_engine # レンダリング用ソフトウェア(OGRE)
                                                  )
    simulation_app_arn = response["arn"]
    print("Created a new simulation app with ARN:", simulation_app_arn)
except Exception as e:
    if "AccessDeniedException" in str(e):
        display(Markdown(generate_help_for_robomaker_all_permissions(role)))
        raise e
    else:
        raise e

シミュレーションアプリケーションを基にシミュレーションジョブを作成します。

# シミュレーションジョブを並列実行する数(増やすことでモデルの学習が早く進みます)
num_simulation_workers = 1

envriron_vars = {
                 "MODEL_S3_BUCKET": s3_bucket,
                 "MODEL_S3_PREFIX": s3_prefix,
                 "ROS_AWS_REGION": aws_region,
                 "WORLD_NAME": "hard_track",  # 環境の名前です。今回の場合は次の3つから選択する必要があります。"easy_track", "medium_track", "hard_track"
                 "MARKOV_PRESET_FILE": "%s.py" % RLCOACH_PRESET, # 強化学習用パラメータのプリセットスクリプト
                 "NUMBER_OF_ROLLOUT_WORKERS": str(num_simulation_workers)} # 並列実行するジョブの数

simulation_application = {"application": simulation_app_arn,
                          "launchConfig": {"packageName": "deepracer_simulation",
                                           "launchFile": "distributed_training.launch",
                                           "environmentVariables": envriron_vars}
                         }

vpcConfig = {"subnets": default_subnets,
             "securityGroups": default_security_groups,
             "assignPublicIp": True}

responses = []

# "num_simulation_workers"の数だけシミュレーションジョブを作成する
for job_no in range(num_simulation_workers):
    response =  robomaker.create_simulation_job(iamRole=role, # 使うIAMロール
                                            clientRequestToken=strftime("%Y-%m-%d-%H-%M-%S", gmtime()), # 作成リクエスト時に使用するトークン
                                            maxJobDurationInSeconds=job_duration_in_seconds, # 最大ジョブ実行時間
                                            failureBehavior="Continue", # ジョブの実行に失敗した時の挙動
                                            simulationApplications=[simulation_application], # シミュレーションジョブに使用するアプリケーション
                                            vpcConfig=vpcConfig, # シミュレーションジョブを実行するVPC設定
                                            outputLocation={"s3Bucket":s3_bucket, "s3Prefix":s3_prefix_robomaker} # 実行結果の保存場所
                                            )
    responses.append(response)

print("Created the following jobs:")
job_arns = [response["arn"] for response in responses]
for job_arn in job_arns:
    print("Job ARN", job_arn)

学習中のDeepRacerを見てみる

SageMakerで学習ジョブとRoboMakerでシミュレーションジョブを作成することができると、遂にDeepRacerが動き出します。 以下の処理を実行することでRoboMakerのシミュレーションジョブのページへのリンクを作成してくれます。そのリンクからシミュレーションジョブページを開きます。

display(Markdown(generate_robomaker_links(job_arns, aws_region)))

まずはGazeboでDeepRacerが動いている様子をみてみます。GazeboのアイコンをクリックすることでGazeboが開きます。DeepRacerがコースを外れては何度もスタートから走り直す様子をみる事ができます。

※コースアウトしても走り続けたり、ぐるぐる同じところを回っている事があります。狂った訳ではなくSageMakerでモデルの学習を行なっています。しばらくすると、再びコース上を走り始めます。

Gazeboは環境をいじったりシミュレーション速度を変えたりなど、操作が可能です。色々試してみると面白そうです。

次にrqtを使ってDeepRacerの視点を見てみます。シミュレーションジョブのページに戻り、rqtのアイコンからrqtを開きます。

はい、何も表示されません。左上のメニューからPluginsを開き、VisualizationのなかのImage Viewを開きます。 するとImage View用の画面に変わるので、左上のプルダウンから/camera/zed/rgb/image_rect_colorを選ぶことで、画像を表示できます。

DeepRacerの視点での画像が表示されます。更新間隔に従って自動的に画像は変わっていきます。この画像を使ってDeepRacerはどういう行動が良いかを選択します。すごい。

獲得報酬の変化を見てみる

では次にノートブックに戻って学習時の獲得報酬の変化を見てみます。

%matplotlib inline
import pandas as pd

# 一時フォルダを作成
tmp_dir = "/tmp/{}".format(job_name)
os.system("mkdir {}".format(tmp_dir))
print("Create local folder {}".format(tmp_dir))
intermediate_folder_key = "{}/output/intermediate".format(job_name)



# 学習時のデータをダウンロード
csv_file_name = "worker_0.simple_rl_graph.main_level.main_level.agent_0.csv"
key = intermediate_folder_key + "/" + csv_file_name
wait_for_s3_object(s3_bucket, key, tmp_dir)

# データをpandasのDataFrameに読み込む
csv_file = "{}/{}".format(tmp_dir, csv_file_name)
df = pd.read_csv(csv_file)
df = df.dropna(subset=['Training Reward'])
x_axis = 'Episode #'
y_axis = 'Training Reward'

# 報酬量の変化をプロットする
plt = df.plot(x=x_axis,y=y_axis, figsize=(12,5), legend=True, style='b-')
plt.set_ylabel(y_axis);
plt.set_xlabel(x_axis);

学習ジョブとシミュレーションジョブの停止

次に評価を行うので、十分に学習をした段階でDeepRacerの強化学習を終わらせます。 学習を終了させる際には以下のコードでSageMakerの学習ジョブとRoboMakerのシミュレーションジョブを停止できます。

for job_arn in job_arns:
    robomaker.cancel_simulation_job(job=job_arn)
sage_session.sagemaker_client.stop_training_job(TrainingJobName=estimator._current_job_name)

評価

RoboMakerのシミュレーションジョブを新たに作成することで、学習させたモデルがどれだけ賢いかを改めて確認することができます。

envriron_vars = {"MODEL_S3_BUCKET": s3_bucket,
                 "MODEL_S3_PREFIX": s3_prefix,
                 "ROS_AWS_REGION": aws_region,
                 "NUMBER_OF_TRIALS": str(20), # 20回の試行(エピソード)で終了
                 "MARKOV_PRESET_FILE": "%s.py" % RLCOACH_PRESET,
                 "WORLD_NAME": "hard_track",
                 }

simulation_application = {"application":simulation_app_arn,
                          "launchConfig": {"packageName": "deepracer_simulation",
                                           "launchFile": "evaluation.launch",
                                           "environmentVariables": envriron_vars}
                         }

vpcConfig = {"subnets": default_subnets,
             "securityGroups": default_security_groups,
             "assignPublicIp": True}

response =  robomaker.create_simulation_job(iamRole=role,
                                        clientRequestToken=strftime("%Y-%m-%d-%H-%M-%S", gmtime()),
                                        maxJobDurationInSeconds=job_duration_in_seconds,
                                        failureBehavior="Continue",
                                        simulationApplications=[simulation_application],
                                        vpcConfig=vpcConfig,
                                        outputLocation={"s3Bucket":s3_bucket, "s3Prefix":s3_prefix_robomaker}
                                        )
print("Created the following job:")
print("Job ARN", response["arn"])

ジョブの作成が完了したら学習時と同様にシミュレーションジョブの実行画面に移動し、DeepRacerの動いている様子をみることができます。今回のシミュレーションは20回の試行で終了するため、結構早くにシミュレーションが終わります。シミュレーションが作成中の間待っているとシミュレーションの状態が失敗になる事がありました。どうやらシミュレーションが完了すると、状態が失敗になる事があるようです。その場合はシミュレーションを作成し直すと良さそうです。

確認が完了したらシミュレーションジョブを停止させます。

robomaker.delete_simulation_application(application=simulation_app_arn)

これで一通りの流れは終了となります。

さいごに

今回はSageMakerとRoboMakerを連携させて、仮想環境上で「DeepRacerを走らせる&強化学習させる」サンプルノートブックを試してみました。色々変な動きをしながらも少しずつ前に進めるようになっていくDeepRacerは可愛いかったです。文字だけ追ってみるとなんか難しそうに見えますが、ノートブックに沿ってぽちぽちするだけで、DeepRacerを仮想環境上で動かせることができます。 また、src/robomaker/environments/deepracer_env.pyにある報酬関数やアクションの定義などを変更することで、学習の仕方やDeepRacerの行動の選択肢などを変えることができます。DeepRacerの高速化に向けてこれらのスクリプトの変更を試しつつ、機械学習に関連する学習率などのハイパーパラメータの変更なども試すことで、楽しみながら強化学習を学ぶことができます。ぜひ試してみてください!

最後までお読みくださり、ありがとうございました〜!

参考