この記事ではOpenAI Gym用のラッパーを自作する方法を紹介していきます。
内容は、OpenAI Gymのインターフェースを活用した強化学習環境の自作方法の記事(以下)で紹介した内容の延長になりますので、先に目を通しておくと理解しやすいかと思いますが、本記事だけでも理解できるように解説していきます。
こんな方におすすめ
- OpenAI Gymのインターフェースを活用して強化学習を行いたい
- OpenAI Gymで利用する強化学習環境に機能を追加したい
OpenAI Gymのインターフェース形式
OpenAI Gymのインターフェース形式を利用するメリット
まず最初に、OpenAI Gymのインターフェース形式を利用するメリットについて下図を使用して説明します。OpenAI Gymのインターフェース形式に沿って環境を作成する場合は、以下の図のように4つの階層構造で実現されます。まず最も下層に、各々が学習させたい環境があります(④)。環境とは、ダイナミクスを決定するものであるため、環境ダイナミクスと呼ばれたりします。それをプログラムで扱うために、OpenAI Gymが採用している取り決めに従ってプログラムを作成するのが③になります。③でOpenAI Gymのインターフェース形式で環境ダイナミクスをカプセル化してしまえば、どのような環境ダイナミクスであろうと、OpenAI Gymでの利用を想定したプログラムであれば利用可能になります。これが、OpenAI Gym用のラッパーになります(②)。OpenAI Gym用のラッパーは多くの人が作成&公開しているので、自分の環境に容易に再利用して機能を追加することができますし、自分で作成することもできます。そして、一般的な強化学習ライブラリであれば基本的にOpenAI Gymに対応しているため、これも容易に利用することができます。すなわち、OpenAI Gymのインターフェース形式を採用して強化学習環境を作成すると①や②を省略することができる可能性があります。
もし、OpenAI Gymのインターフェース形式を利用しなかったら、強化学習プログラムを含め、全てスクラッチで作成する必要があり開発効率が悪くなります。
他にも、OpenAI Gymのインターフェース形式で強化学習環境を作成するとプログラムの可読性が向上するなどの利点があります。ここまでで紹介した恩恵を以下にまとめます。
主な恩恵
- OpenAI Gymの利用者にとってプログラムの可読性が向上する
- OpenAI Gymインターフェースにより環境(Environment)と強化学習プログラム(Agent)が互いに依存しないプログラムにできるためモジュール性が向上する
- OpenAI Gym向けに用意されている多種多様なラッパーや強化学習ライブラリが利用できる
本記事で扱う内容は、②の部分になります。
③の部分を扱った記事「OpenAI Gym用の環境自作方法」については以下をご覧ください。
以降では、OpenAI Gymのインターフェース形式をGym形式と呼ぶことにします。
Gym形式のラッパーの作成ルール
以下に、Gym形式のラッパーを作成する際のルールを紹介します。
基本ルール
- 以下(②~④)の場合以外は
gym.Wrapper
を継承する - 観測(observation)のみ再定義する場合は
gym.ObservationWrapper
を継承する - 報酬(reward)のみ再定義する場合は
gym.RewardWrapper
を継承する - 行動(action)のみ再定義する場合は
gym.ActionWrapper
を継承する - コンストラクタで
super(your_wrapper_name, self).__init__(env)
を呼び出す
観測、報酬、行動の再定義を行う際に利用する、gym.ObservationWrapper
、gym.RewardWrapper
、gym.ActionWrapper
はgym.Wrapper
を継承していて、クラス図で表現すると以下のようになります。これは余談ですが、gym.Wrapper
はgym.Env
を継承しています。
以下で、実際に公開されているラッパーの中身を見ながら、使い方を学んでいきましょう。
観測を再定義するラッパーの作成
ObservationWrapperの使い方
gym.ObservationWrapperは、resetやstepメソッドが呼び出されたときの返り値observationの再定義を行うラッパーを作成するときに使用する抽象クラスです。
クラス内で、以下のようのobservationメソッドが抽象メソッドとして定義されていますので、カスタムなラッパークラスでこのメソッドを上書きしていきましょう。
@abstractmethod
def observation(self, observation):
raise NotImplementedError
具体例
以下のプログラムは、観測の値を指定の関数で変換する機能を追加するラッパークラスです。これは、OpenAI Gymのライブラリの中に、transform_observation.pyとして定義されています。ソースコードはここから引用しました。このラッパークラスの名前は、TransformObservationで、ObservationWrapperを継承しています。コンストラクタでは、ラップしたい環境と、観測値の変換式を引数として受け取り、それを、ObservationWrapperで抽象メソッドとして定義されているobservationメソッドに機能を上書きしている単純な例です。コメントの書き方も参考になりますね。
from gym import ObservationWrapper
class TransformObservation(ObservationWrapper):
r"""Transform the observation via an arbitrary function.
Example::
>>> import gym
>>> env = gym.make('CartPole-v1')
>>> env = TransformObservation(env, lambda obs: obs + 0.1*np.random.randn(*obs.shape))
>>> env.reset()
array([-0.08319338, 0.04635121, -0.07394746, 0.20877492])
Args:
env (Env): environment
f (callable): a function that transforms the observation
"""
def __init__(self, env, f):
super().__init__(env)
assert callable(f)
self.f = f
def observation(self, observation):
return self.f(observation)
引用元:openai gym
報酬を再定義するラッパーの作成
RewardWrapperの使い方
gym.RewardWrapperは、stepメソッドが呼び出されたときの返り値の1つであるrewardの再定義を行うラッパーを作成するときに使用する抽象クラスです。
クラス内で、以下のようのrewardメソッドが抽象メソッドとして定義されていますので、カスタムなラッパークラスでこのメソッドを上書きしていきましょう。
@abstractmethod
def reward(self, reward):
raise NotImplementedError
具体例
以下のプログラムは、報酬の値を指定の関数で変換する機能を追加するラッパークラスです。これは、OpenAI Gymのライブラリの中に、transform_reward.pyとして定義されています。ソースコードはここから引用しました。このラッパークラスの名前は、TransformRewardで、RewardWrapperを継承しています。コンストラクタでは、ラップしたい環境と、報酬値の変換式を引数として受け取り、それを、RewardWrapperで抽象メソッドとして定義されているrewardメソッドに機能を上書きしている単純な例です。
from gym import RewardWrapper
class TransformReward(RewardWrapper):
r"""Transform the reward via an arbitrary function.
Example::
>>> import gym
>>> env = gym.make('CartPole-v1')
>>> env = TransformReward(env, lambda r: 0.01*r)
>>> env.reset()
>>> observation, reward, done, info = env.step(env.action_space.sample())
>>> reward
0.01
Args:
env (Env): environment
f (callable): a function that transforms the reward
"""
def __init__(self, env, f):
super().__init__(env)
assert callable(f)
self.f = f
def reward(self, reward):
return self.f(reward)
引用元:openai gym
行動を再定義するラッパーの作成
ActionWrapperの使い方
gym.ActionWrapperは、stepメソッドを呼び出すときにエージェントから渡された行動値に変換を施すラッパーを作成する際に使用する抽象クラスです。
クラス内で、以下のようのactionメソッドとreverse_actionメソッドが抽象メソッドとして定義されていますので、カスタムなラッパークラスでこのメソッドを上書きしていきましょう。
@abstractmethod
def action(self, action):
raise NotImplementedError
@abstractmethod
def reverse_action(self, action):
raise NotImplementedError
具体例
以下のプログラムは、行動の値をクリッピングする機能を追加するラッパークラスです。これは、OpenAI Gymのライブラリの中に、clip_action.pyとして定義されています。ソースコードはここから引用しました。このラッパークラスの名前は、ClipActionで、ActionWrapperを継承しています。コンストラクタでは、ラップしたい環境の行動空間(action_space)がBox型(Gymにおいて連続値空間を扱う型)であれば利用できますが、それ以外、すなわち離散値を扱っている場合はAssertionErrorになるよう作成されています。そして、ActionWrapperで抽象メソッドとして定義されているactionメソッドでクリップ機能が実現されるように上書きされていることが確認できます。
import numpy as np
from gym import ActionWrapper
from gym.spaces import Box
class ClipAction(ActionWrapper):
r"""Clip the continuous action within the valid bound."""
def __init__(self, env):
assert isinstance(env.action_space, Box)
super().__init__(env)
def action(self, action):
return np.clip(action, self.action_space.low, self.action_space.high)
引用元:openai gym
新しい関数を用意したい場合
ここまで、観測や行動、報酬に限定して機能を追加する場合について説明してきました。しかし、それ以外の全く別の機能、例えば、学習結果を自動で可視化する、などの場合についてはまだ説明していないので、ここで説明します。ここまでは、OpenAI GymがGitHubでMITライセンスの元公開しているプログラムに甘えて具体例を示しましたが、ここは私が作ったWrapperを紹介しつつ説明していきます。
Wrapperの使い方
観測や報酬、行動について変換を加えたい場合は先に示した方法を用いれば十分ですが、それ以外の機能を追加する場合は、Wrapperクラスを用いる必要があります。使い方は、今までと同様で、Wrapperを継承し、自作の関数でstepメソッドやresetメソッドなどを再定義していきます。
具体例
以下は、Google Colabを使用して強化学習を行うときに、各ステップ毎にrenderを保持し、動画化したものをインライン表示もしくは動画ファイルとして保存する機能を環境に追加するラッパーになります。
このラッパークラスの名前は、GymVideoで、Wrapperを継承しています。そして、新たなメソッドsave_videoを作成しています。save_videoの引数は、保存時の動画ファイル名、インライン表示の有無を示すブール値です。今回作成した、save_videoメソッドは、各step毎のrenderを使用するため、stepメソッドで毎回renderメソッドを呼び出し、値を記録しています。
ぜひ使用してみてください。
!apt update
!apt install xvfb
!pip install pyvirtualdisplay
from pyvirtualdisplay import Display
d = Display()
d.start()
import gym
import matplotlib.pyplot as plt
from matplotlib import animation
from matplotlib.animation import PillowWriter
from IPython.display import HTML
class GymVideo(gym.Wrapper):
def __init__(self, env):
super().__init__(env)
self.frames = []
def step(self, ac, monitor_frame=False):
if monitor_frame:
self.frames.append(self.env.render(mode='rgb_array'))
return self.env.step(ac)
def save_video(self, save_name=None, display_inline=False):
"""
params:
save_name : default None. If you set save_name, gif animation will be saved.
display_inline : You should use when you set save_name.
if you set display_inline True, gif animation will be displayed inline.
"""
patch = plt.imshow(self.frames[0], cmap='gray')
plt.axis('off')
def animate(i):
patch.set_data(self.frames[i])
anim = animation.FuncAnimation(plt.gcf(), animate, frames=len(self.frames), interval=50)
if save_name != None:
if display_inline != True:
anim.save(save_name, writer=PillowWriter(fps=60))
else:
anim.save(save_name, writer=PillowWriter(fps=60))
display(HTML(anim.to_jshtml(default_mode='once')))
else:
display(HTML(anim.to_jshtml(default_mode='once')))
plt.close()
最後に
この記事では、OpenAI Gymのインターフェース形式を利用した環境のラッパーを作成する方法を説明してきました。最後に、作成に関する基本ルールを再掲します。ぜひ、カスタムラッパー作成に挑戦してみてください。最後までお読みいただきありがとうございます。
基本ルール
- 以下(②~④)の場合以外は
gym.Wrapper
を継承する - 観測(observation)のみ再定義する場合は
gym.ObservationWrapper
を継承する - 報酬(reward)のみ再定義する場合は
gym.RewardWrapper
を継承する - 行動(action)のみ再定義する場合は
gym.ActionWrapper
を継承する - コンストラクタで
super(your_wrapper_name, self).__init__(env)
を呼び出す