カスタムGym環境作成(17) - 広げたマップを強化学習で攻略

今回は、広げたマップを強化学習で攻略していきます。

[広くしたマップイメージ]

広げたマップマップを強化学習

前々回に実装したカスタムGym環境(env7.py)を9行目で読み込み、強化学習を行います。

学習アルゴリズムはACKTR(25~32行目)で、学習率は0.01から50.0に段階的に変更して結果がどのように変わるか確認していきます。

学習ステップ数は128000(35行目)としています。

[ソース]

train7.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
# 警告を非表示
import os
import warnings
warnings.simplefilter('ignore')
import tensorflow as tf
tf.get_logger().setLevel("ERROR")

import gym
from env7 import MyEnv

from stable_baselines.common.vec_env import DummyVecEnv
from stable_baselines import ACKTR
from stable_baselines.bench import Monitor

# ログフォルダの作成
log_dir = './logs/'
os.makedirs(log_dir, exist_ok=True)

# 環境の生成
env = MyEnv()
env = Monitor(env, log_dir, allow_early_resets=True)
env = DummyVecEnv([lambda: env])

# モデルの生成
model = ACKTR('MlpPolicy', env, verbose=1, learning_rate=0.01)
#model = ACKTR('MlpPolicy', env, verbose=1, learning_rate=0.05)
#model = ACKTR('MlpPolicy', env, verbose=1, learning_rate=0.1)
#model = ACKTR('MlpPolicy', env, verbose=1, learning_rate=0.5)
#model = ACKTR('MlpPolicy', env, verbose=1, learning_rate=1.0)
#model = ACKTR('MlpPolicy', env, verbose=1, learning_rate=5.0)
#model = ACKTR('MlpPolicy', env, verbose=1, learning_rate=10.0)
#model = ACKTR('MlpPolicy', env, verbose=1, learning_rate=50.0)

# モデルの学習
model.learn(total_timesteps=128000)

# モデルの保存
model.save('model7')

# モデルのテスト
state = env.reset()
total_reward = 0
while True:
# 環境の描画
env.render()

# モデルの推論
action, _ = model.predict(state)

# 1ステップの実行
state, reward, done, info = env.step(action)
total_reward += reward
print('reward:', reward, 'total_reward', total_reward)
print('-----------')

print('')
# エピソード完了
if done:
# 環境の描画
print('total_reward:', total_reward)
break

各実行結果をまとめると下記のようになりました。

[結果]

学習率 最終位置・最終報酬 平均報酬遷移
0.01
0.05
0.1
0.5
1.0
5.0
10.0
50.0

全然ゴールできていません。

ランダムで実行していたときは、半分ほどゴールまでたどり着いていたのでランダム実行よりはるかに劣る結果ということになります😥

スタート地点付近からほとんど動かないことが多く、学習率の調整以前の問題だと考えられます。

ということで次回はカスタム環境の報酬設定を見直していきたいと思います。

カスタムGym環境作成(16) - 広げたマップをランダム実行

前回記事で、マップを拡張し林を追加しました。

[広くしたマップイメージ]

今回は、そのマップに対してランダムで実行しています。

広げたマップをランダム実行

ランダム実行するソースは下記の通りです。

2行目でインポートするカスタムGym環境をenv7にしているのが、唯一の変更箇所となります。

[ソース]

