これは フェンリル デザインとテクノロジー Advent Calendar 2023 4日目の記事です。
はじめに
インフラエンジニアの三﨑です。
先日、AWS Jr.ChampionによるDeepRacerの大会に参加しました。
DeepRacerを体験したのは今回が初めてです。
本記事では、その大会に向けて学習モデルを作成し、試行錯誤しながら活用した報酬関数をご紹介します。
<Jr.Championについては下記ページをご覧ください> https://www.wantedly.com/companies/fenrir/post_articles/509397
前提
コースやアクション、ハイパーパラメータは以下のように設定しました。
- コース : Fumiaki Loop - Clockwise
- アクション
- ハイパーパラメータ : デフォルト
- 速度 : 最高速 4.0m/s、最低速 1.3m/s
- 速度の粒度 : デフォルト
- ステアリング角度と粒度 : デフォルト
- アクションタイプ : Discrete
- 学習時間 : 初回のモデルは2時間、クローン時のモデルは3時間

報酬関数の実装
報酬関数の実装では、DeepRacerのパラメータ(params)を使用して報酬を与えることにより、どのように走行すれば良いかと言った判定を制御します。
以下のサイトに掲載されているパラメータと実装例を参考にして、報酬関数を作成しました。
ケース1 : 直線では高速、カーブでは減速して走行させる
速度の判定にはspeedパラメータを使用します。
speedが設定した速度より上回っている、または下回っている場合に報酬を与えます。
直線かカーブかの判定には、closest_waypointsパラメータを使用します。この時、closest_waypoints[1]とclosest_waypoints[0]の差を計算した結果のアークタンジェントを取得することで、曲がり角度を計算しています。
# パラメータ
speed = params['speed']
closest_waypoints = params['closest_waypoints'] # 車の現在地点から次に最も近い waypoint、前に最も近い waypoint
next_point = waypoints[closest_waypoints[1]]
prev_point = waypoints[closest_waypoints[0]]
# カーブを曲がるために必要なステアリング角度に基づいて報酬を決定する
# 次の waypoint への方向を計算
track_direction = math.atan2(next_point[1] - prev_point[1],next_point[0] - prev_point[0]) # 値はラジアンで返す
track_direction = math.degrees(track_direction) # 角度で返すように変更
# 進行方向とトラック方向の差を計算
direction_diff = abs(track_direction - heading)
# カーブと直線では適正速度が異なる
if speed > 3.0:
reward += 10.0 # 直線では加速
elif (speed < 3.0) and (30 <= direction_diff):
reward += 5.0 # 曲線では多少減速
ケース2 : 直線ではセンターラインに沿って走行させる
トラック幅をtrack_widthパラメータから取得し、中心から離れている距離によって報酬を与えるようにしました。
本ケースの報酬関数を直線のみに適用した理由は、カーブ中に中央を走ることが必ずしも最善であると考えなかったからです。
ケース3でも触れますが、今回作成したモデルでは「アウト・イン・アウト」の走行を目指してモデルを作成したため、カーブ中にはなるべく内側を走るように報酬関数を実装しました。
「アウト・イン・アウト」について簡単に説明すると、カーブの進入時と脱出時には外側を走行し、カーブの通過中には内側を走る理想的なコーナリングラインのことです。
よくF1で使用されるテクニックであり、DeepRacerでも有効なのかは分からなかったのですが、今回は試してみようと思いました。
話がそれてしまいましたが、ケース2の報酬関数は以下の通りです。
# パラメータ
track_width = params['track_width'] # トラック幅
# トラック幅によって 3 段階のパラメータを作成
marker_1 = 0.1 * track_width
marker_2 = 0.2 * track_width
marker_3 = 0.3 * track_width
# センターラインからの距離によって加点
# 直線ではなるべく中央を走る様にする
if distance_from_center <= marker_1 and direction_diff < 30:
reward += 5.0
elif distance_from_center <= marker_2 and direction_diff < 30:
reward += 3.0
elif distance_from_center <= marker_3 and direction_diff < 30:
reward += 1.5
ケース3 : カーブ進入時では外側、カーブの通過中には内側を走るように走行させる
先ほどご紹介した「アウト・イン・アウト」に沿った報酬関数がこちらです。
カーブ脱出時に外側を走るためのアルゴリズムの実装が間に合わなかったので、カーブ進入時とカーブ中のアルゴリズムを実装しました。
また、前方の道が直線の場合は、ステアリングを切らない実装も加えました。
# パラメータ
heading = params['heading']
steering_angle = abs(params['steering_angle'])
is_left_of_center = params['is_left_of_center']
# カーブの角度を決定
if direction_diff < 30: # 直線であれば
if steering_angle > 5: # ハンドルは切らない
return 1e-3
# カーブ走行時の位置取り
if 30 <= direction_diff: # カーブ直前
if track_direction > heading: # 右カーブ直前
if is_left_of_center:
reward += 5.0
else: # 左カーブ直前
if not is_left_of_center:
reward += 5.0
else: # カーブ中
if track_direction > heading: # 右カーブ中
if not is_left_of_center:
reward += 7.0
else: # 左カーブ中
if is_left_of_center:
reward += 7.0
ケース4 : 直線ではまっすぐ走行させる
学習させたモデルの挙動は、直線でのジグザグ走行が目立ちました。
そのため、車体の向きと道の向きが近いほど報酬を与えるようにしました。
# 車体の向きと次のwaypointの方向のさが小さい場合に加点
if direction_diff < 5:
reward += 4.0
elif 5 <= direction_diff < 10:
reward += 2.0
試行錯誤の末のタイム
このように報酬関数の設定値を変更しながら試行錯誤した結果、Fumiaki Loopで1周あたり最高で32秒のタイムを出せるようになりました。
Fumiaki Loopはコースの性質上、急カーブが複数存在するコースであり、コーナリングと速度調整にかなりの時間とお金を費やしました。
レース本番では34秒だったので、おおよそ練習通りのタイムを記録しました。

まとめ
学習モデルを速く走らせるために、自身がよく使用している報酬関数を4ケースご紹介しました。
より速い学習モデルを作成するためには、報酬関数の他にハイパーパラメータも調整する必要があるので、DeepRacerは奥が深いサービスだと感じました。
コミュニティでのレースを通してDeepRacerの楽しさを知ることができたので、次は毎月開催されるDeepRacerの大会に出場してみようと思います。