カスタム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環境作成(7) - ちょっと複雑なマップをACKTRで完全攻略

前回ACKTRアルゴリズムで学習モデルを作成しました。

今回はそのモデルを読み込んで、ちょっとだけ複雑にしたマップを攻略できるかどうか確認します。

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

ACKTRアルゴリズムで学習したモデルを読み込んで、カスタム環境にて実行させます。

[ソース]

play5.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 env4 import MyEnv

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

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

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

# モデルのテスト
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: [-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: [100.] total_reward [84.]
-----------

total_reward: [84.]

まっすぐにスタート地点からゴールまで進んでいることが分かります。

少しだけ複雑にしたマップでもきちんと学習できるようになりました。改善したは次の2点です。

  • 学習済みアルゴリズムを変更
    PPO2からACKTRに学習アルゴリズムを変更しました。
  • 学習ステップ数を5倍に変更
    学習ステップ数を128000から128000*5に変更しました。

カスタム環境を変えて、うまく学習できなくなった場合はこのような変更を行って試行錯誤することが必要になるようです。

また、まったく同じ条件で学習してもうまく学習できる場合とそうでない場合があります。

何度か実行してみて平均報酬平均エピソード長などをグラフ化しきちんと収束(学習)できているかどうかを確認する必要もあります。

AI全般そうかと思いますが、強化学習に関する作業もなかなか地道なものが多いですね😌

カスタムGym環境作成(6) - ちょっと複雑なマップをACKTRで学習・攻略

前回記事にてカスタムGym環境として、ちょっと複雑なマップをPPO(PPO2)アルゴリズムを使って、強化学習してみましたがうまく攻略できませんでした。

今回は学習アルゴリズムをACKTRに変えて、攻略できるかどうかを見ていきたいと思います。

(ACKTRは、以前学習アルゴリズムをいろいろ試していたときにかなり優秀だと感じていたアルゴリズムです。)

ACKTRで強化学習

カスタム環境を読み込み、ACKTRアルゴリズムで学習を行います。

変更箇所は下記の3点です。

  • 12,13行目
    読み込むアルゴリズムをPPO2からACKTRに変更
  • 26,27行目
    使用するアルゴリズムをPPO2からACKTRに変更
  • 39行目
    ステップ実行回数を5倍に増やす。

[ソース]

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

import gym
from env4 import MyEnv

from stable_baselines.common.vec_env import DummyVecEnv
#from stable_baselines import PPO2
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 = PPO2('MlpPolicy', env, verbose=1)
model = ACKTR('MlpPolicy', env, verbose=1)

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

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

[実行結果(途中略)]

---------------------------------
| explained_variance | -0.0363  |(誤差の分散)
| fps                | 12       |(1秒あたりのフレーム数)
| nupdates           | 1        |(更新回数)
| policy_entropy     | 1.39     |(方策のエントロピー)
| policy_loss        | -13.5    |(方策の損失)
| total_timesteps    | 20       |(全環境でのタイムステップ数)
| value_loss         | 122      |(価値関数更新時の平均損失)
---------------------------------
---------------------------------
| ep_len_mean        | 969      |
| ep_reward_mean     | -868     |
| explained_variance | 9.41e-05 |
| fps                | 379      |
| nupdates           | 100      |
| policy_entropy     | 1.28     |
| policy_loss        | -9.35    |
| total_timesteps    | 2000     |
| value_loss         | 51.2     |
---------------------------------
---------------------------------
| ep_len_mean        | 969      |
| ep_reward_mean     | -868     |
| explained_variance | 1.19e-07 |
| fps                | 445      |
| nupdates           | 200      |
| policy_entropy     | 0.499    |
| policy_loss        | -5.09    |
| total_timesteps    | 4000     |
| value_loss         | 44.8     |
---------------------------------
         :
        (略)
         :
----------------------------------
| ep_len_mean        | 17        |
| ep_reward_mean     | 84        |
| explained_variance | 0         |
| fps                | 534       |
| nupdates           | 31800     |
| policy_entropy     | 0.000239  |
| policy_loss        | -0.000114 |
| total_timesteps    | 636000    |
| value_loss         | 42.5      |
----------------------------------
----------------------------------
| ep_len_mean        | 17        |
| ep_reward_mean     | 84        |
| explained_variance | 0         |
| fps                | 534       |
| nupdates           | 31900     |
| policy_entropy     | 0.000267  |
| policy_loss        | -0.000112 |
| total_timesteps    | 638000    |
| value_loss         | 32.2      |
----------------------------------
----------------------------------
| ep_len_mean        | 17        |←平均エピソード長
| ep_reward_mean     | 84        |←平均報酬
| explained_variance | 0         |
| fps                | 534       |
| nupdates           | 32000     |
| policy_entropy     | 0.000279  |
| policy_loss        | -0.000116 |
| total_timesteps    | 640000    |
| value_loss         | 31.2      |
----------------------------------

今回は最終的な平均報酬がプラスになっていて、平均エピソード長も最初に比べて少なく(短く)なっているので、きちんと学習(攻略)できているように思えます。

平均報酬をグラフ化して確認

学習したモデルを実行する前にmonitor.csvを読み込み、グラフ化して平均報酬の遷移を確認します。
(前回ソースlog_graph.pyと全く同じものです。)

[ソース]

log_graph.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd
import matplotlib.pyplot as plt

# ログファイルの読み込み(報酬,エピソード,経過時間)
df = pd.read_csv('./logs/monitor.csv', names=['r', 'l', 't'])
df = df.drop(range(2)) # 先頭2行を排除

# 報酬グラフの表示
x = range(len(df['r']))
y = df['r'].astype(float)
plt.plot(x, y)
plt.xlabel('episode')
plt.ylabel('reward')
plt.show()

[結果]

マイナス報酬が大きいため判断しにくいのですが、エピソード数が多く、最終的にはマイナスに大きく振れることがないのでうまく学習できているような気がします。

log/monitor.csvを確認すると次のようになっていました。

[monitor.csv(途中略)]

#{"t_start": 1622147186.477496, "env_id": null}
r,l,t
-4,105,6.601282
-1732,1833,10.039687
-7852,7953,24.873165
-5720,5821,35.86051
-4527,4628,44.683237
-27310,27411,96.671992
-2342,2443,101.065658
-305,406,101.811046
         :
        (略)
         :
84,17,1181.376593
84,17,1181.400596
84,17,1181.441152
84,17,1181.466216
84,17,1181.499782
84,17,1181.531546
84,17,1181.566779
84,17,1181.595796

最初はマイナス報酬ばかりでしたが、最終的にはプラス報酬(84)となり、まっすぐゴールに向かっているようです。

次回は、この学習したモデルを使ってきちんと攻略できているかどうかを確認します。


Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×