DeepRacer Empire City Circuitでとりあえず15秒を切る方法をまとめてみた #AWSDeepRacer #AWSDeepRacerJP

せーのでございます。
クラメソでは現在DeepRacerにハマる人が続出中です。

DeepRacerの走行ログをTableauで分析してみた #AWSDeepRacer #AWSDeepRacerJP

社内でも一斉にバーチャルサーキットにサブミットする人が出て、Weekly Challengeのひとつ「The Rookie Challenge(初めて出したモデルでのランキング)」をクラメソチームが独占する、という現象も起こっています。

私は「DeepRacer同好会」というコミュニティにも参加していまして、そこでは「DeepRacer世界大会で日本人から1番を出す」ことを目標に、社外の人たちとも日々情報交換を行っております。
そこで先日こんな意見を目にしました。

同好会の仲間が困っとる!助けねば! ということで今回は、現在バーチャルサーキットで行われている「Empire City Circuit」にてとりあえず15秒を切ることを目標に色々試行錯誤したTipsを共有します。

現在の順位

私の現在の順位はこちらです。

もう一伸びできそうだと思うので、色々試しながらTop10入りを目指しているところです。

注意事項

これが正しいわけではない

この記事はあくまで私の経験からのみくるTips集です。考え方は人それぞれです。社内でもアプローチはバラバラです。強化学習に詳しい方で「こういう考え方もあるよ」という方はぜひコメントをこの記事かDeepRacer同好会にお寄せください。

Empire Cityに特化している

これはバーチャルサーキット「Empire City」を攻略するためのTipsです。他のコースでは通じないものもありますし、実機レースでは全く違うアプローチを取ります。

reward functionまるごとは掲載できない

DeepRacerの規約内に「reward functionのshareは禁止」という項目があります。ですのでreward functionをまるっとここに掲載することはできません。

基本

ログは必ずチェックする

まず基本として、トレーニングやEvaluationsの走行ログ(SIM_TRACE_LOG)は必ずチェックしましょう。自分の車がどのようなコースを通り、どこでコースアウトしているのかを把握することで、戦略や改善が可能となります。
ログを見るには直接CloudWatch Logsから生データを見るのもいいですが、AWSの公式ログ分析ツールがあるのでそちらを利用してみましょう。Tableauをお持ちの方はTableauを使っても分析できます。

【DeepRacer】ワークショップに参加してきたので初心者なりに色々いじってみる #AWSSummit #AWSDeepRacer #AWSDeepRacerJP

完走率は大体で捉える

実機のレースでは何よりも完走率を重視しました。

【参加レポート】DeepRacer League参加で感じた「実機用」モデルの鍛え方 #AWSSummit #AWSDeepRacer #AWSDeepRacerJP

ですが今回は後述しますがトレーニングコースと本番コースで違いがあります。Evaluations5回で5回完走100%いくまで育ててしまうと、本番コースに対応できない車ができてしまうことが多いです。
経験的には100%のラップが1〜2回、70%〜80%のラップが1〜2回あれば充分かと思います。

では、順にTipsを書いていきます。扱うテーマは

  • コース検証
  • Action Space
  • 報酬関数
  • ハイパーパラメータ
  • 学習時間

です。それでは順にいきましょう。

コース検証

トレーニングコースと本番コースの違い

先にも言ったように、Empire Cityはトレーニングと本番のコースが違います。

トレーニングコースがこちら。

そして、本番コースがこちらです。

重ねて比べてみましょう。赤いコースが本番です。

最大の違いは第三コーナーを抜けた後です。

トレーニングでは内側に入りながらS字を曲がっていくことになりますが、本番では少し膨らみながらストレートに入っていきます。トレーニングにフィットしすぎるとどうしてもこのコーナーを抜けた後に内側に入ろうとしてしまい、本番でコースアウトすることが続出します。progressがちょうど70%前後であることから私たちの間では「70%ゾーン」と呼ばれています。

他にコースアウトしやすいポイントをいくつかあげておきます。

第一コーナー(progress: 17%前後)

スピードを速く設定していると、ここで内側にコースアウトすることが多いです。

第二コーナー(progress: 30%)

第一コーナーでストレートをキープできていないと、車がぶれてしまいここでコースアウトします。

右カーブ(progress: 40%)

第二コーナーを膨らみながら曲がってしまうと、ここで右にハンドルを切ってしまって外側にコースアウトします。

