انضم إلى TensorFlow في مؤتمر Google I / O ، 11-12 مايو سجل الآن

البيئات

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثب تحميل دفتر

مقدمة

الهدف من التعلم المعزز (RL) هو تصميم وكلاء يتعلمون من خلال التفاعل مع البيئة. في إعداد RL القياسي ، يتلقى الوكيل ملاحظة في كل خطوة زمنية ويختار إجراءً. يتم تطبيق الإجراء على البيئة وتعيد البيئة مكافأة وملاحظة جديدة. يقوم الوكيل بتدريب سياسة لاختيار الإجراءات لتعظيم مجموع المكافآت ، والمعروف أيضًا باسم العائد.

في TF-Agents ، يمكن تنفيذ البيئات إما في Python أو TensorFlow. عادةً ما تكون بيئات Python أسهل في التنفيذ والفهم والتصحيح ، لكن بيئات TensorFlow أكثر كفاءة وتسمح بالتوازي الطبيعي. يتمثل سير العمل الأكثر شيوعًا في تنفيذ بيئة في Python واستخدام أحد أغلفةنا لتحويلها تلقائيًا إلى TensorFlow.

دعونا نلقي نظرة على بيئات بايثون أولاً. تتبع بيئات TensorFlow واجهة برمجة تطبيقات مشابهة جدًا.

اقامة

إذا لم تقم بتثبيت وكلاء tf أو صالة الألعاب الرياضية حتى الآن ، فقم بتشغيل:

pip install "gym>=0.21.0"
pip install tf-agents
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import abc
import tensorflow as tf
import numpy as np

from tf_agents.environments import py_environment
from tf_agents.environments import tf_environment
from tf_agents.environments import tf_py_environment
from tf_agents.environments import utils
from tf_agents.specs import array_spec
from tf_agents.environments import wrappers
from tf_agents.environments import suite_gym
from tf_agents.trajectories import time_step as ts

بيئات بايثون

بيئات الثعبان لها step(action) -> next_time_step الأسلوب الذي ينطبق هذا الإجراء على البيئة، وإرجاع المعلومات التالية حول الخطوة التالية:

  1. observation : هذا هو جزء من الدولة بيئة أن وكيل يمكن مراقبة في اختيار أعمالها في الخطوة التالية.
  2. reward : عامل هو التعلم لتحقيق أقصى قدر مجموع هذه المكافآت عبر خطوات متعددة.
  3. step_type : التفاعلات مع البيئة وعادة ما تكون جزءا من سلسلة / حلقة. على سبيل المثال حركات متعددة في لعبة الشطرنج. step_type يمكن أن تكون إما FIRST ، MID أو LAST لبيان ما إذا كانت هذه الخطوة مرة هي الخطوة الأولى والمتوسطة أو الأخيرة في تسلسل.
  4. discount : هذا هو تعويم يمثل كم لوزن جزاء في الوقت المقبلة خطوة بالنسبة للمكافأة في الخطوة الزمنية الحالية.

يتم تجميع هذه في الصفوف (tuple) اسمه TimeStep(step_type, reward, discount, observation) .

واجهة أن جميع البيئات بيثون يجب أن تنفذ هي في environments/py_environment.PyEnvironment . الطرق الرئيسية هي:

class PyEnvironment(object):

  def reset(self):
    """Return initial_time_step."""
    self._current_time_step = self._reset()
    return self._current_time_step

  def step(self, action):
    """Apply action and return new time_step."""
    if self._current_time_step is None:
        return self.reset()
    self._current_time_step = self._step(action)
    return self._current_time_step

  def current_time_step(self):
    return self._current_time_step

  def time_step_spec(self):
    """Return time_step_spec."""

  @abc.abstractmethod
  def observation_spec(self):
    """Return observation_spec."""

  @abc.abstractmethod
  def action_spec(self):
    """Return action_spec."""

  @abc.abstractmethod
  def _reset(self):
    """Return initial_time_step."""

  @abc.abstractmethod
  def _step(self, action):
    """Apply action and return new time_step."""

بالإضافة إلى step() الأسلوب، كما توفر بيئات ل reset() الأسلوب الذي يبدأ سلسلة جديدة ويوفر الأولي TimeStep . وليس من الضروري استدعاء reset طريقة صراحة. نفترض أن البيئات تتم إعادة تعيينها تلقائيًا ، إما عند وصولها إلى نهاية الحلقة أو عندما يتم استدعاء الخطوة () في المرة الأولى.

