強化学習でスーパーマリオ1-1をクリアする

f:id:zakopilo:20210130172417p:plain
人間誰しも一度はAIを使ってスーパーマリオをクリアしたいと思ったことがあるかと思います。
本記事では、難しいことを考えずとりあえず便利なツールを使ってAIにスーパーマリオ1-1をクリアさせたいと思います。
スーパーマリオのROMを自分で用意する必要もありませんし、難しいアルゴリズムも理解する必要がありません。
「とにかく動かしたい!」という人向けです。

前提

pythonを書ける。
・OpenAI Gymを知っている。

実行環境を作る

利用言語:python3.6
必要なライブラリ
・gym
・stable_baselines
・gym_super_mario_bros
・tensorflow==1.14.0(stable_baselinesが2.Xに対応してないようです。)
・pyqt5
・imageio

Open AI Gymのインストール方法
gym.openai.com

stable_baselinesのインストール方法
github.com

gym_super_mario_brosのインストール方法
github.com

tensorflow/pyqt5/imageioのインストール方法

$ pip install tensorflow==1.14.0
$ pip install pyqt5  
$ pip install imageio

Stable Baselinesとは

Stable Baselineは、Open AIが提供する「OpenAI Baselines」の改良版です。
OpenAI Baselinesとは強化学習アルゴリズムの実装セットライブラリで、難しいアルゴリズムを簡単にゲームに応用することができます。
Stable BaselineとOpenAI Baselinesの比較は以下の様になっているそうです。
f:id:zakopilo:20210130181331p:plain

スーパーマリオの環境を見てみるf:id:zakopilo:20210130180617p:plain:w35

学習させるためにはまず環境が必要になります。
今回はインストールしたgym_super_mario_brosを使って環境を作ります。
以下を実行してみてください。

from nes_py.wrappers import JoypadSpace
import gym_super_mario_bros
from gym_super_mario_bros.actions import SIMPLE_MOVEMENT
env = gym_super_mario_bros.make('SuperMarioBros-v0')
env = JoypadSpace(env, SIMPLE_MOVEMENT)

done = True
for step in range(5000):
    if done:
        state = env.reset()
    state, reward, done, info = env.step(env.action_space.sample())
    env.render()

env.close()

おそらくこのような画面が出てきたと思います。これが今回学習する環境となります。
今回はランダムに動作をさせているので適当な動きをしています。

テスト画面表示

環境を作る

画像処理

上記の環境ではそのまま学習を行ってもなかなかうまく学習が進まないことが多いです。
そのため、上記の画面をAIが学習しやすいように処理をしてやります。
試行錯誤の結果以下で学習がうまく行きました。

from nes_py.wrappers import JoypadSpace
import gym_super_mario_bros
from gym_super_mario_bros.actions import SIMPLE_MOVEMENT,RIGHT_ONLY
from stable_baselines.common.vec_env import DummyVecEnv
from baselines.common.retro_wrappers import *
from stable_baselines.bench import Monitor

def make_env():
    env = gym_super_mario_bros.make('SuperMarioBros-v3') # 画像荒く
    env = JoypadSpace(env, RIGHT_ONLY)
    # env = CustomRewardAndDoneEnv(env) # 報酬とエピソード完了の変更 <- 後ほど説明
    env = StochasticFrameSkip(env, n=4, stickprob=0.25) # スティッキーフレームスキップ
    env = Downsample(env, 2) # ダウンサンプリング
    env = FrameStack(env, 4) # フレームスタック
    env = ScaledFloatFrame(env) # 状態の正規化
    env = Monitor(env, log_dir, allow_early_resets=True)
    env.seed(0) # シードの指定
    set_global_seeds(0)
    env = DummyVecEnv([lambda: env]) # ベクトル環境の生成

    return env

env = make_env()
done = True
for step in range(5000):
    if done:
        state = env.reset()
    env.render()

env.close()

以下のような画面が出てきたかと思います。
もはや、「マリオとは・・」という感じですがこのような画像処理が大事になります。

f:id:zakopilo:20210130185045p:plain
画像処理後のマリオ

報酬の設定

報酬などの説明は以下の記事を参考にしてください。
qiita.com

