LoginSignup
2
1

More than 1 year has passed since last update.

ランド研究所の「機械学習による航空支配」を実装する(その16): 2D問題 mission_3

Last updated at Posted at 2021-06-27

1. はじめに

本記事は、ランド研究所の「機械学習による航空支配」を実装する(その16)です。

Air Dominance Through Machine Learning: A Preliminary Exploration of Artificial Intelligence–Assisted Mission Planning, 2020

今回使用したコードは、下記 GitHub の 'mission_3' フォルダです。強化学習アルゴリズムは、SAC を用いました。

https://github.com/DreamMaker-Ai/AirDominance_2D_RL

強化学習アルゴリズムは、SAC を用いました。

2. Mission 3

ミッション3は、Fighter 3 機を使って、SAM 1 基を攻撃するミッションです。ただし、SAM の射程よりも長いウェポンを搭載している Fighter は 3 機の内 1 機のみとします。したがって、プランナー(エージェント)は、適切な 1 機を 3 機の中から選んで、SAM を攻撃するプランを生成しなければなりません。

SAM の射程よりも長いウェポンを搭載している Fighter は、下記コードで、エピソード毎に3機の内からランダムに設定します。

def condition_f1(self, fighter_1, fighter_2, fighter_3, sam):
    sam_firing_range_list = np.arange(sam.min_firing_range,
                                      sam.max_firing_range + self.resolution, self.resolution)
    sam_firing_range_list = sam_firing_range_list[1:-1]
    sam.firing_range = np.random.choice(sam_firing_range_list)

    long_weapon_fighter = np.random.choice(['fighter_1', 'fighter_2', 'fighter_3'])
    if long_weapon_fighter == 'fighter_1':
        fighter_1.long_weapon = 1
        fighter_2.long_weapon = 0
        fighter_3.long_weapon = 0
    elif long_weapon_fighter == 'fighter_2':
        fighter_1.long_weapon = 0
        fighter_2.long_weapon = 1
        fighter_3.long_weapon = 0
    elif long_weapon_fighter == 'fighter_3':
        fighter_1.long_weapon = 0
        fighter_2.long_weapon = 0
        fighter_3.long_weapon = 1

    condition_1 = self.condition_f1_fighter(self.fighter_1, sam)
    condition_2 = self.condition_f1_fighter(self.fighter_2, sam)
    condition_3 = self.condition_f1_fighter(self.fighter_3, sam)
    condition = condition_1 or condition_2 or condition_3

    if not condition:
        raise Exception('Error in condition mission f1!')

3. 相対観測量とアクション

3.1 相対観測量

マルコフ決定過程(MDP: Markov Decision Process)を規定するためのミッション3の相対観測量は下図の距離(ベクトルのノルム)、速度(ベクトルのノルム)、角度で規定できます。全部で 37 観測量です。これにより、下図を慣性空間で並行移動しても回転移動しても、同じ観測量が観測されるので、絶対量で観測した場合に比べ、ずっと小さな観測空間で済みます。この結果、ネットワークサイズが小さくて済み、学習も早くなることが期待できます。

relative_geometry_mission_3.jpg

これは、表で書くと以下になります。プランナーは、この観測量に含まれている SAM の射程と3機の Fighter の射程情報をもとに適切なアクション(プラン)を生成することを学習します。

observation table mission 3.jpg

コードでは以下の通りです。