random7.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 env7 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 -4328
  • 2回目(ゴールに到着
    total_reward -675
  • 3回目(ゴールにたどり着けず)
    total_reward -4917
  • 4回目(ゴールに到着
    total_reward -4220
  • 5回目(ゴールに到着
    total_reward 1753
  • 6回目(ゴールにたどり着けず)
    total_reward -3657
  • 7回目(ゴールに到着
    total_reward 2255
  • 8回目(ゴールに到着
    total_reward 533
  • 9回目(ゴールにたどり着けず)
    total_reward -4570
  • 10回目(ゴールにたどり着けず)
    total_reward -3591

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

ランダム実行の割には10回の試行のうち5回ゴールにたどり着く結果になったのは意外でした。

試行回数の制限(env6.pyのMAX_STEPS)を2000回と多めに設定したのがその原因かと思います。


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

カスタムGym環境作成(15) - マップ更新(マップ範囲を広げる)

4つ目のマップとして、範囲を広くしてみました。

マップ範囲を広く

作成するマップイメージは次の通りです。

[広くしたマップイメージ]

まずこれまで8×8マスだったマップの定義を16×16マスに拡張しました。

さらに林を追加し、ここに移動すると平地よりも少し多めに疲れるように設定しました。(-3ポイント)

カスタムGym環境の変更点とソースは以下の通りです。

[変更点]

  • 17行目
    林の定義を追加。
  • 20~31行目
    マップ定義を8×8から16×16に拡張。
  • 104~106行目
    林に入った時の判定を追加し、報酬として-3ポイントを返す。

[ソース]

env7.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
123
124
125
126
127
128
129
130
131
132
133
# activate openai_gym
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: 川
'林', # 7: 林
]
MAP = np.array([
[0, 3, 3, 3, 2, 3, 7, 7, 3, 2, 2, 3],
[2, 2, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2],
[3, 2, 3, 2, 3, 7, 7, 2, 2, 2, 2, 3],
[3, 2, 2, 2, 7, 7, 6, 6, 3, 3, 2, 2],
[7, 7, 3, 2, 6, 6, 6, 3, 7, 7, 3, 3],
[3, 2, 3, 2, 3, 7, 3, 1, 2, 7, 2, 2],
[3, 7, 2, 2, 3, 2, 3, 3, 3, 3, 2, 7],
[2, 2, 3, 2, 2, 3, 2, 3, 2, 3, 3, 2],
[7, 3, 2, 7, 7, 2, 2, 2, 7, 6, 6, 7],
[2, 3, 2, 6, 6, 6, 2, 3, 2, 7, 2, 2],
[3, 3, 2, 3, 2, 3, 6, 2, 3, 2, 2, 3],
[7, 2, 2, 2, 3, 3, 6, 2, 7, 3, 2, 2],
])
MAX_STEPS = 2000

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.trees = 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):
# 報酬を返す。
# - ゴールにたどり着くと 3000 ポイント
# - 川に入ったら -100 ポイント
# - 林に入ったら -3 ポイント
# - 1ステップごとに-1ポイント(できるだけ短いステップでゴールにたどり着きたい)
if moved:
if (self.goal == pos).all():
return 3000
for x in self.river:
if (x == pos).all():
return -10
for x in self.trees:
if (x == pos).all():
return -3
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環境に対して、ランダム実行を行ってみます。

カスタムGym環境作成(14) - 学習率と報酬の調整

前回記事で山超えのコースを学習・攻略しようとしましたが、うまく学習できませんでした。

[橋を渡るコースを山でふさいだマップイメージ]

今回は、学習率と報酬を調整してマップ攻略を目指します。

学習率と報酬の調整

学習率と報酬をいろいろと変更してみて、最終的には下記のように設定し攻略することができました。

  • 学習率を1に設定(train6.py 25行目のlearning_rateオプションで設定)
  • ゴール時の報酬を2000に変更(env6.py 92行目を修正)

[ソース]

train6.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
# 警告を非表示
import os
import warnings
warnings.simplefilter('ignore')
import tensorflow as tf
tf.get_logger().setLevel("ERROR")

import gym
from env6 import MyEnv

from stable_baselines.common.vec_env import DummyVecEnv
from stable_baselines import ACKTR
from stable_baselines.bench import Monitor

# ログフォルダの作成
log_dir = './logs/'
os.makedirs(log_dir, exist_ok=True)

# 環境の生成
env = MyEnv()
env = Monitor(env, log_dir, allow_early_resets=True)
env = DummyVecEnv([lambda: env])

# モデルの生成
model = ACKTR('MlpPolicy', env, verbose=1, learning_rate=1)

# モデルの学習
model.learn(total_timesteps=128000)

# モデルの保存
model.save('model6')

# モデルのテスト
state = env.reset()
total_reward = 0
while True:
# 環境の描画
env.render()

# モデルの推論
action, _ = model.predict(state)

# 1ステップの実行
state, reward, done, info = env.step(action)
total_reward += reward
print('reward:', reward, 'total_reward', total_reward)
print('-----------')

print('')
# エピソード完了
if done:
# 環境の描画
print('total_reward:', total_reward)
break

実行結果(ログ)は以下のようになります。

[実行結果]

---------------------------------
| explained_variance | 2.03e-06 |
| fps                | 31       |
| nupdates           | 1        |
| policy_entropy     | 1.39     |
| policy_loss        | -406     |
| total_timesteps    | 20       |
| value_loss         | 1.37e+05 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -322     |
| explained_variance | 0        |
| fps                | 948      |
| nupdates           | 100      |
| policy_entropy     | 0.0541   |
| policy_loss        | -0.00449 |
| total_timesteps    | 2000     |
| value_loss         | 0.218    |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -258     |
| explained_variance | 0.00392  |
| fps                | 1099     |
| nupdates           | 200      |
| policy_entropy     | 0.388    |
| policy_loss        | -0.0641  |
| total_timesteps    | 4000     |
| value_loss         | 0.0191   |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -269     |
| explained_variance | -0.0365  |
| fps                | 1158     |
| nupdates           | 300      |
| policy_entropy     | 1.35     |
| policy_loss        | -0.052   |
| total_timesteps    | 6000     |
| value_loss         | 0.002    |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -409     |
| explained_variance | -4.18    |
| fps                | 1188     |
| nupdates           | 400      |
| policy_entropy     | 1.35     |
| policy_loss        | -0.00553 |
| total_timesteps    | 8000     |
| value_loss         | 5.43e-05 |
---------------------------------
----------------------------------
| ep_len_mean        | 201       |
| ep_reward_mean     | -466      |
| explained_variance | -0.000672 |
| fps                | 1207      |
| nupdates           | 500       |
| policy_entropy     | 1.32      |
| policy_loss        | -55.1     |
| total_timesteps    | 10000     |
| value_loss         | 3.66e+03  |
----------------------------------
----------------------------------
| ep_len_mean        | 201       |
| ep_reward_mean     | -498      |
| explained_variance | -3.39e+04 |
| fps                | 1219      |
| nupdates           | 600       |
| policy_entropy     | 1.31      |
| policy_loss        | -0.00111  |
| total_timesteps    | 12000     |
| value_loss         | 0.00047   |
----------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -512     |
| explained_variance | -37      |
| fps                | 1228     |
| nupdates           | 700      |
| policy_entropy     | 1.29     |
| policy_loss        | -0.0169  |
| total_timesteps    | 14000    |
| value_loss         | 3.5e-05  |
---------------------------------
----------------------------------
| ep_len_mean        | 201       |
| ep_reward_mean     | -512      |
| explained_variance | -1.11e-05 |
| fps                | 1235      |
| nupdates           | 800       |
| policy_entropy     | 1.28      |
| policy_loss        | -57.6     |
| total_timesteps    | 16000     |
| value_loss         | 3.66e+03  |
----------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -482     |
| explained_variance | -7       |
| fps                | 1240     |
| nupdates           | 900      |
| policy_entropy     | 1.36     |
| policy_loss        | -0.0157  |
| total_timesteps    | 18000    |
| value_loss         | 5.22e-06 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -476     |
| explained_variance | -4.79    |
| fps                | 1244     |
| nupdates           | 1000     |
| policy_entropy     | 1.32     |
| policy_loss        | -0.0135  |
| total_timesteps    | 20000    |
| value_loss         | 1.72e-06 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -490     |
| explained_variance | -461     |
| fps                | 1246     |
| nupdates           | 1100     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0131  |
| total_timesteps    | 22000    |
| value_loss         | 1.42e-06 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -518     |
| explained_variance | -20.4    |
| fps                | 1248     |
| nupdates           | 1200     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0222  |
| total_timesteps    | 24000    |
| value_loss         | 6.51e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -531     |
| explained_variance | -22.4    |
| fps                | 1250     |
| nupdates           | 1300     |
| policy_entropy     | 1.37     |
| policy_loss        | -0.015   |
| total_timesteps    | 26000    |
| value_loss         | 2.2e-06  |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -480     |
| explained_variance | -0.00395 |
| fps                | 1251     |
| nupdates           | 1400     |
| policy_entropy     | 1.34     |
| policy_loss        | -79.9    |
| total_timesteps    | 28000    |
| value_loss         | 5.28e+03 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -450     |
| explained_variance | -3.2     |
| fps                | 1252     |
| nupdates           | 1500     |
| policy_entropy     | 1.3      |
| policy_loss        | -0.0117  |
| total_timesteps    | 30000    |
| value_loss         | 3.33e-06 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -426     |
| explained_variance | 0.413    |
| fps                | 1253     |
| nupdates           | 1600     |
| policy_entropy     | 1.35     |
| policy_loss        | -0.0134  |
| total_timesteps    | 32000    |
| value_loss         | 1.12e-06 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -392     |
| explained_variance | -0.00011 |
| fps                | 1254     |
| nupdates           | 1700     |
| policy_entropy     | 1.27     |
| policy_loss        | -22      |
| total_timesteps    | 34000    |
| value_loss         | 970      |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -377     |
| explained_variance | 1.26e-05 |
| fps                | 1255     |
| nupdates           | 1800     |
| policy_entropy     | 1.27     |
| policy_loss        | -124     |
| total_timesteps    | 36000    |
| value_loss         | 7.82e+03 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -377     |
| explained_variance | 0.000336 |
| fps                | 1256     |
| nupdates           | 1900     |
| policy_entropy     | 1.32     |
| policy_loss        | 59.4     |
| total_timesteps    | 38000    |
| value_loss         | 4.07e+03 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -364     |
| explained_variance | 0.000371 |
| fps                | 1257     |
| nupdates           | 2000     |
| policy_entropy     | 1.35     |
| policy_loss        | 116      |
| total_timesteps    | 40000    |
| value_loss         | 7.81e+03 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -359     |
| explained_variance | -1.94    |
| fps                | 1257     |
| nupdates           | 2100     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0157  |
| total_timesteps    | 42000    |
| value_loss         | 4.23e-06 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -336     |
| explained_variance | 0.494    |
| fps                | 1258     |
| nupdates           | 2200     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0175  |
| total_timesteps    | 44000    |
| value_loss         | 8.93e-06 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -313     |
| explained_variance | 0.071    |
| fps                | 1259     |
| nupdates           | 2300     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0205  |
| total_timesteps    | 46000    |
| value_loss         | 2.63e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -318     |
| explained_variance | -3.05    |
| fps                | 1259     |
| nupdates           | 2400     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0126  |
| total_timesteps    | 48000    |
| value_loss         | 1.01e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -308     |
| explained_variance | -0.223   |
| fps                | 1260     |
| nupdates           | 2500     |
| policy_entropy     | 1.37     |
| policy_loss        | -0.0161  |
| total_timesteps    | 50000    |
| value_loss         | 4.16e-06 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -304     |
| explained_variance | -0.795   |
| fps                | 1260     |
| nupdates           | 2600     |
| policy_entropy     | 1.37     |
| policy_loss        | -0.0182  |
| total_timesteps    | 52000    |
| value_loss         | 1.62e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -300     |
| explained_variance | -0.311   |
| fps                | 1261     |
| nupdates           | 2700     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0186  |
| total_timesteps    | 54000    |
| value_loss         | 1.6e-05  |
---------------------------------
---------------------------------
| ep_len_mean        | 199      |
| ep_reward_mean     | -264     |
| explained_variance | 0.0573   |
| fps                | 1261     |
| nupdates           | 2800     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0212  |
| total_timesteps    | 56000    |
| value_loss         | 3.45e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 199      |
| ep_reward_mean     | -226     |
| explained_variance | -3.36    |
| fps                | 1262     |
| nupdates           | 2900     |
| policy_entropy     | 1.36     |
| policy_loss        | -0.0107  |
| total_timesteps    | 58000    |
| value_loss         | 1.11e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 199      |
| ep_reward_mean     | -221     |
| explained_variance | -0.187   |
| fps                | 1262     |
| nupdates           | 3000     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0219  |
| total_timesteps    | 60000    |
| value_loss         | 4.85e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 199      |
| ep_reward_mean     | -215     |
| explained_variance | -5.68    |
| fps                | 1262     |
| nupdates           | 3100     |
| policy_entropy     | 1.36     |
| policy_loss        | -0.0262  |
| total_timesteps    | 62000    |
| value_loss         | 0.000103 |
---------------------------------
---------------------------------
| ep_len_mean        | 198      |
| ep_reward_mean     | -170     |
| explained_variance | 0.000507 |
| fps                | 1263     |
| nupdates           | 3200     |
| policy_entropy     | 1.38     |
| policy_loss        | 119      |
| total_timesteps    | 64000    |
| value_loss         | 7.81e+03 |
---------------------------------
---------------------------------
| ep_len_mean        | 198      |
| ep_reward_mean     | -175     |
| explained_variance | -0.506   |
| fps                | 1263     |
| nupdates           | 3300     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0174  |
| total_timesteps    | 66000    |
| value_loss         | 1.03e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 197      |
| ep_reward_mean     | -145     |
| explained_variance | 3.67e-05 |
| fps                | 1263     |
| nupdates           | 3400     |
| policy_entropy     | 1.38     |
| policy_loss        | 20.5     |
| total_timesteps    | 68000    |
| value_loss         | 1.44e+03 |
---------------------------------
---------------------------------
| ep_len_mean        | 196      |
| ep_reward_mean     | -114     |
| explained_variance | -1.71    |
| fps                | 1263     |
| nupdates           | 3500     |
| policy_entropy     | 1.37     |
| policy_loss        | -0.0166  |
| total_timesteps    | 70000    |
| value_loss         | 1.17e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 196      |
| ep_reward_mean     | -66.8    |
| explained_variance | -1.37    |
| fps                | 1263     |
| nupdates           | 3600     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0194  |
| total_timesteps    | 72000    |
| value_loss         | 2.63e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 195      |
| ep_reward_mean     | -11.9    |
| explained_variance | -3.93    |
| fps                | 1264     |
| nupdates           | 3700     |
| policy_entropy     | 1.37     |
| policy_loss        | -0.0467  |
| total_timesteps    | 74000    |
| value_loss         | 0.000802 |
---------------------------------
---------------------------------
| ep_len_mean        | 196      |
| ep_reward_mean     | 5.91     |
| explained_variance | -1.15    |
| fps                | 1264     |
| nupdates           | 3800     |
| policy_entropy     | 1.38     |
| policy_loss        | -0.0054  |
| total_timesteps    | 76000    |
| value_loss         | 6.35e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 193      |
| ep_reward_mean     | 73       |
| explained_variance | -1.33    |
| fps                | 1264     |
| nupdates           | 3900     |
| policy_entropy     | 1.36     |
| policy_loss        | -0.0216  |
| total_timesteps    | 78000    |
| value_loss         | 5.23e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 189      |
| ep_reward_mean     | 255      |
| explained_variance | -2.78    |
| fps                | 1264     |
| nupdates           | 4000     |
| policy_entropy     | 1.29     |
| policy_loss        | -0.0768  |
| total_timesteps    | 80000    |
| value_loss         | 0.00266  |
---------------------------------
---------------------------------
| ep_len_mean        | 189      |
| ep_reward_mean     | 278      |
| explained_variance | 0.157    |
| fps                | 1264     |
| nupdates           | 4100     |
| policy_entropy     | 1.37     |
| policy_loss        | -0.0131  |
| total_timesteps    | 82000    |
| value_loss         | 1.75e-05 |
---------------------------------
---------------------------------
| ep_len_mean        | 170      |
| ep_reward_mean     | 707      |
| explained_variance | 0.000953 |
| fps                | 1264     |
| nupdates           | 4200     |
| policy_entropy     | 0.94     |
| policy_loss        | 1.01e+03 |
| total_timesteps    | 84000    |
| value_loss         | 2.01e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 137      |
| ep_reward_mean     | 1.21e+03 |
| explained_variance | 0.471    |
| fps                | 1263     |
| nupdates           | 4300     |
| policy_entropy     | 0.83     |
| policy_loss        | -0.0847  |
| total_timesteps    | 86000    |
| value_loss         | 0.114    |
---------------------------------
---------------------------------
| ep_len_mean        | 68.7     |
| ep_reward_mean     | 1.86e+03 |
| explained_variance | 0.00681  |
| fps                | 1263     |
| nupdates           | 4400     |
| policy_entropy     | 0.517    |
| policy_loss        | 74.5     |
| total_timesteps    | 88000    |
| value_loss         | 1.03e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 23.5     |
| ep_reward_mean     | 1.97e+03 |
| explained_variance | 0.00852  |
| fps                | 1262     |
| nupdates           | 4500     |
| policy_entropy     | 0.211    |
| policy_loss        | 59.4     |
| total_timesteps    | 90000    |
| value_loss         | 3.15e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 21.2     |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0106   |
| fps                | 1261     |
| nupdates           | 4600     |
| policy_entropy     | 0.149    |
| policy_loss        | 20.5     |
| total_timesteps    | 92000    |
| value_loss         | 2.26e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 22.6     |
| ep_reward_mean     | 1.96e+03 |
| explained_variance | 0.0142   |
| fps                | 1260     |
| nupdates           | 4700     |
| policy_entropy     | 0.141    |
| policy_loss        | 399      |
| total_timesteps    | 94000    |
| value_loss         | 2.72e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 20.8     |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.011    |
| fps                | 1260     |
| nupdates           | 4800     |
| policy_entropy     | 0.104    |
| policy_loss        | 7.25     |
| total_timesteps    | 96000    |
| value_loss         | 1.78e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 20.4     |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0091   |
| fps                | 1260     |
| nupdates           | 4900     |
| policy_entropy     | 0.0783   |
| policy_loss        | 5.12     |
| total_timesteps    | 98000    |
| value_loss         | 1.99e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 20.4     |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.00713  |
| fps                | 1260     |
| nupdates           | 5000     |
| policy_entropy     | 0.115    |
| policy_loss        | -0.78    |
| total_timesteps    | 100000   |
| value_loss         | 1.28e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 20.4     |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0218   |
| fps                | 1259     |
| nupdates           | 5100     |
| policy_entropy     | 0.0424   |
| policy_loss        | 0.498    |
| total_timesteps    | 102000   |
| value_loss         | 1.57e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 20.1     |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.00849  |
| fps                | 1259     |
| nupdates           | 5200     |
| policy_entropy     | 0.0179   |
| policy_loss        | -0.0378  |
| total_timesteps    | 104000   |
| value_loss         | 1.72e+05 |
---------------------------------
---------------------------------
| ep_len_mean        | 20.1     |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0211   |
| fps                | 1259     |
| nupdates           | 5300     |
| policy_entropy     | 0.024    |
| policy_loss        | 0.205    |
| total_timesteps    | 106000   |
| value_loss         | 1.26e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 20.1     |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.039    |
| fps                | 1259     |
| nupdates           | 5400     |
| policy_entropy     | 0.0104   |
| policy_loss        | 0.972    |
| total_timesteps    | 108000   |
| value_loss         | 2.55e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 20       |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0334   |
| fps                | 1259     |
| nupdates           | 5500     |
| policy_entropy     | 0.00561  |
| policy_loss        | 0.349    |
| total_timesteps    | 110000   |
| value_loss         | 2.52e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 20       |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0375   |
| fps                | 1259     |
| nupdates           | 5600     |
| policy_entropy     | 0.0026   |
| policy_loss        | 0.306    |
| total_timesteps    | 112000   |
| value_loss         | 2.62e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 20       |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0361   |
| fps                | 1258     |
| nupdates           | 5700     |
| policy_entropy     | 0.00159  |
| policy_loss        | 0.145    |
| total_timesteps    | 114000   |
| value_loss         | 2.61e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 20       |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0308   |
| fps                | 1258     |
| nupdates           | 5800     |
| policy_entropy     | 0.00105  |
| policy_loss        | 0.089    |
| total_timesteps    | 116000   |
| value_loss         | 2.6e+06  |
---------------------------------
---------------------------------
| ep_len_mean        | 20       |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0279   |
| fps                | 1258     |
| nupdates           | 5900     |
| policy_entropy     | 0.000787 |
| policy_loss        | 0.0623   |
| total_timesteps    | 118000   |
| value_loss         | 2.59e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 21.8     |
| ep_reward_mean     | 1.96e+03 |
| explained_variance | -0.983   |
| fps                | 1258     |
| nupdates           | 6000     |
| policy_entropy     | 0.135    |
| policy_loss        | -8.94    |
| total_timesteps    | 120000   |
| value_loss         | 919      |
---------------------------------
---------------------------------
| ep_len_mean        | 36.1     |
| ep_reward_mean     | 1.8e+03  |
| explained_variance | 0.0285   |
| fps                | 1258     |
| nupdates           | 6100     |
| policy_entropy     | 0.202    |
| policy_loss        | 2.62     |
| total_timesteps    | 122000   |
| value_loss         | 8.08e+05 |
---------------------------------
---------------------------------
| ep_len_mean        | 21.1     |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0263   |
| fps                | 1258     |
| nupdates           | 6200     |
| policy_entropy     | 0.0413   |
| policy_loss        | 0.446    |
| total_timesteps    | 124000   |
| value_loss         | 9.49e+05 |
---------------------------------
---------------------------------
| ep_len_mean        | 20.3     |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0725   |
| fps                | 1257     |
| nupdates           | 6300     |
| policy_entropy     | 0.0795   |
| policy_loss        | 1.3      |
| total_timesteps    | 126000   |
| value_loss         | 2.49e+06 |
---------------------------------
---------------------------------
| ep_len_mean        | 20.6     |
| ep_reward_mean     | 1.98e+03 |
| explained_variance | 0.0398   |
| fps                | 1256     |
| nupdates           | 6400     |
| policy_entropy     | 0.0273   |
| policy_loss        | 1.16     |
| total_timesteps    | 128000   |
| value_loss         | 2.37e+06 |
---------------------------------
☆山山山   山
  川川山山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-1.]
-----------

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

☆山山山   山
  川川山山 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: [-1.] total_reward [-12.]
-----------

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

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

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

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

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

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

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

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

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

S山山山   山
  川川山山☆G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [2000.] total_reward [1979.]
-----------

total_reward: [1979.]

スタート時に一度出戻りしますが、その後はまっすぐゴールに向かっています。

平均報酬をグラフ化

確認のために学習中の平均報酬推移をグラフにします。

400エピソードを超えたあたりからゴール報酬の2000を取得できるようになっていることが分かります。

調整内容のまとめ

報酬に関しては、ゴールまでの距離が遠くなるほど大きめの報酬にしないといけないと感じました。

ゴールの報酬が、ゴール地点から波紋のように広がっていくイメージです。


また学習率はエージェントの大胆さを調整しているイメージでした。

学習率が低い場合は、臆病な感じで近くに障害(今回の場合は川)があるとそこに近づこうとはせずにスタート位置付近から移動しようとしません。

逆に学習率を高く設定した場合は、大胆にいろいろ行動するようになり、(川に飛び込んでしまうこともありますが)最終的にゴールにたどり着くことができるようになりました。

カスタムGym環境に合わせ報酬も学習率も適切に設定する必要性を強く感じました。

カスタムGym環境作成(13) - 川と橋があるけど山から遠回りしてほしい編

今回は、山道コースからゴールに向かうマップを攻略していきます。

[橋を渡るコースを山でふさいだマップイメージ]

ACKTRデフォルトで学習・実行

まずはこれまでと同じようにACKTRアルゴリズムをデフォルトの設定で学習・実行してみます。

[ソース]

train6.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
# 警告を非表示
import os
import warnings
warnings.simplefilter('ignore')
import tensorflow as tf
tf.get_logger().setLevel("ERROR")

import gym
from env6 import MyEnv

from stable_baselines.common.vec_env import DummyVecEnv
from stable_baselines import ACKTR
from stable_baselines.bench import Monitor

# ログフォルダの作成
log_dir = './logs/'
os.makedirs(log_dir, exist_ok=True)

# 環境の生成
env = MyEnv()
env = Monitor(env, log_dir, allow_early_resets=True)
env = DummyVecEnv([lambda: env])

# モデルの生成
model = ACKTR('MlpPolicy', env, verbose=1)

# モデルの学習
model.learn(total_timesteps=128000)

# モデルの保存
model.save('model6')

# モデルのテスト
state = env.reset()
total_reward = 0
while True:
# 環境の描画
env.render()

# モデルの推論
action, _ = model.predict(state)

# 1ステップの実行
state, reward, done, info = env.step(action)
total_reward += reward
print('reward:', reward, 'total_reward', total_reward)
print('-----------')

print('')
# エピソード完了
if done:
# 環境の描画
print('total_reward:', total_reward)
break

実行結果(ログ)は以下のようになります。

[実行結果(途中略)]

---------------------------------
| explained_variance | 0.00043  |
| fps                | 28       |
| nupdates           | 1        |
| policy_entropy     | 1.39     |
| policy_loss        | -13.6    |
| total_timesteps    | 20       |
| value_loss         | 123      |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -685     |
| explained_variance | 1.19e-07 |
| fps                | 922      |
| nupdates           | 100      |
| policy_entropy     | 0.0925   |
| policy_loss        | -2.3     |
| total_timesteps    | 2000     |
| value_loss         | 45.3     |
---------------------------------
         :
        (略)
         :
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -236     |
| explained_variance | 0        |
| fps                | 1321     |
| nupdates           | 6300     |
| policy_entropy     | 0.935    |
| policy_loss        | -0.536   |
| total_timesteps    | 126000   |
| value_loss         | 0.509    |
---------------------------------
---------------------------------
| ep_len_mean        | 201      |
| ep_reward_mean     | -232     |
| explained_variance | 0        |
| fps                | 1320     |
| nupdates           | 6400     |
| policy_entropy     | 0.9      |
| policy_loss        | -0.643   |
| total_timesteps    | 128000   |
| value_loss         | 0.522    |
---------------------------------
☆山山山   山
  川川山山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-1.]
-----------

☆山山山   山
  川川山山 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: [-1.] total_reward [-12.]
-----------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

S山山山   山
  川川山山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
☆ 山 山山 山
reward: [-1.] total_reward [-74.]
-----------
         :
        (略)
         :
S山山山   山
  川川山山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
☆ 山 山山 山
reward: [-1.] total_reward [-199.]
-----------

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

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

total_reward: [-201.]

まずスタート位置近くでもたつき、移動し始めたと思ったら左下の角に移動してその後まったく動かなくなってしまいました。

ゴールにたどり着けていないので学習はうまくいかなかったということになります。

平均報酬をグラフ化

平均報酬をグラフ化すると次のようになります。

マイナス報酬は少しずつ減っているようですが、最終的にプラス報酬には転じていません。

次回は下記の2点を調整して、マップ攻略を目指します。

  • 報酬を変更
  • 学習率を変更

カスタムGym環境作成(12) - 川と橋があるけど山から遠回りしてほしい編

前回は、川に入った時の報酬を変えて橋を渡るコースを通るようになりました。

ただ今度は橋を渡る近道コースではなく、山を回る遠回りのコースも通ってほしいと思いました。

そんな訳で下記のように、橋経由のコースを山でふさぎ、遠回りの山越コースを通るように修正していきます。

[橋を渡るコースを山でふさいだマップイメージ]

赤い枠のところが修正箇所となります。

橋を渡るコースを山でふさいだカスタムGym環境作成

まずカスタムGym環境を修正します。

修正箇所はマップのみで19行目の平地(2)の一部を山(3)に変更しています。

[ソース]

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, 3, 3, 2, 1],# 修正前 [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
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)))))

