#1. はじめに
本記事は、ランド研究所の「機械学習による航空支配」を実装する(その16)です。
今回使用したコードは、下記 GitHub の 'mission_3' フォルダです。強化学習アルゴリズムは、SAC を用いました。
強化学習アルゴリズムは、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 観測量です。これにより、下図を慣性空間で並行移動しても回転移動しても、同じ観測量が観測されるので、絶対量で観測した場合に比べ、ずっと小さな観測空間で済みます。この結果、ネットワークサイズが小さくて済み、学習も早くなることが期待できます。
これは、表で書くと以下になります。プランナーは、この観測量に含まれている SAM の射程と3機の Fighter の射程情報をもとに適切なアクション(プラン)を生成することを学習します。
コードでは以下の通りです。
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. 学習履歴
この学習は割と簡単らしく、収束は早いです。
学習が進むのに合わせて、エピソード長も短くなります。
ランダムに生成した初期条件の下で、1000エピソードで成功率を計算したのが下図です。安定して100%近い成功率を達成できています。
#6. 学習結果と汎化能力
初期位置が、学習時の円環から外側に離れていくと、未経験の初期状態になるので成功率が低くなって行きます。下図に示すように、この低下が思ったよりも大きかったのですが、原因は、(後出の生成プラン例に示しますが)、SAM の攻撃に使わない2機が SAM から離反する動きをするため、攻撃機が SAM に到達する前に、SAM の攻撃に使わない2機が戦場エリアを逸脱し、エピソード終了となってしまうエピソードが増えるためでした。射程の短い Fighter を SAM の攻撃に使うようなミスは僅かでした(後出の生成プラン例に示すように、ゼロではありませんでした)。したがって、戦場をもっと広くとってシミュレーション時間を確保すれば成功率はもっと高くなる(つまり、実際の成功率はもっと高い)はずですが、ここでは学習時と同じ値に設定しています。
##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
}
}
{
"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
}
}
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
}
}
###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
}
}
##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
}
}
{
"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
}
}
{
"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
}
}
###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
}
}
これは、珍しく、誤った 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
}
}
#7. まとめ
複数の Fighter から適切な Fighter を選択して SAM を攻撃するプランナーで学習できました。
#(その17)へ続く
(その17)では、最後のミッションとして、デコイを使うミッション・プランナーを強化学習します。
過去記事へのリンク
- ランド研究所の「機械学習による航空支配」を実装する(その1):レポートのまとめ
- ランド研究所の「機械学習による航空支配」を実装する(その2):1次元問題について
- ランド研究所の「機械学習による航空支配」を実装する(その3): 1D simulator for GAN と Random mission planner の実装)
- ランド研究所の「機械学習による航空支配」を実装する(その4): conditional GAN の実装とトレーニング
- ランド研究所の「機械学習による航空支配」を実装する(その5):トレーニング結果の分析
- ランド研究所の「機械学習による航空支配」を実装する(その6):トレーニング・データの重要性と GAN の性能向上
- ランド研究所の「機械学習による航空支配」を実装する(その7):1D simulator for RL の実装
- ランド研究所の「機械学習による航空支配」を実装する(その8): Stable Baselines による強化学習
- ランド研究所の「機械学習による航空支配」を実装する(その9): 少し複雑な環境
- ランド研究所の「機械学習による航空支配」を実装する(その10):GAN / 強化学習プランナーの連携を考える
- ランド研究所の「機械学習による航空支配」を実装する(その11): 2次元問題の概要
- ランド研究所の「機械学習による航空支配」を実装する(その12): 2D simulator for mission_1 の実装
- ランド研究所の「機械学習による航空支配」を実装する(その13): 2D 問題 mission_1 を強化学習する
- ランド研究所の「機械学習による航空支配」を実装する(その14): Relative Observation による mission planner の能力アップ
- ランド研究所の「機械学習による航空支配」を実装する(その15): 2D問題 mission_2