ChainerRLのサンプルを読んでみる(examples/ale/train_dqn_ale.py編)の続きです。
(2017/12/31現在、ChainerRLのAPIドキュメントが簡素なので、解説してみる記事です。)
今度は、OpenAI Gym の方のサンプルを見てみます。
examples/gym/train_dqn_gym.py
例によって、Trainingの部分だけを切り出してみました。が、そんなに短くなっていないです。
def main(args):
args.outdir = experiments.prepare_output_dir(
args, args.outdir, argv=sys.argv)
print('Output files are saved in {}'.format(args.outdir))
def clip_action_filter(a):
return np.clip(a, action_space.low, action_space.high)
def make_env(for_eval):
env = gym.make(args.env)
if args.monitor:
env = gym.wrappers.Monitor(env, args.outdir)
if isinstance(env.action_space, spaces.Box):
misc.env_modifiers.make_action_filtered(env, clip_action_filter)
if not for_eval:
misc.env_modifiers.make_reward_filtered(
env, lambda x: x * args.reward_scale_factor)
if ((args.render_eval and for_eval) or
(args.render_train and not for_eval)):
misc.env_modifiers.make_rendered(env)
return env
env = make_env(for_eval=False)
timestep_limit = env.spec.tags.get(
'wrapper_config.TimeLimit.max_episode_steps')
obs_space = env.observation_space
obs_size = obs_space.low.size
action_space = env.action_space
if isinstance(action_space, spaces.Box):
action_size = action_space.low.size
# Use NAF to apply DQN to continuous action spaces
q_func = q_functions.FCQuadraticStateQFunction(
obs_size, action_size,
n_hidden_channels=args.n_hidden_channels,
n_hidden_layers=args.n_hidden_layers,
action_space=action_space)
# Use the Ornstein-Uhlenbeck process for exploration
ou_sigma = (action_space.high - action_space.low) * 0.2
explorer = explorers.AdditiveOU(sigma=ou_sigma)
else:
n_actions = action_space.n
q_func = q_functions.FCStateQFunctionWithDiscreteAction(
obs_size, n_actions,
n_hidden_channels=args.n_hidden_channels,
n_hidden_layers=args.n_hidden_layers)
# Use epsilon-greedy for exploration
explorer = explorers.LinearDecayEpsilonGreedy(
args.start_epsilon, args.end_epsilon, args.final_exploration_steps,
action_space.sample)
# Draw the computational graph and save it in the output directory.
chainerrl.misc.draw_computational_graph(
[q_func(np.zeros_like(obs_space.low, dtype=np.float32)[None])],
os.path.join(args.outdir, 'model'))
opt = optimizers.Adam()
opt.setup(q_func)
rbuf_capacity = 5 * 10 ** 5
if args.episodic_replay:
if args.minibatch_size is None:
args.minibatch_size = 4
if args.prioritized_replay:
betasteps = (args.steps - args.replay_start_size) \
// args.update_interval
rbuf = replay_buffer.PrioritizedEpisodicReplayBuffer(
rbuf_capacity, betasteps=betasteps)
else:
rbuf = replay_buffer.EpisodicReplayBuffer(rbuf_capacity)
else:
if args.minibatch_size is None:
args.minibatch_size = 32
if args.prioritized_replay:
betasteps = (args.steps - args.replay_start_size) \
// args.update_interval
rbuf = replay_buffer.PrioritizedReplayBuffer(
rbuf_capacity, betasteps=betasteps)
else:
rbuf = replay_buffer.ReplayBuffer(rbuf_capacity)
def phi(obs):
return obs.astype(np.float32)
agent = DQN(q_func, opt, rbuf, gpu=args.gpu, gamma=args.gamma,
explorer=explorer, replay_start_size=args.replay_start_size,
target_update_interval=args.target_update_interval,
update_interval=args.update_interval,
phi=phi, minibatch_size=args.minibatch_size,
target_update_method=args.target_update_method,
soft_update_tau=args.soft_update_tau,
episodic_update=args.episodic_replay, episodic_update_len=16)
if args.load:
agent.load(args.load)
eval_env = make_env(for_eval=True)
experiments.train_agent_with_evaluation(
agent=agent, env=env, steps=args.steps,
eval_n_runs=args.eval_n_runs, eval_interval=args.eval_interval,
outdir=args.outdir, eval_env=eval_env,
max_episode_len=timestep_limit)
上から見ていきます
chainerrl.experiments.prepare_output_dir
args.outdir = experiments.prepare_output_dir(
args, args.outdir, argv=sys.argv)
print('Output files are saved in {}'.format(args.outdir))
chainerrl.experiments.prepare_output_dir
はログ等の出力用のディレクトリをつくるための関数です。公式ドキュメントにはありませんが、ソース中にドキュメントがありました。
(この出力が、Chainerの"result/log"と同じなら、ChainerUIが使えるんですけどね。。)
"""Prepare a directory for outputting training results.
An output directory, which ends with the current datetime string,
is created. Then the following information is saved into the directory:
args.txt: command line arguments
command.txt: command itself
environ.txt: environmental variables
Additionally, if the current directory is under git control, the following
information is saved:
git-head.txt: result of `git rev-parse HEAD`
git-status.txt: result of `git status`
git-log.txt: result of `git log`
git-diff.txt: result of `git diff`
Args:
args (dict or argparse.Namespace): Arguments to save
user_specified_dir (str or None): If str is specified, the output
directory is created under that path. If not specified, it is
created as a new temporary directory instead.
argv (list or None): The list of command line arguments passed to a
script. If not specified, sys.argv is used instead.
time_format (str): Format used to represent the current datetime. The
default format is the basic format of ISO 8601.
Returns:
Path of the output directory created by this function (str).
"""
chianerrl.misc.env_modifiers.make_action_filtered
def make_env(for_eval):
env = gym.make(args.env)
if args.monitor:
env = gym.wrappers.Monitor(env, args.outdir)
if isinstance(env.action_space, spaces.Box):
misc.env_modifiers.make_action_filtered(env, clip_action_filter)
if not for_eval:
misc.env_modifiers.make_reward_filtered(
env, lambda x: x * args.reward_scale_factor)
if ((args.render_eval and for_eval) or
(args.render_train and not for_eval)):
misc.env_modifiers.make_rendered(env)
return env
env = make_env(for_eval=False)
gym
は、OpenAI Gymのパッケージで、ChainerRLのAPIではありません。gym.make
についてはこちらを参照してください。gym.wrappers.Monitor
についてはドキュメントが無いのですが、ログのようなもののようです。この辺りを見れば分かると思います
chianerrl.misc.env_modifiers.make_action_filtered
は、その名の通りActionをフィルターするもので、入力されたアクションに対して、アクションフィルターの関数(上記コードの場合Lambda)を適用します。
chianerrl.misc.env_modifiers.make_rendered
先ほどのコードにもう一つ、chainerrl.misc.env_modifiers.make_rendered
というAPIがありました。
これは以下のような実装です。
def make_rendered(env, *render_args, **render_kwargs):
base_step = env.step
base_close = env.close
def step(action):
ret = base_step(action)
env.render(*render_args, **render_kwargs)
return ret
def close():
env.render(*render_args, close=True, **render_kwargs)
base_close()
env.step = step
env.close = close
Open AI Gym の step
とclose
を書き換えているようです。step
については、ここに記述があります。
ようは、step
が呼ばれる度にrender
をしよう、close
が呼ばれたら一度render
してウインドウをCloseしよう、という書き換えをしているようです。
OpenAI Gym 関連
以下は、ChainerRLではなく、OpenAI GymのAPI達です。
timestep_limit = env.spec.tags.get(
'wrapper_config.TimeLimit.max_episode_steps')
obs_space = env.observation_space
obs_size = obs_space.low.size
action_space = env.action_space
env.spec.tags.get
は、envの値を取るAPIの用です。タグのリスト等は見つからないのですが。。'wrapper_config.TimeLimit.max_episode_steps'
で、1エピソード中のステップ数の最大値を取得しています。
env.observation_space
は、環境を観測するときのオブジェクトを返し、env.action_space
は、行動のオブジェクトを返します。これらは、Box(4,)
かDiscrete(2)
が返ってきます。こちらの'Spaces'に詳しくかかれてます。
chainerrl.q_functions
Open AI Gymの方もドキュメントの記述はあまりないのですが、、Box()
はN次元の空間を持ち、Discrete()
は1次元のようです。
次のコードでは、どちらのタイプかによって、Q関数の構造を変えています。
if isinstance(action_space, spaces.Box):
action_size = action_space.low.size
# Use NAF to apply DQN to continuous action spaces
q_func = q_functions.FCQuadraticStateQFunction(
obs_size, action_size,
n_hidden_channels=args.n_hidden_channels,
n_hidden_layers=args.n_hidden_layers,
action_space=action_space)
# Use the Ornstein-Uhlenbeck process for exploration
ou_sigma = (action_space.high - action_space.low) * 0.2
explorer = explorers.AdditiveOU(sigma=ou_sigma)
else:
n_actions = action_space.n
q_func = q_functions.FCStateQFunctionWithDiscreteAction(
obs_size, n_actions,
n_hidden_channels=args.n_hidden_channels,
n_hidden_layers=args.n_hidden_layers)
# Use epsilon-greedy for exploration
explorer = explorers.LinearDecayEpsilonGreedy(
args.start_epsilon, args.end_epsilon, args.final_exploration_steps,
action_space.sample)
chainerrl.q_functions.FCQuadraticStateQFunction
は、コード中にドキュメントがありました。
その名の通り、Fully-connected で、2次元(Quadratic)のStateを持つ、Q関数のようです。
"""Fully-connected state-input continuous Q-function.
Args:
n_input_channels: number of input channels
n_dim_action: number of dimensions of action space
n_hidden_channels: number of hidden channels
n_hidden_layers: number of hidden layers
action_space: action_space
scale_mu (bool): scale mu by applying tanh if True
"""
もう一方のchainerrl.q_functions.FCStateQFunctionWithDiscreteAction
もその名の通り、Discrete Action(離散的な行動)のためのQ関数のようです。
"""Fully-connected state-input Q-function with discrete actions.
Args:
n_dim_obs: number of dimensions of observation space
n_dim_action: number of dimensions of action space
n_hidden_channels: number of hidden channels
n_hidden_layers: number of hidden layers
"""
chainerrl.explorers
上のコードには、explorers.AdditiveOU
とexplorers.LinearDecayEpsilonGreedy
も使われていました。
action_space
が、Box
の場合に使われているexplorers.AdditiveOU
のコード中のドキュメントは以下の通りです。
"""Additive Ornstein-Uhlenbeck process.
Used in https://arxiv.org/abs/1509.02971 for exploration.
Args:
mu (float): Mean of the OU process
theta (float): Friction to pull towards the mean
sigma (float or ndarray): Scale of noise
start_with_mu (bool): Start the process without noise
"""
https://arxiv.org/abs/1509.02971 を見てみると、"Continuous control with deep reinforcement learning" という論文である事が分かります。
よく分かっていないのですが、action_space
がBox
なので、行動空間がN次元で連続である、という事でいいんでしょうか。ちなみに、"Ornstein-Uhlenbeck process"は、平均回帰過程です。
一方、explorers.LinearDecayEpsilonGreedy
は、$\epsilon$を低減させる、普通のEpsilon-greedyです。ChainerRLのサンプルを読んでみる(examples/ale/train_dqn_ale.py編)にも出てきました。
"""Epsilon-greedy with linearyly decayed epsilon
Args:
start_epsilon: max value of epsilon
end_epsilon: min value of epsilon
decay_steps: how many steps it takes for epsilon to decay
random_action_func: function with no argument that returns action
logger: logger used
"""
chainerrl.misc.draw_computational_graph
次にあるchainerrl.misc.draw_computational_graph
はq関数のモデルをPNGファイルにして保存しています。
# Draw the computational graph and save it in the output directory.
chainerrl.misc.draw_computational_graph(
[q_func(np.zeros_like(obs_space.low, dtype=np.float32)[None])],
os.path.join(args.outdir, 'model'))
chainerrl.replay_buffer
次は、Replay bufferの生成をしています。
大きく分けて、Episodic Replayかどうか、と、Prioritizedかどうかで、以下の4つのAPIがあります。
chainerrl.replay_buffer.PrioritizedEpisodicReplayBuffer
chainerrl.replay_buffer.EpisodicReplayBuffer
chainerrl.replay_buffer.PrioritizedReplayBuffer
chainerrl.replay_buffer.ReplayBuffer
if args.episodic_replay:
if args.minibatch_size is None:
args.minibatch_size = 4
if args.prioritized_replay:
betasteps = (args.steps - args.replay_start_size) \
// args.update_interval
rbuf = replay_buffer.PrioritizedEpisodicReplayBuffer(
rbuf_capacity, betasteps=betasteps)
else:
rbuf = replay_buffer.EpisodicReplayBuffer(rbuf_capacity)
else:
if args.minibatch_size is None:
args.minibatch_size = 32
if args.prioritized_replay:
betasteps = (args.steps - args.replay_start_size) \
// args.update_interval
rbuf = replay_buffer.PrioritizedReplayBuffer(
rbuf_capacity, betasteps=betasteps)
else:
rbuf = replay_buffer.ReplayBuffer(rbuf_capacity)
PrioritizedReplayBufferは、以下の論文のようです。
Prioritized Experience Replay
https://arxiv.org/abs/1511.05952
エピソード記憶については、どの論文、という事ではないようですが、エピソード毎にReplayBufferを保存している手法のようです。
chainerrl.agents.DQN
DQNのAgentです。こちらにはドキュメントがあります。
agent = DQN(q_func, opt, rbuf, gpu=args.gpu, gamma=args.gamma,
explorer=explorer, replay_start_size=args.replay_start_size,
target_update_interval=args.target_update_interval,
update_interval=args.update_interval,
phi=phi, minibatch_size=args.minibatch_size,
target_update_method=args.target_update_method,
soft_update_tau=args.soft_update_tau,
episodic_update=args.episodic_replay, episodic_update_len=16)
chainerrl.experiments.train_agent_with_evaluation
最後、chainer.experiments.train_agent_with_evaluationで学習と評価をします。
これは、Webにドキュメントがあります。
http://chainerrl.readthedocs.io/en/latest/experiments.html#training-and-evaluation
ChainerRLのサンプルを読んでみる(examples/ale/train_dqn_ale.py編)にも出てきました。
eval_env = make_env(for_eval=True)
experiments.train_agent_with_evaluation(
agent=agent, env=env, steps=args.steps,
eval_n_runs=args.eval_n_runs, eval_interval=args.eval_interval,
outdir=args.outdir, eval_env=eval_env,
max_episode_len=timestep_limit)