次回はこのマップをこれまでと同じようにACKTRアルゴリズムを使って学習・攻略してみます。

カスタム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.]

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

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

カスタムGym環境作成(10) - 川と橋のあるマップを強化学習で攻略

今回は、川と橋のあるマップを強化学習で攻略していきます。

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

川と橋と橋のあるマップを強化学習

前々回に実装したカスタムGym環境(env6.py)を9行目で読み込み、強化学習を行います。

学習アルゴリズムはACKTR(25行目)で、学習ステップ数は128000(28行目)としています。

[ソース]

train6.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
# 警告を非表示
import os
import warnings
warnings.simplefilter('ignore')
import tensorflow as tf
tf.get_logger().setLevel("ERROR")

import gym
from env6 import MyEnv

from stable_baselines.common.vec_env import DummyVecEnv
from stable_baselines import ACKTR
from stable_baselines.bench import Monitor

# ログフォルダの作成
log_dir = './logs/'
os.makedirs(log_dir, exist_ok=True)

# 環境の生成
env = MyEnv()
env = Monitor(env, log_dir, allow_early_resets=True)
env = DummyVecEnv([lambda: env])

# モデルの生成
model = ACKTR('MlpPolicy', env, verbose=1)

# モデルの学習
model.learn(total_timesteps=128000)

