カスタムGym環境作成(11) - 川と橋のあるマップを強化学習で攻略(橋を渡ってほしい編)

前回記事にて、川と橋のあるマップをそれなりに攻略できたのですが、橋を渡ってくれず川をつっきる最短距離のルートとなってしまいました。

[川と橋を追加したマップイメージ]

今回は橋を渡るようにカスタムGym環境を修正していきます。

橋を渡るように修正

やはりまず思いつくのが、川に入った時の報酬のマイナスポイントを変化させることです。

-5に設定している報酬を少しずつ変更して最終的に-100としました。(95行目)

[ソース]

env6.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import sys

import gym
import numpy as np
import gym.spaces

class MyEnv(gym.Env):
FIELD_TYPES = [
'S', # 0: スタート
'G', # 1: ゴール
' ', # 2: 平地
'山', # 3: 山(歩けない)
'☆', # 4: プレイヤー
'三', # 5: 橋
'川', # 6: 川
]
MAP = np.array([
[0, 3, 3, 3, 2, 2, 2, 3],
[2, 2, 6, 6, 2, 3, 2, 1],
[3, 2, 5, 5, 2, 3, 2, 3],
[3, 2, 6, 6, 3, 2, 2, 2],
[2, 2, 3, 2, 2, 3, 3, 2],
[3, 2, 3, 2, 3, 2, 3, 2],
[3, 2, 2, 2, 2, 2, 2, 2],
[2, 2, 3, 2, 3, 3, 2, 3]
])
MAX_STEPS = 200

def __init__(self):
super().__init__()
# action_space, observation_space, reward_range を設定する
self.action_space = gym.spaces.Discrete(4) # 上下左右
self.observation_space = gym.spaces.Box(
low=0,
high=len(self.FIELD_TYPES),
shape=self.MAP.shape
)
self.reset()

def reset(self):
# 諸々の変数を初期化する
self.pos = self._find_pos('S')[0]
self.goal = self._find_pos('G')[0]
self.river = self._find_pos('川')
self.done = False
self.steps = 0
return self._observe()

def step(self, action):
# 1ステップ進める処理を記述。戻り値は observation, reward, done(ゲーム終了したか), info(追加の情報の辞書)
# 左上の座標を(0, 0)とする
if action == 0: # 右移動
next_pos = self.pos + [0, 1]
elif action == 1: # 左移動
next_pos = self.pos + [0, -1]
elif action == 2: # 下移動
next_pos = self.pos + [1, 0]
elif action == 3: # 上移動
next_pos = self.pos + [-1, 0]

if self._is_movable(next_pos):
self.pos = next_pos
moved = True
else:
moved = False

self.steps += 1
observation = self._observe()
reward = self._get_reward(self.pos, moved)
self.done = self._is_done()
return observation, reward, self.done, {}

def render(self, mode='console', close=False):
for row in self._observe():
for elem in row:
print(self.FIELD_TYPES[elem], end='')
print()

def _close(self):
pass

def _seed(self, seed=None):
pass

def _get_reward(self, pos, moved):
# 報酬を返す。
# - ゴールにたどり着くと 100 ポイント
# - 川に入ったら -5 ポイント
# - 1ステップごとに-1ポイント(できるだけ短いステップでゴールにたどり着きたい)
if moved:
if (self.goal == pos).all():
return 800
for x in self.river:
if (x == pos).all():
return -100 # -5 ⇒ -20 ⇒ -50 ⇒ -100に変更
return -1

def _is_movable(self, pos):
# マップの中にいるか、歩けない場所にいないか
return (
0 <= pos[0] < self.MAP.shape[0]
and 0 <= pos[1] < self.MAP.shape[1]
and self.FIELD_TYPES[self.MAP[tuple(pos)]] != '山'
)

def _observe(self):
# マップにプレイヤーの位置を重ねて返す
observation = self.MAP.copy()
observation[tuple(self.pos)] = self.FIELD_TYPES.index('☆')
return observation

def _is_done(self):
# 最大で self.MAX_STEPS まで
if (self.pos == self.goal).all():
return True
elif self.steps > self.MAX_STEPS:
return True
else:
return False

def _find_pos(self, field_type):
return np.array(list(zip(*np.where(self.MAP == self.FIELD_TYPES.index(field_type)))))

このカスタムGym環境を読み込んで、これまでと同じように学習を行います。
(前回記事のtrain6.pyを実行)

結果は次のようになりました。

[結果]

☆山山山   山
  川川 山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-1.]
-----------

S山山山   山
☆ 川川 山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-2.]
-----------

S山山山   山
 ☆川川 山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-3.]
-----------

S山山山   山
  川川 山 G
山☆三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-4.]
-----------

S山山山   山
  川川 山 G
山 ☆三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-5.]
-----------

S山山山   山
  川川 山 G
山 三☆ 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-6.]
-----------

S山山山   山
  川川 山 G
山 三三☆山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-7.]
-----------

S山山山   山
  川川☆山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-8.]
-----------

S山山山☆  山
  川川 山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-9.]
-----------

S山山山 ☆ 山
  川川 山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-10.]
-----------

S山山山  ☆山
  川川 山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-11.]
-----------

S山山山   山
  川川 山☆G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [800.] total_reward [789.]
-----------

total_reward: [789.]

思った通りに橋を渡ってゴールにまっすぐたどり着いています。

やはり強化学習は報酬の与え方がとても重要だという事を実感しました。