読むとGPAが上がるブログ(仮)

GPA芸人が気の赴くままに何かを書くブログ

強化学習の基礎(その3)

注意:この記事には嘘が含まれています!!!(近く修正します)

前回の記事の続きです。

さて、前回までの記事で、強化学習の具体的なモデルまで説明し終えました。 環境エージェントの相互作用という話でしたね。 今回は、この2つのうち、環境の実装について説明します。 なお、前回に引き続きテトリスをプレイするエージェントの作成を例とします。

OpenAI Gym

強化学習の環境と言えばこれ!というやつです。 環境の実装時にいくつかの条件を満たすことで、「OpenAI Gym形式の環境である」ということができます。 便宜上、「OpenAI Gym形式の環境」を「gym環境」と呼ぶことにします。

大抵の環境の実装はOpenAI Gym形式だと思われますし、(今回は扱いませんが)エージェントもgym環境を仮定して実装されていることが多いです。 gym環境を作ると、誰かが書いたエージェントの実装を利用することができ、実装しなくてはならない量が減ります。

gym環境の作成

準備

Python本体とその知識(基本文法とクラスが分かるくらい)は必要です。 あと、分かる人には当然かと思いますが、virtualenvとかpipenvとかで仮想環境を作ってその中でやります。 詳細は本題からそれるので割愛します。

で、

pip install gym

でgymをインストールすれば準備完了です。

あと、ほとんどの場合numpyを使うので、

pip install numpy

でこちらもインストールしておきます。

ディレクトリ構成

まず、環境用のディレクトリを作成します。 ディレクトリ名はgym-tetrisとします。

作成したら、以下のような構成になるように、各ディレクトリ・ファイルを作成してください。

  • gym-tetris/
    • README.md
    • setup.py
    • gym_tetris/
      • __init__.py
      • envs/
        • __init__.py
        • tetris_env.py

ファイルの中身は以下のようにします。

  • setup.py
from setuptools import setup

setup(name='gym_tetris',
            version='1.0.0',
            install_requires=['gym', 'numpy']
)
  • gym_tetris/__init__.py
from gym.envs.registration import register

register(id='tetris-v0',
         entry_point='gym_tetris.envs:TetrisEnv')
  • gym_tetris/envs/__init__.py
from gym_tetris.envs.tetris_env import TetrisEnv
  • gym_tetris/envs/tetris_env.py

これについては次に説明します。

さて、長々と書きましたが、重要なのは最後のtetris_env.pyです。 これが環境の本体になります。 他はいわゆるおまじないというやつなので、適当にtetrisの部分をリネームしつつほぼコピペでいいです。

環境本体

さて、本体のtetris_env.pyは、以下のようにします。 各メソッドなどの説明は実装の下に書きます。

import gym
from gym import spaces
import numpy as np

class TetrisEnv(gym.Env):
    metadata = {'render.modes': ['human', 'rgb_array']}
    observation_space = spaces.Box(0, 255, (3, 1600, 900), np.uint8)
    action_space = spaces.Discrete(7)
    reward_range = (-5, 4)

    def __init__(self):
        # 必要なフィールドの準備等

    def step(self, action):
        # エージェントの行動を環境に反映
        return obs, reward, done, info

    def reset(self):
        # ゲームをリセット
        return obs

    def render(self, mode='human'):
        if mode == 'human':
            # 環境を表示
        elif mode == 'rgb_array':
            return ary
        else:
            raise ValueError

    def close(self):
        # 環境を終了させる

    def seed(self, seed=None):
        self.rng, seed = gym.utils.seeding.np_random(seed)
        return [seed]

フィールドの説明

metadata

何かしら環境についての情報を外部から見えるところに置いておきたい場合、ここに入れます。 なので中身は環境によって様々なのですが、1つだけ、どの環境も持っておく方がいい情報があります。 後に説明する、renderメソッドの引数として使える文字列です。 詳しくはrenderメソッドのところで説明します。

observation_space

観測結果がどのような形状・値になるかを保持するフィールドです。 今回の例では、「フィールド部分の画像(RGBのピクセル集合)」を観測とします。 この場合、gym.spaces.Boxを用いて

gym.spaces.Box(0, 255, (3, 1600, 900), np.uint8)

と書けます。 引数の意味は以下の通りです。

  • 0255 : 観測結果が取りうる値の最小値・最大値
  • (3, 1600, 900) : 観測結果の形状(画面が縦1600×横900ピクセルであるとする)
  • np.uint8 : 観測結果の型

なお、形状は(1600, 900, 3)とすることもあります。 これはどちらでもいいです。 いわゆるCHWとHWCの違いというやつで、ここらへんは気になったら調べてください。 ちなみに、3はRGBで3チャンネル必要ということです。

また、Boxにはもう1つの形式の定義の仕方があるのですが、その説明は割愛します。