# モデルの保存
model.save('model6')

学習はなかなかうまくいきませんでした。

学習ステップ数を増やしたり、学習アルゴリズムをPPO2に戻したりしたのですが、それでもうまくゴールまでたどり着いてくれません😑

うまく学習できない場合の対処法

いろいろと学習方法を変更したのですがうまくいかなかったので、カスタムGym環境のほうを見直すことにしました。

報酬を見直して、ゴール時の報酬が少ないのかと思い100から800に変更してみました。

(前々回ソースenv6.pyの91行目がその報酬設定となります)

そうするとあっさりゴールまでたどり着くことができました😅

学習済みモデルを使って攻略

うまくゴールまでたどり着くことができるようになった学習済みモデルを使って、プレイしてみます。

[ソース]

play6.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
# 警告を非表示
import warnings
warnings.simplefilter('ignore')
import tensorflow as tf
tf.get_logger().setLevel("ERROR")

import gym
from env6 import MyEnv

from stable_baselines.common.vec_env import DummyVecEnv
from stable_baselines import ACKTR

# 環境の生成
env = MyEnv()
env = DummyVecEnv([lambda: env])

# モデルの読み込み
model = ACKTR.load('model6')

# モデルのテスト
state = env.reset()
total_reward = 0
while True:
# 環境の描画
env.render()

