From 8aa56c12e60ba77325f6b3817ee5f0d8e1ed1a16 Mon Sep 17 00:00:00 2001 From: Eric Liang Date: Sun, 1 Jul 2018 00:05:08 -0700 Subject: [PATCH] [rllib] Document "v2" APIs (#2316) * re * wip * wip * a3c working * torch support * pg works * lint * rm v2 * consumer id * clean up pg * clean up more * fix python 2.7 * tf session management * docs * dqn wip * fix compile * dqn * apex runs * up * impotrs * ddpg * quotes * fix tests * fix last r * fix tests * lint * pass checkpoint restore * kwar * nits * policy graph * fix yapf * com * class * pyt * vectorization * update * test cpe * unit test * fix ddpg2 * changes * wip * args * faster test * common * fix * add alg option * batch mode and policy serving * multi serving test * todo * wip * serving test * doc async env * num envs * comments * thread * remove init hook * update * fix ppo * comments1 * fix * updates * add jenkins tests * fix * fix pytorch * fix * fixes * fix a3c policy * fix squeeze * fix trunc on apex * fix squeezing for real * update * remove horizon test for now * multiagent wip * update * fix race condition * fix ma * t * doc * st * wip * example * wip * working * cartpole * wip * batch wip * fix bug * make other_batches None default * working * debug * nit * warn * comments * fix ppo * fix obs filter * update * wip * tf * update * fix * cleanup * cleanup * spacing * model * fix * dqn * fix ddpg * doc * keep names * update * fix * com * docs * clarify model outputs * Update torch_policy_graph.py * fix obs filter * pass thru worker index * fix * rename * vlad torch comments * fix log action * debug name * fix lstm * remove unused ddpg net * remove conv net * revert lstm * wip * wip * cast * wip * works * fix a3c * works * lstm util test * doc * clean up * update * fix lstm check * move to end * fix sphinx * fix cmd * remove bad doc * envs * vec * doc prep * models * rl * alg * up * clarify * copy * async sa * fix * comments * fix a3c conf * tune lstm * fix reshape * fix * back to 16 * tuned a3c update * update * tuned * optional * merge * wip * fix up * move pg class * rename env * wip * update * tip * alg * readme * fix catalog * readme * doc * context * remove prep * comma * add env * link to paper * paper * update * rnn * update * wip * clean up ev creation * fix * fix * fix * fix lint * up * no comma * ma * Update run_multi_node_tests.sh * fix * sphinx is stupid * sphinx is stupid * clarify torch graph * no horizon * fix config * sb * Update test_optimizers.py --- doc/source/apex.png | Bin 0 -> 62996 bytes doc/source/es.png | Bin 0 -> 39724 bytes doc/source/index.rst | 7 +- doc/source/multi-agent.svg | 1 + doc/source/policy-optimizers.rst | 69 ---- doc/source/ppo.png | Bin 0 -> 30706 bytes doc/source/rllib-algorithms.rst | 67 +++ doc/source/rllib-dev.rst | 102 ----- doc/source/rllib-env.rst | 142 +++++++ doc/source/rllib-envs.svg | 1 + doc/source/rllib-models.rst | 77 ++++ doc/source/rllib-package-ref.rst | 47 +++ doc/source/rllib-stack.svg | 1 + doc/source/rllib-training.rst | 130 ++++++ doc/source/rllib.rst | 386 +++--------------- doc/source/throughput.png | Bin 0 -> 32476 bytes python/ray/experimental/internal_kv.py | 2 +- python/ray/rllib/README.rst | 39 +- python/ray/rllib/__init__.py | 19 +- python/ray/rllib/a3c/__init__.py | 3 - python/ray/rllib/agents/__init__.py | 3 + python/ray/rllib/agents/a3c/__init__.py | 3 + python/ray/rllib/{ => agents}/a3c/a3c.py | 101 ++--- .../rllib/{ => agents}/a3c/a3c_tf_policy.py | 6 +- .../{ => agents}/a3c/a3c_torch_policy.py | 6 +- python/ray/rllib/{ => agents}/agent.py | 126 +++++- python/ray/rllib/agents/bc/__init__.py | 3 + python/ray/rllib/{ => agents}/bc/bc.py | 6 +- .../ray/rllib/{ => agents}/bc/bc_evaluator.py | 6 +- .../{ => agents}/bc/experience_dataset.py | 0 python/ray/rllib/{ => agents}/bc/policy.py | 0 python/ray/rllib/{ => agents}/ddpg/README.md | 0 .../ray/rllib/{ => agents}/ddpg/__init__.py | 4 +- python/ray/rllib/{ => agents}/ddpg/apex.py | 6 +- .../{ => agents}/ddpg/common/__init__.py | 0 python/ray/rllib/{ => agents}/ddpg/ddpg.py | 28 +- .../{ => agents}/ddpg/ddpg_policy_graph.py | 8 +- python/ray/rllib/{ => agents}/dqn/README.md | 0 python/ray/rllib/{ => agents}/dqn/__init__.py | 4 +- python/ray/rllib/{ => agents}/dqn/apex.py | 8 +- .../rllib/{ => agents}/dqn/common/__init__.py | 0 .../rllib/{ => agents}/dqn/common/wrappers.py | 0 python/ray/rllib/{ => agents}/dqn/dqn.py | 80 ++-- .../{ => agents}/dqn/dqn_policy_graph.py | 6 +- python/ray/rllib/agents/es/__init__.py | 3 + python/ray/rllib/{ => agents}/es/es.py | 14 +- .../ray/rllib/{ => agents}/es/optimizers.py | 0 python/ray/rllib/{ => agents}/es/policies.py | 0 .../rllib/{ => agents}/es/tabular_logger.py | 0 python/ray/rllib/{ => agents}/es/utils.py | 0 python/ray/rllib/agents/pg/__init__.py | 3 + python/ray/rllib/agents/pg/pg.py | 54 +++ .../rllib/{ => agents}/pg/pg_policy_graph.py | 6 +- python/ray/rllib/agents/ppo/__init__.py | 3 + python/ray/rllib/{ => agents}/ppo/ppo.py | 123 ++---- .../rllib/{ => agents}/ppo/ppo_tf_policy.py | 5 +- python/ray/rllib/{ => agents}/ppo/rollout.py | 2 +- .../ray/rllib/{ => agents}/ppo/test/test.py | 2 +- python/ray/rllib/{ => agents}/ppo/utils.py | 0 python/ray/rllib/bc/__init__.py | 3 - python/ray/rllib/env/__init__.py | 9 + .../rllib/{utils => env}/async_vector_env.py | 10 +- .../rllib/{utils => env}/atari_wrappers.py | 0 .../ray/rllib/{utils => env}/env_context.py | 0 .../rllib/{utils => env}/multi_agent_env.py | 6 +- .../ray/rllib/{utils => env}/serving_env.py | 0 python/ray/rllib/{utils => env}/vector_env.py | 2 +- python/ray/rllib/es/__init__.py | 3 - python/ray/rllib/evaluation/__init__.py | 14 + .../common_policy_evaluator.py | 123 ++---- .../interface.py} | 15 +- python/ray/rllib/evaluation/metrics.py | 48 +++ .../{utils => evaluation}/policy_graph.py | 0 .../{utils => evaluation}/postprocessing.py | 2 +- .../sample_batch.py | 0 .../rllib/{utils => evaluation}/sampler.py | 4 +- .../{utils => evaluation}/tf_policy_graph.py | 2 +- .../torch_policy_graph.py | 18 +- .../multiagent_mountaincar.py | 2 +- .../legacy_multiagent/multiagent_pendulum.py | 2 +- .../ray/rllib/examples/multiagent_cartpole.py | 4 +- .../rllib/examples/serving/cartpole_server.py | 4 +- python/ray/rllib/models/__init__.py | 4 +- python/ray/rllib/optimizers/__init__.py | 9 +- .../optimizers/async_samples_optimizer.py | 2 +- .../rllib/optimizers/multi_gpu_optimizer.py | 4 +- .../ray/rllib/optimizers/policy_optimizer.py | 30 +- .../rllib/optimizers/sync_replay_optimizer.py | 2 +- .../optimizers/sync_samples_optimizer.py | 2 +- python/ray/rllib/pg/__init__.py | 3 - python/ray/rllib/pg/pg.py | 86 ---- python/ray/rllib/ppo/__init__.py | 3 - python/ray/rllib/rollout.py | 4 +- python/ray/rllib/test/mock_evaluator.py | 2 +- .../ray/rllib/test/test_checkpoint_restore.py | 2 +- .../test/test_common_policy_evaluator.py | 15 +- python/ray/rllib/test/test_evaluators.py | 2 +- python/ray/rllib/test/test_multi_agent_env.py | 14 +- python/ray/rllib/test/test_optimizers.py | 3 +- python/ray/rllib/test/test_serving_env.py | 8 +- .../ray/rllib/test/test_supported_spaces.py | 3 +- .../ray/rllib/tuned_examples/hopper-ppo.yaml | 10 +- .../tuned_examples/humanoid-ppo-gae.yaml | 16 +- .../rllib/tuned_examples/humanoid-ppo.yaml | 15 +- .../tuned_examples/pong-a3c-pytorch.yaml | 2 +- python/ray/rllib/tuned_examples/pong-a3c.yaml | 2 +- python/ray/rllib/tuned_examples/pong-ppo.yaml | 2 +- .../rllib/tuned_examples/walker2d-ppo.yaml | 9 +- python/ray/rllib/utils/__init__.py | 10 +- python/ray/rllib/utils/policy_client.py | 2 +- python/ray/rllib/utils/policy_server.py | 28 ++ .../rllib/{dqn/common => utils}/schedules.py | 0 test/jenkins_tests/run_multi_node_tests.sh | 12 +- 113 files changed, 1148 insertions(+), 1141 deletions(-) create mode 100644 doc/source/apex.png create mode 100644 doc/source/es.png create mode 100644 doc/source/multi-agent.svg delete mode 100644 doc/source/policy-optimizers.rst create mode 100644 doc/source/ppo.png create mode 100644 doc/source/rllib-algorithms.rst delete mode 100644 doc/source/rllib-dev.rst create mode 100644 doc/source/rllib-env.rst create mode 100644 doc/source/rllib-envs.svg create mode 100644 doc/source/rllib-models.rst create mode 100644 doc/source/rllib-package-ref.rst create mode 100644 doc/source/rllib-stack.svg create mode 100644 doc/source/rllib-training.rst create mode 100644 doc/source/throughput.png delete mode 100644 python/ray/rllib/a3c/__init__.py create mode 100644 python/ray/rllib/agents/__init__.py create mode 100644 python/ray/rllib/agents/a3c/__init__.py rename python/ray/rllib/{ => agents}/a3c/a3c.py (53%) rename python/ray/rllib/{ => agents}/a3c/a3c_tf_policy.py (96%) rename python/ray/rllib/{ => agents}/a3c/a3c_torch_policy.py (92%) rename python/ray/rllib/{ => agents}/agent.py (67%) create mode 100644 python/ray/rllib/agents/bc/__init__.py rename python/ray/rllib/{ => agents}/bc/bc.py (95%) rename python/ray/rllib/{ => agents}/bc/bc_evaluator.py (90%) rename python/ray/rllib/{ => agents}/bc/experience_dataset.py (100%) rename python/ray/rllib/{ => agents}/bc/policy.py (100%) rename python/ray/rllib/{ => agents}/ddpg/README.md (100%) rename python/ray/rllib/{ => agents}/ddpg/__init__.py (59%) rename python/ray/rllib/{ => agents}/ddpg/apex.py (91%) rename python/ray/rllib/{ => agents}/ddpg/common/__init__.py (100%) rename python/ray/rllib/{ => agents}/ddpg/ddpg.py (88%) rename python/ray/rllib/{ => agents}/ddpg/ddpg_policy_graph.py (98%) rename python/ray/rllib/{ => agents}/dqn/README.md (100%) rename python/ray/rllib/{ => agents}/dqn/__init__.py (60%) rename python/ray/rllib/{ => agents}/dqn/apex.py (89%) rename python/ray/rllib/{ => agents}/dqn/common/__init__.py (100%) rename python/ray/rllib/{ => agents}/dqn/common/wrappers.py (100%) rename python/ray/rllib/{ => agents}/dqn/dqn.py (75%) rename python/ray/rllib/{ => agents}/dqn/dqn_policy_graph.py (98%) create mode 100644 python/ray/rllib/agents/es/__init__.py rename python/ray/rllib/{ => agents}/es/es.py (97%) rename python/ray/rllib/{ => agents}/es/optimizers.py (100%) rename python/ray/rllib/{ => agents}/es/policies.py (100%) rename python/ray/rllib/{ => agents}/es/tabular_logger.py (100%) rename python/ray/rllib/{ => agents}/es/utils.py (100%) create mode 100644 python/ray/rllib/agents/pg/__init__.py create mode 100644 python/ray/rllib/agents/pg/pg.py rename python/ray/rllib/{ => agents}/pg/pg_policy_graph.py (91%) create mode 100644 python/ray/rllib/agents/ppo/__init__.py rename python/ray/rllib/{ => agents}/ppo/ppo.py (53%) rename python/ray/rllib/{ => agents}/ppo/ppo_tf_policy.py (98%) rename python/ray/rllib/{ => agents}/ppo/rollout.py (95%) rename python/ray/rllib/{ => agents}/ppo/test/test.py (97%) rename python/ray/rllib/{ => agents}/ppo/utils.py (100%) delete mode 100644 python/ray/rllib/bc/__init__.py create mode 100644 python/ray/rllib/env/__init__.py rename python/ray/rllib/{utils => env}/async_vector_env.py (98%) rename python/ray/rllib/{utils => env}/atari_wrappers.py (100%) rename python/ray/rllib/{utils => env}/env_context.py (100%) rename python/ray/rllib/{utils => env}/multi_agent_env.py (91%) rename python/ray/rllib/{utils => env}/serving_env.py (100%) rename python/ray/rllib/{utils => env}/vector_env.py (98%) delete mode 100644 python/ray/rllib/es/__init__.py create mode 100644 python/ray/rllib/evaluation/__init__.py rename python/ray/rllib/{utils => evaluation}/common_policy_evaluator.py (82%) rename python/ray/rllib/{optimizers/policy_evaluator.py => evaluation/interface.py} (88%) create mode 100644 python/ray/rllib/evaluation/metrics.py rename python/ray/rllib/{utils => evaluation}/policy_graph.py (100%) rename python/ray/rllib/{utils => evaluation}/postprocessing.py (96%) rename python/ray/rllib/{optimizers => evaluation}/sample_batch.py (100%) rename python/ray/rllib/{utils => evaluation}/sampler.py (99%) rename python/ray/rllib/{utils => evaluation}/tf_policy_graph.py (99%) rename python/ray/rllib/{utils => evaluation}/torch_policy_graph.py (88%) delete mode 100644 python/ray/rllib/pg/__init__.py delete mode 100644 python/ray/rllib/pg/pg.py delete mode 100644 python/ray/rllib/ppo/__init__.py rename python/ray/rllib/{dqn/common => utils}/schedules.py (100%) diff --git a/doc/source/apex.png b/doc/source/apex.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6a3c5e6b9fccb49363629052d6802d4c35a801 GIT binary patch literal 62996 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

iw1JZBU@(;QeC_1Uc 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 J?#f#R{15n^Muh+X literal 0 HcmV?d00001 diff --git a/doc/source/index.rst b/doc/source/index.rst index ae43afd39..da488dd73 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -79,8 +79,11 @@ Ray comes with libraries that accelerate deep learning and reinforcement learnin :caption: Ray RLlib rllib.rst - policy-optimizers.rst - rllib-dev.rst + rllib-training.rst + rllib-env.rst + rllib-algorithms.rst + rllib-models.rst + rllib-package-ref.rst .. toctree:: :maxdepth: 1 diff --git a/doc/source/multi-agent.svg b/doc/source/multi-agent.svg new file mode 100644 index 000000000..a99b604aa --- /dev/null +++ b/doc/source/multi-agent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/policy-optimizers.rst b/doc/source/policy-optimizers.rst deleted file mode 100644 index 8753c2932..000000000 --- a/doc/source/policy-optimizers.rst +++ /dev/null @@ -1,69 +0,0 @@ -Policy Optimizers -================= - -RLlib supports using its policy optimizer implementations from external algorithms. - -Example of constructing and using a policy optimizer `(link to full example) `__: - -.. code-block:: python - - ray.init() - env_creator = lambda env_config: gym.make("PongNoFrameskip-v4") - optimizer = LocalSyncReplayOptimizer.make( - YourEvaluatorClass, [env_creator], num_workers=0, optimizer_config={}) - - i = 0 - while optimizer.num_steps_sampled < 100000: - i += 1 - print("== optimizer step {} ==".format(i)) - optimizer.step() - print("optimizer stats", optimizer.stats()) - print("local evaluator stats", optimizer.local_evaluator.stats()) - -Read more about policy optimizers in this post: `Distributed Policy Optimizers for Scalable and Reproducible Deep RL `__. - -Here are the steps for using a RLlib policy optimizer with an existing algorithm. - -1. Implement the `Policy evaluator interface `__. - - - Here is an example of porting a `PyTorch Rainbow implementation `__. - - - Another example porting a `TensorFlow DQN implementation `__. - -2. Pick a `Policy optimizer class `__. The `LocalSyncOptimizer `__ is a reasonable choice for local testing. You can also implement your own. Policy optimizers can be constructed using their ``make`` method (e.g., ``LocalSyncOptimizer.make(evaluator_cls, evaluator_args, num_workers, optimizer_config)``), or you can construct them by passing in a list of evaluators instantiated as Ray actors. - - - Here is code showing the `simple Policy Gradient agent `__ using ``make()``. - - - A different example showing an `A3C agent `__ passing in Ray actors directly. - -3. Decide how you want to drive the training loop. - - - Option 1: call ``optimizer.step()`` from some existing training code. Training statistics can be retrieved by querying the ``optimizer.local_evaluator`` evaluator instance, or mapping over the remote evaluators (e.g., ``ray.get([ev.some_fn.remote() for ev in optimizer.remote_evaluators])``) if you are running with multiple workers. - - - Option 2: define a full RLlib `Agent class `__. This might be preferable if you don't have an existing training harness or want to use features provided by `Ray Tune `__. - -Available Policy Optimizers ---------------------------- - -+-----------------------------+---------------------+-----------------+------------------------------+ -| **Policy optimizer class** | **Operating range** | **Works with** | **Description** | -+=============================+=====================+=================+==============================+ -|AsyncOptimizer |1-10s of CPUs |(any) |Asynchronous gradient-based | -| | | |optimization (e.g., A3C) | -+-----------------------------+---------------------+-----------------+------------------------------+ -|LocalSyncOptimizer |0-1 GPUs + |(any) |Synchronous gradient-based | -| |1-100s of CPUs | |optimization with parallel | -| | | |sample collection | -+-----------------------------+---------------------+-----------------+------------------------------+ -|LocalSyncReplayOptimizer |0-1 GPUs + | Off-policy |Adds a replay buffer | -| |1-100s of CPUs | algorithms |to LocalSyncOptimizer | -+-----------------------------+---------------------+-----------------+------------------------------+ -|LocalMultiGPUOptimizer |0-10 GPUs + | Algorithms |Implements data-parallel | -| |1-100s of CPUs | written in |optimization over multiple | -| | | TensorFlow |GPUs, e.g., for PPO | -+-----------------------------+---------------------+-----------------+------------------------------+ -|ApexOptimizer |1 GPU + | Off-policy |Implements the Ape-X | -| |10-100s of CPUs | algorithms |distributed prioritization | -| | | w/sample |algorithm | -| | | prioritization | | -+-----------------------------+---------------------+-----------------+------------------------------+ diff --git a/doc/source/ppo.png b/doc/source/ppo.png new file mode 100644 index 0000000000000000000000000000000000000000..c9d358f05a1cebe6bdf011af682612deac2ebc96 GIT binary patch literal 30706 zcmce;XH-?|wk^2KIe>sl5Ji$?$)W^xp+w0UM9C7BBqBk#fg~a#NEAc?35$$KR*?(> zN=87*f{5e{)fet_?|rY;t7`S9W;^ZNc8{z%zxjnRdher;>7#t<;+~zfI|&5B9$6Wp zDuJ*mfI!$_y?rD8mp&q$0shY>hjX&(+qZA;{Gs$6|LCU}J2S_dSM5y+=C*b= zrrZw3_NJz`4ijoBm&jjTH}!+&7A03XkNql=CY~KVcioDbTJM8@ zY_snx-#(WVr`BdQn>f;|>Wg>tb*(f$`R>%tC8-~#zIdbI)jQ=4+?mHY?}pxAUVZ0T zC&DIG8MR?!+ECx}4TqARR(Hdf3A2+?bKfgX_{pOsxNuHn?+B5LV_{(l4h}9aFK=mS zxp3h^zT7VIzupmiqdDGF;yKY-&o!fNdMMlr%8%wieIW_yq z5713rOm6=6?VDnRFoWpzn9NMoiQ=%Zu&Ai0qM{;yf5lt3Zr!+XGP<6Zmsjgo>3}`k zrO5eaEkmc#7R9-|P2^`eBqZsQQc{*@JMGS&KR@7PnQ$rU!lOecue`srVbfN};f9xH zGPbtA{pmz(zr70#4V@o~_T(4I)BpXeJ*#A8R`24)i|%e)$z!odRo6&1E`OBgGQmV~ zN>o%KO3Z1nHf&{SPHV04@ZrN3AJd<*@2{L1s`sfZD;sD|*6J|dP5vRbSCXaEpYI>= zH&!hvI`6K(#4DKbzDZ4e{P^+e;%J(Ky*&p9hhW_S@?YyRX*7?7379W0FW1)9<#_t5 zbNKuDMev*Y&IVZK_xAOP*mWxqot>S#3U1o{awPwxn+7**Z*M0G-st@lC;xm^DPlKm z^>agycG0!v4fum_@44>VN{RpU3Omb@e?V~hGQ!1qB@XjcD~;L4oK$mYas=CU!}X!hQHGu?PCviI(a_NN zN69;+-yFZJrPVsoS0UoeRQKW4#ehpM&Mc&PuGCajC1yBW+q;)R%*4p(yipJiUpUosVV{9>j*wGWXvT_@5rd_03@IKr;@7*n83Q=5SBAA`G})OXOc#s%(LKN4Q*`;JG=28Kdu@Xec{Wn?44TJcjU~Q zTZUXT2UMJ$XR-8FZ*_HauF7-aMiwF3TFDs0LI}6sbf8?3#PXlgrY3c2hHqV62M!z< zwdCgD(9N~&!pzCb%ZHsF92^W{RqV(#^>bSZk}%S#s;q3wce1KmYHn^OPFFB`_74m+ zThB~S6LZKRrjD|}a%Og{v&UnJ+d6>-7<^OSU@`#+C zcJ%c0j8j!Qx}RRcZT?%H!$BT>m$|N+GZZzN^)JLdmnSO1=R|BeKIy0EWW^g-JeDJ^ zpLOz_eJ6cP)%ExFHHv?8r*j@ZwLFl#!PvM@lcd{RXlG$jso&P#-rPZ+ z^cWo@EmC)hXSDa$-G>^q<2Wb|)6p@CIeKeUwvhC)GBdr|afykAm;TtV5+Q8k=H^CE zPmjR_MMpDIQfAuqNOYCA#HFPC!i=Oy{G-fn!gEB@LtjIqj3eagwQKE(YHWKplAlCN zQD7O|K9gnFQ@kj9f%pEo9~hfYWmA*m-0v=hyb}j$X{WhV+JjfZrs_UxUJN2yg+__K4oKsh~0G1qs#jFj(#KxwJ ze7th)dNeZ#2el82*wRPW=VoSF{vw9^%+I^Tja$Fu`L)WhcjwNI!dp`aJiM0+MO~*Y z#0S*VDgqc2;^QY5+~nnJoX0wfmVOst2Rk{1*coJhiRGU+#!&2L5X;s|yG-kqm8^+LH8M6{Sy@R?iuSneMw+M0V+_{ez8QrJ5ZuV^1yczGav@+L&*$nm_*9qY@Oub=m z={hy2XkyZjxNT%)Wa(kb+f!dM_(^T+^O~HDj1yd3R68F~`Vc3wEDj`x2?kosN%Cr_ zGX>LL7bQ|HF6LbI=iS@=W1^*_{n~;hmi78dD=@dr15

b9@)>JnT$}2 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 +`[implementation] `__ +Ape-X variations of DQN and DDPG (`APEX_DQN `__, `APEX_DDPG `__ in RLlib) use a single GPU learner and many CPU workers for experience collection. Experience collection can scale to hundreds of CPU workers due to the distributed prioritization of experience prior to storage in replay buffers. + +Tuned examples: `PongNoFrameskip-v4 `__, `Pendulum-v0 `__, `MountainCarContinuous-v0 `__ + +.. figure:: apex.png + + Ape-X using 32 workers in RLlib vs vanilla DQN (orange) and A3C (blue) on PongNoFrameskip-v4. + +Asynchronous Advantage Actor-Critic +----------------------------------- +`[paper] `__ `[implementation] `__ +RLlib's A3C uses the AsyncGradientsOptimizer to apply gradients computed remotely on policy evaluation actors. It scales to up to 16-32 worker processes, depending on the environment. Both a TensorFlow (LSTM), and PyTorch version are available. + +Tuned examples: `PongDeterministic-v4 `__, `PyTorch version `__ + +Deep Deterministic Policy Gradients +----------------------------------- +`[paper] `__ `[implementation] `__ +DDPG is implemented similarly to DQN (below). The algorithm can be scaled by increasing the number of workers, switching to AsyncGradientsOptimizer, or using Ape-X. + +Tuned examples: `Pendulum-v0 `__, `MountainCarContinuous-v0 `__, `HalfCheetah-v2 `__ + +Deep Q Networks +--------------- +`[paper] `__ `[implementation] `__ +RLlib DQN is implemented using the SyncReplayOptimizer. The algorithm can be scaled by increasing the number of workers, using the AsyncGradientsOptimizer for async DQN, or using Ape-X. Memory usage is reduced by compressing samples in the replay buffer with LZ4. + +Tuned examples: `PongDeterministic-v4 `__ + +Evolution Strategies +-------------------- +`[paper] `__ `[implementation] `__ +Code here is adapted from https://github.com/openai/evolution-strategies-starter to execute in the distributed setting with Ray. + +Tuned examples: `Humanoid-v1 `__ + +.. figure:: es.png + :width: 500px + :align: center + + RLlib's ES implementation scales further and is faster than a reference Redis implementation. + +Policy Gradients +---------------- +`[paper] `__ `[implementation] `__ We include a vanilla policy gradients implementation as an example algorithm. This is usually outperformed by PPO. + +Tuned examples: `CartPole-v0 `__ + +Proximal Policy Optimization +---------------------------- +`[paper] `__ `[implementation] `__ +PPO's clipped objective supports multiple SGD passes over the same batch of experiences. RLlib's multi-GPU optimizer pins that data in GPU memory to avoid unnecessary transfers from host memory, substantially improving performance over a naive implementation. RLlib's PPO scales out using multiple workers for experience collection, and also with multiple GPUs for SGD. + +Tuned examples: `Humanoid-v1 `__, `Hopper-v1 `__, `Pendulum-v0 `__, `PongDeterministic-v4 `__, `Walker2d-v1 `__ + +.. figure:: ppo.png + :width: 500px + :align: center + + RLlib's PPO is more cost effective and faster than a reference PPO implementation. diff --git a/doc/source/rllib-dev.rst b/doc/source/rllib-dev.rst deleted file mode 100644 index 6b5358f58..000000000 --- a/doc/source/rllib-dev.rst +++ /dev/null @@ -1,102 +0,0 @@ -RLlib Developer Guide -===================== - -.. note:: - - This guide will take you through steps for implementing a new algorithm in RLlib. To apply existing algorithms already implemented in RLlib, please see the `user docs `__. - -Recipe for an RLlib algorithm ------------------------------ - -Here are the steps for implementing a new algorithm in RLlib: - -1. Define an algorithm-specific `Policy evaluator class <#policy-evaluators-and-optimizers>`__ (the core of the algorithm). Evaluators encapsulate framework-specific components such as the policy and loss functions. For an example, see the `simple policy gradient evaluator example `__. - - -2. Pick an appropriate `Policy optimizer class <#policy-evaluators-and-optimizers>`__. Optimizers manage the parallel execution of the algorithm. RLlib provides several built-in optimizers for gradient-based algorithms. Advanced algorithms may find it beneficial to implement their own optimizers. - - -3. Wrap the two up in an `Agent class <#agents>`__. Agents are the user-facing API of RLlib. They provide the necessary "glue" and implement accessory functionality such as statistics reporting and checkpointing. - -To help with implementation, RLlib provides common action distributions, preprocessors, and neural network models, found in `catalog.py `__, which are shared by all algorithms. Note that most of these utilities are currently Tensorflow specific. - -.. image:: rllib-api.svg - - -The Developer API ------------------ - -The following APIs are the building blocks of RLlib algorithms (also take a look at the `user components overview `__). - -Agents -~~~~~~ - -Agents implement a particular algorithm and can be used to run -some number of iterations of the algorithm, save and load the state -of training and evaluate the current policy. All agents inherit from -a common base class: - -.. autoclass:: ray.rllib.agent.Agent - :members: - -Policy Evaluators and Optimizers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: ray.rllib.optimizers.policy_evaluator.PolicyEvaluator - :members: - -.. autoclass:: ray.rllib.optimizers.policy_optimizer.PolicyOptimizer - :members: - -Sample Batches -~~~~~~~~~~~~~~ - -In order for Optimizers to manipulate sample data, they should be returned from Evaluators -in the SampleBatch format (a wrapper around a dict). - -.. autoclass:: ray.rllib.optimizers.SampleBatch - :members: - -Models and Preprocessors -~~~~~~~~~~~~~~~~~~~~~~~~ - -Algorithms share neural network models which inherit from the following class: - -.. autoclass:: ray.rllib.models.Model - :members: - -Currently we support fully connected and convolutional TensorFlow policies on all algorithms: - -.. autoclass:: ray.rllib.models.FullyConnectedNetwork - -A3C also supports a TensorFlow LSTM policy. - -.. autoclass:: ray.rllib.models.LSTM - -Observations are transformed by Preprocessors before used in the model: - -.. autoclass:: ray.rllib.models.preprocessors.Preprocessor - :members: - -Action Distributions -~~~~~~~~~~~~~~~~~~~~ - -Actions can be sampled from different distributions which have a common base -class: - -.. autoclass:: ray.rllib.models.ActionDistribution - :members: - -Currently we support the following action distributions: - -.. autoclass:: ray.rllib.models.Categorical -.. autoclass:: ray.rllib.models.DiagGaussian -.. autoclass:: ray.rllib.models.Deterministic - -The Model Catalog -~~~~~~~~~~~~~~~~~ - -The Model Catalog is the mechanism for algorithms to get canonical preprocessors, models, and action distributions for varying gym environments. It enables easy reuse of these components across different algorithms. - -.. autoclass:: ray.rllib.models.ModelCatalog - :members: diff --git a/doc/source/rllib-env.rst b/doc/source/rllib-env.rst new file mode 100644 index 000000000..20e6eed3b --- /dev/null +++ b/doc/source/rllib-env.rst @@ -0,0 +1,142 @@ +RLlib Environments +================== + +RLlib works with several different types of environments, including `OpenAI Gym `__, user-defined, multi-agent, and also batched environments. + +.. image:: rllib-envs.svg + +In the high-level agent APIs, environments are identified with string names. By default, the string will be interpreted as a gym `environment name `__, however you can also register custom environments by name: + +.. code-block:: python + + import ray + from ray.tune.registry import register_env + from ray.rllib import ppo + + def env_creator(env_config): + import gym + return gym.make("CartPole-v0") # or return your own custom env + + register_env("my_env", env_creator) + ray.init() + trainer = ppo.PPOAgent(env="my-env", config={ + "env_config": {}, # config to pass to env creator + }) + + while True: + print(trainer.train()) + + +OpenAI Gym +---------- + +RLlib uses Gym as its environment interface for single-agent training. For more information on how to implement a custom Gym environment, see the `gym.Env class definition `__. You may also find the `SimpleCorridor `__ and `Carla simulator `__ example env implementations useful as a reference. + +Performance +~~~~~~~~~~~ + +There are two ways to scale experience collection with Gym environments: + + 1. **Vectorization within a single process:** Though many envs can very achieve high frame rates per core, their throughput is limited in practice by policy evaluation between steps. For example, even small TensorFlow models incur a couple milliseconds of latency to evaluate. This can be worked around by creating multiple envs per process and batching policy evaluations across these envs. + + You can configure ``{"num_envs": M}`` to have RLlib create ``M`` concurrent environments per worker. RLlib auto-vectorizes Gym environments via `VectorEnv.wrap() `__. + + 2. **Distribute across multiple processes:** You can also have RLlib create multiple processes (Ray actors) for experience collection. In most algorithms this can be controlled by setting the ``{"num_workers": N}`` config. + +.. image:: throughput.png + +You can also combine vectorization and distributed execution, as shown in the above figure. Here we plot just the throughput of RLlib policy evaluation from 1 to 128 CPUs. PongNoFrameskip-v4 on GPU scales from 2.4k to ∼200k actions/s, and Pendulum-v0 on CPU from 15k to 1.5M actions/s. One machine was used for 1-16 workers, and a Ray cluster of four machines for 32-128 workers. Each worker was configured with ``num_envs=64``. + + +Vectorized +---------- + +RLlib will auto-vectorize Gym envs for batch evaluation if the ``num_envs`` config is set, or you can define a custom environment class that subclasses `VectorEnv `__ to implement ``vector_step()`` and ``vector_reset()``. + +Multi-Agent +----------- + +A multi-agent environment is one which has multiple acting entities per step, e.g., in a traffic simulation, there may be multiple "car" and "traffic light" agents in the environment. The model for multi-agent in RLlib as follows: (1) as a user you define the number of policies available up front, and (2) a function that maps agent ids to policy ids. This is summarized by the below figure: + +.. image:: multi-agent.svg + +The environment itself must subclass the `MultiAgentEnv `__ interface, which can returns observations and rewards from multiple ready agents per step: + +.. code-block:: python + + # Example: using a multi-agent env + > env = MultiAgentTrafficEnv(num_cars=20, num_traffic_lights=5) + + # Observations are a dict mapping agent names to their obs. Not all agents + # may be present in the dict in each time step. + > print(env.reset()) + { + "car_1": [[...]], + "car_2": [[...]], + "traffic_light_1": [[...]], + } + + # Actions should be provided for each agent that returned an observation. + > new_obs, rewards, dones, infos = env.step(actions={"car_1": ..., "car_2": ...}) + + # Similarly, new_obs, rewards, dones, etc. also become dicts + > print(rewards) + {"car_1": 3, "car_2": -1, "traffic_light_1": 0} + + # Individual agents can early exit; env is done when "__all__" = True + > print(dones) + {"car_2": True, "__all__": False} + +If all the agents will be using the same algorithm class to train, then you can setup multi-agent training as follows: + +.. code-block:: python + + trainer = pg.PGAgent(env="my_multiagent_env", config={ + "multiagent": { + "policy_graphs": { + "car1": (PGPolicyGraph, car_obs_space, car_act_space, {"gamma": 0.85}), + "car2": (PGPolicyGraph, car_obs_space, car_act_space, {"gamma": 0.99}), + "traffic_light": (PGPolicyGraph, tl_obs_space, tl_act_space, {}), + }, + "policy_mapping_fn": + lambda agent_id: + "traffic_light" # Traffic lights are always controlled by this policy + if agent_id.startswith("traffic_light_") + else random.choice(["car1", "car2"]) # Randomly choose from car policies + }, + }, + }) + + while True: + print(trainer.train()) + +RLlib will create three distinct policies and route agent decisions to its bound policy. When an agent first appears in the env, ``policy_mapping_fn`` will be called to determine which policy it is bound to. RLlib reports separate training statistics for each policy in the return from ``train()``, along with the combined reward. + +Here is a simple `example training script `__ in which you can vary the number of agents and policies in the environment. For more advanced usage, e.g., different classes of policies per agent, or more control over the training process, you can use the lower-level RLlib APIs directly to define custom policy graphs or algorithms. + +To scale to hundreds of agents, MultiAgentEnv batches policy evaluations across multiple agents internally. It can also be auto-vectorized by setting ``num_envs > 1``. + +Serving +------- + +In many situations, it does not make sense for an environment to be "stepped" by RLlib. For example, if a policy is to be used in a web serving system, then it is more natural to instead *query* a service that serves policy decisions, and for that service to learn from experience over time. + +RLlib provides the `ServingEnv `__ class for this purpose. Unlike other envs, ServingEnv runs as its own thread of control. At any point, that thread can query the current policy for decisions via ``self.get_action()`` and reports rewards via ``self.log_returns()``. This can be done for multiple concurrent episodes as well. + +For example, ServingEnv can be used to implement a simple REST policy `server `__ that learns over time using RLlib. In this example RLlib runs with ``num_workers=0`` to avoid port allocation issues, but in principle this could be scaled by increasing ``num_workers``. + +Offline Data +~~~~~~~~~~~~ + +ServingEnv also provides a ``self.log_action()`` call to support off-policy actions. This allows the client to make independent decisions, e.g., to compare two different policies, and for RLlib to still learn from those off-policy actions. Note that this requires the algorithm used to support learning from off-policy decisions (e.g., DQN). + +The ``log_action`` API of ServingEnv can be used to ingest data from offline logs. The pattern would be as follows: First, some policy is followed to produce experience data which is stored in some offline storage system. Then, RLlib creates a number of workers that use a ServingEnv to read the logs in parallel and ingest the experiences. After a round of training completes, the new policy can be deployed to collect more experiences. + +Note that envs can read from different partitions of the logs based on the ``worker_index`` attribute of the `env context `__ passed into the environment constructor. + +Batch Asynchronous +------------------ + +The lowest-level "catch-all" environment supported by RLlib is `AsyncVectorEnv `__. AsyncVectorEnv models multiple agents executing asynchronously in multiple environments. A call to ``poll()`` returns observations from ready agents keyed by their environment and agent ids, and actions for those agents can be sent back via ``send_actions()``. This interface can be subclassed directly to support batched simulators such as `ELF `__. + +Under the hood, all other envs are converted to AsyncVectorEnv by RLlib so that there is a common internal path for policy evaluation. diff --git a/doc/source/rllib-envs.svg b/doc/source/rllib-envs.svg new file mode 100644 index 000000000..37d6d66e6 --- /dev/null +++ b/doc/source/rllib-envs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/rllib-models.rst b/doc/source/rllib-models.rst new file mode 100644 index 000000000..4978dd4e2 --- /dev/null +++ b/doc/source/rllib-models.rst @@ -0,0 +1,77 @@ +RLlib Models and Preprocessors +============================== + +The following diagram provides a conceptual overview of data flow between different components in RLlib. We start with an ``Environment``, which given an action produces an observation. The observation is preprocessed by a ``Preprocessor`` and ``Filter`` (e.g. for running mean normalization) before being sent to a neural network ``Model``. The model output is in turn interpreted by an ``ActionDistribution`` to determine the next action. + +.. image:: rllib-components.svg + +The components highlighted in green can be replaced with custom user-defined implementations, as described in the next sections. The purple components are RLlib internal, which means they can only be modified by changing the algorithm source code. + + +Built-in Models and Preprocessors +--------------------------------- + +RLlib picks default models based on a simple heuristic: a `vision network `__ for image observations, and a `fully connected network `__ for everything else. These models can be configured via the ``model`` config key, documented in the model `catalog `__. Note that you'll probably have to configure ``conv_filters`` if your environment observations have custom sizes, e.g., ``"model": {"dim": 42, "conv_filters": [[16, [4, 4], 2], [32, [4, 4], 2], [512, [11, 11], 1]]}`` for 42x42 observations. + +In addition, if you set ``"model": {"use_lstm": true}``, then the model output will be further processed by a `LSTM cell `__. More generally, RLlib supports the use of recurrent models for its algorithms (A3C, PG out of the box), and RNN support is built into its policy evaluation utilities. + +For preprocessors, RLlib tries to pick one of its built-in preprocessor based on the environment's observation space. Discrete observations are one-hot encoded, Atari observations downscaled, and Tuple observations flattened (there isn't native tuple support yet, but you can reshape the flattened observation in a custom model). Note that for Atari, DQN defaults to using the `DeepMind preprocessors `__, which are also used by the OpenAI baselines library. + + +Custom Models +------------- + +Custom models should subclass the common RLlib `model class `__ and override the ``_build_layers`` method. This method takes in a tensor input (observation), and returns a feature layer and float vector of the specified output size. The model can then be registered and used in place of a built-in model: + +.. code-block:: python + + import ray + import ray.rllib.agents.ppo as ppo + from ray.rllib.models import ModelCatalog, Model + + class MyModelClass(Model): + def _build_layers(self, inputs, num_outputs, options): + layer1 = slim.fully_connected(inputs, 64, ...) + layer2 = slim.fully_connected(inputs, 64, ...) + ... + return layerN, layerN_minus_1 + + ModelCatalog.register_custom_model("my_model", MyModelClass) + + ray.init() + agent = ppo.PPOAgent(env="CartPole-v0", config={ + "model": { + "custom_model": "my_model", + "custom_options": {}, # extra options to pass to your model + }, + }) + +For a full example of a custom model in code, see the `Carla RLlib model `__ and associated `training scripts `__. The ``CarlaModel`` class defined there operates over a composite (Tuple) observation space including both images and scalar measurements. + +Custom Preprocessors +-------------------- + +Similarly, custom preprocessors should subclass the RLlib `preprocessor class `__ and registered in the model catalog: + +.. code-block:: python + + import ray + import ray.rllib.agents.ppo as ppo + from ray.rllib.models.preprocessors import Preprocessor + + class MyPreprocessorClass(Preprocessor): + def _init(self): + self.shape = ... # perhaps varies depending on self._options + + def transform(self, observation): + return ... # return the preprocessed observation + + ModelCatalog.register_custom_preprocessor("my_prep", MyPreprocessorClass) + + ray.init() + agent = ppo.PPOAgent(env="CartPole-v0", config={ + "model": { + "custom_preprocessor": "my_prep", + "custom_options": {}, # extra options to pass to your preprocessor + }, + }) diff --git a/doc/source/rllib-package-ref.rst b/doc/source/rllib-package-ref.rst new file mode 100644 index 000000000..38a578dbd --- /dev/null +++ b/doc/source/rllib-package-ref.rst @@ -0,0 +1,47 @@ +RLlib Package Reference +======================= + +ray.rllib.agents +---------------- + +.. automodule:: ray.rllib.agents + :members: + +.. autoclass:: ray.rllib.agents.a3c.A3CAgent +.. autoclass:: ray.rllib.agents.ddpg.ApexDDPGAgent +.. autoclass:: ray.rllib.agents.ddpg.DDPGAgent +.. autoclass:: ray.rllib.agents.dqn.ApexAgent +.. autoclass:: ray.rllib.agents.dqn.DQNAgent +.. autoclass:: ray.rllib.agents.es.ESAgent +.. autoclass:: ray.rllib.agents.pg.PGAgent +.. autoclass:: ray.rllib.agents.ppo.PPOAgent + +ray.rllib.env +------------- + +.. automodule:: ray.rllib.env + :members: + +ray.rllib.evaluation +-------------------- + +.. automodule:: ray.rllib.evaluation + :members: + +ray.rllib.models +---------------- + +.. automodule:: ray.rllib.models + :members: + +ray.rllib.optimizers +-------------------- + +.. automodule:: ray.rllib.optimizers + :members: + +ray.rllib.utils +--------------- + +.. automodule:: ray.rllib.utils + :members: diff --git a/doc/source/rllib-stack.svg b/doc/source/rllib-stack.svg new file mode 100644 index 000000000..c3c18f0be --- /dev/null +++ b/doc/source/rllib-stack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/rllib-training.rst b/doc/source/rllib-training.rst new file mode 100644 index 000000000..0171de62f --- /dev/null +++ b/doc/source/rllib-training.rst @@ -0,0 +1,130 @@ +RLlib Training APIs +=================== + +Getting Started +--------------- + +At a high level, RLlib provides an ``Agent`` class which +holds a policy for environment interaction. Through the agent interface, the policy can +be trained, checkpointed, or an action computed. + +.. image:: rllib-api.svg + +You can train a simple DQN agent with the following command + +.. code-block:: bash + + python ray/python/ray/rllib/train.py --run DQN --env CartPole-v0 + +By default, the results will be logged to a subdirectory of ``~/ray_results``. +This subdirectory will contain a file ``params.json`` which contains the +hyperparameters, a file ``result.json`` which contains a training summary +for each episode and a TensorBoard file that can be used to visualize +training process with TensorBoard by running + +.. code-block:: bash + + tensorboard --logdir=~/ray_results + + +The ``train.py`` script has a number of options you can show by running + +.. code-block:: bash + + python ray/python/ray/rllib/train.py --help + +The most important options are for choosing the environment +with ``--env`` (any OpenAI gym environment including ones registered by the user +can be used) and for choosing the algorithm with ``--run`` +(available options are ``PPO``, ``PG``, ``A3C``, ``ES``, ``DDPG``, ``DDPG2``, ``DQN``, ``APEX``, and ``APEX_DDPG``). + +Specifying Parameters +~~~~~~~~~~~~~~~~~~~~~ + +Each algorithm has specific hyperparameters that can be set with ``--config``. See the +`algorithms documentation `__ for more information. + +In an example below, we train A3C by specifying 8 workers through the config flag. +function that creates the env to refer to it by name. The contents of the env_config agent config field will be passed to that function to allow the environment to be configured. The return type should be an OpenAI gym.Env. For example: + + +.. code-block:: bash + + python ray/python/ray/rllib/train.py --env=PongDeterministic-v4 \ + --run=A3C --config '{"num_workers": 8}' + +Evaluating Trained Agents +~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to save checkpoints from which to evaluate agents, +set ``--checkpoint-freq`` (number of training iterations between checkpoints) +when running ``train.py``. + + +An example of evaluating a previously trained DQN agent is as follows: + +.. code-block:: bash + + python ray/python/ray/rllib/rollout.py \ + ~/ray_results/default/DQN_CartPole-v0_0upjmdgr0/checkpoint-1 \ + --run DQN --env CartPole-v0 + +The ``rollout.py`` helper script reconstructs a DQN agent from the checkpoint +located at ``~/ray_results/default/DQN_CartPole-v0_0upjmdgr0/checkpoint-1`` +and renders its behavior in the environment specified by ``--env``. + +Tuned Examples +-------------- + +Some good hyperparameters and settings are available in +`the repository `__ +(some of them are tuned to run on GPUs). If you find better settings or tune +an algorithm on a different domain, consider submitting a Pull Request! + +Python API +---------- + +The Python API provides the needed flexibility for applying RLlib to new problems. You will need to use this API if you wish to use custom environments, preprocesors, or models with RLlib. + +Here is an example of the basic usage: + +.. code-block:: python + + import ray + import ray.rllib.agents.ppo as ppo + + ray.init() + config = ppo.DEFAULT_CONFIG.copy() + agent = ppo.PPOAgent(config=config, env="CartPole-v0") + + # Can optionally call agent.restore(path) to load a checkpoint. + + for i in range(1000): + # Perform one iteration of training the policy with PPO + result = agent.train() + print("result: {}".format(result)) + + if i % 100 == 0: + checkpoint = agent.save() + print("checkpoint saved at", checkpoint) + +All RLlib agents implement the tune Trainable API, which means they support incremental training and checkpointing. This enables them to be easily used in experiments with Ray Tune. + +Accessing Global State +~~~~~~~~~~~~~~~~~~~~~~ +It is common to need to access an agent's internal state, e.g., to set or get internal weights. In RLlib an agent's state is replicated across multiple *policy evaluators* (Ray actors) in the cluster. However, you can easily get and update this state between calls to ``train()`` via ``agent.optimizer.foreach_evaluator()`` or ``agent.optimizer.foreach_evaluator_with_index()``. These functions take a lambda function that is applied with the evaluator as an arg. You can also return values from these functions and those will be returned as a list. + +You can also access just the "master" copy of the agent state through ``agent.optimizer.local_evaluator``, but note that updates here may not be reflected in remote replicas if you have configured ``num_workers > 0``. + +REST API +-------- + +In some cases (i.e., when interacting with an external environment) it makes more sense to interact with RLlib as if were an independently running service, rather than RLlib hosting the simulations itself. This is possible via RLlib's serving env `interface `__. + +.. autoclass:: ray.rllib.utils.policy_client.PolicyClient + :members: + +.. autoclass:: ray.rllib.utils.policy_server.PolicyServer + :members: + +For a full client / server example that you can run, see the example `client script `__ and also the corresponding `server script `__, here configured to serve a policy for the toy CartPole-v0 environment. diff --git a/doc/source/rllib.rst b/doc/source/rllib.rst index 569975135..7316fc127 100644 --- a/doc/source/rllib.rst +++ b/doc/source/rllib.rst @@ -1,347 +1,73 @@ -Ray RLlib: Scalable Reinforcement Learning -========================================== +RLlib: Scalable Reinforcement Learning +====================================== -Ray RLlib is an RL execution toolkit built on the Ray distributed execution framework. RLlib implements a collection of distributed *policy optimizers* that make it easy to use a variety of training strategies with existing RL algorithms written in frameworks such as PyTorch, TensorFlow, and Theano. +RLlib is an open-source library for reinforcement learning that offers both a collection of reference algorithms and scalable primitives for composing new ones. -You can find the code for RLlib `here on GitHub `__, and the paper `here `__. +.. image:: rllib-stack.svg -RLlib's policy optimizers serve as the basis for RLlib's reference algorithms, which include: - -- Proximal Policy Optimization (`PPO `__) which is a proximal variant of `TRPO `__. - -- Policy Gradients (`PG `__). - -- Asynchronous Advantage Actor-Critic (`A3C `__). - -- Deep Q Networks (`DQN `__). - -- Deep Deterministic Policy Gradients (`DDPG `__). - -- Ape-X Distributed Prioritized Experience Replay, including both `DQN `__ and `DDPG `__ variants. - -- Evolution Strategies (`ES `__), as described in `this paper `__. - -These algorithms can be run on any `OpenAI Gym MDP `__, -including custom ones written and registered by the user. - -.. note:: - - To use RLlib's policy optimizers outside of RLlib, see the `policy optimizers documentation `__. +Learn more about RLlib's design by reading the `ICML paper `__. Installation ------------ -RLlib has extra dependencies on top of **ray**. First, you'll need into install either PyTorch or TensorFlow. -For usage of PyTorch models, visit the `PyTorch website `__ -for instructions on installing PyTorch. +RLlib has extra dependencies on top of ``ray``. First, you'll need to install either `PyTorch `__ or `TensorFlow `__. Then, install the Ray RLlib module: .. code-block:: bash pip install tensorflow # or tensorflow-gpu - -Then, install Ray with extra RLlib dependencies: - -.. code-block:: bash - - pip install 'ray[rllib]' + pip install ray[rllib] You might also want to clone the Ray repo for convenient access to RLlib helper scripts: .. code-block:: bash git clone https://github.com/ray-project/ray - - - -Getting Started ---------------- - -At a high level, RLlib provides an ``Agent`` class which -holds a policy for environment interaction. Through the agent interface, the policy can -be trained, checkpointed, or an action computed. - -.. image:: rllib-api.svg - -You can train a simple DQN agent with the following command - -.. code-block:: bash - - python ray/python/ray/rllib/train.py --run DQN --env CartPole-v0 - -By default, the results will be logged to a subdirectory of ``~/ray_results``. -This subdirectory will contain a file ``params.json`` which contains the -hyperparameters, a file ``result.json`` which contains a training summary -for each episode and a TensorBoard file that can be used to visualize -training process with TensorBoard by running - -.. code-block:: bash - - tensorboard --logdir=~/ray_results - - -The ``train.py`` script has a number of options you can show by running - -.. code-block:: bash - - python ray/python/ray/rllib/train.py --help - -The most important options are for choosing the environment -with ``--env`` (any OpenAI gym environment including ones registered by the user -can be used) and for choosing the algorithm with ``--run`` -(available options are ``PPO``, ``PG``, ``A3C``, ``ES``, ``DDPG``, ``DDPG2``, ``DQN``, ``APEX``, and ``APEX_DDPG``). - -Specifying Parameters -~~~~~~~~~~~~~~~~~~~~~ - -Each algorithm has specific hyperparameters that can be set with ``--config`` - see the -``DEFAULT_CONFIG`` variable in -`PPO `__, -`PG `__, -`A3C `__, -`ES `__, -`DQN `__, -`DDPG `__, -`DDPG2 `__, -`APEX `__, and -`APEX_DDPG `__. - -In an example below, we train A3C by specifying 8 workers through the config flag. -function that creates the env to refer to it by name. The contents of the env_config agent config field will be passed to that function to allow the environment to be configured. The return type should be an OpenAI gym.Env. For example: - - -.. code-block:: bash - - python ray/python/ray/rllib/train.py --env=PongDeterministic-v4 \ - --run=A3C --config '{"num_workers": 8}' - -Evaluating Trained Agents -~~~~~~~~~~~~~~~~~~~~~~~~~ - -In order to save checkpoints from which to evaluate agents, -set ``--checkpoint-freq`` (number of training iterations between checkpoints) -when running ``train.py``. - - -An example of evaluating a previously trained DQN agent is as follows: - -.. code-block:: bash - - python ray/python/ray/rllib/rollout.py \ - ~/ray_results/default/DQN_CartPole-v0_0upjmdgr0/checkpoint-1 \ - --run DQN --env CartPole-v0 - - -The ``rollout.py`` helper script reconstructs a DQN agent from the checkpoint -located at ``~/ray_results/default/DQN_CartPole-v0_0upjmdgr0/checkpoint-1`` -and renders its behavior in the environment specified by ``--env``. - -Tuned Examples --------------- - -Some good hyperparameters and settings are available in -`the repository `__ -(some of them are tuned to run on GPUs). If you find better settings or tune -an algorithm on a different domain, consider submitting a Pull Request! - -Python User API ---------------- - -The Python API provides the needed flexibility for applying RLlib to new problems. You will need to use this API if you wish to use custom environments, preprocesors, or models with RLlib. - -Here is an example of the basic usage: - -.. code-block:: python - - import ray - import ray.rllib.ppo as ppo - - ray.init() - config = ppo.DEFAULT_CONFIG.copy() - agent = ppo.PPOAgent(config=config, env="CartPole-v0") - - # Can optionally call agent.restore(path) to load a checkpoint. - - for i in range(1000): - # Perform one iteration of training the policy with PPO - result = agent.train() - print("result: {}".format(result)) - - if i % 100 == 0: - checkpoint = agent.save() - print("checkpoint saved at", checkpoint) - -Components: User-customizable and Internal -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following diagram provides a conceptual overview of data flow between different components in RLlib. We start with an ``Environment``, which given an action produces an observation. The observation is preprocessed by a ``Preprocessor`` and ``Filter`` (e.g. for running mean normalization) before being sent to a neural network ``Model``. The model output is in turn interpreted by an ``ActionDistribution`` to determine the next action. - -.. image:: rllib-components.svg - -The components highlighted in green above are *User-customizable*, which means RLlib provides APIs for swapping in user-defined implementations, as described in the next sections. The purple components are *RLlib internal*, which means they currently can only be modified by changing the RLlib source code. - -For more information about these components, also see the `RLlib Developer Guide `__. - -Custom Environments -~~~~~~~~~~~~~~~~~~~ - -To train against a custom environment, i.e. one not in the gym catalog, you -can register a function that creates the env to refer to it by name. The contents of the -``env_config`` agent config field will be passed to that function to allow the -environment to be configured. The return type should be an `OpenAI gym.Env `__. For example: - -.. code-block:: python - - import ray - from ray.tune.registry import register_env - from ray.rllib import ppo - - def env_creator(env_config): - import gym - return gym.make("CartPole-v0") # or return your own custom env - - env_creator_name = "custom_env" - register_env(env_creator_name, env_creator) - - ray.init() - agent = ppo.PPOAgent(env=env_creator_name, config={ - "env_config": {}, # config to pass to env creator - }) - -For a code example of a custom env, see the `SimpleCorridor example `__. For a more complex example, also see the `Carla RLlib env `__. - -Custom Preprocessors and Models -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -RLlib includes default preprocessors and models for common gym -environments, but you can also specify your own as follows. At a high level, your neural -network model needs to take an input tensor of the preprocessed observation shape and -output a vector of the size specified in the constructor. The interfaces for -these custom classes can be found in the -`RLlib Developer Guide `__. - -.. code-block:: python - - import ray - from ray.rllib.models import ModelCatalog, Model - from ray.rllib.models.preprocessors import Preprocessor - - class MyPreprocessorClass(Preprocessor): - def _init(self): - self.shape = ... - - def transform(self, observation): - return ... - - class MyModelClass(Model): - def _init(self, inputs, num_outputs, options): - layer1 = slim.fully_connected(inputs, 64, ...) - layer2 = slim.fully_connected(inputs, 64, ...) - ... - return layerN, layerN_minus_1 - - ModelCatalog.register_custom_preprocessor("my_prep", MyPreprocessorClass) - ModelCatalog.register_custom_model("my_model", MyModelClass) - - ray.init() - agent = ppo.PPOAgent(env="CartPole-v0", config={ - "model": { - "custom_preprocessor": "my_prep", - "custom_model": "my_model", - "custom_options": {}, # extra options to pass to your classes - }, - }) - -For a full example of a custom model in code, see the `Carla RLlib model `__ and associated `training scripts `__. The ``CarlaModel`` class defined there operates over a composite (Tuple) observation space including both images and scalar measurements. - -Multi-Agent Models -~~~~~~~~~~~~~~~~~~ -RLlib supports multi-agent training with PPO. Currently it supports both -shared, i.e. all agents have the same model, and non-shared multi-agent models. However, it only supports shared -rewards and does not yet support individual rewards for each agent. - - -While Generalized Advantage Estimation is supported in multiagent scenarios, -it is assumed that it possible for the estimator to access the observations of -all of the agents. - - -Important config parameters are described below - -.. code-block:: python - - config["model"].update({"fcnet_hiddens": [256, 256]}) # dimension of value function - options = {"multiagent_obs_shapes": [3, 3], # length of each observation space - "multiagent_act_shapes": [1, 1], # length of each action space - "multiagent_shared_model": True, # whether the model should be shared - # list of dimensions of multiagent feedforward nets - "multiagent_fcnet_hiddens": [[32, 32]] * 2} - config["model"].update({"custom_options": options}) - -For a full example of a multiagent model in code, see the -`MultiAgent Pendulum `__. -The ``MultiAgentPendulumEnv`` defined there operates -over a composite (Tuple) enclosing a list of Boxes; each Box represents the -observation of an agent. The action space is a list of Discrete actions, each -element corresponding to half of the total torque. The environment will return a list of actions -that can be iterated over and applied to each agent. - -External Data API -~~~~~~~~~~~~~~~~~ -*coming soon!* - - -Using RLlib with Ray Tune -------------------------- - -All Agents implemented in RLlib support the -`tune Trainable `__ interface. - -Here is an example of using the command-line interface with RLlib: - -.. code-block:: bash - - python ray/python/ray/rllib/train.py -f tuned_examples/cartpole-grid-search-example.yaml - -Here is an example using the Python API. The same config passed to ``Agents`` may be placed -in the ``config`` section of the experiments. RLlib agents automatically declare their -resources requirements (e.g., based on ``num_workers``) to Tune, so you don't have to. - -.. code-block:: python - - import ray - from ray.tune.tune import run_experiments - from ray.tune.variant_generator import grid_search - - - experiment = { - 'cartpole-ppo': { - 'run': 'PPO', - 'env': 'CartPole-v0', - 'stop': { - 'episode_reward_mean': 200, - 'time_total_s': 180 - }, - 'config': { - 'num_sgd_iter': grid_search([1, 4]), - 'num_workers': 2, - 'sgd_batchsize': grid_search([128, 256, 512]) - } - }, - # put additional experiments to run concurrently here - } - - ray.init() - run_experiments(experiment) - -For an advanced example of using Population Based Training (PBT) with RLlib, -see the `PPO + PBT Walker2D training example `__. - -Using Policy Optimizers outside of RLlib ----------------------------------------- - -See the `RLlib policy optimizers documentation `__. - -Contributing to RLlib ---------------------- - -See the `RLlib Developer Guide `__. + cd ray/python/ray/rllib + +Training APIs +------------- +* `Command-line `__ +* `Python API `__ +* `REST API `__ + +Environments +------------ +* `RLlib Environments Overview `__ +* `OpenAI Gym `__ +* `Vectorized (Batch) `__ +* `Multi-Agent `__ +* `Serving (Agent-oriented) `__ +* `Offline Data Ingest `__ +* `Batch Asynchronous `__ + +Algorithms +---------- +* `Ape-X Distributed Prioritized Experience Replay `__ +* `Asynchronous Advantage Actor-Critic `__ +* `Deep Deterministic Policy Gradients `__ +* `Deep Q Networks `__ +* `Evolution Strategies `__ +* `Policy Gradients `__ +* `Proximal Policy Optimization `__ + +Models and Preprocessors +------------------------------- +* `RLlib Models and Preprocessors Overview `__ +* `Built-in Models and Preprocessors `__ +* `Custom Models `__ +* `Custom Preprocessors `__ + +RL Building Blocks +------------------ +* Policy Models, Losses, Postprocessing +* Policy Evaluation +* Policy Optimization + +Package Reference +----------------- +* `ray.rllib.agents `__ +* `ray.rllib.env `__ +* `ray.rllib.evaluation `__ +* `ray.rllib.models `__ +* `ray.rllib.optimizers `__ +* `ray.rllib.utils `__ diff --git a/doc/source/throughput.png b/doc/source/throughput.png new file mode 100644 index 0000000000000000000000000000000000000000..3bde99bedc313bf262ca50b7cbdee3cb3892aa96 GIT binary patch literal 32476 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 and `paper `__. +RLlib is an open-source library for reinforcement learning that offers both a collection of reference algorithms and scalable primitives for composing new ones. -RLlib includes the following reference algorithms: +For an overview of RLlib, see the `documentation `__. -- Proximal Policy Optimization (`PPO `__) which is a proximal variant of `TRPO `__. +If you've found RLlib useful for your research, you can cite the `paper `__ as follows: -- Policy Gradients (`PG `__). - -- Asynchronous Advantage Actor-Critic (`A3C `__). - -- Deep Q Networks (`DQN `__). - -- Deep Deterministic Policy Gradients (`DDPG `__). - -- Ape-X Distributed Prioritized Experience Replay, including both `DQN `__ and `DDPG `__ variants. - -- Evolution Strategies (`ES `__), as described in `this paper `__. - -These algorithms can be run on any OpenAI Gym MDP, including custom ones written and registered by the user. +``` +@inproceedings{liang2018rllib, + Author = {Eric Liang and + Richard Liaw and + Robert Nishihara and + Philipp Moritz and + Roy Fox and + Ken Goldberg and + Joseph E. Gonzalez and + Michael I. Jordan and + Ion Stoica}, + Title = {{RLlib}: Abstractions for Distributed Reinforcement Learning}, + Booktitle = {International Conference on Machine Learning ({ICML})}, + Year = {2018} +} +``` diff --git a/python/ray/rllib/__init__.py b/python/ray/rllib/__init__.py index 58aa97f19..ee09c4579 100644 --- a/python/ray/rllib/__init__.py +++ b/python/ray/rllib/__init__.py @@ -6,20 +6,21 @@ from __future__ import print_function # This file is imported from the tune module in order to register RLlib agents. from ray.tune.registry import register_trainable -from ray.rllib.utils.policy_graph import PolicyGraph -from ray.rllib.utils.tf_policy_graph import TFPolicyGraph -from ray.rllib.utils.common_policy_evaluator import CommonPolicyEvaluator -from ray.rllib.utils.async_vector_env import AsyncVectorEnv -from ray.rllib.utils.vector_env import VectorEnv -from ray.rllib.utils.serving_env import ServingEnv -from ray.rllib.optimizers.sample_batch import SampleBatch +from ray.rllib.evaluation.policy_graph import PolicyGraph +from ray.rllib.evaluation.tf_policy_graph import TFPolicyGraph +from ray.rllib.env.async_vector_env import AsyncVectorEnv +from ray.rllib.env.multi_agent_env import MultiAgentEnv +from ray.rllib.env.vector_env import VectorEnv +from ray.rllib.env.serving_env import ServingEnv +from ray.rllib.evaluation.common_policy_evaluator import CommonPolicyEvaluator +from ray.rllib.evaluation.sample_batch import SampleBatch def _register_all(): for key in ["PPO", "ES", "DQN", "APEX", "A3C", "BC", "PG", "DDPG", "APEX_DDPG", "__fake", "__sigmoid_fake_data", "__parameter_tuning"]: - from ray.rllib.agent import get_agent_class + from ray.rllib.agents.agent import get_agent_class register_trainable(key, get_agent_class(key)) @@ -27,5 +28,5 @@ _register_all() __all__ = [ "PolicyGraph", "TFPolicyGraph", "CommonPolicyEvaluator", "SampleBatch", - "AsyncVectorEnv", "VectorEnv", "ServingEnv", + "AsyncVectorEnv", "MultiAgentEnv", "VectorEnv", "ServingEnv", ] diff --git a/python/ray/rllib/a3c/__init__.py b/python/ray/rllib/a3c/__init__.py deleted file mode 100644 index 2d9aaede4..000000000 --- a/python/ray/rllib/a3c/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from ray.rllib.a3c.a3c import A3CAgent, DEFAULT_CONFIG - -__all__ = ["A3CAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/agents/__init__.py b/python/ray/rllib/agents/__init__.py new file mode 100644 index 000000000..da8494a2b --- /dev/null +++ b/python/ray/rllib/agents/__init__.py @@ -0,0 +1,3 @@ +from ray.rllib.agents.agent import Agent, with_common_config + +__all__ = ["Agent", "with_common_config"] diff --git a/python/ray/rllib/agents/a3c/__init__.py b/python/ray/rllib/agents/a3c/__init__.py new file mode 100644 index 000000000..e4ab31764 --- /dev/null +++ b/python/ray/rllib/agents/a3c/__init__.py @@ -0,0 +1,3 @@ +from ray.rllib.agents.a3c.a3c import A3CAgent, DEFAULT_CONFIG + +__all__ = ["A3CAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/a3c/a3c.py b/python/ray/rllib/agents/a3c/a3c.py similarity index 53% rename from python/ray/rllib/a3c/a3c.py rename to python/ray/rllib/agents/a3c/a3c.py index e50a1a04c..f37af7c42 100644 --- a/python/ray/rllib/a3c/a3c.py +++ b/python/ray/rllib/agents/a3c/a3c.py @@ -6,26 +6,17 @@ import pickle import os import ray -from ray.rllib.agent import Agent +from ray.rllib.agents.agent import Agent, with_common_config from ray.rllib.optimizers import AsyncGradientsOptimizer from ray.rllib.utils import FilterManager -from ray.rllib.utils.common_policy_evaluator import CommonPolicyEvaluator, \ - collect_metrics +from ray.rllib.evaluation.metrics import collect_metrics from ray.tune.trial import Resources -DEFAULT_CONFIG = { - # Number of workers (excluding master) - "num_workers": 2, - # Number of environments to evaluate vectorwise per worker. - "num_envs": 1, +DEFAULT_CONFIG = with_common_config({ # Size of rollout batch - "batch_size": 10, + "sample_batch_size": 10, # Use PyTorch as backend - no LSTM support "use_pytorch": False, - # Which observation filter to apply to the observation - "observation_filter": "NoFilter", - # Discount factor of MDP - "gamma": 0.99, # GAE(gamma) parameter "lambda": 1.0, # Max global norm for each gradient calculated by worker @@ -40,6 +31,8 @@ DEFAULT_CONFIG = { "use_gpu_for_workers": False, # Whether to emit extra summary stats "summarize": False, + # Workers sample async + "sample_async": True, # Model and preprocessor options "model": { # Use LSTM model. Requires TF. @@ -55,23 +48,25 @@ DEFAULT_CONFIG = { # (Image statespace) - Converts image shape to (C, dim, dim) "channel_major": False, }, + # Configure TF for single-process operation + "tf_session_args": { + "intra_op_parallelism_threads": 1, + "inter_op_parallelism_threads": 1, + "gpu_options": { + "allow_growth": True, + }, + }, # Arguments to pass to the rllib optimizer "optimizer": { # Number of gradients applied for each `train` step "grads_per_step": 100, }, - # Arguments to pass to the env creator - "env_config": {}, - - # === Multiagent === - "multiagent": { - "policy_graphs": {}, - "policy_mapping_fn": None, - }, -} +}) class A3CAgent(Agent): + """A3C implementations in TensorFlow and PyTorch.""" + _agent_name = "A3C" _default_config = DEFAULT_CONFIG @@ -86,51 +81,18 @@ class A3CAgent(Agent): def _init(self): if self.config["use_pytorch"]: - from ray.rllib.a3c.a3c_torch_policy import A3CTorchPolicyGraph - self.policy_cls = A3CTorchPolicyGraph + from ray.rllib.agents.a3c.a3c_torch_policy import \ + A3CTorchPolicyGraph + policy_cls = A3CTorchPolicyGraph else: - from ray.rllib.a3c.a3c_tf_policy import A3CPolicyGraph - self.policy_cls = A3CPolicyGraph - - if self.config["use_pytorch"]: - session_creator = None - else: - import tensorflow as tf - - def session_creator(): - return tf.Session( - config=tf.ConfigProto( - intra_op_parallelism_threads=1, - inter_op_parallelism_threads=1, - gpu_options=tf.GPUOptions(allow_growth=True))) - - remote_cls = CommonPolicyEvaluator.as_remote( - num_gpus=1 if self.config["use_gpu_for_workers"] else 0) - self.local_evaluator = CommonPolicyEvaluator( - self.env_creator, - self.config["multiagent"]["policy_graphs"] or self.policy_cls, - policy_mapping_fn=self.config["multiagent"]["policy_mapping_fn"], - batch_steps=self.config["batch_size"], - batch_mode="truncate_episodes", - tf_session_creator=session_creator, - env_config=self.config["env_config"], - model_config=self.config["model"], policy_config=self.config, - num_envs=self.config["num_envs"]) - self.remote_evaluators = [ - remote_cls.remote( - self.env_creator, - self.config["multiagent"]["policy_graphs"] or self.policy_cls, - policy_mapping_fn=( - self.config["multiagent"]["policy_mapping_fn"]), - batch_steps=self.config["batch_size"], - batch_mode="truncate_episodes", sample_async=True, - tf_session_creator=session_creator, - env_config=self.config["env_config"], - model_config=self.config["model"], policy_config=self.config, - num_envs=self.config["num_envs"], - worker_index=i+1) - for i in range(self.config["num_workers"])] + from ray.rllib.agents.a3c.a3c_tf_policy import A3CPolicyGraph + policy_cls = A3CPolicyGraph + self.local_evaluator = self.make_local_evaluator( + self.env_creator, policy_cls) + self.remote_evaluators = self.make_remote_evaluators( + self.env_creator, policy_cls, self.config["num_workers"], + {"num_gpus": 1 if self.config["use_gpu_for_workers"] else 0}) self.optimizer = AsyncGradientsOptimizer( self.config["optimizer"], self.local_evaluator, self.remote_evaluators) @@ -168,12 +130,3 @@ class A3CAgent(Agent): for a, o in zip(self.remote_evaluators, extra_data["remote_state"]) ]) self.local_evaluator.restore(extra_data["local_state"]) - - def compute_action(self, observation, state=None): - if state is None: - state = [] - obs = self.local_evaluator.filters["default"]( - observation, update=False) - return self.local_evaluator.for_policy( - lambda p: p.compute_single_action( - obs, state, is_training=False)[0]) diff --git a/python/ray/rllib/a3c/a3c_tf_policy.py b/python/ray/rllib/agents/a3c/a3c_tf_policy.py similarity index 96% rename from python/ray/rllib/a3c/a3c_tf_policy.py rename to python/ray/rllib/agents/a3c/a3c_tf_policy.py index 9657d9b05..706f6824e 100644 --- a/python/ray/rllib/a3c/a3c_tf_policy.py +++ b/python/ray/rllib/agents/a3c/a3c_tf_policy.py @@ -7,8 +7,8 @@ import gym import ray from ray.rllib.utils.error import UnsupportedSpaceException -from ray.rllib.utils.postprocessing import compute_advantages -from ray.rllib.utils.tf_policy_graph import TFPolicyGraph +from ray.rllib.evaluation.postprocessing import compute_advantages +from ray.rllib.evaluation.tf_policy_graph import TFPolicyGraph from ray.rllib.models.misc import linear, normc_initializer from ray.rllib.models.catalog import ModelCatalog @@ -32,7 +32,7 @@ class A3CLoss(object): class A3CPolicyGraph(TFPolicyGraph): def __init__(self, observation_space, action_space, config): - config = dict(ray.rllib.a3c.a3c.DEFAULT_CONFIG, **config) + config = dict(ray.rllib.agents.a3c.a3c.DEFAULT_CONFIG, **config) self.config = config self.sess = tf.get_default_session() diff --git a/python/ray/rllib/a3c/a3c_torch_policy.py b/python/ray/rllib/agents/a3c/a3c_torch_policy.py similarity index 92% rename from python/ray/rllib/a3c/a3c_torch_policy.py rename to python/ray/rllib/agents/a3c/a3c_torch_policy.py index 3813f1e20..a277de945 100644 --- a/python/ray/rllib/a3c/a3c_torch_policy.py +++ b/python/ray/rllib/agents/a3c/a3c_torch_policy.py @@ -9,8 +9,8 @@ from torch import nn import ray from ray.rllib.models.pytorch.misc import var_to_np from ray.rllib.models.catalog import ModelCatalog -from ray.rllib.utils.postprocessing import compute_advantages -from ray.rllib.utils.torch_policy_graph import TorchPolicyGraph +from ray.rllib.evaluation.postprocessing import compute_advantages +from ray.rllib.evaluation.torch_policy_graph import TorchPolicyGraph class A3CLoss(nn.Module): @@ -40,7 +40,7 @@ class A3CTorchPolicyGraph(TorchPolicyGraph): """A simple, non-recurrent PyTorch policy example.""" def __init__(self, obs_space, action_space, config): - config = dict(ray.rllib.a3c.a3c.DEFAULT_CONFIG, **config) + config = dict(ray.rllib.agents.a3c.a3c.DEFAULT_CONFIG, **config) self.config = config _, self.logit_dim = ModelCatalog.get_action_dist( action_space, self.config["model"]) diff --git a/python/ray/rllib/agent.py b/python/ray/rllib/agents/agent.py similarity index 67% rename from python/ray/rllib/agent.py rename to python/ray/rllib/agents/agent.py index 195d76a9b..9739d1f64 100644 --- a/python/ray/rllib/agent.py +++ b/python/ray/rllib/agents/agent.py @@ -2,19 +2,62 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import logging -import numpy as np +import copy import json +import numpy as np import os import pickle import tensorflow as tf +from ray.rllib.evaluation.common_policy_evaluator import CommonPolicyEvaluator from ray.tune.registry import ENV_CREATOR, _global_registry from ray.tune.result import TrainingResult from ray.tune.trainable import Trainable -logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) +COMMON_CONFIG = { + # Discount factor of the MDP + "gamma": 0.99, + # Number of steps after which the rollout gets cut + "horizon": None, + # Number of environments to evaluate vectorwise per worker. + "num_envs": 1, + # Number of actors used for parallelism + "num_workers": 2, + # Default sample batch size + "sample_batch_size": 200, + # Whether to rollout "complete_episodes" or "truncate_episodes" + "batch_mode": "truncate_episodes", + # Whether to use a background thread for sampling (slightly off-policy) + "sample_async": False, + # Which observation filter to apply to the observation + "observation_filter": "NoFilter", + # Whether to use rllib or deepmind preprocessors + "preprocessor_pref": "rllib", + # Arguments to pass to the env creator + "env_config": {}, + # Arguments to pass to model + "model": {}, + # Arguments to pass to the rllib optimizer + "optimizer": {}, + # Override default TF session args if non-empty + "tf_session_args": {}, + # Whether to LZ4 compress observations + "compress_observations": False, + + # === Multiagent === + "multiagent": { + "policy_graphs": {}, + "policy_mapping_fn": None, + }, +} + + +def with_common_config(extra_config): + """Returns the given config dict merged with common agent confs.""" + + config = copy.deepcopy(COMMON_CONFIG) + config.update(extra_config) + return config def _deep_update(original, new_dict, new_keys_allowed, whitelist): @@ -62,6 +105,47 @@ class Agent(Trainable): _allow_unknown_subkeys = [ "tf_session_args", "env_config", "model", "optimizer", "multiagent"] + def make_local_evaluator(self, env_creator, policy_graph): + """Convenience method to return configured local evaluator.""" + + return self._make_evaluator( + CommonPolicyEvaluator, env_creator, policy_graph, 0) + + def make_remote_evaluators( + self, env_creator, policy_graph, count, remote_args): + """Convenience method to return a number of remote evaluators.""" + + cls = CommonPolicyEvaluator.as_remote(**remote_args).remote + return [ + self._make_evaluator(cls, env_creator, policy_graph, i+1) + for i in range(count)] + + def _make_evaluator(self, cls, env_creator, policy_graph, worker_index): + config = self.config + + def session_creator(): + return tf.Session( + config=tf.ConfigProto(**config["tf_session_args"])) + + return cls( + env_creator, + self.config["multiagent"]["policy_graphs"] or policy_graph, + policy_mapping_fn=self.config["multiagent"]["policy_mapping_fn"], + tf_session_creator=( + session_creator if config["tf_session_args"] else None), + batch_steps=config["sample_batch_size"], + batch_mode=config["batch_mode"], + episode_horizon=config["horizon"], + preprocessor_pref=config["preprocessor_pref"], + sample_async=config["sample_async"], + compress_observations=config["compress_observations"], + num_envs=config["num_envs"], + observation_filter=config["observation_filter"], + env_config=config["env_config"], + model_config=config["model"], + policy_config=config, + worker_index=worker_index) + @classmethod def resource_help(cls, config): return ( @@ -116,11 +200,6 @@ class Agent(Trainable): raise NotImplementedError - def compute_action(self, observation): - """Computes an action using the current trained policy.""" - - raise NotImplementedError - @property def iteration(self): """Current training iter, auto-incremented with each train() call.""" @@ -139,6 +218,17 @@ class Agent(Trainable): raise NotImplementedError + def compute_action(self, observation, state=None): + """Computes an action using the current trained policy.""" + + if state is None: + state = [] + obs = self.local_evaluator.filters["default"]( + observation, update=False) + return self.local_evaluator.for_policy( + lambda p: p.compute_single_action( + obs, state, is_training=False)[0]) + class _MockAgent(Agent): """Mock agent for use in tests""" @@ -228,31 +318,31 @@ def get_agent_class(alg): """Returns the class of a known agent given its name.""" if alg == "DDPG": - from ray.rllib import ddpg + from ray.rllib.agents import ddpg return ddpg.DDPGAgent elif alg == "APEX_DDPG": - from ray.rllib import ddpg + from ray.rllib.agents import ddpg return ddpg.ApexDDPGAgent elif alg == "PPO": - from ray.rllib import ppo + from ray.rllib.agents import ppo return ppo.PPOAgent elif alg == "ES": - from ray.rllib import es + from ray.rllib.agents import es return es.ESAgent elif alg == "DQN": - from ray.rllib import dqn + from ray.rllib.agents import dqn return dqn.DQNAgent elif alg == "APEX": - from ray.rllib import dqn + from ray.rllib.agents import dqn return dqn.ApexAgent elif alg == "A3C": - from ray.rllib import a3c + from ray.rllib.agents import a3c return a3c.A3CAgent elif alg == "BC": - from ray.rllib import bc + from ray.rllib.agents import bc return bc.BCAgent elif alg == "PG": - from ray.rllib import pg + from ray.rllib.agents import pg return pg.PGAgent elif alg == "script": from ray.tune import script_runner diff --git a/python/ray/rllib/agents/bc/__init__.py b/python/ray/rllib/agents/bc/__init__.py new file mode 100644 index 000000000..eb0f8dc2d --- /dev/null +++ b/python/ray/rllib/agents/bc/__init__.py @@ -0,0 +1,3 @@ +from ray.rllib.agents.bc.bc import BCAgent, DEFAULT_CONFIG + +__all__ = ["BCAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/bc/bc.py b/python/ray/rllib/agents/bc/bc.py similarity index 95% rename from python/ray/rllib/bc/bc.py rename to python/ray/rllib/agents/bc/bc.py index 1cc05e599..8dee9f6e9 100644 --- a/python/ray/rllib/bc/bc.py +++ b/python/ray/rllib/agents/bc/bc.py @@ -3,9 +3,9 @@ from __future__ import division from __future__ import print_function import ray -from ray.rllib.agent import Agent -from ray.rllib.bc.bc_evaluator import BCEvaluator, GPURemoteBCEvaluator, \ - RemoteBCEvaluator +from ray.rllib.agents.agent import Agent +from ray.rllib.agents.bc.bc_evaluator import BCEvaluator, \ + GPURemoteBCEvaluator, RemoteBCEvaluator from ray.rllib.optimizers import AsyncGradientsOptimizer from ray.tune.result import TrainingResult from ray.tune.trial import Resources diff --git a/python/ray/rllib/bc/bc_evaluator.py b/python/ray/rllib/agents/bc/bc_evaluator.py similarity index 90% rename from python/ray/rllib/bc/bc_evaluator.py rename to python/ray/rllib/agents/bc/bc_evaluator.py index 87a7d4976..a856858c9 100644 --- a/python/ray/rllib/bc/bc_evaluator.py +++ b/python/ray/rllib/agents/bc/bc_evaluator.py @@ -6,10 +6,10 @@ import pickle from six.moves import queue import ray -from ray.rllib.bc.experience_dataset import ExperienceDataset -from ray.rllib.bc.policy import BCPolicy +from ray.rllib.agents.bc.experience_dataset import ExperienceDataset +from ray.rllib.agents.bc.policy import BCPolicy +from ray.rllib.evaluation.interface import PolicyEvaluator from ray.rllib.models import ModelCatalog -from ray.rllib.optimizers import PolicyEvaluator class BCEvaluator(PolicyEvaluator): diff --git a/python/ray/rllib/bc/experience_dataset.py b/python/ray/rllib/agents/bc/experience_dataset.py similarity index 100% rename from python/ray/rllib/bc/experience_dataset.py rename to python/ray/rllib/agents/bc/experience_dataset.py diff --git a/python/ray/rllib/bc/policy.py b/python/ray/rllib/agents/bc/policy.py similarity index 100% rename from python/ray/rllib/bc/policy.py rename to python/ray/rllib/agents/bc/policy.py diff --git a/python/ray/rllib/ddpg/README.md b/python/ray/rllib/agents/ddpg/README.md similarity index 100% rename from python/ray/rllib/ddpg/README.md rename to python/ray/rllib/agents/ddpg/README.md diff --git a/python/ray/rllib/ddpg/__init__.py b/python/ray/rllib/agents/ddpg/__init__.py similarity index 59% rename from python/ray/rllib/ddpg/__init__.py rename to python/ray/rllib/agents/ddpg/__init__.py index 932b9f0c8..7d3390b20 100644 --- a/python/ray/rllib/ddpg/__init__.py +++ b/python/ray/rllib/agents/ddpg/__init__.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from ray.rllib.ddpg.apex import ApexDDPGAgent -from ray.rllib.ddpg.ddpg import DDPGAgent, DEFAULT_CONFIG +from ray.rllib.agents.ddpg.apex import ApexDDPGAgent +from ray.rllib.agents.ddpg.ddpg import DDPGAgent, DEFAULT_CONFIG __all__ = ["DDPGAgent", "ApexDDPGAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/ddpg/apex.py b/python/ray/rllib/agents/ddpg/apex.py similarity index 91% rename from python/ray/rllib/ddpg/apex.py rename to python/ray/rllib/agents/ddpg/apex.py index 8ede5109f..b53d4178e 100644 --- a/python/ray/rllib/ddpg/apex.py +++ b/python/ray/rllib/agents/ddpg/apex.py @@ -2,16 +2,16 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from ray.rllib.ddpg.ddpg import DDPGAgent, DEFAULT_CONFIG as DDPG_CONFIG +from ray.rllib.agents.ddpg.ddpg import DDPGAgent, DEFAULT_CONFIG as DDPG_CONFIG from ray.utils import merge_dicts APEX_DDPG_DEFAULT_CONFIG = merge_dicts( DDPG_CONFIG, { "optimizer_class": "AsyncSamplesOptimizer", - "optimizer_config": + "optimizer": merge_dicts( - DDPG_CONFIG["optimizer_config"], { + DDPG_CONFIG["optimizer"], { "max_weight_sync_delay": 400, "num_replay_buffer_shards": 4, "debug": False diff --git a/python/ray/rllib/ddpg/common/__init__.py b/python/ray/rllib/agents/ddpg/common/__init__.py similarity index 100% rename from python/ray/rllib/ddpg/common/__init__.py rename to python/ray/rllib/agents/ddpg/common/__init__.py diff --git a/python/ray/rllib/ddpg/ddpg.py b/python/ray/rllib/agents/ddpg/ddpg.py similarity index 88% rename from python/ray/rllib/ddpg/ddpg.py rename to python/ray/rllib/agents/ddpg/ddpg.py index 9a93e57c1..c7e45f1b3 100644 --- a/python/ray/rllib/ddpg/ddpg.py +++ b/python/ray/rllib/agents/ddpg/ddpg.py @@ -2,9 +2,10 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from ray.rllib.dqn.common.schedules import ConstantSchedule, LinearSchedule -from ray.rllib.dqn.dqn import DQNAgent -from ray.rllib.ddpg.ddpg_policy_graph import DDPGPolicyGraph +from ray.rllib.agents.agent import with_common_config +from ray.rllib.agents.dqn.dqn import DQNAgent +from ray.rllib.agents.ddpg.ddpg_policy_graph import DDPGPolicyGraph +from ray.rllib.utils.schedules import ConstantSchedule, LinearSchedule OPTIMIZER_SHARED_CONFIGS = [ "buffer_size", "prioritized_replay", "prioritized_replay_alpha", @@ -12,7 +13,7 @@ OPTIMIZER_SHARED_CONFIGS = [ "train_batch_size", "learning_starts", "clip_rewards" ] -DEFAULT_CONFIG = { +DEFAULT_CONFIG = with_common_config({ # === Model === # Hidden layer sizes of the policy network "actor_hiddens": [64, 64], @@ -24,12 +25,6 @@ DEFAULT_CONFIG = { "critic_hidden_activation": "relu", # N-step Q learning "n_step": 1, - # Config options to pass to the model constructor - "model": {}, - # Discount factor for the MDP - "gamma": 0.99, - # Arguments to pass to the env creator - "env_config": {}, # === Exploration === # Max num timesteps for annealing schedules. Exploration is annealed from @@ -99,30 +94,21 @@ DEFAULT_CONFIG = { # to increase if your environment is particularly slow to sample, or if # you"re using the Async or Ape-X optimizers. "num_workers": 0, - # Number of environments to evaluate vectorwise per worker. - "num_envs": 1, # Whether to allocate GPUs for workers (if > 0). "num_gpus_per_worker": 0, # Whether to allocate CPUs for workers (if > 0). "num_cpus_per_worker": 1, # Optimizer class to use. "optimizer_class": "SyncReplayOptimizer", - # Config to pass to the optimizer. - "optimizer_config": {}, # Whether to use a distribution of epsilons across workers for exploration. "per_worker_exploration": False, # Whether to compute priorities on workers. "worker_side_prioritization": False, - - # === Multiagent === - "multiagent": { - "policy_graphs": {}, - "policy_mapping_fn": None, - }, -} +}) class DDPGAgent(DQNAgent): + """DDPG implementation in TensorFlow.""" _agent_name = "DDPG" _default_config = DEFAULT_CONFIG _policy_graph = DDPGPolicyGraph diff --git a/python/ray/rllib/ddpg/ddpg_policy_graph.py b/python/ray/rllib/agents/ddpg/ddpg_policy_graph.py similarity index 98% rename from python/ray/rllib/ddpg/ddpg_policy_graph.py rename to python/ray/rllib/agents/ddpg/ddpg_policy_graph.py index 34aa9682b..a8a44980b 100644 --- a/python/ray/rllib/ddpg/ddpg_policy_graph.py +++ b/python/ray/rllib/agents/ddpg/ddpg_policy_graph.py @@ -8,11 +8,11 @@ import tensorflow as tf import tensorflow.contrib.layers as layers import ray -from ray.rllib.dqn.dqn_policy_graph import _huber_loss, _minimize_and_clip, \ - _scope_vars, _postprocess_dqn +from ray.rllib.agents.dqn.dqn_policy_graph import _huber_loss, \ + _minimize_and_clip, _scope_vars, _postprocess_dqn from ray.rllib.models import ModelCatalog from ray.rllib.utils.error import UnsupportedSpaceException -from ray.rllib.utils.tf_policy_graph import TFPolicyGraph +from ray.rllib.evaluation.tf_policy_graph import TFPolicyGraph A_SCOPE = "a_func" @@ -113,7 +113,7 @@ class ActorCriticLoss(object): class DDPGPolicyGraph(TFPolicyGraph): def __init__(self, observation_space, action_space, config): - config = dict(ray.rllib.ddpg.ddpg.DEFAULT_CONFIG, **config) + config = dict(ray.rllib.agents.ddpg.ddpg.DEFAULT_CONFIG, **config) if not isinstance(action_space, Box): raise UnsupportedSpaceException( "Action space {} is not supported for DDPG.".format( diff --git a/python/ray/rllib/dqn/README.md b/python/ray/rllib/agents/dqn/README.md similarity index 100% rename from python/ray/rllib/dqn/README.md rename to python/ray/rllib/agents/dqn/README.md diff --git a/python/ray/rllib/dqn/__init__.py b/python/ray/rllib/agents/dqn/__init__.py similarity index 60% rename from python/ray/rllib/dqn/__init__.py rename to python/ray/rllib/agents/dqn/__init__.py index a383adeb4..b46c249e6 100644 --- a/python/ray/rllib/dqn/__init__.py +++ b/python/ray/rllib/agents/dqn/__init__.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from ray.rllib.dqn.apex import ApexAgent -from ray.rllib.dqn.dqn import DQNAgent, DEFAULT_CONFIG +from ray.rllib.agents.dqn.apex import ApexAgent +from ray.rllib.agents.dqn.dqn import DQNAgent, DEFAULT_CONFIG __all__ = ["ApexAgent", "DQNAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/dqn/apex.py b/python/ray/rllib/agents/dqn/apex.py similarity index 89% rename from python/ray/rllib/dqn/apex.py rename to python/ray/rllib/agents/dqn/apex.py index d12754b89..1c8b2f6b3 100644 --- a/python/ray/rllib/dqn/apex.py +++ b/python/ray/rllib/agents/dqn/apex.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from ray.rllib.dqn.dqn import DQNAgent, DEFAULT_CONFIG as DQN_CONFIG +from ray.rllib.agents.dqn.dqn import DQNAgent, DEFAULT_CONFIG as DQN_CONFIG from ray.tune.trial import Resources from ray.utils import merge_dicts @@ -10,9 +10,9 @@ APEX_DEFAULT_CONFIG = merge_dicts( DQN_CONFIG, { "optimizer_class": "AsyncSamplesOptimizer", - "optimizer_config": + "optimizer": merge_dicts( - DQN_CONFIG["optimizer_config"], { + DQN_CONFIG["optimizer"], { "max_weight_sync_delay": 400, "num_replay_buffer_shards": 4, "debug": False @@ -47,7 +47,7 @@ class ApexAgent(DQNAgent): def default_resource_request(cls, config): cf = dict(cls._default_config, **config) return Resources( - cpu=1 + cf["optimizer_config"]["num_replay_buffer_shards"], + cpu=1 + cf["optimizer"]["num_replay_buffer_shards"], gpu=cf["gpu"] and 1 or 0, extra_cpu=cf["num_cpus_per_worker"] * cf["num_workers"], extra_gpu=cf["num_gpus_per_worker"] * cf["num_workers"]) diff --git a/python/ray/rllib/dqn/common/__init__.py b/python/ray/rllib/agents/dqn/common/__init__.py similarity index 100% rename from python/ray/rllib/dqn/common/__init__.py rename to python/ray/rllib/agents/dqn/common/__init__.py diff --git a/python/ray/rllib/dqn/common/wrappers.py b/python/ray/rllib/agents/dqn/common/wrappers.py similarity index 100% rename from python/ray/rllib/dqn/common/wrappers.py rename to python/ray/rllib/agents/dqn/common/wrappers.py diff --git a/python/ray/rllib/dqn/dqn.py b/python/ray/rllib/agents/dqn/dqn.py similarity index 75% rename from python/ray/rllib/dqn/dqn.py rename to python/ray/rllib/agents/dqn/dqn.py index 8c0e55391..ba1224732 100644 --- a/python/ray/rllib/dqn/dqn.py +++ b/python/ray/rllib/agents/dqn/dqn.py @@ -7,11 +7,10 @@ import os import ray from ray.rllib import optimizers -from ray.rllib.dqn.common.schedules import ConstantSchedule, LinearSchedule -from ray.rllib.dqn.dqn_policy_graph import DQNPolicyGraph -from ray.rllib.utils.common_policy_evaluator import CommonPolicyEvaluator, \ - collect_metrics -from ray.rllib.agent import Agent +from ray.rllib.agents.agent import Agent, with_common_config +from ray.rllib.agents.dqn.dqn_policy_graph import DQNPolicyGraph +from ray.rllib.evaluation.metrics import collect_metrics +from ray.rllib.utils.schedules import ConstantSchedule, LinearSchedule from ray.tune.trial import Resources @@ -20,7 +19,7 @@ OPTIMIZER_SHARED_CONFIGS = [ "prioritized_replay_beta", "prioritized_replay_eps", "sample_batch_size", "train_batch_size", "learning_starts", "clip_rewards"] -DEFAULT_CONFIG = { +DEFAULT_CONFIG = with_common_config({ # === Model === # Whether to use dueling dqn "dueling": True, @@ -30,12 +29,8 @@ DEFAULT_CONFIG = { "hiddens": [256], # N-step Q learning "n_step": 1, - # Config options to pass to the model constructor - "model": {}, - # Discount factor for the MDP - "gamma": 0.99, - # Arguments to pass to the env creator - "env_config": {}, + # Whether to use rllib or deepmind preprocessors + "preprocessor_pref": "deepmind", # === Exploration === # Max num timesteps for annealing schedules. Exploration is annealed from @@ -66,6 +61,8 @@ DEFAULT_CONFIG = { "prioritized_replay_eps": 1e-6, # Whether to clip rewards to [-1, 1] prior to adding to the replay buffer. "clip_rewards": True, + # Whether to LZ4 compress observations + "compress_observations": True, # === Optimization === # Learning rate for adam optimizer @@ -89,30 +86,22 @@ DEFAULT_CONFIG = { # to increase if your environment is particularly slow to sample, or if # you"re using the Async or Ape-X optimizers. "num_workers": 0, - # Number of environments to evaluate vectorwise per worker. - "num_envs": 1, # Whether to allocate GPUs for workers (if > 0). "num_gpus_per_worker": 0, # Whether to allocate CPUs for workers (if > 0). "num_cpus_per_worker": 1, # Optimizer class to use. "optimizer_class": "SyncReplayOptimizer", - # Config to pass to the optimizer. - "optimizer_config": {}, # Whether to use a distribution of epsilons across workers for exploration. "per_worker_exploration": False, # Whether to compute priorities on workers. "worker_side_prioritization": False, - - # === Multiagent === - "multiagent": { - "policy_graphs": {}, - "policy_mapping_fn": None, - }, -} +}) class DQNAgent(Agent): + """DQN implementation in TensorFlow.""" + _agent_name = "DQN" _default_config = DEFAULT_CONFIG _policy_graph = DQNPolicyGraph @@ -126,32 +115,10 @@ class DQNAgent(Agent): extra_gpu=cf["num_gpus_per_worker"] * cf["num_workers"]) def _init(self): + # Update effective batch size to include n-step adjusted_batch_size = ( self.config["sample_batch_size"] + self.config["n_step"] - 1) - self.local_evaluator = CommonPolicyEvaluator( - self.env_creator, - self.config["multiagent"]["policy_graphs"] or self._policy_graph, - policy_mapping_fn=self.config["multiagent"]["policy_mapping_fn"], - batch_steps=adjusted_batch_size, - batch_mode="truncate_episodes", preprocessor_pref="deepmind", - compress_observations=True, - env_config=self.config["env_config"], - model_config=self.config["model"], policy_config=self.config, - num_envs=self.config["num_envs"]) - remote_cls = CommonPolicyEvaluator.as_remote( - num_cpus=self.config["num_cpus_per_worker"], - num_gpus=self.config["num_gpus_per_worker"]) - self.remote_evaluators = [ - remote_cls.remote( - self.env_creator, self._policy_graph, - batch_steps=adjusted_batch_size, - batch_mode="truncate_episodes", preprocessor_pref="deepmind", - compress_observations=True, - env_config=self.config["env_config"], - model_config=self.config["model"], policy_config=self.config, - num_envs=self.config["num_envs"], - worker_index=i+1) - for i in range(self.config["num_workers"])] + self.config["sample_batch_size"] = adjusted_batch_size self.exploration0 = self._make_exploration_schedule(0) self.explorations = [ @@ -159,11 +126,17 @@ class DQNAgent(Agent): for i in range(self.config["num_workers"])] for k in OPTIMIZER_SHARED_CONFIGS: - if k not in self.config["optimizer_config"]: - self.config["optimizer_config"][k] = self.config[k] + if k not in self.config["optimizer"]: + self.config["optimizer"][k] = self.config[k] + self.local_evaluator = self.make_local_evaluator( + self.env_creator, self._policy_graph) + self.remote_evaluators = self.make_remote_evaluators( + self.env_creator, self._policy_graph, self.config["num_workers"], + {"num_cpus": self.config["num_cpus_per_worker"], + "num_gpus": self.config["num_gpus_per_worker"]}) self.optimizer = getattr(optimizers, self.config["optimizer_class"])( - self.config["optimizer_config"], self.local_evaluator, + self.config["optimizer"], self.local_evaluator, self.remote_evaluators) self.last_target_update_ts = 0 @@ -247,10 +220,3 @@ class DQNAgent(Agent): self.optimizer.restore(extra_data[2]) self.num_target_updates = extra_data[3] self.last_target_update_ts = extra_data[4] - - def compute_action(self, observation, state=None): - if state is None: - state = [] - return self.local_evaluator.for_policy( - lambda p: p.compute_single_action( - observation, state, is_training=False)[0]) diff --git a/python/ray/rllib/dqn/dqn_policy_graph.py b/python/ray/rllib/agents/dqn/dqn_policy_graph.py similarity index 98% rename from python/ray/rllib/dqn/dqn_policy_graph.py rename to python/ray/rllib/agents/dqn/dqn_policy_graph.py index ecf6ac5dc..f94cc16a3 100644 --- a/python/ray/rllib/dqn/dqn_policy_graph.py +++ b/python/ray/rllib/agents/dqn/dqn_policy_graph.py @@ -9,9 +9,9 @@ import tensorflow.contrib.layers as layers import ray from ray.rllib.models import ModelCatalog -from ray.rllib.optimizers.sample_batch import SampleBatch +from ray.rllib.evaluation.sample_batch import SampleBatch from ray.rllib.utils.error import UnsupportedSpaceException -from ray.rllib.utils.tf_policy_graph import TFPolicyGraph +from ray.rllib.evaluation.tf_policy_graph import TFPolicyGraph Q_SCOPE = "q_func" @@ -79,7 +79,7 @@ class QLoss(object): class DQNPolicyGraph(TFPolicyGraph): def __init__(self, observation_space, action_space, config): - config = dict(ray.rllib.dqn.dqn.DEFAULT_CONFIG, **config) + config = dict(ray.rllib.agents.dqn.dqn.DEFAULT_CONFIG, **config) if not isinstance(action_space, Discrete): raise UnsupportedSpaceException( "Action space {} is not supported for DQN.".format( diff --git a/python/ray/rllib/agents/es/__init__.py b/python/ray/rllib/agents/es/__init__.py new file mode 100644 index 000000000..3ea5f2edc --- /dev/null +++ b/python/ray/rllib/agents/es/__init__.py @@ -0,0 +1,3 @@ +from ray.rllib.agents.es.es import (ESAgent, DEFAULT_CONFIG) + +__all__ = ["ESAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/es/es.py b/python/ray/rllib/agents/es/es.py similarity index 97% rename from python/ray/rllib/es/es.py rename to python/ray/rllib/agents/es/es.py index b900f88a7..62249e380 100644 --- a/python/ray/rllib/es/es.py +++ b/python/ray/rllib/agents/es/es.py @@ -12,13 +12,13 @@ import pickle import time import ray -from ray.rllib import agent +from ray.rllib.agents import Agent from ray.tune.trial import Resources -from ray.rllib.es import optimizers -from ray.rllib.es import policies -from ray.rllib.es import tabular_logger as tlogger -from ray.rllib.es import utils +from ray.rllib.agents.es import optimizers +from ray.rllib.agents.es import policies +from ray.rllib.agents.es import tabular_logger as tlogger +from ray.rllib.agents.es import utils Result = namedtuple("Result", [ @@ -134,7 +134,9 @@ class Worker(object): eval_lengths=eval_lengths) -class ESAgent(agent.Agent): +class ESAgent(Agent): + """Large-scale implementation of Evolution Strategies in Ray.""" + _agent_name = "ES" _default_config = DEFAULT_CONFIG diff --git a/python/ray/rllib/es/optimizers.py b/python/ray/rllib/agents/es/optimizers.py similarity index 100% rename from python/ray/rllib/es/optimizers.py rename to python/ray/rllib/agents/es/optimizers.py diff --git a/python/ray/rllib/es/policies.py b/python/ray/rllib/agents/es/policies.py similarity index 100% rename from python/ray/rllib/es/policies.py rename to python/ray/rllib/agents/es/policies.py diff --git a/python/ray/rllib/es/tabular_logger.py b/python/ray/rllib/agents/es/tabular_logger.py similarity index 100% rename from python/ray/rllib/es/tabular_logger.py rename to python/ray/rllib/agents/es/tabular_logger.py diff --git a/python/ray/rllib/es/utils.py b/python/ray/rllib/agents/es/utils.py similarity index 100% rename from python/ray/rllib/es/utils.py rename to python/ray/rllib/agents/es/utils.py diff --git a/python/ray/rllib/agents/pg/__init__.py b/python/ray/rllib/agents/pg/__init__.py new file mode 100644 index 000000000..f0665f448 --- /dev/null +++ b/python/ray/rllib/agents/pg/__init__.py @@ -0,0 +1,3 @@ +from ray.rllib.agents.pg.pg import PGAgent, DEFAULT_CONFIG + +__all__ = ["PGAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/agents/pg/pg.py b/python/ray/rllib/agents/pg/pg.py new file mode 100644 index 000000000..05a600cdb --- /dev/null +++ b/python/ray/rllib/agents/pg/pg.py @@ -0,0 +1,54 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from ray.rllib.agents.agent import Agent, with_common_config +from ray.rllib.agents.pg.pg_policy_graph import PGPolicyGraph +from ray.rllib.evaluation.metrics import collect_metrics +from ray.rllib.optimizers import SyncSamplesOptimizer +from ray.tune.trial import Resources + + +DEFAULT_CONFIG = with_common_config({ + # No remote workers by default + "num_workers": 0, + # Learning rate + "lr": 0.0004, + # Override model config + "model": { + # Use LSTM model. + "use_lstm": False, + # Max seq length for LSTM training. + "max_seq_len": 20, + }, +}) + + +class PGAgent(Agent): + """Simple policy gradient agent. + + This is an example agent to show how to implement algorithms in RLlib. + In most cases, you will probably want to use the PPO agent instead. + """ + + _agent_name = "PG" + _default_config = DEFAULT_CONFIG + + @classmethod + def default_resource_request(cls, config): + cf = dict(cls._default_config, **config) + return Resources(cpu=1, gpu=0, extra_cpu=cf["num_workers"]) + + def _init(self): + self.local_evaluator = self.make_local_evaluator( + self.env_creator, PGPolicyGraph) + self.remote_evaluators = self.make_remote_evaluators( + self.env_creator, PGPolicyGraph, self.config["num_workers"], {}) + self.optimizer = SyncSamplesOptimizer( + self.config["optimizer"], self.local_evaluator, + self.remote_evaluators) + + def _train(self): + self.optimizer.step() + return collect_metrics( + self.optimizer.local_evaluator, self.optimizer.remote_evaluators) diff --git a/python/ray/rllib/pg/pg_policy_graph.py b/python/ray/rllib/agents/pg/pg_policy_graph.py similarity index 91% rename from python/ray/rllib/pg/pg_policy_graph.py rename to python/ray/rllib/agents/pg/pg_policy_graph.py index 2fec360f2..42124e3d1 100644 --- a/python/ray/rllib/pg/pg_policy_graph.py +++ b/python/ray/rllib/agents/pg/pg_policy_graph.py @@ -6,8 +6,8 @@ import tensorflow as tf import ray from ray.rllib.models.catalog import ModelCatalog -from ray.rllib.utils.postprocessing import compute_advantages -from ray.rllib.utils.tf_policy_graph import TFPolicyGraph +from ray.rllib.evaluation.postprocessing import compute_advantages +from ray.rllib.evaluation.tf_policy_graph import TFPolicyGraph class PGLoss(object): @@ -17,7 +17,7 @@ class PGLoss(object): class PGPolicyGraph(TFPolicyGraph): def __init__(self, obs_space, action_space, config): - config = dict(ray.rllib.pg.pg.DEFAULT_CONFIG, **config) + config = dict(ray.rllib.agents.pg.pg.DEFAULT_CONFIG, **config) self.config = config # Setup policy diff --git a/python/ray/rllib/agents/ppo/__init__.py b/python/ray/rllib/agents/ppo/__init__.py new file mode 100644 index 000000000..e4d0c7cf0 --- /dev/null +++ b/python/ray/rllib/agents/ppo/__init__.py @@ -0,0 +1,3 @@ +from ray.rllib.agents.ppo.ppo import (PPOAgent, DEFAULT_CONFIG) + +__all__ = ["PPOAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/ppo/ppo.py b/python/ray/rllib/agents/ppo/ppo.py similarity index 53% rename from python/ray/rllib/ppo/ppo.py rename to python/ray/rllib/agents/ppo/ppo.py index dfd2c594d..a83c10f3b 100644 --- a/python/ray/rllib/ppo/ppo.py +++ b/python/ray/rllib/agents/ppo/ppo.py @@ -5,22 +5,16 @@ from __future__ import print_function import os import numpy as np import pickle -import tensorflow as tf import ray -from ray.tune.trial import Resources -from ray.rllib.agent import Agent -from ray.rllib.utils.common_policy_evaluator import ( - CommonPolicyEvaluator, collect_metrics) +from ray.rllib.agents import Agent, with_common_config +from ray.rllib.agents.ppo.ppo_tf_policy import PPOTFPolicyGraph +from ray.rllib.evaluation.metrics import collect_metrics from ray.rllib.utils import FilterManager -from ray.rllib.ppo.ppo_tf_policy import PPOTFPolicyGraph from ray.rllib.optimizers.multi_gpu_optimizer import LocalMultiGPUOptimizer +from ray.tune.trial import Resources -DEFAULT_CONFIG = { - # Discount factor of the MDP - "gamma": 0.995, - # Number of steps after which the rollout gets cut - "horizon": 2000, +DEFAULT_CONFIG = with_common_config({ # If true, use the Generalized Advantage Estimator (GAE) # with a value function, see https://arxiv.org/pdf/1506.02438.pdf. "use_gae": True, @@ -28,22 +22,12 @@ DEFAULT_CONFIG = { "lambda": 1.0, # Initial coefficient for KL divergence "kl_coeff": 0.2, + # Number of timesteps collected for each SGD round + "timesteps_per_batch": 4000, # Number of SGD iterations in each outer loop "num_sgd_iter": 30, # Stepsize of SGD "sgd_stepsize": 5e-5, - # TODO(pcm): Expose the choice between gpus and cpus - # as a command line argument. - "devices": ["/cpu:%d" % i for i in range(4)], - "tf_session_args": { - "device_count": {"CPU": 4}, - "log_device_placement": False, - "allow_soft_placement": True, - "intra_op_parallelism_threads": 1, - "inter_op_parallelism_threads": 1, - }, - # Batch size for policy evaluations for rollouts - "rollout_batchsize": 1, # Total SGD batch size across all devices for SGD "sgd_batchsize": 128, # Coefficient of the value function loss @@ -54,82 +38,41 @@ DEFAULT_CONFIG = { "clip_param": 0.3, # Target value for KL divergence "kl_target": 0.01, - # Config params to pass to the model - "model": {"free_log_std": False}, - # Which observation filter to apply to the observation - "observation_filter": "MeanStdFilter", - # If >1, adds frameskip - "extra_frameskip": 1, - # Number of timesteps collected in each outer loop - "timesteps_per_batch": 4000, - # Each tasks performs rollouts until at least this - # number of steps is obtained - "min_steps_per_task": 200, - # Number of actors used to collect the rollouts - "num_workers": 2, + # Number of GPUs to use for SGD + "num_gpus": 0, # Whether to allocate GPUs for workers (if > 0). "num_gpus_per_worker": 0, # Whether to allocate CPUs for workers (if > 0). "num_cpus_per_worker": 1, - # Dump TensorFlow timeline after this many SGD minibatches - "full_trace_nth_sgd_batch": -1, - # Whether to profile data loading - "full_trace_data_load": False, - # Outer loop iteration index when we drop into the TensorFlow debugger - "tf_debug_iteration": -1, - # If this is True, the TensorFlow debugger is invoked if an Inf or NaN - # is detected - "tf_debug_inf_or_nan": False, - # If True, we write tensorflow logs and checkpoints - "write_logs": True, - # Arguments to pass to the env creator - "env_config": {}, -} + # Whether to rollout "complete_episodes" or "truncate_episodes" + "batch_mode": "complete_episodes", + # Which observation filter to apply to the observation + "observation_filter": "MeanStdFilter", +}) class PPOAgent(Agent): + """Multi-GPU optimized implementation of PPO in TensorFlow.""" + _agent_name = "PPO" _default_config = DEFAULT_CONFIG - _default_policy_graph = PPOTFPolicyGraph @classmethod def default_resource_request(cls, config): cf = dict(cls._default_config, **config) return Resources( cpu=1, - gpu=len([d for d in cf["devices"] if "gpu" in d.lower()]), + gpu=cf["num_gpus"], extra_cpu=cf["num_cpus_per_worker"] * cf["num_workers"], extra_gpu=cf["num_gpus_per_worker"] * cf["num_workers"]) def _init(self): - def session_creator(): - return tf.Session( - config=tf.ConfigProto(**self.config["tf_session_args"])) - self.local_evaluator = CommonPolicyEvaluator( - self.env_creator, - self._default_policy_graph, - tf_session_creator=session_creator, - batch_mode="complete_episodes", - observation_filter=self.config["observation_filter"], - env_config=self.config["env_config"], - model_config=self.config["model"], - policy_config=self.config - ) - RemoteEvaluator = CommonPolicyEvaluator.as_remote( - num_cpus=self.config["num_cpus_per_worker"], - num_gpus=self.config["num_gpus_per_worker"]) - self.remote_evaluators = [ - RemoteEvaluator.remote( - self.env_creator, - self._default_policy_graph, - batch_mode="complete_episodes", - observation_filter=self.config["observation_filter"], - env_config=self.config["env_config"], - model_config=self.config["model"], - policy_config=self.config - ) - for _ in range(self.config["num_workers"])] - + self.local_evaluator = self.make_local_evaluator( + self.env_creator, PPOTFPolicyGraph) + self.remote_evaluators = self.make_remote_evaluators( + self.env_creator, PPOTFPolicyGraph, self.config["num_workers"], + {"num_cpus": self.config["num_cpus_per_worker"], + "num_gpus": self.config["num_gpus_per_worker"]}) self.optimizer = LocalMultiGPUOptimizer( {"sgd_batch_size": self.config["sgd_batchsize"], "sgd_stepsize": self.config["sgd_stepsize"], @@ -137,10 +80,6 @@ class PPOAgent(Agent): "timesteps_per_batch": self.config["timesteps_per_batch"]}, self.local_evaluator, self.remote_evaluators) - # TODO(rliaw): Push into Policy Graph - with self.local_evaluator.tf_sess.graph.as_default(): - self.saver = tf.train.Saver() - def _train(self): def postprocess_samples(batch): # Divide by the maximum of value.std() and 1e-4 @@ -183,10 +122,8 @@ class PPOAgent(Agent): ev.__ray_terminate__.remote() def _save(self, checkpoint_dir): - checkpoint_path = self.saver.save( - self.local_evaluator.tf_sess, - os.path.join(checkpoint_dir, "checkpoint"), - global_step=self.iteration) + checkpoint_path = os.path.join(checkpoint_dir, + "checkpoint-{}".format(self.iteration)) agent_state = ray.get( [a.save.remote() for a in self.remote_evaluators]) extra_data = [ @@ -196,18 +133,8 @@ class PPOAgent(Agent): return checkpoint_path def _restore(self, checkpoint_path): - self.saver.restore(self.local_evaluator.tf_sess, checkpoint_path) extra_data = pickle.load(open(checkpoint_path + ".extra_data", "rb")) self.local_evaluator.restore(extra_data[0]) ray.get([ a.restore.remote(o) for (a, o) in zip(self.remote_evaluators, extra_data[1])]) - - def compute_action(self, observation, state=None): - if state is None: - state = [] - obs = self.local_evaluator.filters["default"]( - observation, update=False) - return self.local_evaluator.for_policy( - lambda p: p.compute_single_action( - obs, state, is_training=False)[0]) diff --git a/python/ray/rllib/ppo/ppo_tf_policy.py b/python/ray/rllib/agents/ppo/ppo_tf_policy.py similarity index 98% rename from python/ray/rllib/ppo/ppo_tf_policy.py rename to python/ray/rllib/agents/ppo/ppo_tf_policy.py index 3fd8b06fc..887357d9a 100644 --- a/python/ray/rllib/ppo/ppo_tf_policy.py +++ b/python/ray/rllib/agents/ppo/ppo_tf_policy.py @@ -4,9 +4,9 @@ from __future__ import print_function import tensorflow as tf +from ray.rllib.evaluation.postprocessing import compute_advantages +from ray.rllib.evaluation.tf_policy_graph import TFPolicyGraph from ray.rllib.models.catalog import ModelCatalog -from ray.rllib.utils.postprocessing import compute_advantages -from ray.rllib.utils.tf_policy_graph import TFPolicyGraph class PPOLoss(object): @@ -120,6 +120,7 @@ class PPOTFPolicyGraph(TFPolicyGraph): ("logprobs", logprobs_ph), ("vf_preds", vf_preds_ph) ] + # TODO(ekl) feed RNN states in here # KL Coefficient self.kl_coeff = tf.get_variable( diff --git a/python/ray/rllib/ppo/rollout.py b/python/ray/rllib/agents/ppo/rollout.py similarity index 95% rename from python/ray/rllib/ppo/rollout.py rename to python/ray/rllib/agents/ppo/rollout.py index 9f7c39a30..54a235680 100644 --- a/python/ray/rllib/ppo/rollout.py +++ b/python/ray/rllib/agents/ppo/rollout.py @@ -3,7 +3,7 @@ from __future__ import division from __future__ import print_function import ray -from ray.rllib.optimizers import SampleBatch +from ray.rllib.evaluation.sample_batch import SampleBatch def collect_samples(agents, timesteps_per_batch): diff --git a/python/ray/rllib/ppo/test/test.py b/python/ray/rllib/agents/ppo/test/test.py similarity index 97% rename from python/ray/rllib/ppo/test/test.py rename to python/ray/rllib/agents/ppo/test/test.py index 6ab59af93..d6454eb56 100644 --- a/python/ray/rllib/ppo/test/test.py +++ b/python/ray/rllib/agents/ppo/test/test.py @@ -8,7 +8,7 @@ import tensorflow as tf from numpy.testing import assert_allclose from ray.rllib.models.action_dist import Categorical -from ray.rllib.ppo.utils import flatten, concatenate +from ray.rllib.agents.ppo.utils import flatten, concatenate # TODO(ekl): move to rllib/models dir diff --git a/python/ray/rllib/ppo/utils.py b/python/ray/rllib/agents/ppo/utils.py similarity index 100% rename from python/ray/rllib/ppo/utils.py rename to python/ray/rllib/agents/ppo/utils.py diff --git a/python/ray/rllib/bc/__init__.py b/python/ray/rllib/bc/__init__.py deleted file mode 100644 index 8b6e41297..000000000 --- a/python/ray/rllib/bc/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from ray.rllib.bc.bc import BCAgent, DEFAULT_CONFIG - -__all__ = ["BCAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/env/__init__.py b/python/ray/rllib/env/__init__.py new file mode 100644 index 000000000..752d27cec --- /dev/null +++ b/python/ray/rllib/env/__init__.py @@ -0,0 +1,9 @@ +from ray.rllib.env.async_vector_env import AsyncVectorEnv +from ray.rllib.env.multi_agent_env import MultiAgentEnv +from ray.rllib.env.serving_env import ServingEnv +from ray.rllib.env.vector_env import VectorEnv +from ray.rllib.env.env_context import EnvContext + +__all__ = [ + "AsyncVectorEnv", "MultiAgentEnv", "ServingEnv", "VectorEnv", "EnvContext" +] diff --git a/python/ray/rllib/utils/async_vector_env.py b/python/ray/rllib/env/async_vector_env.py similarity index 98% rename from python/ray/rllib/utils/async_vector_env.py rename to python/ray/rllib/env/async_vector_env.py index 268a7896c..fcd661cb9 100644 --- a/python/ray/rllib/utils/async_vector_env.py +++ b/python/ray/rllib/env/async_vector_env.py @@ -2,9 +2,9 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from ray.rllib.utils.serving_env import ServingEnv -from ray.rllib.utils.vector_env import VectorEnv -from ray.rllib.utils.multi_agent_env import MultiAgentEnv +from ray.rllib.env.serving_env import ServingEnv +from ray.rllib.env.vector_env import VectorEnv +from ray.rllib.env.multi_agent_env import MultiAgentEnv class AsyncVectorEnv(object): @@ -84,7 +84,8 @@ class AsyncVectorEnv(object): The returns are two-level dicts mapping from env_id to a dict of agent_id to values. The number of agents and envs can vary over time. - Returns: + Returns + ------- obs (dict): New observations for each ready agent. rewards (dict): Reward values for each ready agent. If the episode is just started, the value will be None. @@ -95,6 +96,7 @@ class AsyncVectorEnv(object): that happens, there will be an entry in this dict that contains the taken action. There is no need to send_actions() for agents that have already chosen off-policy actions. + """ raise NotImplementedError diff --git a/python/ray/rllib/utils/atari_wrappers.py b/python/ray/rllib/env/atari_wrappers.py similarity index 100% rename from python/ray/rllib/utils/atari_wrappers.py rename to python/ray/rllib/env/atari_wrappers.py diff --git a/python/ray/rllib/utils/env_context.py b/python/ray/rllib/env/env_context.py similarity index 100% rename from python/ray/rllib/utils/env_context.py rename to python/ray/rllib/env/env_context.py diff --git a/python/ray/rllib/utils/multi_agent_env.py b/python/ray/rllib/env/multi_agent_env.py similarity index 91% rename from python/ray/rllib/utils/multi_agent_env.py rename to python/ray/rllib/env/multi_agent_env.py index 9a3015fff..42f7cee8c 100644 --- a/python/ray/rllib/utils/multi_agent_env.py +++ b/python/ray/rllib/env/multi_agent_env.py @@ -6,7 +6,8 @@ from __future__ import print_function class MultiAgentEnv(object): """An environment that hosts multiple independent agents. - Agents are identified by (string) agent ids. + Agents are identified by (string) agent ids. Note that these "agents" here + are not to be confused with RLlib agents. Examples: >>> env = MyMultiAgentEnv() @@ -49,7 +50,8 @@ class MultiAgentEnv(object): The returns are dicts mapping from agent_id strings to values. The number of agents in the env can vary over time. - Returns: + Returns + ------- obs (dict): New observations for each ready agent. rewards (dict): Reward values for each ready agent. If the episode is just started, the value will be None. diff --git a/python/ray/rllib/utils/serving_env.py b/python/ray/rllib/env/serving_env.py similarity index 100% rename from python/ray/rllib/utils/serving_env.py rename to python/ray/rllib/env/serving_env.py diff --git a/python/ray/rllib/utils/vector_env.py b/python/ray/rllib/env/vector_env.py similarity index 98% rename from python/ray/rllib/utils/vector_env.py rename to python/ray/rllib/env/vector_env.py index 926048c48..ef57be859 100644 --- a/python/ray/rllib/utils/vector_env.py +++ b/python/ray/rllib/env/vector_env.py @@ -14,7 +14,7 @@ class VectorEnv(object): Attributes: action_space (gym.Space): Action space of individual envs. observation_space (gym.Space): Observation space of individual envs. - num_envs (int): Number of envs to batch over. + num_envs (int): Number of envs in this vector env. """ @staticmethod diff --git a/python/ray/rllib/es/__init__.py b/python/ray/rllib/es/__init__.py deleted file mode 100644 index b459494a9..000000000 --- a/python/ray/rllib/es/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from ray.rllib.es.es import (ESAgent, DEFAULT_CONFIG) - -__all__ = ["ESAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/evaluation/__init__.py b/python/ray/rllib/evaluation/__init__.py new file mode 100644 index 000000000..bc6acb472 --- /dev/null +++ b/python/ray/rllib/evaluation/__init__.py @@ -0,0 +1,14 @@ +from ray.rllib.evaluation.common_policy_evaluator import CommonPolicyEvaluator +from ray.rllib.evaluation.interface import PolicyEvaluator +from ray.rllib.evaluation.policy_graph import PolicyGraph +from ray.rllib.evaluation.tf_policy_graph import TFPolicyGraph +from ray.rllib.evaluation.torch_policy_graph import TorchPolicyGraph +from ray.rllib.evaluation.sample_batch import SampleBatch, MultiAgentBatch, \ + SampleBatchBuilder, MultiAgentSampleBatchBuilder +from ray.rllib.evaluation.sampler import SyncSampler, AsyncSampler + +__all__ = [ + "PolicyEvaluator", "CommonPolicyEvaluator", "PolicyGraph", "TFPolicyGraph", + "TorchPolicyGraph", "SampleBatch", "MultiAgentBatch", "SampleBatchBuilder", + "MultiAgentSampleBatchBuilder", "SyncSampler", "AsyncSampler", +] diff --git a/python/ray/rllib/utils/common_policy_evaluator.py b/python/ray/rllib/evaluation/common_policy_evaluator.py similarity index 82% rename from python/ray/rllib/utils/common_policy_evaluator.py rename to python/ray/rllib/evaluation/common_policy_evaluator.py index c25ad30b0..76cefe9f9 100644 --- a/python/ray/rllib/utils/common_policy_evaluator.py +++ b/python/ray/rllib/evaluation/common_policy_evaluator.py @@ -2,112 +2,75 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import collections import gym -import numpy as np import pickle import tensorflow as tf import ray from ray.rllib.models import ModelCatalog -from ray.rllib.optimizers.policy_evaluator import PolicyEvaluator -from ray.rllib.optimizers.sample_batch import MultiAgentBatch, \ +from ray.rllib.env.async_vector_env import AsyncVectorEnv +from ray.rllib.env.atari_wrappers import wrap_deepmind, is_atari +from ray.rllib.env.env_context import EnvContext +from ray.rllib.env.serving_env import ServingEnv +from ray.rllib.env.vector_env import VectorEnv +from ray.rllib.env.multi_agent_env import MultiAgentEnv +from ray.rllib.evaluation.interface import PolicyEvaluator +from ray.rllib.evaluation.sample_batch import MultiAgentBatch, \ DEFAULT_POLICY_ID -from ray.rllib.utils.async_vector_env import AsyncVectorEnv -from ray.rllib.utils.atari_wrappers import wrap_deepmind, is_atari +from ray.rllib.evaluation.sampler import AsyncSampler, SyncSampler from ray.rllib.utils.compression import pack -from ray.rllib.utils.env_context import EnvContext from ray.rllib.utils.filter import get_filter -from ray.rllib.utils.multi_agent_env import MultiAgentEnv -from ray.rllib.utils.policy_graph import PolicyGraph -from ray.rllib.utils.sampler import AsyncSampler, SyncSampler -from ray.rllib.utils.serving_env import ServingEnv -from ray.rllib.utils.tf_policy_graph import TFPolicyGraph +from ray.rllib.evaluation.policy_graph import PolicyGraph +from ray.rllib.evaluation.tf_policy_graph import TFPolicyGraph from ray.rllib.utils.tf_run_builder import TFRunBuilder -from ray.rllib.utils.vector_env import VectorEnv -from ray.tune.result import TrainingResult - - -def collect_metrics(local_evaluator, remote_evaluators=[]): - """Gathers episode metrics from CommonPolicyEvaluator instances.""" - - episode_rewards = [] - episode_lengths = [] - policy_rewards = collections.defaultdict(list) - metric_lists = ray.get( - [a.apply.remote(lambda ev: ev.sampler.get_metrics()) - for a in remote_evaluators]) - metric_lists.append(local_evaluator.sampler.get_metrics()) - for metrics in metric_lists: - for episode in metrics: - episode_lengths.append(episode.episode_length) - episode_rewards.append(episode.episode_reward) - for (_, policy_id), reward in episode.agent_rewards.items(): - policy_rewards[policy_id].append(reward) - if episode_rewards: - min_reward = min(episode_rewards) - max_reward = max(episode_rewards) - else: - min_reward = float('nan') - max_reward = float('nan') - avg_reward = np.mean(episode_rewards) - avg_length = np.mean(episode_lengths) - timesteps = np.sum(episode_lengths) - - for policy_id, rewards in policy_rewards.copy().items(): - policy_rewards[policy_id] = np.mean(rewards) - - return TrainingResult( - episode_reward_max=max_reward, - episode_reward_min=min_reward, - episode_reward_mean=avg_reward, - episode_len_mean=avg_length, - episodes_total=len(episode_lengths), - timesteps_this_iter=timesteps, - policy_reward_mean=dict(policy_rewards)) class CommonPolicyEvaluator(PolicyEvaluator): - """Policy evaluator implementation that operates on a rllib.PolicyGraph. + """Common ``PolicyEvaluator`` implementation that wraps a ``PolicyGraph``. - TODO: multi-gpu + This class wraps a policy graph instance and an environment class to + collect experiences from the environment. You can create many replicas of + this class as Ray actors to scale RL training. + + This class supports vectorized and multi-agent policy evaluation (e.g., + VectorEnv, MultiAgentEnv, etc.) Examples: - # Create a policy evaluator and using it to collect experiences. + >>> # Create a policy evaluator and using it to collect experiences. >>> evaluator = CommonPolicyEvaluator( - env_creator=lambda _: gym.make("CartPole-v0"), - policy_graph=PGPolicyGraph) + ... env_creator=lambda _: gym.make("CartPole-v0"), + ... policy_graph=PGPolicyGraph) >>> print(evaluator.sample()) SampleBatch({ "obs": [[...]], "actions": [[...]], "rewards": [[...]], "dones": [[...]], "new_obs": [[...]]}) - # Creating policy evaluators using optimizer_cls.make(). + >>> # Creating policy evaluators using optimizer_cls.make(). >>> optimizer = SyncSamplesOptimizer.make( - evaluator_cls=CommonPolicyEvaluator, - evaluator_args={ - "env_creator": lambda _: gym.make("CartPole-v0"), - "policy_graph": PGPolicyGraph, - }, - num_workers=10) + ... evaluator_cls=CommonPolicyEvaluator, + ... evaluator_args={ + ... "env_creator": lambda _: gym.make("CartPole-v0"), + ... "policy_graph": PGPolicyGraph, + ... }, + ... num_workers=10) >>> for _ in range(10): optimizer.step() - # Creating a multi-agent policy evaluator + >>> # Creating a multi-agent policy evaluator >>> evaluator = CommonPolicyEvaluator( - env_creator=lambda _: MultiAgentTrafficGrid(num_cars=25), - policy_graph={ - # Use an ensemble of two policies for car agents - "car_policy1": - (PGPolicyGraph, Box(...), Discrete(...), {"gamma": 0.99}), - "car_policy2": - (PGPolicyGraph, Box(...), Discrete(...), {"gamma": 0.95}), - # Use a single shared policy for all traffic lights - "traffic_light_policy": - (PGPolicyGraph, Box(...), Discrete(...), {}), - }, - policy_mapping_fn=lambda agent_id: - random.choice(["car_policy1", "car_policy2"]) - if agent_id.startswith("car_") else "traffic_light_policy") + ... env_creator=lambda _: MultiAgentTrafficGrid(num_cars=25), + ... policy_graphs={ + ... # Use an ensemble of two policies for car agents + ... "car_policy1": + ... (PGPolicyGraph, Box(...), Discrete(...), {"gamma": 0.99}), + ... "car_policy2": + ... (PGPolicyGraph, Box(...), Discrete(...), {"gamma": 0.95}), + ... # Use a single shared policy for all traffic lights + ... "traffic_light_policy": + ... (PGPolicyGraph, Box(...), Discrete(...), {}), + ... }, + ... policy_mapping_fn=lambda agent_id: + ... random.choice(["car_policy1", "car_policy2"]) + ... if agent_id.startswith("car_") else "traffic_light_policy") >>> print(evaluator.sample().keys()) MultiAgentBatch({ "car_policy1": SampleBatch(...), diff --git a/python/ray/rllib/optimizers/policy_evaluator.py b/python/ray/rllib/evaluation/interface.py similarity index 88% rename from python/ray/rllib/optimizers/policy_evaluator.py rename to python/ray/rllib/evaluation/interface.py index e3bf9518e..f419bf0d6 100644 --- a/python/ray/rllib/optimizers/policy_evaluator.py +++ b/python/ray/rllib/evaluation/interface.py @@ -6,12 +6,9 @@ import os class PolicyEvaluator(object): - """Algorithms implement this interface to leverage policy optimizers. + """This is the interface between policy optimizers and policy evaluation. - Policy evaluators are the "data plane" of an algorithm. - - Any algorithm that implements Evaluator can plug in any PolicyOptimizer, - e.g. async SGD, Ape-X, local multi-GPU SGD, etc. + See also: CommonPolicyEvaluator """ def sample(self): @@ -21,7 +18,7 @@ class PolicyEvaluator(object): Returns: SampleBatch|MultiAgentBatch: A columnar batch of experiences - (e.g., tensors), or a multi-agent batch. + (e.g., tensors), or a multi-agent batch. Examples: >>> print(ev.sample()) @@ -37,9 +34,9 @@ class PolicyEvaluator(object): Returns: (grads, info): A list of gradients that can be applied on a - compatible evaluator. In the multi-agent case, returns a dict - of gradients keyed by policy graph ids. An info dictionary of - extra metadata is also returned. + compatible evaluator. In the multi-agent case, returns a dict + of gradients keyed by policy graph ids. An info dictionary of + extra metadata is also returned. Examples: >>> batch = ev.sample() diff --git a/python/ray/rllib/evaluation/metrics.py b/python/ray/rllib/evaluation/metrics.py new file mode 100644 index 000000000..5c5f0cfba --- /dev/null +++ b/python/ray/rllib/evaluation/metrics.py @@ -0,0 +1,48 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import collections + +import ray +from ray.tune.result import TrainingResult + + +def collect_metrics(local_evaluator, remote_evaluators=[]): + """Gathers episode metrics from CommonPolicyEvaluator instances.""" + + episode_rewards = [] + episode_lengths = [] + policy_rewards = collections.defaultdict(list) + metric_lists = ray.get( + [a.apply.remote(lambda ev: ev.sampler.get_metrics()) + for a in remote_evaluators]) + metric_lists.append(local_evaluator.sampler.get_metrics()) + for metrics in metric_lists: + for episode in metrics: + episode_lengths.append(episode.episode_length) + episode_rewards.append(episode.episode_reward) + for (_, policy_id), reward in episode.agent_rewards.items(): + policy_rewards[policy_id].append(reward) + if episode_rewards: + min_reward = min(episode_rewards) + max_reward = max(episode_rewards) + else: + min_reward = float('nan') + max_reward = float('nan') + avg_reward = np.mean(episode_rewards) + avg_length = np.mean(episode_lengths) + timesteps = np.sum(episode_lengths) + + for policy_id, rewards in policy_rewards.copy().items(): + policy_rewards[policy_id] = np.mean(rewards) + + return TrainingResult( + episode_reward_max=max_reward, + episode_reward_min=min_reward, + episode_reward_mean=avg_reward, + episode_len_mean=avg_length, + episodes_total=len(episode_lengths), + timesteps_this_iter=timesteps, + policy_reward_mean=dict(policy_rewards)) diff --git a/python/ray/rllib/utils/policy_graph.py b/python/ray/rllib/evaluation/policy_graph.py similarity index 100% rename from python/ray/rllib/utils/policy_graph.py rename to python/ray/rllib/evaluation/policy_graph.py diff --git a/python/ray/rllib/utils/postprocessing.py b/python/ray/rllib/evaluation/postprocessing.py similarity index 96% rename from python/ray/rllib/utils/postprocessing.py rename to python/ray/rllib/evaluation/postprocessing.py index 1e2f2ebef..667d8eea4 100644 --- a/python/ray/rllib/utils/postprocessing.py +++ b/python/ray/rllib/evaluation/postprocessing.py @@ -4,7 +4,7 @@ from __future__ import print_function import numpy as np import scipy.signal -from ray.rllib.optimizers import SampleBatch +from ray.rllib.evaluation.sample_batch import SampleBatch def discount(x, gamma): diff --git a/python/ray/rllib/optimizers/sample_batch.py b/python/ray/rllib/evaluation/sample_batch.py similarity index 100% rename from python/ray/rllib/optimizers/sample_batch.py rename to python/ray/rllib/evaluation/sample_batch.py diff --git a/python/ray/rllib/utils/sampler.py b/python/ray/rllib/evaluation/sampler.py similarity index 99% rename from python/ray/rllib/utils/sampler.py rename to python/ray/rllib/evaluation/sampler.py index 1d8509179..4ea09652c 100644 --- a/python/ray/rllib/utils/sampler.py +++ b/python/ray/rllib/evaluation/sampler.py @@ -7,9 +7,9 @@ import numpy as np import six.moves.queue as queue import threading -from ray.rllib.optimizers.sample_batch import MultiAgentSampleBatchBuilder, \ +from ray.rllib.evaluation.sample_batch import MultiAgentSampleBatchBuilder, \ MultiAgentBatch -from ray.rllib.utils.async_vector_env import AsyncVectorEnv +from ray.rllib.env.async_vector_env import AsyncVectorEnv from ray.rllib.utils.tf_run_builder import TFRunBuilder diff --git a/python/ray/rllib/utils/tf_policy_graph.py b/python/ray/rllib/evaluation/tf_policy_graph.py similarity index 99% rename from python/ray/rllib/utils/tf_policy_graph.py rename to python/ray/rllib/evaluation/tf_policy_graph.py index 23e6bf02a..0df9d9935 100644 --- a/python/ray/rllib/utils/tf_policy_graph.py +++ b/python/ray/rllib/evaluation/tf_policy_graph.py @@ -5,8 +5,8 @@ from __future__ import print_function import tensorflow as tf import ray +from ray.rllib.evaluation.policy_graph import PolicyGraph from ray.rllib.models.lstm import chop_into_sequences -from ray.rllib.utils.policy_graph import PolicyGraph from ray.rllib.utils.tf_run_builder import TFRunBuilder diff --git a/python/ray/rllib/utils/torch_policy_graph.py b/python/ray/rllib/evaluation/torch_policy_graph.py similarity index 88% rename from python/ray/rllib/utils/torch_policy_graph.py rename to python/ray/rllib/evaluation/torch_policy_graph.py index 96114cc5c..778eeff2e 100644 --- a/python/ray/rllib/utils/torch_policy_graph.py +++ b/python/ray/rllib/evaluation/torch_policy_graph.py @@ -5,11 +5,14 @@ from __future__ import print_function import numpy as np from threading import Lock -import torch -import torch.nn.functional as F +try: + import torch + import torch.nn.functional as F + from ray.rllib.models.pytorch.misc import var_to_np +except ImportError: + pass # soft dep -from ray.rllib.models.pytorch.misc import var_to_np -from ray.rllib.utils.policy_graph import PolicyGraph +from ray.rllib.evaluation.policy_graph import PolicyGraph class TorchPolicyGraph(PolicyGraph): @@ -35,11 +38,12 @@ class TorchPolicyGraph(PolicyGraph): observation_space (gym.Space): observation space of the policy. action_space (gym.Space): action space of the policy. model (nn.Module): PyTorch policy module. Given observations as - input, this module must a list of outputs where the first item - are action logits, and the remainder can be any value. + input, this module must return a list of outputs where the + first item is action logits, and the rest can be any value. loss (nn.Module): Loss defined as a PyTorch module. The inputs for this module are defined by the `loss_inputs` param. This module - returns a single scalar loss. + returns a single scalar loss. Note that this module should + internally be using the model module. loss_inputs (list): List of SampleBatch columns that will be passed to the loss module's forward() function when computing the loss. For example, ["obs", "action", "advantages"]. diff --git a/python/ray/rllib/examples/legacy_multiagent/multiagent_mountaincar.py b/python/ray/rllib/examples/legacy_multiagent/multiagent_mountaincar.py index e3e20344b..1e97264a5 100644 --- a/python/ray/rllib/examples/legacy_multiagent/multiagent_mountaincar.py +++ b/python/ray/rllib/examples/legacy_multiagent/multiagent_mountaincar.py @@ -7,7 +7,7 @@ import gym from gym.envs.registration import register import ray -import ray.rllib.ppo as ppo +import ray.rllib.agents.ppo as ppo from ray.tune.registry import register_env env_name = "MultiAgentMountainCarEnv" diff --git a/python/ray/rllib/examples/legacy_multiagent/multiagent_pendulum.py b/python/ray/rllib/examples/legacy_multiagent/multiagent_pendulum.py index baf5bc29a..c78b5d601 100644 --- a/python/ray/rllib/examples/legacy_multiagent/multiagent_pendulum.py +++ b/python/ray/rllib/examples/legacy_multiagent/multiagent_pendulum.py @@ -7,7 +7,7 @@ import gym from gym.envs.registration import register import ray -import ray.rllib.ppo as ppo +import ray.rllib.agents.ppo as ppo from ray.tune.registry import register_env env_name = "MultiAgentPendulumEnv" diff --git a/python/ray/rllib/examples/multiagent_cartpole.py b/python/ray/rllib/examples/multiagent_cartpole.py index 158fec293..75c678c53 100644 --- a/python/ray/rllib/examples/multiagent_cartpole.py +++ b/python/ray/rllib/examples/multiagent_cartpole.py @@ -18,8 +18,8 @@ import gym import random import ray -from ray.rllib.pg.pg import PGAgent -from ray.rllib.pg.pg_policy_graph import PGPolicyGraph +from ray.rllib.agents.pg.pg import PGAgent +from ray.rllib.agents.pg.pg_policy_graph import PGPolicyGraph from ray.rllib.test.test_multi_agent_env import MultiCartpole from ray.tune.logger import pretty_print from ray.tune.registry import register_env diff --git a/python/ray/rllib/examples/serving/cartpole_server.py b/python/ray/rllib/examples/serving/cartpole_server.py index ffbf9f6c6..7e6d79996 100755 --- a/python/ray/rllib/examples/serving/cartpole_server.py +++ b/python/ray/rllib/examples/serving/cartpole_server.py @@ -13,8 +13,8 @@ import os from gym import spaces import ray -from ray.rllib.dqn import DQNAgent -from ray.rllib.utils.serving_env import ServingEnv +from ray.rllib.agents.dqn import DQNAgent +from ray.rllib.env.serving_env import ServingEnv from ray.rllib.utils.policy_server import PolicyServer from ray.tune.logger import pretty_print from ray.tune.registry import register_env diff --git a/python/ray/rllib/models/__init__.py b/python/ray/rllib/models/__init__.py index d381985dc..8ece52228 100644 --- a/python/ray/rllib/models/__init__.py +++ b/python/ray/rllib/models/__init__.py @@ -2,11 +2,11 @@ from ray.rllib.models.catalog import ModelCatalog from ray.rllib.models.action_dist import (ActionDistribution, Categorical, DiagGaussian, Deterministic) from ray.rllib.models.model import Model +from ray.rllib.models.preprocessors import Preprocessor from ray.rllib.models.fcnet import FullyConnectedNetwork from ray.rllib.models.lstm import LSTM -from ray.rllib.models.multiagentfcnet import MultiAgentFullyConnectedNetwork __all__ = ["ActionDistribution", "ActionDistribution", "Categorical", "DiagGaussian", "Deterministic", "ModelCatalog", "Model", - "FullyConnectedNetwork", "LSTM", "MultiAgentFullyConnectedNetwork"] + "Preprocessor", "FullyConnectedNetwork", "LSTM"] diff --git a/python/ray/rllib/optimizers/__init__.py b/python/ray/rllib/optimizers/__init__.py index 70d70a197..eaddfd0cd 100644 --- a/python/ray/rllib/optimizers/__init__.py +++ b/python/ray/rllib/optimizers/__init__.py @@ -1,15 +1,12 @@ +from ray.rllib.optimizers.policy_optimizer import PolicyOptimizer from ray.rllib.optimizers.async_samples_optimizer import AsyncSamplesOptimizer from ray.rllib.optimizers.async_gradients_optimizer import \ AsyncGradientsOptimizer from ray.rllib.optimizers.sync_samples_optimizer import SyncSamplesOptimizer from ray.rllib.optimizers.sync_replay_optimizer import SyncReplayOptimizer from ray.rllib.optimizers.multi_gpu_optimizer import LocalMultiGPUOptimizer -from ray.rllib.optimizers.sample_batch import SampleBatch, MultiAgentBatch -from ray.rllib.optimizers.policy_evaluator import PolicyEvaluator, \ - TFMultiGPUSupport __all__ = [ - "AsyncSamplesOptimizer", "AsyncGradientsOptimizer", "SyncSamplesOptimizer", - "SyncReplayOptimizer", "LocalMultiGPUOptimizer", "SampleBatch", - "PolicyEvaluator", "TFMultiGPUSupport", "MultiAgentBatch"] + "PolicyOptimizer", "AsyncSamplesOptimizer", "AsyncGradientsOptimizer", + "SyncSamplesOptimizer", "SyncReplayOptimizer", "LocalMultiGPUOptimizer"] diff --git a/python/ray/rllib/optimizers/async_samples_optimizer.py b/python/ray/rllib/optimizers/async_samples_optimizer.py index 8e4772909..dfc52e1d8 100644 --- a/python/ray/rllib/optimizers/async_samples_optimizer.py +++ b/python/ray/rllib/optimizers/async_samples_optimizer.py @@ -17,7 +17,7 @@ from six.moves import queue import ray from ray.rllib.optimizers.policy_optimizer import PolicyOptimizer from ray.rllib.optimizers.replay_buffer import PrioritizedReplayBuffer -from ray.rllib.optimizers.sample_batch import SampleBatch +from ray.rllib.evaluation.sample_batch import SampleBatch from ray.rllib.utils.actors import TaskPool, create_colocated from ray.rllib.utils.timer import TimerStat from ray.rllib.utils.window_stat import WindowStat diff --git a/python/ray/rllib/optimizers/multi_gpu_optimizer.py b/python/ray/rllib/optimizers/multi_gpu_optimizer.py index f1a80d749..1562b96ea 100644 --- a/python/ray/rllib/optimizers/multi_gpu_optimizer.py +++ b/python/ray/rllib/optimizers/multi_gpu_optimizer.py @@ -8,9 +8,9 @@ import os import tensorflow as tf import ray +from ray.rllib.evaluation.tf_policy_graph import TFPolicyGraph from ray.rllib.optimizers.policy_optimizer import PolicyOptimizer from ray.rllib.optimizers.multi_gpu_impl import LocalSyncParallelOptimizer -from ray.rllib.utils.tf_policy_graph import TFPolicyGraph from ray.rllib.utils.timer import TimerStat @@ -87,7 +87,7 @@ class LocalMultiGPUOptimizer(PolicyOptimizer): with self.sample_timer: if self.remote_evaluators: # TODO(rliaw): remove when refactoring - from ray.rllib.ppo.rollout import collect_samples + from ray.rllib.agents.ppo.rollout import collect_samples samples = collect_samples(self.remote_evaluators, self.timesteps_per_batch) else: diff --git a/python/ray/rllib/optimizers/policy_optimizer.py b/python/ray/rllib/optimizers/policy_optimizer.py index f44aa4847..4a30b7521 100644 --- a/python/ray/rllib/optimizers/policy_optimizer.py +++ b/python/ray/rllib/optimizers/policy_optimizer.py @@ -3,7 +3,7 @@ from __future__ import division from __future__ import print_function import ray -from ray.rllib.optimizers.sample_batch import MultiAgentBatch +from ray.rllib.evaluation.sample_batch import MultiAgentBatch class PolicyOptimizer(object): @@ -31,34 +31,6 @@ class PolicyOptimizer(object): evaluators created by this optimizer. """ - @classmethod - def make( - cls, evaluator_cls, evaluator_args, num_workers, optimizer_config, - evaluator_resources={"num_cpus": None}): - """Create evaluators and an optimizer instance using those evaluators. - - Args: - evaluator_cls (class): Python class of the evaluators to create. - evaluator_args (list|dict): Constructor args for the evaluators. - num_workers (int): Number of remote evaluators to create in - addition to a local evaluator. This can be zero or greater. - optimizer_config (dict): Keyword arguments to pass to the - optimizer class constructor. - """ - - remote_cls = ray.remote(**evaluator_resources)(evaluator_cls) - if isinstance(evaluator_args, list): - local_evaluator = evaluator_cls(*evaluator_args) - remote_evaluators = [ - remote_cls.remote(*evaluator_args) - for _ in range(num_workers)] - else: - local_evaluator = evaluator_cls(**evaluator_args) - remote_evaluators = [ - remote_cls.remote(worker_index=i+1, **evaluator_args) - for i in range(num_workers)] - return cls(optimizer_config, local_evaluator, remote_evaluators) - def __init__(self, config, local_evaluator, remote_evaluators): """Create an optimizer instance. diff --git a/python/ray/rllib/optimizers/sync_replay_optimizer.py b/python/ray/rllib/optimizers/sync_replay_optimizer.py index 771695472..1058b0d5a 100644 --- a/python/ray/rllib/optimizers/sync_replay_optimizer.py +++ b/python/ray/rllib/optimizers/sync_replay_optimizer.py @@ -9,7 +9,7 @@ import ray from ray.rllib.optimizers.replay_buffer import ReplayBuffer, \ PrioritizedReplayBuffer from ray.rllib.optimizers.policy_optimizer import PolicyOptimizer -from ray.rllib.optimizers.sample_batch import SampleBatch, DEFAULT_POLICY_ID, \ +from ray.rllib.evaluation.sample_batch import SampleBatch, DEFAULT_POLICY_ID, \ MultiAgentBatch from ray.rllib.utils.compression import pack_if_needed from ray.rllib.utils.filter import RunningStat diff --git a/python/ray/rllib/optimizers/sync_samples_optimizer.py b/python/ray/rllib/optimizers/sync_samples_optimizer.py index 2995a1034..c1c8e7c1a 100644 --- a/python/ray/rllib/optimizers/sync_samples_optimizer.py +++ b/python/ray/rllib/optimizers/sync_samples_optimizer.py @@ -4,7 +4,7 @@ from __future__ import print_function import ray from ray.rllib.optimizers.policy_optimizer import PolicyOptimizer -from ray.rllib.optimizers.sample_batch import SampleBatch +from ray.rllib.evaluation.sample_batch import SampleBatch from ray.rllib.utils.filter import RunningStat from ray.rllib.utils.timer import TimerStat diff --git a/python/ray/rllib/pg/__init__.py b/python/ray/rllib/pg/__init__.py deleted file mode 100644 index fa566536d..000000000 --- a/python/ray/rllib/pg/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from ray.rllib.pg.pg import PGAgent, DEFAULT_CONFIG - -__all__ = ["PGAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/pg/pg.py b/python/ray/rllib/pg/pg.py deleted file mode 100644 index e64af9fe6..000000000 --- a/python/ray/rllib/pg/pg.py +++ /dev/null @@ -1,86 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from ray.rllib.agent import Agent -from ray.rllib.optimizers import SyncSamplesOptimizer -from ray.rllib.pg.pg_policy_graph import PGPolicyGraph -from ray.rllib.utils.common_policy_evaluator import CommonPolicyEvaluator, \ - collect_metrics -from ray.tune.trial import Resources - - -DEFAULT_CONFIG = { - # Number of workers (excluding master) - "num_workers": 0, - # Number of environments to evaluate vectorwise per worker. - "num_envs": 1, - # Size of rollout batch - "batch_size": 512, - # Discount factor of MDP - "gamma": 0.99, - # Number of steps after which the rollout gets cut - "horizon": 500, - # Learning rate - "lr": 0.0004, - # Arguments to pass to the rllib optimizer - "optimizer": {}, - # Model parameters - "model": {"fcnet_hiddens": [128, 128], "max_seq_len": 20}, - # Arguments to pass to the env creator - "env_config": {}, - - # === Multiagent === - "multiagent": { - "policy_graphs": {}, - "policy_mapping_fn": None, - }, -} - - -class PGAgent(Agent): - """Simple policy gradient agent. - - This is an example agent to show how to implement algorithms in RLlib. - In most cases, you will probably want to use the PPO agent instead. - """ - - _agent_name = "PG" - _default_config = DEFAULT_CONFIG - - @classmethod - def default_resource_request(cls, config): - cf = dict(cls._default_config, **config) - return Resources(cpu=1, gpu=0, extra_cpu=cf["num_workers"]) - - def _init(self): - self.optimizer = SyncSamplesOptimizer.make( - evaluator_cls=CommonPolicyEvaluator, - evaluator_args={ - "env_creator": self.env_creator, - "policy_graph": ( - self.config["multiagent"]["policy_graphs"] or - PGPolicyGraph), - "policy_mapping_fn": - self.config["multiagent"]["policy_mapping_fn"], - "batch_steps": self.config["batch_size"], - "batch_mode": "truncate_episodes", - "model_config": self.config["model"], - "env_config": self.config["env_config"], - "policy_config": self.config, - "num_envs": self.config["num_envs"], - }, - num_workers=self.config["num_workers"], - optimizer_config=self.config["optimizer"]) - - def _train(self): - self.optimizer.step() - return collect_metrics( - self.optimizer.local_evaluator, self.optimizer.remote_evaluators) - - def compute_action(self, observation, state=None): - if state is None: - state = [] - return self.local_evaluator.for_policy( - lambda p: p.compute_single_action( - observation, state, is_training=False)[0]) diff --git a/python/ray/rllib/ppo/__init__.py b/python/ray/rllib/ppo/__init__.py deleted file mode 100644 index c039f1248..000000000 --- a/python/ray/rllib/ppo/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from ray.rllib.ppo.ppo import (PPOAgent, DEFAULT_CONFIG) - -__all__ = ["PPOAgent", "DEFAULT_CONFIG"] diff --git a/python/ray/rllib/rollout.py b/python/ray/rllib/rollout.py index 09fd52a31..ab647b8f2 100755 --- a/python/ray/rllib/rollout.py +++ b/python/ray/rllib/rollout.py @@ -11,8 +11,8 @@ import pickle import gym import ray -from ray.rllib.agent import get_agent_class -from ray.rllib.dqn.common.wrappers import wrap_dqn +from ray.rllib.agents.agent import get_agent_class +from ray.rllib.agents.dqn.common.wrappers import wrap_dqn from ray.rllib.models import ModelCatalog EXAMPLE_USAGE = """ diff --git a/python/ray/rllib/test/mock_evaluator.py b/python/ray/rllib/test/mock_evaluator.py index 4762bb877..711a250e7 100644 --- a/python/ray/rllib/test/mock_evaluator.py +++ b/python/ray/rllib/test/mock_evaluator.py @@ -3,7 +3,7 @@ from __future__ import division from __future__ import print_function import numpy as np -from ray.rllib.optimizers import SampleBatch +from ray.rllib.evaluation import SampleBatch from ray.rllib.utils.filter import MeanStdFilter diff --git a/python/ray/rllib/test/test_checkpoint_restore.py b/python/ray/rllib/test/test_checkpoint_restore.py index fe954423e..f94e08b5a 100644 --- a/python/ray/rllib/test/test_checkpoint_restore.py +++ b/python/ray/rllib/test/test_checkpoint_restore.py @@ -7,7 +7,7 @@ from __future__ import print_function import numpy as np import ray -from ray.rllib.agent import get_agent_class +from ray.rllib.agents.agent import get_agent_class def get_mean_action(alg, obs): diff --git a/python/ray/rllib/test/test_common_policy_evaluator.py b/python/ray/rllib/test/test_common_policy_evaluator.py index 1f6b77956..a86d902bf 100644 --- a/python/ray/rllib/test/test_common_policy_evaluator.py +++ b/python/ray/rllib/test/test_common_policy_evaluator.py @@ -7,12 +7,12 @@ import time import unittest import ray -from ray.rllib.pg import PGAgent -from ray.rllib.utils.common_policy_evaluator import CommonPolicyEvaluator, \ - collect_metrics -from ray.rllib.utils.policy_graph import PolicyGraph -from ray.rllib.utils.postprocessing import compute_advantages -from ray.rllib.utils.vector_env import VectorEnv +from ray.rllib.agents.pg import PGAgent +from ray.rllib.evaluation.common_policy_evaluator import CommonPolicyEvaluator +from ray.rllib.evaluation.metrics import collect_metrics +from ray.rllib.evaluation.policy_graph import PolicyGraph +from ray.rllib.evaluation.postprocessing import compute_advantages +from ray.rllib.env.vector_env import VectorEnv from ray.tune.registry import register_env @@ -101,7 +101,8 @@ class TestCommonPolicyEvaluator(unittest.TestCase): def testQueryEvaluators(self): register_env("test", lambda _: gym.make("CartPole-v0")) - pg = PGAgent(env="test", config={"num_workers": 2, "batch_size": 5}) + pg = PGAgent( + env="test", config={"num_workers": 2, "sample_batch_size": 5}) results = pg.optimizer.foreach_evaluator(lambda ev: ev.batch_steps) results2 = pg.optimizer.foreach_evaluator_with_index( lambda ev, i: (i, ev.batch_steps)) diff --git a/python/ray/rllib/test/test_evaluators.py b/python/ray/rllib/test/test_evaluators.py index d2abf1e6d..6d493099e 100644 --- a/python/ray/rllib/test/test_evaluators.py +++ b/python/ray/rllib/test/test_evaluators.py @@ -4,7 +4,7 @@ from __future__ import print_function import unittest -from ray.rllib.dqn.dqn_policy_graph import adjust_nstep +from ray.rllib.agents.dqn.dqn_policy_graph import adjust_nstep class DQNTest(unittest.TestCase): diff --git a/python/ray/rllib/test/test_multi_agent_env.py b/python/ray/rllib/test/test_multi_agent_env.py index c058e7714..e1146dcca 100644 --- a/python/ray/rllib/test/test_multi_agent_env.py +++ b/python/ray/rllib/test/test_multi_agent_env.py @@ -7,17 +7,17 @@ import random import unittest import ray -from ray.rllib.pg import PGAgent -from ray.rllib.pg.pg_policy_graph import PGPolicyGraph -from ray.rllib.dqn.dqn_policy_graph import DQNPolicyGraph +from ray.rllib.agents.pg import PGAgent +from ray.rllib.agents.pg.pg_policy_graph import PGPolicyGraph +from ray.rllib.agents.dqn.dqn_policy_graph import DQNPolicyGraph from ray.rllib.optimizers import SyncSamplesOptimizer, \ SyncReplayOptimizer, AsyncGradientsOptimizer from ray.rllib.test.test_common_policy_evaluator import MockEnv, MockEnv2, \ MockPolicyGraph -from ray.rllib.utils.common_policy_evaluator import CommonPolicyEvaluator, \ - collect_metrics -from ray.rllib.utils.async_vector_env import _MultiAgentEnvToAsync -from ray.rllib.utils.multi_agent_env import MultiAgentEnv +from ray.rllib.evaluation.common_policy_evaluator import CommonPolicyEvaluator +from ray.rllib.evaluation.metrics import collect_metrics +from ray.rllib.env.async_vector_env import _MultiAgentEnvToAsync +from ray.rllib.env.multi_agent_env import MultiAgentEnv from ray.tune.registry import register_env diff --git a/python/ray/rllib/test/test_optimizers.py b/python/ray/rllib/test/test_optimizers.py index a9a109aa3..f3a4fc917 100644 --- a/python/ray/rllib/test/test_optimizers.py +++ b/python/ray/rllib/test/test_optimizers.py @@ -8,7 +8,8 @@ import numpy as np import ray from ray.rllib.test.mock_evaluator import _MockEvaluator -from ray.rllib.optimizers import AsyncGradientsOptimizer, SampleBatch +from ray.rllib.optimizers import AsyncGradientsOptimizer +from ray.rllib.evaluation import SampleBatch class AsyncOptimizerTest(unittest.TestCase): diff --git a/python/ray/rllib/test/test_serving_env.py b/python/ray/rllib/test/test_serving_env.py index 94b7f8673..5802b66f2 100644 --- a/python/ray/rllib/test/test_serving_env.py +++ b/python/ray/rllib/test/test_serving_env.py @@ -9,10 +9,10 @@ import unittest import uuid import ray -from ray.rllib.dqn import DQNAgent -from ray.rllib.pg import PGAgent -from ray.rllib.utils.common_policy_evaluator import CommonPolicyEvaluator -from ray.rllib.utils.serving_env import ServingEnv +from ray.rllib.agents.dqn import DQNAgent +from ray.rllib.agents.pg import PGAgent +from ray.rllib.evaluation.common_policy_evaluator import CommonPolicyEvaluator +from ray.rllib.env.serving_env import ServingEnv from ray.rllib.test.test_common_policy_evaluator import BadPolicyGraph, \ MockPolicyGraph, MockEnv from ray.tune.registry import register_env diff --git a/python/ray/rllib/test/test_supported_spaces.py b/python/ray/rllib/test/test_supported_spaces.py index 5c6e8c362..cb14fa93b 100644 --- a/python/ray/rllib/test/test_supported_spaces.py +++ b/python/ray/rllib/test/test_supported_spaces.py @@ -7,7 +7,7 @@ from gym.envs.registration import EnvSpec import numpy as np import ray -from ray.rllib.agent import get_agent_class +from ray.rllib.agents.agent import get_agent_class from ray.rllib.utils.error import UnsupportedSpaceException from ray.tune.registry import register_env @@ -95,7 +95,6 @@ class ModelSupportedSpaces(unittest.TestCase): check_support( "PPO", {"num_workers": 1, "num_sgd_iter": 1, "timesteps_per_batch": 1, - "devices": ["/cpu:0"], "min_steps_per_task": 1, "sgd_batchsize": 1}, stats) check_support( diff --git a/python/ray/rllib/tuned_examples/hopper-ppo.yaml b/python/ray/rllib/tuned_examples/hopper-ppo.yaml index d881362ac..27441d394 100644 --- a/python/ray/rllib/tuned_examples/hopper-ppo.yaml +++ b/python/ray/rllib/tuned_examples/hopper-ppo.yaml @@ -1,4 +1,12 @@ hopper-ppo: env: Hopper-v1 run: PPO - config: {"gamma": 0.995, "kl_coeff": 1.0, "num_sgd_iter": 20, "sgd_stepsize": .0001, "sgd_batchsize": 32768, "devices": ["/gpu:0", "/gpu:1", "/gpu:2", "/gpu:3"], "tf_session_args": {"device_count": {"GPU": 4}, "log_device_placement": false, "allow_soft_placement": true}, "timesteps_per_batch": 160000, "num_workers": 64} + config: + gamma: 0.995 + kl_coeff: 1.0 + num_sgd_iter: 20 + sgd_stepsize: .0001 + sgd_batchsize: 32768 + timesteps_per_batch: 160000 + num_workers: 64 + num_gpus: 4 diff --git a/python/ray/rllib/tuned_examples/humanoid-ppo-gae.yaml b/python/ray/rllib/tuned_examples/humanoid-ppo-gae.yaml index 60055d2aa..5dfbf4315 100644 --- a/python/ray/rllib/tuned_examples/humanoid-ppo-gae.yaml +++ b/python/ray/rllib/tuned_examples/humanoid-ppo-gae.yaml @@ -3,5 +3,17 @@ humanoid-ppo-gae: run: PPO stop: episode_reward_mean: 6000 - config: {"lambda": 0.95, "clip_param": 0.2, "kl_coeff": 1.0, "num_sgd_iter": 20, "sgd_stepsize": .0001, "sgd_batchsize": 32768, "horizon": 5000, "devices": ["/gpu:0", "/gpu:1", "/gpu:2", "/gpu:3"], "tf_session_args": {"device_count": {"GPU": 4}, "log_device_placement": false, "allow_soft_placement": true}, "timesteps_per_batch": 320000, "num_workers": 64, "model": {"free_log_std": true}, "write_logs": false} - + config: + gamma: 0.995 + lambda: 0.95 + clip_param: 0.2 + kl_coeff: 1.0 + num_sgd_iter: 20 + sgd_stepsize: .0001 + sgd_batchsize: 32768 + horizon: 5000 + timesteps_per_batch: 320000 + model: + free_log_std: true + num_workers: 64 + num_gpus: 4 diff --git a/python/ray/rllib/tuned_examples/humanoid-ppo.yaml b/python/ray/rllib/tuned_examples/humanoid-ppo.yaml index 9619d5389..c896f7d3b 100644 --- a/python/ray/rllib/tuned_examples/humanoid-ppo.yaml +++ b/python/ray/rllib/tuned_examples/humanoid-ppo.yaml @@ -2,5 +2,16 @@ humanoid-ppo: env: Humanoid-v1 run: PPO stop: - episode_reward_mean: 6000 - config: {"kl_coeff": 1.0, "num_sgd_iter": 20, "sgd_stepsize": .0001, "sgd_batchsize": 32768, "devices": ["/gpu:0", "/gpu:1", "/gpu:2", "/gpu:3"], "tf_session_args": {"device_count": {"GPU": 4}, "log_device_placement": false, "allow_soft_placement": true}, "timesteps_per_batch": 320000, "num_workers": 64, "model": {"free_log_std": true}, "use_gae": false} + episode_reward_mean: 6000 + config: + gamma: 0.995 + kl_coeff: 1.0 + num_sgd_iter: 20 + sgd_stepsize: .0001 + sgd_batchsize: 32768 + timesteps_per_batch: 320000 + model: + free_log_std: true + use_gae: false + num_workers: 64 + num_gpus: 4 diff --git a/python/ray/rllib/tuned_examples/pong-a3c-pytorch.yaml b/python/ray/rllib/tuned_examples/pong-a3c-pytorch.yaml index 46d84e6ae..891c4b991 100644 --- a/python/ray/rllib/tuned_examples/pong-a3c-pytorch.yaml +++ b/python/ray/rllib/tuned_examples/pong-a3c-pytorch.yaml @@ -3,7 +3,7 @@ pong-a3c-pytorch-cnn: run: A3C config: num_workers: 16 - batch_size: 20 + sample_batch_size: 20 use_pytorch: true vf_loss_coeff: 0.5 entropy_coeff: -0.01 diff --git a/python/ray/rllib/tuned_examples/pong-a3c.yaml b/python/ray/rllib/tuned_examples/pong-a3c.yaml index 4cb868bb5..d5b011ee3 100644 --- a/python/ray/rllib/tuned_examples/pong-a3c.yaml +++ b/python/ray/rllib/tuned_examples/pong-a3c.yaml @@ -5,7 +5,7 @@ pong-a3c: run: A3C config: num_workers: 16 - batch_size: 20 + sample_batch_size: 20 use_pytorch: false vf_loss_coeff: 0.5 entropy_coeff: -0.01 diff --git a/python/ray/rllib/tuned_examples/pong-ppo.yaml b/python/ray/rllib/tuned_examples/pong-ppo.yaml index fcb27c1f7..144748164 100644 --- a/python/ray/rllib/tuned_examples/pong-ppo.yaml +++ b/python/ray/rllib/tuned_examples/pong-ppo.yaml @@ -14,4 +14,4 @@ pong-deterministic-ppo: gamma: 0.99 num_workers: 4 num_sgd_iter: 20 - devices: ["/gpu:0"] + num_gpus: 1 diff --git a/python/ray/rllib/tuned_examples/walker2d-ppo.yaml b/python/ray/rllib/tuned_examples/walker2d-ppo.yaml index 22197081b..4591b4b58 100644 --- a/python/ray/rllib/tuned_examples/walker2d-ppo.yaml +++ b/python/ray/rllib/tuned_examples/walker2d-ppo.yaml @@ -1,4 +1,11 @@ walker2d-v1-ppo: env: Walker2d-v1 run: PPO - config: {"kl_coeff": 1.0, "num_sgd_iter": 20, "sgd_stepsize": .0001, "sgd_batchsize": 32768, "devices": ["/gpu:0", "/gpu:1", "/gpu:2", "/gpu:3"], "tf_session_args": {"device_count": {"GPU": 4}, "log_device_placement": false, "allow_soft_placement": true}, "timesteps_per_batch": 320000, "num_workers": 64} + config: + kl_coeff: 1.0 + num_sgd_iter: 20 + sgd_stepsize: .0001 + sgd_batchsize: 32768 + timesteps_per_batch: 320000 + num_workers: 64 + num_gpus: 4 diff --git a/python/ray/rllib/utils/__init__.py b/python/ray/rllib/utils/__init__.py index 3e2b5e0e6..9c1d441df 100644 --- a/python/ray/rllib/utils/__init__.py +++ b/python/ray/rllib/utils/__init__.py @@ -1,3 +1,11 @@ from ray.rllib.utils.filter_manager import FilterManager +from ray.rllib.utils.filter import Filter +from ray.rllib.utils.policy_client import PolicyClient +from ray.rllib.utils.policy_server import PolicyServer -__all__ = ["FilterManager"] +__all__ = [ + "Filter", + "FilterManager", + "PolicyClient", + "PolicyServer", +] diff --git a/python/ray/rllib/utils/policy_client.py b/python/ray/rllib/utils/policy_client.py index 623d32c1e..901dc983b 100644 --- a/python/ray/rllib/utils/policy_client.py +++ b/python/ray/rllib/utils/policy_client.py @@ -13,7 +13,7 @@ except ImportError: class PolicyClient(object): - """Client to interact with a RLlib policy server.""" + """REST client to interact with a RLlib policy server.""" START_EPISODE = "START_EPISODE" GET_ACTION = "GET_ACTION" diff --git a/python/ray/rllib/utils/policy_server.py b/python/ray/rllib/utils/policy_server.py index 708b14e05..554d74974 100644 --- a/python/ray/rllib/utils/policy_server.py +++ b/python/ray/rllib/utils/policy_server.py @@ -18,6 +18,34 @@ elif sys.version_info[0] == 3: class PolicyServer(ThreadingMixIn, HTTPServer): + """REST server than can be launched from a ServingEnv. + + This launches a multi-threaded server that listens on the specified host + and port to serve policy requests and forward experiences to RLlib. + + Examples: + >>> class CartpoleServing(ServingEnv): + def __init__(self): + ServingEnv.__init__( + self, spaces.Discrete(2), + spaces.Box(low=-10, high=10, shape=(4,))) + def run(self): + server = PolicyServer(self, "localhost", 8900) + server.serve_forever() + >>> register_env("srv", lambda _: CartpoleServing()) + >>> pg = PGAgent(env="srv", config={"num_workers": 0}) + >>> while True: + pg.train() + + >>> client = PolicyClient("localhost:8900") + >>> eps_id = client.start_episode() + >>> action = client.get_action(eps_id, obs) + >>> ... + >>> client.log_returns(eps_id, reward) + >>> ... + >>> client.log_returns(eps_id, reward) + """ + def __init__(self, serving_env, address, port): handler = _make_handler(serving_env) HTTPServer.__init__(self, (address, port), handler) diff --git a/python/ray/rllib/dqn/common/schedules.py b/python/ray/rllib/utils/schedules.py similarity index 100% rename from python/ray/rllib/dqn/common/schedules.py rename to python/ray/rllib/utils/schedules.py diff --git a/test/jenkins_tests/run_multi_node_tests.sh b/test/jenkins_tests/run_multi_node_tests.sh index 15cb2beb7..86c325da1 100755 --- a/test/jenkins_tests/run_multi_node_tests.sh +++ b/test/jenkins_tests/run_multi_node_tests.sh @@ -98,7 +98,7 @@ docker run --rm --shm-size=10G --memory=10G $DOCKER_SHA \ --env MontezumaRevenge-v0 \ --run PPO \ --stop '{"training_iteration": 2}' \ - --config '{"kl_coeff": 1.0, "num_sgd_iter": 10, "sgd_stepsize": 1e-4, "sgd_batchsize": 64, "timesteps_per_batch": 2000, "num_workers": 1, "model": {"dim": 40, "conv_filters": [[16, [8, 8], 4], [32, [4, 4], 2], [512, [5, 5], 1]]}, "extra_frameskip": 4}' + --config '{"kl_coeff": 1.0, "num_sgd_iter": 10, "sgd_stepsize": 1e-4, "sgd_batchsize": 64, "timesteps_per_batch": 2000, "num_workers": 1, "model": {"dim": 40, "conv_filters": [[16, [8, 8], 4], [32, [4, 4], 2], [512, [5, 5], 1]]}}' docker run --rm --shm-size=10G --memory=10G $DOCKER_SHA \ python /ray/python/ray/rllib/train.py \ @@ -126,35 +126,35 @@ docker run --rm --shm-size=10G --memory=10G $DOCKER_SHA \ --env CartPole-v0 \ --run PG \ --stop '{"training_iteration": 2}' \ - --config '{"batch_size": 500, "num_workers": 1}' + --config '{"sample_batch_size": 500, "num_workers": 1}' docker run --rm --shm-size=10G --memory=10G $DOCKER_SHA \ python /ray/python/ray/rllib/train.py \ --env CartPole-v0 \ --run PG \ --stop '{"training_iteration": 2}' \ - --config '{"batch_size": 500, "num_workers": 1, "model": {"use_lstm": true, "max_seq_len": 100}}' + --config '{"sample_batch_size": 500, "num_workers": 1, "model": {"use_lstm": true, "max_seq_len": 100}}' docker run --rm --shm-size=10G --memory=10G $DOCKER_SHA \ python /ray/python/ray/rllib/train.py \ --env CartPole-v0 \ --run PG \ --stop '{"training_iteration": 2}' \ - --config '{"batch_size": 500, "num_workers": 1, "num_envs": 10}' + --config '{"sample_batch_size": 500, "num_workers": 1, "num_envs": 10}' docker run --rm --shm-size=10G --memory=10G $DOCKER_SHA \ python /ray/python/ray/rllib/train.py \ --env Pong-v0 \ --run PG \ --stop '{"training_iteration": 2}' \ - --config '{"batch_size": 500, "num_workers": 1}' + --config '{"sample_batch_size": 500, "num_workers": 1}' docker run --rm --shm-size=10G --memory=10G $DOCKER_SHA \ python /ray/python/ray/rllib/train.py \ --env FrozenLake-v0 \ --run PG \ --stop '{"training_iteration": 2}' \ - --config '{"batch_size": 500, "num_workers": 1}' + --config '{"sample_batch_size": 500, "num_workers": 1}' docker run --rm --shm-size=10G --memory=10G $DOCKER_SHA \ python /ray/python/ray/rllib/train.py \