atari - 模倣学習② 人間のデモを使って事前学習を行う

前回収集した人間のデモ操作データを使って事前学習を行います。

環境設定に関しては、前回の記事(模倣学習② 人間のデモを使って事前学習を行う)を参照して下さい。

(Ubuntu 19.10で動作確認しています。)

模倣学習

人間のデモ操作データであるbowling_demo.npzファイルrecorded_imagesフォルダを使って模倣学習を行うコードは以下の通りです。

[コード]

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
import gym
import time
from stable_baselines import PPO2
from stable_baselines.common.vec_env import DummyVecEnv
from stable_baselines.gail import ExpertDataset, generate_expert_traj
from baselines.common.atari_wrappers import *

# 環境の生成
env = gym.make('BowlingNoFrameskip-v0')
env = MaxAndSkipEnv(env, skip=4) # 4フレームごとに行動を選択
env = WarpFrame(env) # 画面イメージを84x84のグレースケールに変換
env = DummyVecEnv([lambda: env])

# デモデータの読み込み
dataset = ExpertDataset(expert_path='bowling_demo.npz',verbose=1)

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

# モデルの読み込み
# model = PPO2.load('bowling_model', env=env)

# モデルの事前訓練
model.pretrain(dataset, n_epochs=1000)

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

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

# モデルのテスト
state = env.reset()
total_reward = 0
while True:
env.render() # 環境の描画
time.sleep(1/60) # スリープ
action, _ = model.predict(state) # モデルの推論
state, reward, done, info = env.step(action) # 1ステップ実行
total_reward += reward[0]
if done:
print('reward:', total_reward)
state = env.reset()
total_reward = 0

人間の操作したデータを事前学習するにはmodel.pretrain関数(24行目)を使います。
引数の意味は下記の通りです。

  • dataset(ExpertDataset型)
    データセット
  • n_epochs(int型)
    学習の反復回数
  • learning_rate(float型)
    学習率
  • adam_epsilon(float型)
    Adamオプティマイザーのε(エプシロン)
  • val_interval(int型)
    nエポック毎に学習と検証の損失を出力

また模倣学習を行った後、さらに強化学習を行う場合はmodel.learn関数(27行目)をコメントアウトします。

実行

実行すると、スコアは「120.2」になりました。(人間のデモ操作によって結果は変わります。)

模倣学習と合わせて強化学習も合わせて実行した結果や、強化学習のみで実行した結果も調査していきたいと思います。

atari - 模倣学習① 人間のデモ収集

ランダム行動では報酬を見つけにくい環境に対応するために模倣学習を試してみます。

Atari環境の1つであるボーリングゲーム(Bowling)を実行環境とします。

(Windowsではうまく動作しなかったので、Ubuntu 19.10で動作確認しています。)

インストール

下記のコマンドを実行し、実行環境をインストールします。

1
2
3
4
5
6
pip3 install gym
apt install cmake libopenmpi-dev python3-dev zlib1g-dev
pip3 install stable_baselines[mpi]
pip3 install tensorflow==1.14.0
pip3 install imageio
pip3 install baselines

人間のデモ収集

人間のデモ収集を行うコードは下記になります。

[コード]

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
import random
import pyglet
import gym
import time
from pyglet.window import key
from stable_baselines.gail import generate_expert_traj
from baselines.common.atari_wrappers import *

# 環境を作成
env = gym.make('BowlingNoFrameskip-v0')
env = MaxAndSkipEnv(env, skip=4) # 4フレームごとに行動を選択
env = WarpFrame(env) # 画面イメージを84x84のグレースケールに変換
env.render()

# キーイベント用のウィンドウ作成
win = pyglet.window.Window(width=300, height=100, vsync=False)
key_handler = pyglet.window.key.KeyStateHandler()
win.push_handlers(key_handler)
pyglet.app.platform_event_loop.start()

# キー状態の取得
def get_key_state():
key_state = set()
win.dispatch_events()
for key_code, pressed in key_handler.items():
if pressed:
key_state.add(key_code)
return key_state

# キー入力待ち
while len(get_key_state()) == 0:
time.sleep(1.0/30.0)

# 人間のデモを収集するコールバック
def human_expert(_state):
key_state = get_key_state() # キー状態の取得
action = 0 # 行動の選択

if key.SPACE in key_state:
action = 1
elif key.UP in key_state:
action = 2
elif key.DOWN in key_state:
action = 3