علما بأن الفئات الفرعية لا تنفذ step() أو reset() مباشرة. أنها بدلا من ذلك تجاوز _step() و _reset() الأساليب. سوف يكون مؤقتا الخطوات الوقت الذي عاد من هذه الأساليب ويتعرض من خلال current_time_step() .

و observation_spec و action_spec طرق العودة عش (Bounded)ArraySpecs التي تصف الاسم والشكل ونوع البيانات ونطاقات الملاحظات والإجراءات على التوالي.

في TF-Agents نشير مرارًا وتكرارًا إلى الأعشاش التي يتم تعريفها على أنها أي بنية تشبه الشجرة تتكون من قوائم أو مجموعات أو مجموعات مسماة أو قواميس. يمكن أن تتكون هذه بشكل تعسفي للحفاظ على هيكل الملاحظات والإجراءات. لقد وجدنا أن هذا مفيد جدًا للبيئات الأكثر تعقيدًا حيث لديك العديد من الملاحظات والإجراءات.

استخدام البيئات القياسية

وقد بنيت في TF وكلاء مغلفة للعديد من البيئات القياسية مثل OpenAI رياضة، DeepMind السيطرة وأتاري، بحيث أنها تتبع لدينا py_environment.PyEnvironment اجهة. يمكن تحميل هذه البيئات المغلفة بسهولة باستخدام أجنحة البيئة الخاصة بنا. لنقم بتحميل بيئة CartPole من صالة الألعاب الرياضية OpenAI وننظر إلى الإجراء و time_step_spec.

environment = suite_gym.load('CartPole-v0')
print('action_spec:', environment.action_spec())
print('time_step_spec.observation:', environment.time_step_spec().observation)
print('time_step_spec.step_type:', environment.time_step_spec().step_type)
print('time_step_spec.discount:', environment.time_step_spec().discount)
print('time_step_spec.reward:', environment.time_step_spec().reward)
action_spec: BoundedArraySpec(shape=(), dtype=dtype('int64'), name='action', minimum=0, maximum=1)
time_step_spec.observation: BoundedArraySpec(shape=(4,), dtype=dtype('float32'), name='observation', minimum=[-4.8000002e+00 -3.4028235e+38 -4.1887903e-01 -3.4028235e+38], maximum=[4.8000002e+00 3.4028235e+38 4.1887903e-01 3.4028235e+38])
time_step_spec.step_type: ArraySpec(shape=(), dtype=dtype('int32'), name='step_type')
time_step_spec.discount: BoundedArraySpec(shape=(), dtype=dtype('float32'), name='discount', minimum=0.0, maximum=1.0)
time_step_spec.reward: ArraySpec(shape=(), dtype=dtype('float32'), name='reward')

لذلك نرى أن البيئة تتوقع إجراءات من نوع int64 في [0، 1] والعوائد TimeSteps حيث الملاحظات هي float32 ناقل بطول 4 وعامل الخصم هو float32 في [0.0، 1.0]. الآن، دعونا في محاولة لاتخاذ إجراءات ثابتة (1,) للحلقة كاملة.

action = np.array(1, dtype=np.int32)
time_step = environment.reset()
print(time_step)
while not time_step.is_last():
  time_step = environment.step(action)
  print(time_step)
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.0138565 , -0.03582913,  0.04861612, -0.03755046], dtype=float32),
 'reward': array(0., dtype=float32),
 'step_type': array(0, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.01313992,  0.15856317,  0.0478651 , -0.3145069 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.01631118,  0.35297176,  0.04157497, -0.5917188 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.02337062,  0.54748774,  0.02974059, -0.87102115], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.03432037,  0.74219286,  0.01232017, -1.1542072 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.04916423,  0.93715197, -0.01076398, -1.4430016 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.06790727,  1.1324048 , -0.03962401, -1.7390285 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.09055536,  1.327955  , -0.07440457, -2.04377   ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.11711447,  1.523758  , -0.11527998, -2.3585167 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([ 0.14758962,  1.7197047 , -0.16245031, -2.6843033 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(0., dtype=float32),
 'observation': array([ 0.18198372,  1.9156038 , -0.21613638, -3.0218334 ], dtype=float32),
 'reward': array(1., dtype=float32),
 'step_type': array(2, dtype=int32)})

إنشاء بيئة بايثون الخاصة بك

بالنسبة للعديد من العملاء ، فإن حالة الاستخدام الشائعة هي تطبيق أحد الوكلاء المعياريين (انظر الوكلاء /) في TF-Agents على مشكلتهم. للقيام بذلك ، يتعين عليهم تأطير مشكلتهم كبيئة. لذلك دعونا نلقي نظرة على كيفية تنفيذ بيئة في بايثون.