action_space

エージェントが取りうる行動を保持するフィールドです。 今回はテトリスなので、行動はどのボタンを押すか、ということになります。 具体的には、十字キー(4つ)・回転(2つ)・ホールドの、合計7操作があると思います。 このように、行動が離散的な場合は、gym.spaces.Discreteを用いて

gym.spaces.Discrete(7)

みたいにします。

なお、Discreteの実体はPython標準のrangeと同じ感じです。 すなわち、行動を数値0〜6で扱うことになります。

また、行動が連続値になる場合(レバー操作が必要で、どれくらいレバーを倒しているかを扱う場合など)は、先ほど用いたBoxaction_spaceを用意します。

reward_range

報酬が取りうる値の範囲を保持するフィールドです。 今回は例として、

  • ラインを消した場合、消したライン分の報酬を得る
  • ゲームオーバーになった場合、-5の報酬を得る
  • 上記に当てはまらない場合、報酬は0である

ということにします。 この場合、範囲は[-5, 4]になるので、これをタプルかリストかで保持します。 今回の例ではタプルにしましたが、どちらでもいいです。


ここまでが実装する必要のあるフィールドです。 ちなみに、これらは今回クラスフィールドとしていますが、Pythonはクラスフィールドとインスタンスフィールドを同じように扱えるので、インスタンスフィールドとして実装しても大丈夫です。

メソッドの説明

step(self, action)

行動を受け取り、環境に反映するメソッドです。 action_spaceの説明で書いた通り、今回は行動が数値なので、数値によって場合分けして環境を変化させます。

そして、前回の記事で書いた通り、「(観測したエージェントが)行動する→環境が変化する・報酬を得る・新たに観測する」というのが一連の流れです。 そこでこのメソッドは、環境を変化させるだけでなく、新しい観測結果obsと報酬rewardを返します。

さらに、実際にはもう2つの情報を返します。 1つ目はdoneで、これは環境が終了状態にあるかどうかをboolで返します。 テトリスの例では、ゲームオーバーになったらdoneTrueになります。 一度Trueとして返した場合、環境を利用する側はresetメソッドを呼ぶ義務があります。 逆に言うと、環境側は、一度Trueにして返したら、resetメソッドが呼ばれるまでの動作は保証しなくていいということです。

とはいえ、例外とかが投げられてしまうと不親切すぎるし、困る場合があるので、最低限動作はさせましょう。 次に説明するinfoに「doneが一度Trueになった」という情報を持たせるなどすると丁寧ですね。

2つ目はinfoで、こちらは何かしら追加で返したい情報を辞書で返します。 何もない場合は空の辞書を返します。

reset(self)

環境をリセットします。 テトリスの例では、「最初から始める」ボタンを押す感じです。

また、リセットした後、観測を返します。 テトリスの例では、何もテトリミノが置かれていない、まっさらなフィールドの画像が返るわけですね。

render(self, mode='human')

renderメソッドは、(観測ではなく)環境を確認するためのメソッドです。 学習に使うわけではなく、我々人間が学習が上手く進んでいるかどうかを確かめるためなどに用います。

引数として、どのように環境を表現するかを指定します。 別に数値でも文字列でもなんでもいいんですが、慣習的によく使われるのは、以下の3つです。

  • 'human' : 我々人間に向けて環境を表示する。要は画面を表示する。
  • 'rgb_array' : RGBのリスト(3次元)を返す。
  • 'ansi' : 文字列もしくはStringIOインスタンスを返す。テトリスの例では環境を表す文字列を考えるのは難しいため、実装していない。

また、今回の例も当てはまりますが、これら全てを実装しないこともあります。 そのため、環境の利用者がどの引数が使えるか分かるように、フィールドmetadataに取りうるmodeの種類を保存しておきます。

close(self)

環境を閉じるメソッドです。 プログラム終了時などに勝手に呼ばれます。 ゲーム用のプロセスを立ち上げていた際に、それをkillするなどの作業をします。

ちなみに、このメソッドと次に説明するseedメソッドは実装が必須ではありません。 実装しない場合、何もしないメソッドとなります。

seed(self, seed=None)

環境内で乱数を用いる場合に、そのシードを設定するためのメソッドです。 実装例に示したやつのコピペでいいですし、ほとんどの場合実装しなくても大丈夫です。

gym環境の利用準備

さて、こうして作った環境ですが、これを使うには、まずpipを使って環境をインストールする必要があります。 具体的には、gym-tetrisディレクトリをカレントディレクトリとして、そこで

pip install -e .

すればいいです。

これで環境のインストールが完了したので、あとはエージェントと学習本体のコードを実装すれば強化学習ができる状態となります。

エージェントと学習本体の実装紹介は、次回の記事になります。 3回じゃ終わらなかったですね。