def get_relative_observation_mission_3(self):
    obs = []
    r = (self.space_x ** 2 + self.space_y ** 2) ** 0.5

    v_1 = self.make_v(self.fighter_1)
    r_1s = self.make_r(self.sam_1, self.fighter_1)
    sin_1, cos_1 = self.compute_heading_error(v_1, r_1s)
    obs.append(sin_1)
    obs.append(cos_1)
    obs.append(np.linalg.norm(r_1s, ord=2) / r)

    v_2 = self.make_v(self.fighter_2)
    r_2s = self.make_r(self.sam_1, self.fighter_2)
    sin_2, cos_2 = self.compute_heading_error(v_2, r_2s)
    obs.append(sin_2)
    obs.append(cos_2)
    obs.append(np.linalg.norm(r_2s, ord=2) / r)

    v_3 = self.make_v(self.fighter_3)
    r_3s = self.make_r(self.sam_1, self.fighter_3)
    sin_3, cos_3 = self.compute_heading_error(v_3, r_3s)
    obs.append(sin_3)
    obs.append(cos_3)
    obs.append(np.linalg.norm(r_3s, ord=2) / r)

    r_12 = self.make_r(self.fighter_2, self.fighter_1)
    r_13 = self.make_r(self.fighter_3, self.fighter_1)
    r_23 = self.make_r(self.fighter_3, self.fighter_2)

    sin_phai_12, cos_phai_12 = self.compute_heading_error(v_1, r_12)
    sin_phai_13, cos_phai_13 = self.compute_heading_error(v_1, r_13)
    sin_phai_21, cos_phai_21 = self.compute_heading_error(v_2, r_12)
    sin_phai_23, cos_phai_23 = self.compute_heading_error(v_2, r_23)
    sin_phai_31, cos_phai_31 = self.compute_heading_error(v_3, r_13)
    sin_phai_32, cos_phai_32 = self.compute_heading_error(v_3, r_23)
    obs.append(sin_phai_12)
    obs.append(cos_phai_12)
    obs.append(sin_phai_13)
    obs.append(cos_phai_13)
    obs.append(sin_phai_21)
    obs.append(cos_phai_21)
    obs.append(sin_phai_23)
    obs.append(cos_phai_23)
    obs.append(sin_phai_31)
    obs.append(cos_phai_31)
    obs.append(sin_phai_32)
    obs.append(cos_phai_32)
    obs.append(np.linalg.norm(r_12, ord=2) / r)
    obs.append(np.linalg.norm(r_13, ord=2) / r)
    obs.append(np.linalg.norm(r_23, ord=2) / r)

    obs.append(self.fighter_1.firing_range / self.fighter_1.max_firing_range)
    obs.append(self.fighter_1.weapon_count)
    obs.append(self.fighter_1.alive)

    obs.append(self.fighter_2.firing_range / self.fighter_2.max_firing_range)
    obs.append(self.fighter_2.weapon_count)
    obs.append(self.fighter_2.alive)

    obs.append(self.fighter_3.firing_range / self.fighter_3.max_firing_range)
    obs.append(self.fighter_3.weapon_count)
    obs.append(self.fighter_3.alive)

    obs.append(self.sam_1.firing_range / self.sam_1.max_firing_range)
    obs.append(self.sam_1.jammed_firing_range /
               (self.sam_1.max_firing_range * self.jammer_1.jam_effectiveness))
    obs.append(self.sam_1.weapon_count)
    obs.append(self.sam_1.alive)

    return obs

観測量が多くなってきたので、RLLIB の default ネットワーク(unit数 256, 256 の2層 MLP)で大丈夫かと少し心配しましたが、問題はありませんでした。

3.2 アクション

アクションは、3機の Fighter のヘディングの1タイムステップでの変化量(の正規化値)ですので、コードでは以下となります。

self.fighter_1.action = actions[0] * self.fighter_1.max_heading_change_step
self.fighter_2.action = actions[1] * self.fighter_2.max_heading_change_step
self.fighter_3.action = actions[2] * self.fighter_3.max_heading_change_step

4. 学習条件

エピソードの初期状態として、SAM を戦場中心 (50, 50) km に配置し、その周りの円環上に、SAM に対してヘディングエラーを持った 3 機の Fighter をランダムに配置します。具体的には、以下の設定でプランナーの学習を行いました。

  • Fighters initial position: SAM から半径 [40, 50] km の円環上
  • Fighters initial heading error: SAM に対し [-20, 20] deg のずれ