time.sleep(1.0/30.0) # スリープ
env.render() # 環境の描画
return action # 行動の選択

# 人間のデモの収集
generate_expert_traj(human_expert, 'bowling_demo', env, n_episodes=1)

デモ収集にはgenerate_expert_trajを使います。引数の意味は下記の通りです。

  • model(モデルまたはコールバック型)
    モデルまたはコールバック
  • save_path(str型)
    保存先のデモファイルのパス(拡張子なし)
  • env(gym.Env型)
    環境
  • n_timesteps(int型)
    モデルの学習ステップ数
  • n_episodes(int型)
    記録するエピソード数
  • image_folder(str型)
    画像を使用する場合の保存フォルダ

返値はデモ demo(dict型)となります。

実行

実行すると、次のような画面が表示されます。右側のウィンドウにフォーカスをあてるとゲームを操作することができます。

実行結果

updownで位置を選択し、fireでボールを投げます。
ボールを投げた後にupdownでボールの起動を曲げることができます。

10ゲーム(1エピソード)の人間の操作が収集され、bowling_demo.npzファイルrecorded_imagesフォルダが出力されます。

  • bowling_demo.npzファイル
    Pythonの辞書形式で保存されます。
    キーとしてactionsepisode_returnsrewardsobsepisode_startsがあり、obsには画像への相対パスが格納されます。
  • recorded_imagesフォルダ
    各状態の画像が保存されます。

次回は、今回収集した人間のデモデータを使って事前学習を行います。

gym-duckietown - 強化学習環境の紹介④

Duckietownは、MIT(マサチューセッツ工科大学)発の、ロボット工学とAIを学ぶためのプラットフォームです。

自動車(Duckiebot)、道路、信号、標識、障害物、輸送を必要とする人(Duckies)、そしてそれらが住む都市(Duckietown)で構成されています。

Duckietown - https://www.duckietown.org/


gym-duckietownは、Duckietownのシミュレータ環境になります。

duckietown/gym-duckietown - https://github.com/duckietown/gym-duckietown

Gym Retro - 利用できるゲーム一覧

Gym Retro環境で利用できるゲームの一覧を取得します。

Gym Retroで利用できるゲーム一覧

下記のコードを準備します。

[コード]

1
2
3
import retro
for i, game in enumerate(retro.data.list_games()):
print(i, game, retro.data.list_states(game))

実行すると下記のようにゲーム一覧が表示されます。

[実行結果]

0 1942-Nes ['1Player.Level1']
1 1943-Nes ['Level1']
2 3NinjasKickBack-Genesis ['1Player.Colt.Level1']
3 8Eyes-Nes ['1Player.Arabia.Level1']
4 AaahhRealMonsters-Genesis ['Level1']
5 AbadoxTheDeadlyInnerWar-Nes ['Level1']
6 AcceleBrid-Snes ['SilverMarine.DefaultSettings.Level1']
7 ActRaiser2-Snes ['Level1']
8 ActionPachio-Snes ['Level1']
9 AddamsFamily-GameBoy ['Level1']
(途中略)
985 ZombiesAteMyNeighbors-Snes ['Start']
986 ZoolNinjaOfTheNthDimension-Genesis ['Level1']
987 ZoolNinjaOfTheNthDimension-Sms ['Level1']
988 ZoolNinjaOfTheNthDimension-Snes ['Level1']

989種類のゲームが利用できることが確認できます。

ここで表示されるのは「ゲームインテグレーション」と呼ばれ、「開始状態」「報酬」「エピソード完了」という情報が定義されているゲームになります。

Gym Retro - シューティングゲームを実行する② 学習編

前回はシューティングゲームをランダムで実行しましたが、今回は強化学習アルゴリズムを実装してゲームを攻略してみます。

baselinesインストール

baselinesがインストールされていない場合は、下記コマンドを実行してインストールしておきます。

1
2
3
git clone https://github.com/openai/baselines.git
cd baselines
pip install -e .

Airstrikerを学習して実行

Airstrikerを学習して実行するコードは次のようになります。

  • コールバック
    10更新ごとにベストモデルの保存を行います。
  • Airstrikerラッパー
    行動パターンを4096通りから3通りに減らして学習効率を上げています。
  • CustomRewardAndDoneラッパー
    報酬を20で割って範囲を「0~1.0」に近づけます。報酬は「0~1.0」の範囲に収めると、学習が安定する傾向があります。