لنفترض أننا نريد تدريب وكيل على لعب لعبة الورق التالية (مستوحاة من Black Jack):

  1. تُلعب اللعبة باستخدام مجموعة لا نهائية من البطاقات مرقمة 1 ... 10.
  2. عند كل منعطف يمكن للوكيل القيام بأمرين: الحصول على بطاقة عشوائية جديدة ، أو إيقاف الجولة الحالية.
  3. الهدف هو الحصول على مجموع أوراقك أقرب ما يمكن إلى 21 في نهاية الجولة ، دون تجاوز.

يمكن أن تبدو البيئة التي تمثل اللعبة كما يلي:

  1. الإجراءات: لدينا عملين. الإجراء 0: الحصول على بطاقة جديدة ، والإجراء 1: إنهاء الجولة الحالية.
  2. ملاحظات: مجموع الأوراق في الجولة الحالية.
  3. المكافأة: الهدف هو الاقتراب من 21 قدر الإمكان دون تجاوز ، لذلك يمكننا تحقيق ذلك باستخدام المكافأة التالية في نهاية الجولة: sum_of_cards - 21 إذا كان sum_of_cards <= 21 ، وإلا -21
class CardGameEnv(py_environment.PyEnvironment):

  def __init__(self):
    self._action_spec = array_spec.BoundedArraySpec(
        shape=(), dtype=np.int32, minimum=0, maximum=1, name='action')
    self._observation_spec = array_spec.BoundedArraySpec(
        shape=(1,), dtype=np.int32, minimum=0, name='observation')
    self._state = 0
    self._episode_ended = False

  def action_spec(self):
    return self._action_spec

  def observation_spec(self):
    return self._observation_spec

  def _reset(self):
    self._state = 0
    self._episode_ended = False
    return ts.restart(np.array([self._state], dtype=np.int32))

  def _step(self, action):

    if self._episode_ended:
      # The last action ended the episode. Ignore the current action and start
      # a new episode.
      return self.reset()

    # Make sure episodes don't go on forever.
    if action == 1:
      self._episode_ended = True
    elif action == 0:
      new_card = np.random.randint(1, 11)
      self._state += new_card
    else:
      raise ValueError('`action` should be 0 or 1.')

    if self._episode_ended or self._state >= 21:
      reward = self._state - 21 if self._state <= 21 else -21
      return ts.termination(np.array([self._state], dtype=np.int32), reward)
    else:
      return ts.transition(
          np.array([self._state], dtype=np.int32), reward=0.0, discount=1.0)

دعونا نتأكد من أننا فعلنا كل شيء بشكل صحيح لتحديد البيئة المذكورة أعلاه. عند إنشاء بيئتك الخاصة ، يجب عليك التأكد من أن الملاحظات والخطوات الزمنية التي تم إنشاؤها تتبع الأشكال والأنواع الصحيحة على النحو المحدد في المواصفات الخاصة بك. تُستخدم هذه لإنشاء رسم بياني TensorFlow وبالتالي يمكن أن تخلق مشاكل يصعب تصحيحها إذا فهمناها بشكل خاطئ.

للتحقق من صحة بيئتنا ، سنستخدم سياسة عشوائية لإنشاء إجراءات وسنكرر أكثر من 5 حلقات للتأكد من أن الأشياء تعمل على النحو المنشود. يظهر خطأ إذا تلقينا time_step لا يتبع مواصفات البيئة.

environment = CardGameEnv()
utils.validate_py_environment(environment, episodes=5)

الآن بعد أن علمنا أن البيئة تعمل على النحو المنشود ، فلنقم بتشغيل هذه البيئة باستخدام سياسة ثابتة: اطلب 3 بطاقات ثم ننهي الجولة.

get_new_card_action = np.array(0, dtype=np.int32)
end_round_action = np.array(1, dtype=np.int32)

environment = CardGameEnv()
time_step = environment.reset()
print(time_step)
cumulative_reward = time_step.reward

for _ in range(3):
  time_step = environment.step(get_new_card_action)
  print(time_step)
  cumulative_reward += time_step.reward

time_step = environment.step(end_round_action)
print(time_step)
cumulative_reward += time_step.reward
print('Final Reward = ', cumulative_reward)
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([0], dtype=int32),
 'reward': array(0., dtype=float32),
 'step_type': array(0, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([9], dtype=int32),
 'reward': array(0., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(1., dtype=float32),
 'observation': array([12], dtype=int32),
 'reward': array(0., dtype=float32),
 'step_type': array(1, dtype=int32)})
TimeStep(
{'discount': array(0., dtype=float32),
 'observation': array([21], dtype=int32),
 'reward': array(0., dtype=float32),
 'step_type': array(2, dtype=int32)})
