Gym Retroにバーチャファイター2を追加して学習する
『Gym Retroにバーチャファイター2を追加する』に続きです。Gym Retroにバーチャファイター2を学習します。難易度は「EASY」でROUND 1からクリアを目指します。
1. バーチャファイター2の学習コード
バーチャファイター2を学習するコードは次の通りです。
import retro
import os
import time
from stable_baselines import PPO2
from stable_baselines.common.policies import CnnPolicy
from stable_baselines.common.vec_env import DummyVecEnv
from baselines.common.retro_wrappers import *
from stable_baselines.bench import Monitor
from util import Vf2Discretizer, CustomRewardAndDoneEnv, callback, log_dir
from stable_baselines.common import set_global_seeds
# 環境の生成
env = retro.make(game='VirtuaFighter2-Genesis', state='VirtuaFighter2.Level1.AkiraVsLau.state')
env = Vf2Discretizer(env) # 行動空間を離散空間に変換
env = StochasticFrameSkip(env, n=4, stickprob=0.25) # スティッキーフレームスキップ
env = Downsample(env, 2) # ダウンサンプリング
env = Rgb2gray(env) # グレースケール
env = FrameStack(env, 4) # フレームスタック
env = ScaledFloatFrame(env) # 状態の正規化
env = CustomRewardAndDoneEnv(env) # カスタム報酬関数・完了条件
env = Monitor(env, log_dir, allow_early_resets=True)
print('状態空間: ', env.observation_space)
print('行動空間: ', env.action_space)
# シードの指定
env.seed(0)
set_global_seeds(0)
# ベクトル環境の生成
env = DummyVecEnv([lambda: env])
# モデルの生成
model = PPO2(policy=CnnPolicy, env=env, verbose=0, learning_rate=0.0000025)
# モデルの読み込み
# model = PPO2.load('./vf2/best_model.pkl', env=env, verbose=0)
# モデルの学習
print('train...')
model.learn(total_timesteps=20000000, callback=callback)
# モデルのテスト
'''
print('test...')
state = env.reset()
while True:
env.render()
time.sleep(1.0/60.0)
action, _ = model.predict(state)
state, reward, done, info = env.step(action)
if done:
env.reset()
'''
import gym
import os
import numpy as np
import datetime
import pytz
from stable_baselines.results_plotter import load_results, ts2xy
# 定数
HEALTH_GAUGE = 287840.0
WIN_COUNT = 2
ROUND_START = 1
ROUND_END = 9
# ログフォルダの生成
log_dir = './logs/'
os.makedirs(log_dir, exist_ok=True)
# 行動空間の変換
class Vf2Discretizer(gym.ActionWrapper):
# 初期化
def __init__(self, env):
super(Vf2Discretizer, self).__init__(env)
buttons = ["B", "A", "MODE", "START", "UP", "DOWN", "LEFT", "RIGHT", "C", "Y", "X", "Z"]
actions = [['B'], ['C'], ['A'],
['DOWN', 'B'], ['DOWN', 'C'], ['DOWN', 'A'],
['LEFT'], ['DOWN'], ['RIGHT'],
['UP', 'LEFT'], ['UP'], ['UP', 'RIGHT'],
['A', 'B'],
['UP', '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)
self.round = ROUND_START
self.win_count1 = 0
self.win_count2 = 0
# リセット
def reset(self, **kwargs):
self.round = ROUND_START
self.win_count1 = 0
self.win_count2 = 0
return self.env.reset(**kwargs)
# ステップ
def step(self, action):
obs, rew, done, info = self.env.step(action)
# ラウンド内の勝利数の初期化
if info['win_count1'] == 0 and info['win_count2'] == 0:
self.win_count1 = 0
self.win_count2 = 0
# 勝利
if info['win_count1'] > self.win_count1:
self.win_count1 = info['win_count1']
rew = 1.0 + info['health_gauge1']/HEALTH_GAUGE
if self.win_count1 == WIN_COUNT:
if self.round == ROUND_END:
done = True
else:
self.round += 1
# 敗北
if info['win_count2'] > self.win_count2:
self.win_count2 = info['win_count2']
rew = -info['health_gauge2']/HEALTH_GAUGE
if self.win_count2 == WIN_COUNT:
done = True
if rew != 0 or done:
print('round, rew, done>',self.round, rew, done)
# スケールの調整
return obs, rew, done, info
# コールバック
best_mean_reward = -np.inf
nupdates = 1
def callback(_locals, _globals):
global nupdates
global best_mean_reward
# print('callback:', nupdates)
# 10更新毎
if nupdates > 100 and (nupdates + 1) % 10 == 0:
# 平均エピソード長、平均報酬の取得
x, y = ts2xy(load_results(log_dir), 'timesteps')
if len(x) > 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(log_dir + 'best_model.pkl')
# ログ
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
2. 前処理
今回は以下の「前処理」を追加しています。
・Vf2Discretizer : 行動空間の変更
・StochasticFrameSkip : フレームスキップ
・Downsample : ダウンサンプリング
・Rgb2gray : グレースケール
・FrameStack : フレームスタック
・ScaledFloatFrame : 状態の正規化
3. 完了条件
今回は「完了条件」を次のようにしました。
・ラウンド中の相手に2本取られた時。
・DUALに勝利した時。
4. 報酬関数
今回は「報酬関数」を次のようにしました。
相手に勝利すると0.5*2=1、ノーダメージでクリアすると0.1のボーナス加算になります。
・1本取った時
rew = 0.5 + info['health_gauge1']/HEALTH_GAUGE
・1本取られた時
rew = -info['health_gauge2']/HEALTH_GAUGE
5. 学習パラメータ
平均報酬が安定しなかったので、学習率を小さくしました。
# モデルの生成
model = PPO2(policy=CnnPolicy, env=env, verbose=0, learning_rate=0.0000025)
6. ROUND 1からの学習の実行
ROUND 1から学習を実行してみました。
1時間程度で平均報酬6.18まで上がりましたが、それ以降は上がりませんでした。ROUND 6のJackyに負けてるようです。
2019-09-22 21:39:51.017439+09:00, nupdates: 109, mean: 3.77, best_mean: 3.77, model_update: True
:
2019-09-22 22:54:46.817440+09:00, nupdates: 3479, mean: 5.18, best_mean: 5.18, model_update: True
7. Jackyを倒す訓練の実行
Jackyを倒す訓練のため、ROUND6から学習を実行してみました。stateファイルを変更し、ROUND_STARTを6とします。
2時間程度で平均報酬が1.48まで上がり、Jackyに勝てるようになりました。
time: 2019-09-23 10:52:23.198121+09:00, nupdates: 109, mean: 0.83, best_mean: 0.83, model_update: True
:
time: 2019-09-23 13:06:33.692358+09:00, nupdates: 9069, mean: 1.48, best_mean: 2.48, model_update: True
8. Duralを倒す訓練の実行
Duralを倒す訓練のため、ROUND 9から学習を実行してみました。stateファイルを変更し、ROUND_STARTを9とします。
2時間程度で平均報酬が0.85まで上がり、Duralにまぁまぁ勝てるようになりました。
time: 2019-09-23 15:30:07.500548+09:00, nupdates: 109, mean: 0.35, best_mean: 0.35, model_update: True
:
time: 2019-09-23 17:53:02.688691+09:00, nupdates: 7699, mean: 0.85, best_mean: 0.85, model_update: True
9. 今日の結果
できれば1つのモデルで全ラウンドを攻略したいが今の所できていない。
簡単な面を攻略してる間に、後ろの面の攻略を忘れてしまう?
各対戦相手と対戦する確率を等しくしたら効果はある?
層を増やすなどパラメータ調整はまだいろいろありそう。
ひとまず、1ラウンドごとであれば攻略したので今日は終了。
この記事が気に入ったらサポートをしてみませんか?