[コード]

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
import retro
import time
import os
import gym
import numpy as np
import datetime
import pytz
from stable_baselines.results_plotter import load_results, ts2xy
from stable_baselines.common.vec_env import DummyVecEnv
from stable_baselines import PPO2
from stable_baselines.common import set_global_seeds
from stable_baselines.bench import Monitor
from baselines.common.retro_wrappers import *
from util import log_dir, callback, AirstrikerDiscretizer, CustomRewardAndDoneEnv

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

# コールバック
best_mean_reward = -np.inf
nupdates = 1
def callback(_locals, _globals):
global nupdates
global best_mean_reward

# 10更新毎
if (nupdates + 1) % 10 == 0:
# 平均エピソード長、平均報酬の取得
x, y = ts2xy(load_results(log_dir), 'timesteps')
if len(y) > 0:
# 直近10件の平均報酬
mean_reward = np.mean(y[-10:])

# 平均報酬がベスト報酬以上の時はモデルを保存
update_model = mean_reward > best_mean_reward
if update_model:
best_mean_reward = mean_reward
_locals['self'].save('airstriker_model')

# ログ
print('time: {}, nupdates: {}, mean: {:.2f}, best_mean: {:.2f}, model_update: {}'.format(
datetime.datetime.now(pytz.timezone('Asia/Tokyo')),
nupdates, mean_reward, best_mean_reward, update_model))
nupdates += 1
return True

# Airstrikerラッパー
class AirstrikerDiscretizer(gym.ActionWrapper):
# 初期化
def __init__(self, env):
super(AirstrikerDiscretizer, self).__init__(env)
buttons = ['B', 'A', 'MODE', 'START', 'UP', 'DOWN', 'LEFT', 'RIGHT', 'C', 'Y', 'X', 'Z']
actions = [['LEFT'], ['RIGHT'], ['B']]
self._actions = []
for action in actions:
arr = np.array([False] * 12)
for button in action:
arr[buttons.index(button)] = True
self._actions.append(arr)
self.action_space = gym.spaces.Discrete(len(self._actions))
# 行動の取得
def action(self, a):
return self._actions[a].copy()

# CustomRewardAndDoneラッパー
class CustomRewardAndDoneEnv(gym.Wrapper):
# 初期化
def __init__(self, env):
super(CustomRewardAndDoneEnv, self).__init__(env)
# ステップ
def step(self, action):
state, rew, done, info = self.env.step(action)
# 報酬の変更
rew /= 20
# エピソード完了の変更
if info['gameover'] == 1:
done = True
return state, rew, done, info

# 環境の生成
env = retro.make(game='Airstriker-Genesis', state='Level1')
env = AirstrikerDiscretizer(env) # 行動空間を離散空間に変換
env = CustomRewardAndDoneEnv(env) # 報酬とエピソード完了の変更
env = StochasticFrameSkip(env, n=4, stickprob=0.25) # スティッキーフレームスキップ
env = Downsample(env, 2) # ダウンサンプリング
env = Rgb2gray(env) # グレースケール
env = FrameStack(env, 4) # フレームスタック
env = ScaledFloatFrame(env) # 状態の正規化
env = Monitor(env, log_dir, allow_early_resets=True)
print('行動空間: ', env.action_space)
print('状態空間: ', env.observation_space)

# シードの指定
env.seed(0)
set_global_seeds(0)

# ベクトル化環境の作成
env = DummyVecEnv([lambda: env])

# モデルの作成
model = PPO2('CnnPolicy', env, verbose=0)

# モデルの読み込み
#model = PPO2.load('airstriker_model', env=env, verbose=0)

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

# モデルのテスト
state = env.reset()
total_reward = 0
while True:
# 環境の描画
env.render()
# スリープ
time.sleep(1/60)
# モデルの推論
action, _ = model.predict(state)
# 1ステップ実行
state, reward, done, info = env.step(action)
total_reward += reward[0]
# エピソード完了
if done:
print('reward:', total_reward)
state = env.reset()
total_reward = 0

上記コードを実行すると最初に学習処理が実行されます。(私の環境ではGPUなしで60分以上かかりました。)

それが完了されると下記のようなウィンドウが表示され、学習したモデルでゲームが実行されます。

実行結果

ランダム行動よりまともにゲームができているようです。