#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Explanation: Copyright 2021 The TF-Agents Authors.
End of explanation
"""


!pip install tf-agents


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import tensorflow as tf
import numpy as np

from tf_agents import specs
from tf_agents.agents.dqn import dqn_agent
from tf_agents.drivers import dynamic_step_driver
from tf_agents.environments import suite_gym
from tf_agents.environments import tf_py_environment
from tf_agents.networks import q_network
from tf_agents.replay_buffers import py_uniform_replay_buffer
from tf_agents.replay_buffers import tf_uniform_replay_buffer
from tf_agents.specs import tensor_spec
from tf_agents.trajectories import time_step

"""
Explanation: 再生バッファ
<table class="tfo-notebook-buttons" align="left">
  <td><a target="_blank" href="https://www.tensorflow.org/agents/tutorials/5_replay_buffers_tutorial"><img src="https://www.tensorflow.org/images/tf_logo_32px.png"> TensorFlow.orgで表示</a></td>
  <td><a target="_blank" href="https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/agents/tutorials/5_replay_buffers_tutorial.ipynb">     <img src="https://www.tensorflow.org/images/colab_logo_32px.png">Google Colabで実行</a></td>
  <td><a target="_blank" href="https://github.com/tensorflow/docs-l10n/blob/master/site/ja/agents/tutorials/5_replay_buffers_tutorial.ipynb"><img src="https://www.tensorflow.org/images/GitHub-Mark-32px.png">GitHub でソースを表示{</a></td>
  <td><a href="https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ja/agents/tutorials/5_replay_buffers_tutorial.ipynb"><img src="https://www.tensorflow.org/images/download_logo_32px.png">ノートブックをダウンロード/a0}</a></td>
</table>

はじめに
強化学習アルゴリズムは、環境でポリシーを実行するときに再生バッファを使用して経験のトラジェクトリを格納します。トレーニング中、再生バッファはトラジェクトリのサブセット（シーケンシャルサブセットまたはサンプルのいずれか）がエージェントの経験を「再生」するために照会されます。
このコラボでは、共通の API を共有する 2 種類の再生バッファ（python 方式と tensorflow 方式）について考察します。次のセクションでは API と各バッファの実装、およびデータ収集トレーニングでそれらを使用する方法を説明します。
セットアップ
tf-agents をまだインストールしていない場合はインストールしてください。
End of explanation
"""


data_spec =  (
        tf.TensorSpec([3], tf.float32, 'action'),
        (
            tf.TensorSpec([5], tf.float32, 'lidar'),
            tf.TensorSpec([3, 2], tf.float32, 'camera')
        )
)

batch_size = 32
max_length = 1000

replay_buffer = tf_uniform_replay_buffer.TFUniformReplayBuffer(
    data_spec,
    batch_size=batch_size,
    max_length=max_length)

"""
Explanation: 再生バッファ API
再生バッファのクラスには、次の定義とメソッドがあります。
```python
class ReplayBuffer(tf.Module):
  """Abstract base class for TF-Agents replay buffer."""
def init(self, data_spec, capacity):
    """Initializes the replay buffer.
Args:
  data_spec: A spec or a list/tuple/nest of specs describing
    a single item that can be stored in this buffer
  capacity: number of elements that the replay buffer can hold.
"""

@property
  def data_spec(self):
    """Returns the spec for items in the replay buffer."""
@property
  def capacity(self):
    """Returns the capacity of the replay buffer."""
def add_batch(self, items):
    """Adds a batch of items to the replay buffer."""
def get_next(self,
               sample_batch_size=None,
               num_steps=None,
               time_stacked=True):
    """Returns an item or batch of items from the buffer."""
def as_dataset(self,
                 sample_batch_size=None,
                 num_steps=None,
                 num_parallel_calls=None):
    """Creates and returns a dataset that returns entries from the buffer."""
def gather_all(self):
    """Returns all the items in buffer."""
    return self._gather_all()
def clear(self):
    """Resets the contents of replay buffer"""
```
再生オブジェクトバッファのオブジェクトが初期化される際には、そのオブジェクトが格納する要素の data_spec が必要です。この仕様は、バッファに追加される軌道要素の TensorSpec に対応しています。この仕様は通常、トレーニング時にエージェントが期待する形状、タイプ、構造を定義するエージェントの agent.collect_data_spec を調査することで取得できます（詳細は後述します）。
TFUniformReplayBuffer
TFUniformReplayBuffer は TF-Agents で最も一般的に使用される再生バッファであるため、このチュートリアルではこの再生バッファを使用します。TFUniformReplayBuffer では、バッキングバッファの記憶は tensorflow 変数によって行われるため、計算グラフの一部になっています。
バッファには複数の要素がバッチ単位で格納され、バッチセグメントごとに最大容量の max_length 要素があります。したがって、合計バッファ容量は、batch_size x max_length 要素となります。バッファに格納される要素はすべて、対応するデータ仕様を持っている必要があります。再生バッファがデータ収集に使用される場合、仕様はエージェントの収集データ仕様になります。
バッファの作成:
TFUniformReplayBuffer を作成するには、次の値を渡します。