最終シケイン(progress: 94%)

もう少しでゴール、というところで最後に立ちはだかるのがここです。トレーニングではS字を抜けた所にあるため、左に大きくステアリングを取って抜けていくのですが、本番では大きなカーブを通った後にあるため、左に大きめに曲がるクセがついていると、ここで内側にコースアウトします。

トレーニングではこのあたりのポイントをいかにクリアしていくような車を育てていくか、が鍵となります。

Ovalで調整する

コースに関してもう一つ私がやっていることがあります。それは「ある程度モデルが育ってきたら「Oval」コースで数時間学習させてからEmpireに戻す」ということです。

トレーニングコースだけで学習するとオーバーフィッティング(過学習)気味になってしまい、本番に対応できない、というのもあるのですが、トレーニングコースは細かいカーブが多く、車が「曲がりながら進んでいく」ことを覚えていってしまいます。そこで、本番コースに形が似ていて、細かいRのない「Oval」で学習させることで「ステアリングをなるべく切らずにまっすぐ走ること」を学習させるようにしています。

Action Space

Action Spaceでは速度とステアリングの段階を決めます。
ここでのポイントは「速度とステアリングの組み合わせを多くしすぎない」「速度を落としすぎない」ということです。

組み合わせは2 x 3くらいまで

組み合わせが多くなると、当然適切なActionを車が学習できるまで時間がかかります。また、私たちは「より速く走って欲しい」と思っていますが、車は「得られる報酬の累積をなるべく多くしたい」と思いながら走っています。stepをより多くする、つまりより長く生き残る方が車にとっては報酬が多くなる確率が高くなるので、遅くても生き残る方を選びがちです。

ですのでAction Spaceの考え方としては「与えられる条件をなるべく少なくした上で最適なコース取りを学習するように仕向ける」という考え方になります。

ステアリングは0、15°、30°の3パターンで、中速と高速の2パターンくらいまでが設定としては適切かと思います。

速度を落としすぎない

これも先程の「より最適なコース取りをしてもらう」ための方法論です。
実験した感じではEmpire Cityは6m/sくらいのSpeed設定でもギリギリ曲がりきれました。また「Speed」とあるのでこの設定を車の「速度」と考えてしまいがちですが、これは速度ではなく「スロットル設定」であるようです。つまり、6m/sに設定していて、車が6m/sのアクションを取ったからといってその瞬間6m/sの速さで進むわけではなく、6m/sを目指してアクセルを踏んでいく、という感じです。
実際の車の挙動を想像してください。まっすぐの道を左右にハンドルを切りながら進むのと、まっすぐに進むのはどちらが速いでしょうか。
DeepRacerのシミュレーターは結構優秀で、道路との摩擦や慣性などがリアルに考慮されています。そうなると理想の走り方としては「カーブはギリギリ曲がれる速度で駆け抜ける」「ハンドルを切らなくていい所はなるべく切らずに速度を稼ぐ」という戦略が思い浮かびます。

ステアリング30°で6m/sに設定していると完走できる車に育てるまでは相当な時間がかかるかと思うので、5m/s〜5.6m/sくらいで設定すると良いかと思います。

報酬関数

次に報酬関数です。色々試した経験だと、報酬関数設定時に参考にできる「reward example」が使えるので、これらの組み合わせだけでも充分15秒は切れるかと思います。またここは学習時間と密接に関わります。複雑な報酬関数を作ると学習時間をかけないと良いモデルになりません。
報酬関数の基本は「最初はシンプルに、クローンを重ねる度にちょっとずつ増やしていく」です。最初は「コースアウトしない」くらいでも構いません。クローンを重ねながら徐々に増やしていきましょう。

報酬関数でのポイントとしては「先を見越した報酬設定を考える」ことです。

まず、理想のコース取りとしてはこんな感じかな、と思います。

細かいRはストレートでなるべく突っ切ってもらって、カーブは大きめに曲がって次のカーブのためにアウトに向かってもらいたい。

このようなコース取りをするためには

  • 細かいRを気にせずストレートで突っ込む => これが「細かいRである(大きく曲がるカーブではない)」と理解する
  • カーブを大きめに曲がる => ステアリングを極端に切らない

という情報がそれぞれ必要です。

それを踏まえてExample以外でよく組む方法論を共有します。

道路の角度を計算する

