Acme : コンポーネント (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 06/13/2020
* 本ページは、Acme の以下のドキュメントを翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
コンポーネント
環境
Acme は dm_env 環境インターフェイス を実装する環境と動作するように設計されています。これはアクションを取り観測を受け取るために環境と相互作用するための一般的な API を提供します。この環境 API はまた environment.action_spec() のようなメソッドを通してその環境に関連する入力と出力空間を指定するための環境のための標準的な方法も提供します。Acme はまたこれらの spec type を acme.specs を通して直接公開することにも注意してください。けれどもまた、Acme エージェントが完全な環境 spec を要求することは普通のことです、これは acme.make_environment_spec(environment) を利用して得ることができます。
Acme はまた acme.wrappers の下で幾つかのクラスを公開しています、これは dm_env 環境をラップ and/or 公開します。総てのそのようなラッパーは次の形式です :
environment = Wrapper(raw_environment, ...)
ここで動作を制御するために追加パラメータがラッパーに渡されるかもしれません (より詳細については個々の実装を見てください)。直接公開されるラッパーは以下を含みます :
- SinglePrecisionWrapper: 環境により返される任意の倍精度浮動小数点と整数コンポーネントを単精度に変換します。
- AtariWrapper: “Human Level Control Through Deep Reinforcement Learning” 公開物で使用された変更に対応するラッパーのスタックを使用して標準的 ALE Atari 環境を変換します。
Acme はまた acme.wrappers.gym_wrapper モジュールを含みます、これは OpenAI Gym 環境と相互作用するために利用できます。これは一般的な GymWrapper クラスとともに AtariGymWrapper を含みます、これは AtariWrapper によりオプションで公開できる lives カウント観測を公開します。
ネットワーク
任意のエージェント実装のための重要なビルディングブロックはパラメータ化された関数あるいはネットワークから成ります、これはポリシー、値関数等を構築するために使用されます。Acme で実装されるエージェントは (その上で適用される) 環境にはできる限り不可知論であるとして構築されます。結果としてそれらは典型的にはネットワークを必要とします、これらは観測を消費するか、アクションを生成する、あるいは両者のために環境と直接相互作用するために使用されます。これらは典型的にはエージェントに初期化時に直接渡されます、e.g.
policy_network = ... critic_network = ... agent = MyActorCriticAgent(policy_network, critic_network, ...)
Tensorflow と Sonnet
TensorFlow エージェントについては、Acme のネットワークは典型的には Sonnet ニューラルネットワーク・ライブラリを使用して実装されています。これらのネットワーク・オブジェクトは Callable オブジェクトの形式を取ります、これは入力として (ネストされた) tf.Tensor オブジェクトのコレクションを取り (nested) tf.Tensor か tfp.Distribution オブジェクトのコレクションを出力します。以後、次のエイリアスを使用します。
import sonnet as snt import tensorflow as tf import tensorflow_probability as tfp tfd = tfp.distributions
カスタム Sonnet モジュールが直接実装されて利用できる一方で、Acme はまた幾つかの有用なネットワーク・プリミティブを提供します、これは RL タスクに仕立てられています ; これらは acme.tf.networks からインポートできます、より詳細については ネットワーク を見てください。これらのプリミティブは状態を持つネットワークモジュールをスタックするとき snt.Sequential か snt.DeepRNN を使用して結合できます。
モジュールをスタックするとき、トルソー (= torso)、ヘッドそしてマルチプレクサ・ネットワークとしばしば呼ばれるものの間で識別することは、常にではありませんが、しばしば有用です。ネットワーク・アーキテクチャを議論するときこのカテゴリー分類は純粋に教育上のものですがそれにもかかわらず有用であることがわかることに注意してください。
トルソーは最初に入力 (観測、アクションか組み合わせ) を変換して深層学習文献では埋め込みベクトルとして一般に知られるものを生成します。これらのモジュールはスタックできます、その結果埋め込みはそれがヘッドに供給される前に複数回変換されます。
例えばそれを Atari ゲームで訓練するとき Impala エージェントで使用する単純なネットワークを考えましょう :
impala_network = snt.DeepRNN([ # Torsos. networks.AtariTorso(), # Default Atari ConvNet offered as convenience. snt.LSTM(256), # Custom LSTM core. snt.Linear(512), # Custom perceptron layer before head. tf.nn.relu, # Seemlessly stack Sonnet modules and TF ops as usual. # Head producing 18 action logits and a value estimate for the input # observation. networks.PolicyValueHead(num_actions=18), ])
ヘッドは望まれる出力 (アクション・ロジットや分布、値推定, etc) を生成するために埋め込みベクトルを消費するネットワークです。これらのモジュールもまたスタックできます、これは確率的ポリシーを扱うときに特に有用です。例えば、制御スーツで訓練される、MPO エージェントで使用される次の確率的ポリシーを考えます :
policy_layer_sizes: Sequence[int] = (256, 256, 256) stochastic_policy_network = snt.Sequential([ # MLP torso with initial layer normalization; activate the final layer since # it feeds into another module. networks.LayerNormMLP(policy_layer_sizes, activate_final=True), # Head producing a tfd.Distribution: in this case `num_dimensions` # independent normal distributions. networks.MultivariateNormalDiagHead(num_dimensions), ])
確率的ポリシーは対数確率と Kullback-Leibler (KL) ダイバージェンスを計算するために MPO アルゴリズムで内部的に使用されます。greedy アクションとして確率的ポリシーの平均を選択する追加のヘッドをスタックすることもできます :
greedy_policy_network = snt.Sequential([ networks.LayerNormMLP(policy_layer_sizes, activate_final=True), networks.MultivariateNormalDiagHead(num_dimensions), networks.StochasticModeHead(), ])
連続的制御タスクのための actor-critic エージェントを設計するとき、一つの単純なモジュールが特に有用であることを見出しました : CriticMultiplexer です。この callable Sonnet モジュールは 2 つの入力、観測とアクションを取り、そして [observation|action]_network のいずれかか両者が渡された場合それらを多分変換した後、それらをバッチ次元を除いて総て一緒に結合します。例えば、次は D4PG 実験のために適応された C51 (Bellemare et al., 2017 参照) 分散 critic ネットワークです :
critic_layer_sizes: Sequence[int] = (512, 512, 256) distributional_critic_network = snt.Sequential([ # Flattens and concatenates inputs; see `tf2_utils.batch_concat` for more. networks.CriticMultiplexer(), networks.LayerNormMLP(critic_layer_sizes, activate_final=True), # Distributional head corresponding to the C51 network. networks.DiscreteValuedHead(vmin=-150., vmax=150., num_atoms=51), ])
最後に、actor-critic 制御エージェントはまたポリシーと critic により共有される観測ネットワークの仕様を許します。このネットワークは観測を一度埋め込み変換された入力を必要に応じてポリシーと critic の両者で利用します、これは特に変換が高価であるとき計算を節約します。これは例えばピクセルから学習するときです、そこでは観測ネットワークは大きい ResNet であり得ます。そのような場合、共有 visual ネットワークは単純に以下を定義して渡すことにより任意の DDPG, D4PG, MPO, DMPO に指定できます :
shared_resnet = networks.ResNetTorso() # Default (deep) Impala network. agent = dmpo.DMPO( # Networks defined above. policy_network=stochastic_policy_network, critic_network=distributional_critic_network, # New ResNet visual module, shared by both policy and critic. observation_network=shared_resnet, # ... )
この場合、policy_ と critic_network は共有 visual トルソーの上のヘッドとして動作します。
内部コンポーネント
Acme はまた幾つかのコンポーネントとコンセプトを含みます、これらは典型的にはエージェントの実装の内側です。これらのコンポーネントはもし貴方が Acme エージェントを利用することにだけ関心がある場合、一般に無視できます。けれどもそれらは新規のエージェントを実装するとき、あるいは既存のエージェントを変更するときに有用であることがわかります。
損失
幾つかの良く利用される損失関数があります。一般に可能なところでは TFRL に従うことに注意してください、それがそのために TensorFlow 2 をサポートしない場合を除いて。
実装された RL-固有の損失は以下を含みます :
- カテゴリカル分布のための 分散 TD 損失 (訳注: リンク切れ) ; Bellemare et al., 2017 参照。
- 決定論的ポリシー勾配 (DPG) 損失 (訳注: リンク切れ) ; Silver et al., 2014 参照。
- Maximum a posteriori ポリシー最適化 (MPO) 損失 (訳注: リンク切れ) ; Abdolmaleki et al., 2018 参照。
そして実装された (そして上で言及した損失内で有用な) ものは :
- ロバスト回帰のための Huber 損失 (訳注: リンク切れ)。
Adders
Adder はデータを再生バッファに送るために一緒にパックして、そしてその過程でこのデータに何某かの削減/変換を行なう可能性があります。
総ての Acme Adder はそれらの add(), end_episode(), または add_async(), end_episode_async() と reset() メソッドを通して相互作用できます。
add() メソッドは形式 : (state_t, action_t, reward_t+1, discount_t+1, [extras_t]) のタプルを取ります。
エピソードの最後で、学習に利用可能な、形式 (state, (a, r, d…), *next_state*) のデータを持つという RL アルゴリズムの一般的な要請の結果、どのような新しいアクション、報酬 etc. を追加することなく最後の next_state を再生バッファに追加する必要があります。Adder の end_episode() メソッドはこれを貴方のために処理します、通常はタプルの他のパートを適切にパディングして最後の next_state を単純に追加することによって。
adder のサンプル使用方法は :
timestep = env.reset() while not timestep.last(): action = my_policy(timestep) new_timestep = env.step(action) # Adds a (s, a, r, d) tuple to the adder's internal buffer. adder.add(timestep.observation, action, new_timestep.reward, new_timestep.discount) timestep = new_timestep # When the episode ends, tell the adder about the final arrival state. adder.end_episode(new_timestep.observation)
ReverbAdder
Acme は RL 経験をストアするために再生バッファのようなデータ構造を作成するために Reverb を利用します。
便利のため、actor 経験を Reverb テーブルに追加するために Acme は幾つかの ReverbAdder を提供します。提供される ReverbAdder は以下を含みます :
- NStepTransitionAdder は環境/エージェント・ループから単一ステップを取り、自動的にそれらを N-ステップ遷移に結合して、そして将来の再取得 (= retrieval) のために遷移を Reverb に追加します。ステップはバッファリングされてから N-ステップ遷移に結合されます、これは再生 (バッファ) にストアされてそこから返されます。
N が 1 であるところでは、遷移は次の形式です :
`(s_t, a_t, r_t, d_t, s_{t+1}, e_t)`
1 より大きい N については、遷移は次の形式です :
`(s_t, a_t, R_{t:t+n}, D_{t:t+n}, s_{t+n}, e_t)`,
遷移はシークエンスかエピソードとしてストアできます。
- EpisodeAdder、これはエピソード全体を次の形式の trajectories (軌道) として追加します :
(s_0, a_0, r_0, d_0, e_0, s_1, a_1, r_1, d_1, e_1, . . . s_T, a_T, r_T, 0., e_T)
- PaddedEpisodeAdder、これは max_sequence_length へのパディングを伴う EpisodeAdder と同じです。
- SequenceAdder、これは次の形式の固定 sequence_length n のシークエンスを追加します :
(s_0, a_0, r_0, d_0, e_0, s_1, a_1, r_1, d_1, e_1, . . . s_n, a_n, r_n, d_n, e_n)
シークエンスは overlapping (if period パラメータ = sequence_length n) か non-overlapping (if period < sequence_length) であり得ます。
Loggers
Acme は、総て write() メソッドを持つ抽象 Logger クラスに基づいて、データを共通の場所に書き出すための幾つかの logger を含みます。
NOTE: デフォルトでは、logger を構築するとき logger 出力の間の秒数を表す、time_delta 引数のために非ゼロ値が与えられない限りは logger は write() を通して渡された総てのデータを直ちに出力します。
Terminal Logger
データを直接端末にログ出力します。
サンプル :
terminal_logger = loggers.TerminalLogger(label='TRAINING',time_delta=5) terminal_logger.write({'step': 0, 'reward': 0.0}) >> TRAINING: step: 0, reward: 0.0
CSV Logger
指定された CSV ファイルにログ出力します。
サンプル :
csv_logger = loggers.CSVLogger(logdir='logged_data', label='my_csv_file') csv_logger.write({'step': 0, 'reward': 0.0})
Tensorflow savers
訓練された TensorFlow モデルをセーブするため、それらをチェックポイントあるいはスナップショットを撮ることができます。
チェックポイントとスナップショットの両者は後で利用するためにモデル状態をセーブしてリストアする方法です。違いはチェックポイントをリストアするときに発生します。
チェックポイントでは、最初に正確なグラフを再構築してから、チェックポイントをリストアしなければなりません。それらは実験を実行している間、実験が中断/阻止されて実験状態を失うことなく実験実行を続けるためにリストアされなければならないケースで、持つことは有用です。
スナップショットはグラフを内部的に再構築しますので、貴方がしなければならないことの総てはスナップショットをリストアすることです。
Acme の Checkpointer クラスは望まれるモデル状態の異なるパートを (objects_to_save 引数を持つ) チェックポイントと (objects_to_snapshot 引数を持つ) スナップショットの両者を行なう機能を提供します。
model = snt.Linear(10) checkpointer = utils.tf2_utils.Checkpointer( objects_to_save={'model': model}, objects_to_snapshot={'model': model}) for _ in range(100): # ... checkpointer.save()
以上