本記事の概要
10月で2019年のDeepRacerのVirtual Raceが終了しました。
先に結果を述べると、最終的な2019年の10月のVirtual Raceの結果で最高順位52位でした。
本記事、そして次の記事では、2019年の10月のVirtual Raceで試行錯誤した結果を報告させていただきたいと思います。
10月(2019年)のVirtual Raceのコース
10月(2019年)のVirtual Raceのコースは下のようなToronto Turnpikeというコースです。
Virtual Raceでは、上のように学習用のコースと実際にVirtual Raceの大会でのコースが違います。
そのため、過学習には気をつける必要があります。
そして今回から最大速度が12m/sまで出せるようになっています。
DeepRacerでの結果
コースに対応したモデルの作成
Virtual Raceのコースを見る限り、急カーブが多いように見えます。
そのためまず、以下のようにストレートとカーブで場合分けをした報酬関数を作成して学習してみました。
def reward_function(params): import math DIF = 20 STRAIGHT = 0 CURVE = 1 target_waypoint = 0 track_directions = [] waypoints = params['waypoints'] closest_waypoints = params['closest_waypoints'] all_wheels_on_track = params['all_wheels_on_track'] speed = params['speed'] track_waypoints = [waypoints[closest_waypoints[1]]] steering = abs(params['steering_angle']) heading = params['heading'] # ストレート、カーブで場合分け 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[15] - track_directions[0] < 0.28: direction_type = STRAIGHT reward = 1e-3 # Set the speed threshold based your action space SPEED_THRESHOLD = 12.0 if all_wheels_on_track: # 12m/sよりも遅かったら reward = 0.7 if speed > SPEED_THRESHOLD: # 12 m/s だったら reward = 1.0 # ジグザグ防止 ABS_STEERING_THRESHOLD = 15 if steering > ABS_STEERING_THRESHOLD: reward *= 0.8 else: reward = 1e-3 if all_wheels_on_track: reward = 1.0 #道と同じ方向じゃなかったらペナルティ direction_diff = abs(track_directions[0] - heading) if direction_diff > 180: direction_diff = 360 - direction_diff # Penalize the reward if the difference is too large DIRECTION_THRESHOLD = 10.0 if direction_diff > DIRECTION_THRESHOLD: reward *= 0.5 return float(reward)
そして、長いストレートからのカーブに対応できるようにアクションスペースを広めに設定しました。
その結果、以下のような評価結果になり、5回走っても完走できませんでした。
また、ログ等を見た結果、以下のようなことを感じたため、ジグザク走行を抑制する報酬関数で5時間学習した後、目標ステップ数より少ないステップ数で走行できた場合に報酬を与える関数で1時間学習しました。また、その際のアクションスペースでは速度の最大値を6m/sで学習しました。
- ジグザグ走行を抑制するため、大きなステアリングにはペナルティを与えていたため、余計なカーブが少ない。
- 速度については触れていないためよりたくさんの報酬を得るためにステップを多くしてしまい, 直線でも低速で走行してしまっている。
def reward_function(params): ''' Example of penalize steering, which helps mitigate zig-zag behaviors ''' # Read input parameters distance_from_center = params['distance_from_center'] track_width = params['track_width'] steering = abs(params['steering_angle']) # Only need the absolute steering angle # Calculate 3 markers that are at varying distances away from the center line marker_1 = 0.1 * track_width marker_2 = 0.25 * track_width marker_3 = 0.5 * track_width # Give higher reward if the agent is closer to center line and vice versa if distance_from_center <= marker_1: reward = 1 elif distance_from_center <= marker_2: reward = 0.5 elif distance_from_center <= marker_3: reward = 0.1 else: reward = 1e-3 # likely crashed/ close to off track # Steering penality threshold, change the number based on your action space setting ABS_STEERING_THRESHOLD = 15 # Penalize reward if the agent is steering too much if steering > ABS_STEERING_THRESHOLD: reward *= 0.8 return float(reward)
def reward_function(params): # Read input parameters track_width = params['track_width'] distance_from_center = params['distance_from_center'] steps = params['steps'] progress = params['progress'] print('progress: %.2f' % progress) print('steps: %d' % steps) # ここに目標のステップ数 TOTAL_NUM_STEPS = 270 # Calculate 3 markers that are at varying distances away from the center line marker_1 = 0.1 * track_width marker_2 = 0.25 * track_width marker_3 = 0.5 * track_width # Give higher reward if the car is closer to center line and vice versa if distance_from_center <= marker_1: reward = 1.0 elif distance_from_center <= marker_2: reward = 0.5 elif distance_from_center <= marker_3: reward = 0.1 else: reward = 1e-3 # likely crashed/ close to off track # Give additional reward if the car pass every 100 steps faster than expected if (steps % 100) == 0 and progress > (steps / TOTAL_NUM_STEPS) * 100 : reward += 10.0 print('reward: %.2f' % reward) return float(reward)
しかし、結果としては最大速度で走っている部分はむしろ減少していたため、学習したモデルをクローンし、今度は上述ストレートとカーブの場合分けをして報酬を与えるようにしました。
しかし、ログを出力した結果、以下のようになり、緩いカーブでもカーブと認識されてしまい、スピードが落ちてしまっていました。
そのため今度は、現在地と次のwaypointとの角度からカーブを予測し、カーブとストレートの場合分けを行ってみました。
その結果画像のように、現在地から2ポイント先のwaypointとの角度が0.2radのときを閾値として場合分けしたものが最も分類できているように思えました。
def reward_function(params): import math DIF = 20 STRAIGHT = 0 CURVE = 1 target_waypoint = 0 track_directions = [] waypoints = params['waypoints'] closest_waypoints = params['closest_waypoints'] all_wheels_on_track = params['all_wheels_on_track'] speed = params['speed'] track_waypoints = [waypoints[closest_waypoints[1]]] track_width = params['track_width'] distance_from_center = params['distance_from_center'] half_track_width = track_width * 0.5 # Calculate 3 markers that are at varying distances away from the center line marker_1 = 0.1 * track_width marker_2 = 0.25 * track_width marker_3 = 0.5 * track_width max_speed= 12 #ログで見れるようにprint()追加 print('closest_waypoints1: %d' % closest_waypoints[0]) print('closest_waypoints2: %d' % 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[2] - track_directions[0] < 0.2: #ストレートの時 direction_type = STRAIGHT #ログで見れるようprint()を追加 print('STRAIGHT') reward = 1e-3 if all_wheels_on_track: #速度が速いほどいい, トラックの真ん中に近いほどいい. reward = (half_track_width - distance_from_center) / half_track_width reward += speed / max_speed else: #ログで見れるようにprint()を追加 print('CURVE') # Give higher reward if the car is closer to center line and vice versa if distance_from_center <= marker_1: reward = 1.0 elif distance_from_center <= marker_2: reward = 0.5 elif distance_from_center <= marker_3: reward = 0.1 else: reward = 1e-3 # likely crashed/ close to off track return float(reward)
この閾値を用いて、以下のような報酬関数、アクションスペースで実行したところ、6時間の学習で16.317s出すことができました。
しかし、トップは7秒台なため、さらにタイムの向上が必要になります。
アクションスペースを少なくしたモデルの作成
次に、アクションスペースを少なくしてみました。
今月のコースはストレートと大きなカーブがメインなため、アクションスペースを小さくして、直線か曲がるかの2択にすることで、カーブではしっかり曲がり、それ以外ではできるだけ直線で走れるようなモデルを目指そうと考えました。
そのため、報酬関数とアクションスペースは以下のように設定しました。
import math def reward_function(params): # Read input parameters is_left = params['is_left_of_center'] track_width = params['track_width'] quarter_track_width = track_width / 4 distance_from_center = params['distance_from_center'] progress = params["progress"] steps = params["steps"] all_wheels_on_track = params['all_wheels_on_track'] steer = params["steering_angle"] # waypointsの取得 waypoints = params['waypoints'] closest_waypoints = params['closest_waypoints'] heading = params['heading'] # 前のwaypointsと次のwaypointsを取得 next_point = waypoints[closest_waypoints[1]] prev_point = waypoints[closest_waypoints[0]] reward = 0 if all_wheels_on_track and distance_from_center > 0 and steps > 0: reward += 1/distance_from_center reward += progress / steps # 次の角度を計算 # 前のwaypointから次のwaypointに向かう角度(radian)を計算する track_direction = math.atan2(next_point[1] - prev_point[1], next_point[0] - prev_point[0]) # degreeに変換 track_direction = math.degrees(track_direction) # コース上の基準軸に対する車体の向きと直近のwaypointを繋ぐ向きの差分を取る direction_diff = track_direction - heading # 30度ある→曲がれるのでステアリングさせる if (direction_diff > 30 and not is_left) and steer > 0: reward += 20 elif (direction_diff < -30 and is_left) and steer < 0: reward += 20 # 30度未満→曲がれない可能性あるので、中心線沿っているものに報酬 elif abs(direction_diff) < 30 and distance_from_center < quarter_track_width: reward += 20 else : reward += 1e-5 return float(reward)
この報酬関数では、基本的にタイヤがトラックの上にあるときに報酬を与えるようにして、車体が直前に通過したwaypointと次のwaypointとの角度(現在走行しているトラックの角度)と車体の角度を計算し、その角度とコースの車体の位置によって報酬を調整するように設定しています。
この関数で6時間学習したところ、学習結果と評価結果は以下のようになりました。
学習の結果のグラフのピンク色のグラフ(完走率)は上昇しているので、学習は進んでいるように見えるのですが、結果は3回走って完走できませんでした。
その後さらに4時間学習させたのですが、完走率はさらに下がってしまい、少し速度を意識しすぎたのと、報酬関数に偏りが大きすぎた可能性があるかなと思いました。
まとめ
今回は2019年10月のVirtual Raceで試行錯誤した結果を報告させていただきました。
次回は52位になったモデルも含めてこの続きを報告させていただきたいと思います。