バッファが格納するデータ要素の仕様
バッファのバッチサイズに対応する batch size
バッチセグメントごとの max_length 要素数

サンプルデータ仕様（batch_size 32 および max_length 1000）を使用して TFUniformReplayBuffer を作成する例を以下に示します。
End of explanation
"""


action = tf.constant(1 * np.ones(
    data_spec[0].shape.as_list(), dtype=np.float32))
lidar = tf.constant(
    2 * np.ones(data_spec[1][0].shape.as_list(), dtype=np.float32))
camera = tf.constant(
    3 * np.ones(data_spec[1][1].shape.as_list(), dtype=np.float32))
  
values = (action, (lidar, camera))
values_batched = tf.nest.map_structure(lambda t: tf.stack([t] * batch_size),
                                       values)
  
replay_buffer.add_batch(values_batched)

"""
Explanation: バッファへの書き込み:
再生バッファに要素を追加するため、items がバッファに追加されるアイテムのバッチを表すテンソルのリスト/タプル/ネストになっている add_batch(items) メソッドを使用します。items の各要素には外側の次元に等しい batch_size が必要であり、残りの次元は（再生バッファのコンストラクタに渡されたデータ仕様と同じ）アイテムのデータ仕様に準拠していなければなりません。
アイテムのバッチを追加する例を以下に示します。
End of explanation
"""


# add more items to the buffer before reading
for _ in range(5):
  replay_buffer.add_batch(values_batched)

# Get one sample from the replay buffer with batch size 10 and 1 timestep:

sample = replay_buffer.get_next(sample_batch_size=10, num_steps=1)

# Convert the replay buffer to a tf.data.Dataset and iterate through it
dataset = replay_buffer.as_dataset(
    sample_batch_size=4,
    num_steps=2)

iterator = iter(dataset)
print("Iterator trajectories:")
trajectories = []
for _ in range(3):
  t, _ = next(iterator)
  trajectories.append(t)
  
print(tf.nest.map_structure(lambda t: t.shape, trajectories))

# Read all elements in the replay buffer:
trajectories = replay_buffer.gather_all()

print("Trajectories from gather all:")
print(tf.nest.map_structure(lambda t: t.shape, trajectories))


"""
Explanation: バッファからの読み込み
TFUniformReplayBuffer からデータを読み込む方法は 3 つあります。

get_next() - バッファからサンプルを 1 つ返します。返されるサンプルバッチサイズとタイムステップ数は、このメソッドの引数で指定できます。
as_dataset() - 再生バッファを tf.data.Dataset として返します。その後、データセットイテレータを作成し、バッファ内のアイテムのサンプルをイテレートできます。
gather_all() - バッファ内のすべてのアイテムを形状 [batch, time, data_spec] を含むテンソルとして返します。