# モデルの推論
action, _ = model.predict(state)

# 1ステップの実行
state, reward, done, info = env.step(action)
total_reward += reward
print('reward:', reward, 'total_reward', total_reward)
print('-----------')

print('')
# エピソード完了
if done:
# 環境の描画
print('total_reward:', total_reward)
break

実行結果は以下のようになりました。

[結果]

Loading a model without an environment, this model cannot be trained until it has a valid environment.
☆山山山   山
  川川 山 G
山 三三 山 山
山 川川山   
  山  山山 
山 山 山 山 
山       
  山 山山 山
reward: [-1.] total_reward [-1.]
-----------

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

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

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

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

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

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

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

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

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

total_reward: [783.]

まっすぐゴールまで向かっていますが、なにか引っ掛かります。

そう、橋を渡らず川を突っ切っているのです😥

確かに距離的には川を渡った方が近いのかもしれませんが、マップを作った時の想定としては橋をわたって欲しかったのです。

報酬的には、川に入ると-5ポイント橋を渡ると-1ポイントなので、橋をわたる方が総報酬としは上になるはずなんですが・・・

次回は、川ではなくきちんと橋を渡ってくれるように調整を行いたいと思います。

(おそらく報酬設定を変えるだけで対応可能だと思うのですが・・・)

