カスタムGym環境作成(4) - マップを少し複雑に

前回までの記事で、簡単なマップに対して強化学習を行い効率のよいルートで攻略できるようになったことが分かりました。

今回はマップを少しだけ複雑にしてみて、それでも強化学習で攻略できるのかを確認したいと思います。

今回作成するマップイメージは下記のようなものです。

[マップイメージ]

センスのかけらもないマップですが、これをカスタムGym環境にしてみます。

カスタムGym環境作成

マップイメージのカスタムGym環境作成するには、今まで使用してきたenv3.pyの配列データMAPを修正するだけです。

また最大ステップ数(MAX_STEPS)を50から200に変更しました。

[ソース]

env4.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
import sys

import gym
import numpy as np
import gym.spaces

class MyEnv(gym.Env):
FIELD_TYPES = [
'S', # 0: スタート
'G', # 1: ゴール
' ', # 2: 平地
'山', # 3: 山(歩けない)
'☆', # 4: プレイヤー
]
MAP = np.array([
[0, 3, 3, 2, 3, 2, 3, 1],
[2, 3, 2, 2, 3, 2, 3, 2],
[2, 2, 2, 2, 3, 2, 2, 2],
[3, 2, 3, 3, 3, 2, 2, 3],
[3, 2, 3, 2, 2, 2, 2, 2],
[2, 2, 2, 2, 3, 2, 2, 3],
[3, 3, 2, 2, 3, 3, 2, 2],
[3, 3, 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.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

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 ポイント
# - 1ステップごとに-1ポイント(できるだけ短いステップでゴールにたどり着きたい)
if moved and (self.goal == pos).all():
return 100
else:
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)))))

ランダム実行

上記で作成したカスタム環境を読み込み、ランダムで実行してみます。

修正箇所は2行目の読み込むカスタム環境をenv3からenv4に変更した点のみとなります。

[ソース]

test4.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
import gym
from env4 import MyEnv

# 環境の生成
env = MyEnv()

# 環境リセット
state = env.reset()

total_reward = 0
while True:
# ランダム行動の取得
action = env.action_space.sample()
# 1ステップの実行
state, reward, done, info = env.step(action)
total_reward += reward
#print('state=', state, 'reward=', reward)
# 環境の描画
env.render()
print('reward:', reward, 'total_reward', total_reward)
print('-----------')
# エピソード完了
if done:
print('done')
break

ランダム実行のため毎回結果が異なります。
10回ほど実行した結果は下記のようになりました。

[結果]

  • 1回目
    total_reward -733
  • 2回目
    total_reward -1259
  • 3回目
    total_reward -1582
  • 4回目
    total_reward -2923
  • 5回目
    total_reward -479
  • 6回目
    total_reward -327
  • 7回目
    total_reward -785
  • 8回目
    total_reward -2065
  • 9回目
    total_reward -314
  • 10回目
    total_reward -235

トータル報酬は -2923から-235 の範囲となりました。

次回はStable BaselinesPPO2アルゴリズムを使って、強化学習を行ってみます。