上記のコード内のここで報酬を設定しています。

env = CustomRewardAndDoneEnv(env) # 報酬とエピソード完了の変更 <- 後ほど説明

ここでは、マリオの位置が前回の位置よりも右にあれば報酬を+1,それ以外であれば報酬を-1にしています。
また、Goalをすると報酬を+2にしています。

# CustomRewardAndDoneラッパー
class CustomRewardAndDoneEnv(gym.Wrapper):
    # 初期化
    def __init__(self, env):
        super(CustomRewardAndDoneEnv, self).__init__(env)
        self._cur_x = 0
        self._max_x = 0
        self.reward = 0

    # リセット
    def reset(self, **kwargs):
        self._cur_x = 0
        self._max_x = 0
        self.reward = 0
        return self.env.reset(**kwargs)

    # ステップ
    def step(self, action):
        state, reward, done, info = self.env.step(action)

        # 報酬の変更
        if (info['x_pos'] > self._cur_x) & (self._cur_x != 0):
            # reward += 1
            self.reward += 1
        else:
            # reward -= 1
            self.reward -= 1
        self.reward /= 1000
        self._cur_x = info['x_pos']

        if info['life'] <= 1:
            self.reward -= 0.3

        if info['life'] == 1:
            done = True

        # エピソード完了の変更
        if info['flag_get']:
            self.reward += 2
            done =True
            print('GOAAL')
        return state, self.reward, done, info

学習させる

github.com
上記のコードをcloneして、以下を実行すればとりあえず学習が始まると思います。

$ python mario_baseline.py

 
mario_baseline.py

from nes_py.wrappers import JoypadSpace
import gym_super_mario_bros
from gym_super_mario_bros.actions import SIMPLE_MOVEMENT,RIGHT_ONLY
import time
from stable_baselines import PPO2,DQN
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 CustomRewardAndDoneEnv,log_dir,CustomCallback
from stable_baselines.common import set_global_seeds
from stable_baselines.common.callbacks import *
from stable_baselines.deepq.policies import CnnPolicy as DQNCnnPolicy

def make_env():
    env = gym_super_mario_bros.make('SuperMarioBros-v3')
    env = JoypadSpace(env, RIGHT_ONLY)
    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)
    env.seed(0) # シードの指定
    set_global_seeds(0)
    env = DummyVecEnv([lambda: env]) # ベクトル環境の生成

    print('行動空間: ', env.action_space)
    print('状態空間: ', env.observation_space)

    return env

env = make_env()
custom_callback = CustomCallback(env,render=True)
model = PPO2(policy=CnnPolicy, env=env, verbose=0,learning_rate=0.000025,tensorboard_log=log_dir) # モデル定義
model.learn(total_timesteps=2000000, callback=custom_callback) # 学習
model = PPO2.load('./agents/best_mario_ppo2model', env=env, verbose=0) # ベストなモデルを読み込む


state = env.reset()
total_reward = 0
while True:
    # 環境の描画
    env.render()

    # スリープ
    time.sleep(1/25)

    # モデルの推論
    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
        # state = env2.reset()

学習はPCのスペックにもよりますが普通のPCだと12時間程度かかるかと思います。

学習の過程はtensorboradを用いることで以下のように確認できます。

$ tensorboard --logdir=./logs/

http://localhost:6006」にアクセスすれば以下のような画面が出てくるかと思います。
f:id:zakopilo:20210130193709p:plain

学習がうまく行っていれば、rewardが徐々に上がっていくかと思います。(学習がうまく行かない場合は報酬や画像処理の見直しをおすすめします)

学習後

私の環境では、2~3回に1回ゴールするようになりました。

学習後マリオ

自分のAIがゲームをクリアするの嬉しいですね😢

おわりに

今回は強化学習を用いてスーパーマリオ1-1をクリアしました。
みなさんも、自分なりに報酬を変更したりして試して見てください。
OpenAI Gymなどの他のゲームも試してみると面白いと思います!👾

参考

OpenAI Gym / Baselines 深層学習・強化学習 人工知能プログラミング 実践入門 | 布留川 英一, 佐藤 英一 |本 | 通販 | Amazon