コードでは、下記になります。

    def set_initial_condition_fighter(self, fighter):
        r = 40 + 10 * np.random.rand()  # default
        # r = 40 + 50 * np.random.rand()

        c = np.random.rand()
        if c < .5:
            theta = - np.pi * np.random.rand()
            fighter.x = 50 + r * np.sin(theta)
            fighter.y = 50 + r * np.cos(theta)
            fighter.heading = - (-np.pi - theta)
        else:
            theta = np.pi * np.random.rand()
            fighter.x = 50 + r * np.sin(theta)
            fighter.y = 50 + r * np.cos(theta)
            fighter.heading = - (np.pi - theta)

        heading_error = 20 * np.pi / 180 * (np.random.rand() - .5) * 2  # default
        # heading_error = 180 * np.pi / 180 * (np.random.rand() - .5) * 2
        fighter.heading += heading_error
        fighter.heading_sin = np.sin(fighter.heading)
        fighter.heading_cos = np.cos(fighter.heading)

5. 学習履歴

この学習は割と簡単らしく、収束は早いです。

episode reward mean_trial.jpg

学習が進むのに合わせて、エピソード長も短くなります。

episode length mean_trail.jpg

ランダムに生成した初期条件の下で、1000エピソードで成功率を計算したのが下図です。安定して100%近い成功率を達成できています。

learning_history.png

6. 学習結果と汎化能力

初期位置が、学習時の円環から外側に離れていくと、未経験の初期状態になるので成功率が低くなって行きます。下図に示すように、この低下が思ったよりも大きかったのですが、原因は、(後出の生成プラン例に示しますが)、SAM の攻撃に使わない2機が SAM から離反する動きをするため、攻撃機が SAM に到達する前に、SAM の攻撃に使わない2機が戦場エリアを逸脱し、エピソード終了となってしまうエピソードが増えるためでした。射程の短い Fighter を SAM の攻撃に使うようなミスは僅かでした(後出の生成プラン例に示すように、ゼロではありませんでした)。したがって、戦場をもっと広くとってシミュレーション時間を確保すれば成功率はもっと高くなる(つまり、実際の成功率はもっと高い)はずですが、ここでは学習時と同じ値に設定しています。

Mission 3_ Robustness to initial position of Fighters.png

Mission 3_ Robustness to initial heading error.png

6.1 Nominal Condition での生成プラン例

以下の動画では、3つの青い丸がFighterを、その周りの薄い青い円が搭載ウェポンの射程を表します。また、赤い丸がSAM、その周りの薄い赤い円がSAMの射程を表します。

6.1.1 Success

典型的なプランは、3機の Fighter の内、SAM の射程よりも長いウェポンを搭載した Fighter 1機だけを SAM に対して進出させ、残りの二機は SAM から離隔したところで待機させるプランです。

{
  "mission_id": "mission_3",
  "mission_condition": "f1",
  "result": "success",
  "fighter_1": {
    "alive": 1,
    "firing_range": 15.5
  },
  "fighter_2": {
    "alive": 1,
    "firing_range": 18.5
  },
  "fighter_3": {
    "alive": 1,
    "firing_range": 7.5
  },
  "sam_1": {
    "alive": 0,
    "firing_range": 16.5
  }
}

sac_success_1.gif

{
  "mission_id": "mission_3",
  "mission_condition": "f1",
  "result": "success",
  "fighter_1": {
    "alive": 1,
    "firing_range": 16.5
  },
  "fighter_2": {
    "alive": 1,
    "firing_range": 7.5
  },
  "fighter_3": {
    "alive": 1,
    "firing_range": 8.0
  },
  "sam_1": {
    "alive": 0,
    "firing_range": 12.0
  }
}

sac_success_19.gif

SAM を攻撃できるウェポンを搭載した Fighter と近い位置に別の Fighter がいるような初期条件では、最初は両機を SAM に接近させますが、最終的に正しい Fighter のみを進出させるプランを生成することがありました。

{
  "mission_id": "mission_3",
  "mission_condition": "f1",
  "result": "success",
  "fighter_1": {
    "alive": 1,
    "firing_range": 12.0
  },
  "fighter_2": {
    "alive": 1,
    "firing_range": 10.0
  },
  "fighter_3": {
    "alive": 1,
    "firing_range": 20.0
  },
  "sam_1": {
    "alive": 0,
    "firing_range": 19.0
  }
}

sac_success_11.gif

6.1.2 Fail

典型的な失敗プランの例です。SAM を攻撃できるウェポンを搭載した Fighter が、SAM に接近する前に、他の Fighter が戦域を離脱して、エピソード終了となっています。SAM を攻撃できるウェポンを搭載した Fighter は、SAM に近づいて行っている途中なので、戦域がもっと広ければ、このプランは成功した可能性があります。