カスタムGym環境作成(9) - 川と橋のあるマップをランダム実行

前回記事で、マップに川と橋を追加しカスタムGym環境を実装しました。

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

今回は、そのマップに対してランダムで実行しています。

川と橋と橋のあるマップをランダム実行

ランダム実行するソースは下記の通りです。

2行目でインポートするカスタムGym環境をenv6にしているのが、唯一の変更箇所となります。

[ソース]

random6.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 env6 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 -150
  • 2回目
    total_reward -571
  • 3回目
    total_reward 30
  • 4回目
    total_reward -1139
  • 5回目
    total_reward -1867
  • 6回目
    total_reward -142
  • 7回目
    total_reward -46
  • 8回目
    total_reward -298
  • 9回目
    total_reward -1286
  • 10回目
    total_reward -441

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

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

カスタムGym環境作成(8) - マップ更新(川と橋を追加)

前回記事にて、2つ目のマップを攻略することができました。

今回は3つ目のマップとして川と橋を追加してみたいと思います。

マップに川と橋を追加

作成するマップイメージは次の通りです。

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

ゴールにたどり着くには、橋を渡っていくコースと山沿いの道をこえるコースを2つがあり、橋を渡るコースのほうが近道になっています。

川への移動に関しては、不可にしようと思ったんのですが、そうすると山超えのコースとの差別化ができないので、川に落ちたイメージで報酬を-5ポイントに設定してみました。

カスタムGym環境の変更点とソースは以下の通りです。

[変更点]

  • 14,15行目
    橋と川の定義を追加。
  • 92-94行目
    川に入った時の判定を追加し、報酬として-5ポイントを返す。

[ソース]

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
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

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 100
for x in self.river:
if (x == pos).all():
return -5
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環境に対して、ランダム実行を行ってみます。