上記の各メソッドを使用して再生バッファから読み込む例を以下に示します。
End of explanation
"""


replay_buffer_capacity = 1000*32 # same capacity as the TFUniformReplayBuffer

py_replay_buffer = py_uniform_replay_buffer.PyUniformReplayBuffer(
    capacity=replay_buffer_capacity,
    data_spec=tensor_spec.to_nest_array_spec(data_spec))

"""
Explanation: PyUniformReplayBuffer
PyUniformReplayBuffer は TFUniformReplayBuffer と同じ機能を持ちますが、データは tf 変数ではなく numpy 配列に格納されます。このバッファはグラフ外のデータ収集に使用されます。numpy にバッキングストレージを用意すると、Tensorflow 変数を使用せずに一部のアプリケーションがデータ操作（優先度を更新するためのインデックス作成など）を実行しやすくなります。ただし、この実装では Tensorflow を使用したグラフ最適化のメリットを享受できません。
エージェントのポリシートラジェクトリの仕様から PyUniformReplayBuffer をインスタンス化する例を以下に示します。
End of explanation
"""


env = suite_gym.load('CartPole-v0')
tf_env = tf_py_environment.TFPyEnvironment(env)

q_net = q_network.QNetwork(
    tf_env.time_step_spec().observation,
    tf_env.action_spec(),
    fc_layer_params=(100,))

agent = dqn_agent.DqnAgent(
    tf_env.time_step_spec(),
    tf_env.action_spec(),
    q_network=q_net,
    optimizer=tf.compat.v1.train.AdamOptimizer(0.001))

replay_buffer_capacity = 1000

replay_buffer = tf_uniform_replay_buffer.TFUniformReplayBuffer(
    agent.collect_data_spec,
    batch_size=tf_env.batch_size,
    max_length=replay_buffer_capacity)

# Add an observer that adds to the replay buffer:
replay_observer = [replay_buffer.add_batch]

collect_steps_per_iteration = 10
collect_op = dynamic_step_driver.DynamicStepDriver(
  tf_env,
  agent.collect_policy,
  observers=replay_observer,
  num_steps=collect_steps_per_iteration).run()

"""
Explanation: トレーニング中に再生バッファを使用する
再生バッファを作成してアイテムを読み書きする方法がわかったので、エージェントのトレーニング中に再生バッファを使用してトラジェクトリを格納できるようになりました。
データ収集
まず、データ収集中に再生バッファを使用する方法を見てみましょう。
TF-Agents では、Driver（詳細は Driver のチュートリアルをご覧ください）を使用して環境内の経験を収集します。Driver を使用するには、Driver がトラジェクトリを受け取ったときに実行する関数である Observer を指定します。
そのため、軌跡要素を再生バッファーに追加するために add_batch(items) を呼び出すオブザーバーを追加してアイテムのバッチを再生バッファーに追加します。
TFUniformReplayBuffer を使用した例を以下に示します。まず、環境、ネットワーク、エージェントを作成します。その後、TFUniformReplayBuffer を作成します。再生バッファ内のトラジェクトリ要素の仕様は、エージェントの収集データ仕様に等しくなっていることに注意してください。その後、その add_batch メソッドをトレーニング中にデータ収集を実行するドライバーのオブザーバーに設定します。
End of explanation
"""


# Read the replay buffer as a Dataset,
# read batches of 4 elements, each with 2 timesteps:
dataset = replay_buffer.as_dataset(
    sample_batch_size=4,
    num_steps=2)

iterator = iter(dataset)

num_train_steps = 10

for _ in range(num_train_steps):
  trajectories, _ = next(iterator)
  loss = agent.train(experience=trajectories)


"""
Explanation: トレーニングステップ用のデータ読み込み
トラジェクトリ要素を再生バッファに追加した後は、再生バッファからトラジェクトリのバッチを読み取り、トレーニングステップの入力データとして使用できます。
トレーニングループ内で再生バッファのトラジェクトリをトレーニングする方法の例を以下に示します。
End of explanation
"""