TimeStep(
{'discount': array(0., dtype=float32),
 'observation': array([21], dtype=int32),
 'reward': array(0., dtype=float32),
 'step_type': array(2, dtype=int32)})
Final Reward =  0.0

أغلفة البيئة

يأخذ برنامج تضمين البيئة بيئة Python ويعيد نسخة معدلة من البيئة. كل من البيئة الأصلية والبيئة المعدلة هي حالات py_environment.PyEnvironment ، ومغلفة متعددة يمكن بالسلاسل معا.

بعض مغلفة شيوعا يمكن العثور عليها في environments/wrappers.py . على سبيل المثال:

  1. ActionDiscretizeWrapper : تحويل مساحة العمل المستمر لمساحة عمل منفصلة.
  2. RunStats : يلتقط تشغيل إحصاءات البيئة مثل عدد الخطوات التي اتخذت، أكمل عدد من الحلقات الخ
  3. TimeLimit : إنهاء الحلقة بعد عدد محدد من الخطوات.

مثال 1: غلاف تمييز الإجراء

InvertedPendulum هي بيئة PyBullet أن يقبل الإجراءات مستمرة في نطاق [-2, 2] . إذا أردنا تدريب عامل عمل منفصل مثل DQN على هذه البيئة ، فعلينا تحديد (تحديد) مساحة العمل. وهذا هو بالضبط ما ActionDiscretizeWrapper يفعل. مقارنة action_spec قبل وبعد التفاف:

env = suite_gym.load('Pendulum-v1')
print('Action Spec:', env.action_spec())

discrete_action_env = wrappers.ActionDiscretizeWrapper(env, num_actions=5)
print('Discretized Action Spec:', discrete_action_env.action_spec())
Action Spec: BoundedArraySpec(shape=(1,), dtype=dtype('float32'), name='action', minimum=-2.0, maximum=2.0)
Discretized Action Spec: BoundedArraySpec(shape=(), dtype=dtype('int32'), name='action', minimum=0, maximum=4)

واختتم discrete_action_env هو مثيل py_environment.PyEnvironment ويمكن علاج مثل بيئة بيثون العادية.

بيئات TensorFlow

يتم تعريف واجهة لبيئات TF في environments/tf_environment.TFEnvironment وتبدو مشابهة جدا لبيئات بيثون. تختلف بيئات TF عن بيئة Python بطريقتين:

  • يقومون بإنشاء كائنات موتر بدلاً من المصفوفات
  • تضيف بيئات TF بعدًا دفعيًا إلى الموترات التي تم إنشاؤها عند مقارنتها بالمواصفات.

يسمح تحويل بيئات Python إلى TFEnvs لتدفق التوتر بموازنة العمليات. على سبيل المثال، يمكن للمرء تحديد collect_experience_op التي تقوم بجمع البيانات من بيئة ويضيف إلى replay_buffer ، و train_op الذي يقرأ من replay_buffer والقطارات وكيل، وتشغيلها في نفس الوقت بشكل طبيعي في TensorFlow.

class TFEnvironment(object):

  def time_step_spec(self):
    """Describes the `TimeStep` tensors returned by `step()`."""

  def observation_spec(self):
    """Defines the `TensorSpec` of observations provided by the environment."""

  def action_spec(self):
    """Describes the TensorSpecs of the action expected by `step(action)`."""

  def reset(self):
    """Returns the current `TimeStep` after resetting the Environment."""
    return self._reset()

  def current_time_step(self):
    """Returns the current `TimeStep`."""
    return self._current_time_step()

  def step(self, action):
    """Applies the action and returns the new `TimeStep`."""
    return self._step(action)

  @abc.abstractmethod
  def _reset(self):
    """Returns the current `TimeStep` after resetting the Environment."""

  @abc.abstractmethod
  def _current_time_step(self):
    """Returns the current `TimeStep`."""

  @abc.abstractmethod
  def _step(self, action):
    """Applies the action and returns the new `TimeStep`."""

و current_time_step() الأسلوب بإرجاع time_step الحالي وتهيئة البيئة إذا لزم الأمر.

ل reset() قوات طريقة إعادة تعيين في البيئة وعوائد CURRENT_STEP.

إذا كان action لا يتوقف على السابق time_step على tf.control_dependency هو مطلوب في Graph واسطة.

الآن، دعونا نلقي نظرة على كيف TFEnvironments يتم إنشاؤها.

إنشاء بيئة TensorFlow الخاصة بك