コースにはそれぞれ「waypoint」というポイントが設置されており、各waypointのX、Y座標は報酬関数のパラメータとして取れます。
また「closest_waypoint」というパラメータは車の現在地点から次に最も近いwaypoint、前に最も近いwaypointがそれぞれ取れます。
ここから車の前後のwaypointのX、Y座標よりarcTangentを取って道路の角度を計算します。pythonにはatan / atan2という関数があるので、mathというライブラリをimportしてそれを使います。ちなみにatan2で取れる値はラジアンとなっているので、必要に応じてdegrees関数で度単位に直します。

import math

waypoints = params['waypoints']
closest_waypoints = params['closest_waypoints']

if closest_waypoints[1] + i >= len(waypoints):
  next_waypoint = waypoints[closest_waypoints[1] + i - len(waypoints)]
else:
    next_waypoint = waypoints[closest_waypoints[1] + i]

if closest_waypoints[1] + i - 1 >= len(waypoints):
    prev_waypoint = waypoints[closest_waypoints[1] + i - 1 - len(waypoints)]
else:
    prev_waypoint = waypoints[closest_waypoints[1] + i - 1]

track_direction = math.atan2(next_waypoint[1] - prev_waypoint[1], next_waypoint[0] - prev_waypoint[0])

これで今車がいる位置の道路の角度がわかりました。車の角度は「heading」というパラメータで取れるので、道路の角度とheadingのズレが大きければ、コースアウトする可能性が高い、ということになります。

また同様の計算式で、少し先のwaypointの角度を計算して、現地点での道路の角度と比べてみると、今自分がいる道路がまっすぐなのか、カーブなのかが推測できます。

import math
DIF = 20
STRAIGHT = 0
CURVE = 1
target_waypoint = 0
track_directions = []
track_waypoints = [waypoints[closest_waypoints[1]]]

for i in range(1, DIF):
    if closest_waypoints[1] + i >= len(waypoints):
        next_waypoint = waypoints[closest_waypoints[1] + i - len(waypoints)]
        track_waypoints.append(waypoints[closest_waypoints[1] + i - len(waypoints)])

    else:
        next_waypoint = waypoints[closest_waypoints[1] + i]
        track_waypoints.append(waypoints[closest_waypoints[1] + i])

    if closest_waypoints[1] + i - 1 >= len(waypoints):
        prev_waypoint = waypoints[closest_waypoints[1] + i - 1 - len(waypoints)]
    else:
        prev_waypoint = waypoints[closest_waypoints[1] + i - 1]


    track_direction = math.atan2(next_waypoint[1] - prev_waypoint[1], next_waypoint[0] - prev_waypoint[0])
    track_directions.append(track_direction)

if track_directions[4] - track_directions[0] < 0.3:
    direction_type = STRAIGHT
    print("direction_type: STRAIGHT")
else:
    direction_type = CURVE
    print("direction_type: CURVE")

これらを使って、例えば「ストレートならステアリングは切らない」「カーブならコースアウトさせないことが最優先」などの戦略を立てます。

X / Yの差分から先の道路を予測する

Empire Cityは「ななめに通るストレート」というものがありません。まっすぐ走れる所は縦か横、どちらかに伸びています。これを利用します。
縦にまっすぐ、または横にまっすぐ走る、ということはストレートの入口と出口のwaypoint間でX座標、またはY座標の差分があまりない、ということになります。

逆に言うと、例えば少し先にあるwaypointのY座標と現地点のwaypointのY座標の差分がほとんどなければ、それは「横に伸びるストレートにいる」ということになり「まっすぐ進め」ということになります。
道路幅は座標的にはおよそ1なので、差分が0.5以内であれば、多少カーブになっていてもまっすぐ進める、という事になるのではないでしょうか。

ということで20個先のwaypointのX、Y座標との差分を取り、その差が0.5以内であればセンターラインなどは全く無視してとにかく「まっすぐ走ること」に集中させます。

#上の計算式を踏まえて

diff_x = abs(track_waypoints[19][0] - track_waypoints[0][0])
diff_y = abs(track_waypoints[19][1] - track_waypoints[0][1])

if diff_x < 0.5 or diff_y < 0.5:
    print("ほぼストレート")

else:
    print("たぶんカーブ") 

これらの方法論とExampleにある「スピードを維持する」「ジグザグにステアリングを切らない」などの関数を組み合わせると、車のコース取りが理想的なものになることが期待できます。

ハイパーパラメータ