{
  "mission_id": "mission_3",
  "mission_condition": "f1",
  "result": "fail",
  "fighter_1": {
    "alive": 1,
    "firing_range": 19.5
  },
  "fighter_2": {
    "alive": 1,
    "firing_range": 20.5
  },
  "fighter_3": {
    "alive": 1,
    "firing_range": 21.0
  },
  "sam_1": {
    "alive": 1,
    "firing_range": 20.5
  }
}

sac_fail_9.gif

6.2 Robustness to Initial Position and heading error of Fighters の生成プラン例

Fighter 3機の初期位置を、学習時の [40,50] km から外挿方向の [60, 90] km、ヘディングエラーを [-20, 20] deg から [-180, 180] deg にした時の生成プランの例を示します。

以下の動画では、3つの青い丸が Fighter を、その周りの薄い青い円が搭載ウェポンの射程を表します。また、赤い丸が SAM、その周りの薄い赤い円が SAM の射程を表します。

6.2.1 Success

{
  "mission_id": "mission_3",
  "mission_condition": "f1",
  "result": "success",
  "fighter_1": {
    "alive": 1,
    "firing_range": 19.5
  },
  "fighter_2": {
    "alive": 1,
    "firing_range": 11.5
  },
  "fighter_3": {
    "alive": 1,
    "firing_range": 9.5
  },
  "sam_1": {
    "alive": 0,
    "firing_range": 11.5
  }
}

sac_success_2.gif

{
  "mission_id": "mission_3",
  "mission_condition": "f1",
  "result": "success",
  "fighter_1": {
    "alive": 1,
    "firing_range": 9.5
  },
  "fighter_2": {
    "alive": 1,
    "firing_range": 21.0
  },
  "fighter_3": {
    "alive": 1,
    "firing_range": 8.0
  },
  "sam_1": {
    "alive": 0,
    "firing_range": 18.5
  }
}

sac_success_24.gif

{
  "mission_id": "mission_3",
  "mission_condition": "f1",
  "result": "success",
  "fighter_1": {
    "alive": 1,
    "firing_range": 12.5
  },
  "fighter_2": {
    "alive": 1,
    "firing_range": 10.5
  },
  "fighter_3": {
    "alive": 1,
    "firing_range": 20.5
  },
  "sam_1": {
    "alive": 0,
    "firing_range": 19.0
  }
}

sac_success_39.gif

6.2.2 Fail

これは、失敗というよりも、SAM の攻撃に使わない Fighter が戦場離脱したために、ミッション途中でエピソードが終了してしまい、結果として、ミッション失敗に分類されている例です。

{
  "mission_id": "mission_3",
  "mission_condition": "f1",
  "result": "fail",
  "fighter_1": {
    "alive": 1,
    "firing_range": 17.0
  },
  "fighter_2": {
    "alive": 1,
    "firing_range": 7.5
  },
  "fighter_3": {
    "alive": 1,
    "firing_range": 21.0
  },
  "sam_1": {
    "alive": 1,
    "firing_range": 19.5
  }
}

sac_fail_9.gif

これは、珍しく、誤った Fighter を選択して SAM に近づけてしまった失敗例です。初期条件が、学習時の条件を外挿する方向の場合、このような失敗がわずかに発生するようになります。

{
  "mission_id": "mission_3",
  "mission_condition": "f1",
  "result": "fail",
  "fighter_1": {
    "alive": 1,
    "firing_range": 11.5
  },
  "fighter_2": {
    "alive": 1,
    "firing_range": 20.0
  },
  "fighter_3": {
    "alive": 0,
    "firing_range": 8.5
  },
  "sam_1": {
    "alive": 1,
    "firing_range": 16.5
  }
}

sac_fail_21.gif

7. まとめ

複数の Fighter から適切な Fighter を選択して SAM を攻撃するプランナーで学習できました。

(その17)へ続く

(その17)では、最後のミッションとして、デコイを使うミッション・プランナーを強化学習します。

過去記事へのリンク

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1