هذا أكثر تعقيدًا من إنشاء البيئات في بايثون ، لذلك لن نغطيه في هذا الكولاب. مثال على ذلك هو متاح هنا . حالة الاستخدام الأكثر شيوعا هو تنفيذ بيئتك في بيثون والتفاف عليه في TensorFlow استخدام لدينا TFPyEnvironment المجمع (انظر أدناه).

تغليف بيئة Python في TensorFlow

يمكننا بسهولة التفاف أي بيئة بيثون في بيئة TensorFlow باستخدام TFPyEnvironment المجمع.

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

print(isinstance(tf_env, tf_environment.TFEnvironment))
print("TimeStep Specs:", tf_env.time_step_spec())
print("Action Specs:", tf_env.action_spec())
True
TimeStep Specs: TimeStep(
{'discount': BoundedTensorSpec(shape=(), dtype=tf.float32, name='discount', minimum=array(0., dtype=float32), maximum=array(1., dtype=float32)),
 'observation': BoundedTensorSpec(shape=(4,), dtype=tf.float32, name='observation', minimum=array([-4.8000002e+00, -3.4028235e+38, -4.1887903e-01, -3.4028235e+38],
      dtype=float32), maximum=array([4.8000002e+00, 3.4028235e+38, 4.1887903e-01, 3.4028235e+38],
      dtype=float32)),
 'reward': TensorSpec(shape=(), dtype=tf.float32, name='reward'),
 'step_type': TensorSpec(shape=(), dtype=tf.int32, name='step_type')})
Action Specs: BoundedTensorSpec(shape=(), dtype=tf.int64, name='action', minimum=array(0), maximum=array(1))

لاحظ المواصفات هي الآن من نوع: (Bounded)TensorSpec .

أمثلة على الاستخدام

مثال بسيط

env = suite_gym.load('CartPole-v0')

tf_env = tf_py_environment.TFPyEnvironment(env)
# reset() creates the initial time_step after resetting the environment.
time_step = tf_env.reset()
num_steps = 3
transitions = []
reward = 0
for i in range(num_steps):
  action = tf.constant([i % 2])
  # applies the action and returns the new TimeStep.
  next_time_step = tf_env.step(action)
  transitions.append([time_step, action, next_time_step])
  reward += next_time_step.reward
  time_step = next_time_step

np_transitions = tf.nest.map_structure(lambda x: x.numpy(), transitions)
print('\n'.join(map(str, np_transitions)))
print('Total reward:', reward.numpy())
[TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.0078796 , -0.04736348, -0.04966116,  0.04563603]],
      dtype=float32),
 'reward': array([0.], dtype=float32),
 'step_type': array([0], dtype=int32)}), array([0], dtype=int32), TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.00882687, -0.24173944, -0.04874843,  0.32224613]],
      dtype=float32),
 'reward': array([1.], dtype=float32),
 'step_type': array([1], dtype=int32)})]
[TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.00882687, -0.24173944, -0.04874843,  0.32224613]],
      dtype=float32),
 'reward': array([1.], dtype=float32),
 'step_type': array([1], dtype=int32)}), array([1], dtype=int32), TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.01366166, -0.04595843, -0.04230351,  0.01459712]],
      dtype=float32),
 'reward': array([1.], dtype=float32),
 'step_type': array([1], dtype=int32)})]
[TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.01366166, -0.04595843, -0.04230351,  0.01459712]],
      dtype=float32),
 'reward': array([1.], dtype=float32),
 'step_type': array([1], dtype=int32)}), array([0], dtype=int32), TimeStep(
{'discount': array([1.], dtype=float32),
 'observation': array([[-0.01458083, -0.24044897, -0.04201157,  0.2936384 ]],
      dtype=float32),
 'reward': array([1.], dtype=float32),
 'step_type': array([1], dtype=int32)})]
Total reward: [3.]

حلقات كاملة

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

time_step = tf_env.reset()
rewards = []
steps = []
num_episodes = 5

for _ in range(num_episodes):
  episode_reward = 0
  episode_steps = 0
  while not time_step.is_last():
    action = tf.random.uniform([1], 0, 2, dtype=tf.int32)
    time_step = tf_env.step(action)
    episode_steps += 1
    episode_reward += time_step.reward.numpy()
  rewards.append(episode_reward)
  steps.append(episode_steps)
  time_step = tf_env.reset()

num_steps = np.sum(steps)
avg_length = np.mean(steps)
avg_reward = np.mean(rewards)

print('num_episodes:', num_episodes, 'num_steps:', num_steps)
print('avg_length', avg_length, 'avg_reward:', avg_reward)
num_episodes: 5 num_steps: 131
avg_length 26.2 avg_reward: 26.2