AirSimを用いた強化学習

今回はAirSimを用いた強化学習について自分なりに翻訳しまとめたので記事にしたいと思います。

以下では、AirSim API の周りに OpenAI ジム ラッパーを使用し、標準の RL アルゴリズムの安定したベースライン実装を使用して、AirSim に DQN を実装する方法について説明します。これらの例を実行するには、stable-baselines3 をインストールすることをお勧めします (https://github.com/DLR-RM/stable-baselines3 を参照してください)。

## 免責事項
まだ活発に開発されているため以下で共有するのは、パフォーマンスを向上させるために拡張および微調整できるフレームワークです。

## Gym wrapper
AirSim をジム環境として使用するために、AirSim 固有の step、_get_obs、_compute_reward、reset などの基本メソッドと対象のタスクを拡張および再実装します。これらの例で使用されている車とドローンのサンプル環境は、PythonClient/reinforcement_learning/*_env.py で確認できます。

# RL with Car
[Source code](https://github.com/Microsoft/AirSim/tree/main/PythonClient/reinforcement_learning)
この例は、リリースで利用可能な AirSimNeighborhood 環境で動作します。
まず、シミュレーションから画像を取得し、適切に変換する必要があります。以下に、自カメラから深度画像を取得し、ネットワークへの 84X84 入力に変換する方法を示します。 (他のセンサー モダリティやセンサー入力も使用できます。もちろん、それに応じてコードを変更する必要があります)。

```Python
responses = client.simGetImages([ImageRequest(0, AirSimImageType.DepthPerspective, True, False)])
current_state = transform_input(responses)
```
さらに、エージェントが実行できる 6 つのアクション (ブレーキ、スロットルでまっすぐ、スロットルで完全左、スロットルで完全右、スロットルで半分左、スロットルで半分右) を定義します。これは、関数 interpret_action を介して行われます。
```Python
def interpret_action(action):
    car_controls.brake = 0
    car_controls.throttle = 1
    if action == 0:
        car_controls.throttle = 0
        car_controls.brake = 1
    elif action == 1:
        car_controls.steering = 0
    elif action == 2:
        car_controls.steering = 0.5
    elif action == 3:
        car_controls.steering = -0.5
    elif action == 4:
        car_controls.steering = 0.25
    else:
        car_controls.steering = -0.25
    return car_controls
```
次に、compute_reward の報酬関数を、車両の移動速度と中心線からの逸脱量の凸結合として定義します。エージェントは、高速で移動し、レーンの中央に留まると、高い報酬を獲得します。
```Python
def _compute_reward(car_state):
    MAX_SPEED = 300
    MIN_SPEED = 10
    thresh_dist = 3.5
    beta = 3

    z = 0
    pts = [np.array([0, -1, z]), np.array([130, -1, z]), np.array([130, 125, z]), np.array([0, 125, z]), np.array([0, -1, z]), np.array([130, -1, z]), np.array([130, -128, z]), np.array([0, -128, z]), np.array([0, -1, z])]
    pd = car_state.position
    car_pt = np.array(list(pd.values()))

    dist = 10000000
    for i in range(0, len(pts)-1):
        dist = min(dist, np.linalg.norm(np.cross((car_pt - pts[i]), (car_pt - pts[i+1])))/np.linalg.norm(pts[i]-pts[i+1]))

    #print(dist)
    if dist > thresh_dist:
        reward = -3
    else:
        reward_dist = (math.exp(-beta*dist) - 0.5)
        reward_speed = (((car_state.speed - MIN_SPEED)/(MAX_SPEED - MIN_SPEED)) - 0.5)
        reward = reward_dist + reward_speed

    return reward
```
計算報酬関数は、その後、エピソードが終了したかどうかも判断します (例: 衝突による)。車両の速度を確認し、それがしきい値未満の場合、エピソードは終了したと見なされます。
```Python
done = 0
if reward < -1:
    done = 1
if car_controls.brake == 0:
    if car_state.speed <= 5:
        done = 1
return done
```

次に、メインループは、画像の取得、現在のポリシーに従って実行するアクションの計算、報酬の取得などを順番に実行します。エピソードが終了したら、reset() を介して車両を元の状態にリセットします。
```Python
client.reset()
client.enableApiControl(True)
client.armDisarm(True)
car_control = interpret_action(1) // Reset position and drive straight for one second
client.setCarControls(car_control)
time.sleep(1)
```
ジム スタイルの環境ラッパーが car_env.py のように定義されたら、stable-baselines3 を使用して DQN トレーニング ループを実行します。 DQN トレーニングは、dqn_car.py で見られるように、次のように構成できます。
```Python
model = DQN(
    "CnnPolicy",
    env,
    learning_rate=0.00025,
    verbose=1,
    batch_size=32,
    train_freq=4,
    target_update_interval=10000,
    learning_starts=200000,
    buffer_size=500000,
    max_grad_norm=10,
    exploration_fraction=0.1,
    exploration_final_eps=0.01,
    device="cuda",
    tensorboard_log="./tb_logs/",
)
```
トレーニング環境と評価環境 (dqn_car.py の EvalCallback を参照) を定義できます。評価環境は、異なる終了条件/シーン構成で、トレーニングとは異なる場合があります。テンソルボード ログ ディレクトリも、DQN パラメーターの一部として定義されます。最後に、model.learn() が DQN トレーニング ループを開始します。同様に、PPO、A3C などの実装は、stable-baselines3 から使用できます。

dqn_car.py を実行する前に、シミュレーションを実行する必要があることに注意してください。下のビデオは、DQN トレーニングの最初のいくつかのエピソードを示しています。
<iframe width="560" height="315" src="https://www.youtube.com/embed/fv-oFPAqSZ4" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" loading="lazy" allowfullscreen></iframe>

# RL with Quadrotor
[Source code](https://github.com/Microsoft/AirSim/tree/main/PythonClient/reinforcement_learning)
この例は、リリースで利用可能な AirSimMountainLandscape 環境で動作します。

同様に、クワッドローターを使用したさまざまな自律飛行シナリオに RL を適用できます。以下は、RL を使用してクワッドローターを訓練し、高圧送電線を追跡する方法の例です (例: エネルギー インフラストラクチャの検査への適用)。ここには、クワッドローターが移動できるさまざまな方向に対応する 7 つの個別のアクションがあります (6 つの方向 + 1 つのホバリング アクション)。
```Python
def interpret_action(self, action):
    if action == 0:
        quad_offset = (self.step_length, 0, 0)
    elif action == 1:
        quad_offset = (0, self.step_length, 0)
    elif action == 2:
        quad_offset = (0, 0, self.step_length)
    elif action == 3:
        quad_offset = (-self.step_length, 0, 0)
    elif action == 4:
        quad_offset = (0, -self.step_length, 0)
    elif action == 5:
        quad_offset = (0, 0, -self.step_length)
    else:
        quad_offset = (0, 0, 0)
```
報酬は、クワッドが既知の電力線からどれだけ離れているかに関連して、クワッドが移動する速度の関数です。
```Python
def compute_reward(quad_state, quad_vel, collision_info):
    thresh_dist = 7
    beta = 1

    z = -10
    pts = [np.array([-0.55265, -31.9786, -19.0225]),np.array([48.59735, -63.3286, -60.07256]),np.array([193.5974, -55.0786, -46.32256]),np.array([369.2474, 35.32137, -62.5725]),np.array([541.3474, 143.6714, -32.07256]),]

    quad_pt = np.array(list((self.state["position"].x_val, self.state["position"].y_val,self.state["position"].z_val,)))

    if self.state["collision"]:
        reward = -100
    else:
        dist = 10000000
        for i in range(0, len(pts) - 1):
            dist = min(dist, np.linalg.norm(np.cross((quad_pt - pts[i]), (quad_pt - pts[i + 1]))) / np.linalg.norm(pts[i] - pts[i + 1]))

        if dist > thresh_dist:
            reward = -10
        else:
            reward_dist = math.exp(-beta * dist) - 0.5
            reward_speed = (np.linalg.norm([self.state["velocity"].x_val, self.state["velocity"].y_val, self.state["velocity"].z_val,])- 0.5)
            reward = reward_dist + reward_speed
```
エピソードが既知の電力線座標から離れすぎた場合、エピソードは終了すると見なし、ドローンをその開始点にリセットします。

ジム スタイルの環境ラッパーが Drone_env.py のように定義されると、stable-baselines3 を使用して DQN トレーニング ループを実行します。 DQN トレーニングは、dqn_drone.py にある次のように構成できます。
```Python
model = DQN(
    "CnnPolicy",
    env,
    learning_rate=0.00025,
    verbose=1,
    batch_size=32,
    train_freq=4,
    target_update_interval=10000,
    learning_starts=10000,
    buffer_size=500000,
    max_grad_norm=10,
    exploration_fraction=0.1,
    exploration_final_eps=0.01,
    device="cuda",
    tensorboard_log="./tb_logs/",
)
```
トレーニング環境と評価環境 (dqn_drone.py の EvalCallback を参照) を定義できます。評価環境は、異なる終了条件/シーン構成で、トレーニングとは異なる場合があります。テンソルボード ログ ディレクトリも、DQN パラメーターの一部として定義されます。最後に、model.learn() が DQN トレーニング ループを開始します。同様に、PPO、A3C などの実装は、stable-baselines3 から使用できます。

これは、トレーニング中の最初のいくつかのエピソードのビデオです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/uKm15Y3M1Nk" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" loading="lazy" allowfullscreen></iframe>

# 関連
Microsoft Deep Learning and Robotics Garage による[自動運転クックブック](https://github.com/Microsoft/AutonomousDrivingCookbook)も参照してください。