さて、次はハイパーパラメータです。ハイパーパラメータの基本的な知識は公式のworkshopが一番わかり易いので、そちらを御覧ください。

ハイパーパラメータのポイントとしては「毎学習ごとにその値が変えられる」ということです。学習段階や学習状況によって適切なハイパーパラメータを設定しながらクローンしていくと学習が進みます。

そう、大事なのは「学習を進める」ということです。reward graphを見ながら、学習が停滞している、発散している、下がっている、という状況に応じてハイパーパラメータを調整してみましょう。

ここでは私がよく使うハイパーパラメータの値を共有します。

エントロピー

エントロピーは学習でのランダム度(方策、といいます)が設定できます。エントロピーを上げると車は色々な手を試すようになります。
エントロピーは学習初期では少し高め(0.015〜0.02くらい)でもいいかと思います。初期では色々なラインを試してみて、より適切なラインを見つけてほしいからです。
一方ある程度学習が進んで、車がとんでもない動きをしないようになってきたら、エントロピーはデフォルトまで戻した方が良いです。この段階でエントロピーが高いと、いつまで立っても車のラインが落ち着かない感じになります。

また、学習時間を短く、サクッと仕上げたい時は、この値は触らない方が良いです。私は基本、あまり触りません。

バッチサイズ

バッチサイズは一回の学習(ミニバッチ、といいます)にどれくらいの画像情報を使うか、を示します。
バッチサイズが少ないと与えられる情報が少ないので、細かい動きに繊細になり、大きいと鈍感になります

私の場合、何度学習しても「カーブが上手く回れない時」「変なところでステアリングを切ってしまう時」にこのサイズを上げていきます。

先に書いたように「トレーニングと本番のコースが違う」ため、あまり目の前のラインに敏感になってしまうと本番でどんどんコースアウトしてしまいますので、なるべく多くとるように心がけています。
変更タイミングは学習中盤から終盤、最終的にはMAXまで持っていくことも多いです。

学習率(Learning rate)

学習率は学習が同じようなパターンを繰り返してしまいそうな時に、その学習をいい感じに目的点に収束させるために使います。学習率を下げると収束しやすくなります。

このパラメータは「新しい報酬関数を追加した時」「Action Spaceのスピードが高い時」などに下げて置くといいかと思います。新しいことを試す時に、保険として「最終的には落ち着いてくれ」という願いがこもっています。
下げる単位は1/10くらいまでいっても良いです。デフォルトが0.0003なので、そこから0.00007 -> 0.00005 -> 0.00003、という感じでしょうか。

個人的にはエントロピーをあげている時は学習率は変えません。バッチサイズを上げると学習率も下げることが多いです。

学習時間

DeepRacerでは5分から最大480分まで学習させることができます。どれくらい学習させたら良いのでしょうか。

ここでのポイントは「初期は長め、追加は短め」です。
最初は3時間くらいの学習時間を重ねていきます。まだまだモデルが未熟な頃なので、それくらいの学習時間でも学習を重ねていってくれます。
「3時間を2セットやるのも、6時間一気に回すのも変わらないんじゃ」と思われる方もいるかと思います。ここら辺になると経験則になってしまうので明確な説明ができないのですが、最初に報酬関数やハイパーパラメータをカッチリ設定して一気に回すより、その時のモデルの状態やEvaluationの結果を見ながら報酬関数の追加修正やハイパーパラメータの調整をした方が効率が良いと感じます。現在DeepRacerではWeeklyチャレンジとして「90分でどれくらい走れるかチャレンジ」というのをやっていますので、90分の学習時間だとどれくらいの報酬関数とハイパーパラメータを組むと最も効率が良いのかやってみたいですね。

一方クローンを重ねてライン取りがしっかりしてきた中盤以降ですが、学習時間は「1時間」がベストかな、と感じます。1時間を超えると学習進度が落ちてくるパターンと2時間で落ちてくるパターンをよく見かけます。ですので3時間は長すぎる、と感じます。1時間にするか2時間にするかはクローン時にどれくらい報酬関数を変えたか、ハイパーパラメータを変えたか、によるかと思いますが、1時間回してみて「もう少しいけそうだな」と思えばそのままもう1時間回してみる、というのが良いのではないか、と思います。

まとめ

以上、私がEmpire City対策で行っていることをかいつまんで書きました。
さらなるTipsはDeepRacer同好会で書きますので、ぜひご参加のほどお待ちしております。