mirror of
https://github.com/saymrwulf/stable-baselines3.git
synced 2026-09-06 20:40:56 +00:00
Merge branch 'master' into sde
This commit is contained in:
commit
cc6794bd4c
23 changed files with 312 additions and 100 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
|
@ -32,6 +32,9 @@ jobs:
|
||||||
pip install .[extra,tests,docs]
|
pip install .[extra,tests,docs]
|
||||||
# Use headless version
|
# Use headless version
|
||||||
pip install opencv-python-headless
|
pip install opencv-python-headless
|
||||||
|
- name: Build the doc
|
||||||
|
run: |
|
||||||
|
make doc
|
||||||
- name: Type check
|
- name: Type check
|
||||||
run: |
|
run: |
|
||||||
make type
|
make type
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -167,8 +167,8 @@ All the following examples can be executed online using Google colab notebooks:
|
||||||
|
|
||||||
| **Name** | **Recurrent** | `Box` | `Discrete` | `MultiDiscrete` | `MultiBinary` | **Multi Processing** |
|
| **Name** | **Recurrent** | `Box` | `Discrete` | `MultiDiscrete` | `MultiBinary` | **Multi Processing** |
|
||||||
| ------------------- | ------------------ | ------------------ | ------------------ | ------------------- | ------------------ | --------------------------------- |
|
| ------------------- | ------------------ | ------------------ | ------------------ | ------------------- | ------------------ | --------------------------------- |
|
||||||
| A2C | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: |
|
| A2C | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| PPO | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: |
|
| PPO | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| SAC | :x: | :heavy_check_mark: | :x: | :x: | :x: | :x: |
|
| SAC | :x: | :heavy_check_mark: | :x: | :x: | :x: | :x: |
|
||||||
| TD3 | :x: | :heavy_check_mark: | :x: | :x: | :x: | :x: |
|
| TD3 | :x: | :heavy_check_mark: | :x: | :x: | :x: | :x: |
|
||||||
|
|
||||||
|
|
@ -232,5 +232,11 @@ Stable-Baselines3 is currently maintained by [Ashley Hill](https://github.com/hi
|
||||||
To any interested in making the baselines better, there is still some documentation that needs to be done.
|
To any interested in making the baselines better, there is still some documentation that needs to be done.
|
||||||
If you want to contribute, please read [**CONTRIBUTING.md**](./CONTRIBUTING.md) guide first.
|
If you want to contribute, please read [**CONTRIBUTING.md**](./CONTRIBUTING.md) guide first.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
The initial work to develop Stable Baselines3 was partially funded by the project *Reduced Complexity Models* from the *Helmholtz-Gemeinschaft Deutscher Forschungszentren*.
|
||||||
|
|
||||||
|
The original version, Stable Baselines, was created in the [robotics lab U2IS](http://u2is.ensta-paristech.fr/index.php?lang=en) ([INRIA Flowers](https://flowers.inria.fr/) team) at [ENSTA ParisTech](http://www.ensta-paristech.fr/en).
|
||||||
|
|
||||||
|
|
||||||
Logo credits: [L.M. Tenkes](https://www.instagram.com/lucillehue/)
|
Logo credits: [L.M. Tenkes](https://www.instagram.com/lucillehue/)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
.. _cmd_utils:
|
.. _cmd_util:
|
||||||
|
|
||||||
Command Utils
|
Command Utils
|
||||||
=========================
|
=========================
|
||||||
|
|
@ -5,14 +5,14 @@ This table displays the rl algorithms that are implemented in the Stable Baselin
|
||||||
along with some useful characteristics: support for discrete/continuous actions, multiprocessing.
|
along with some useful characteristics: support for discrete/continuous actions, multiprocessing.
|
||||||
|
|
||||||
|
|
||||||
============ =========== ============ ================
|
============ =========== ============ ================= =============== ================
|
||||||
Name ``Box`` ``Discrete`` Multi Processing
|
Name ``Box`` ``Discrete`` ``MultiDiscrete`` ``MultiBinary`` Multi Processing
|
||||||
============ =========== ============ ================
|
============ =========== ============ ================= =============== ================
|
||||||
A2C ✔️ ✔️ ✔️
|
A2C ✔️ ✔️ ✔️ ✔️ ✔️
|
||||||
PPO ✔️ ✔️ ✔️
|
PPO ✔️ ✔️ ✔️ ✔️ ✔️
|
||||||
SAC ✔️ ❌ ❌
|
SAC ✔️ ❌ ❌ ❌ ❌
|
||||||
TD3 ✔️ ❌ ❌
|
TD3 ✔️ ❌ ❌ ❌ ❌
|
||||||
============ =========== ============ ================
|
============ =========== ============ ================= =============== ================
|
||||||
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Try it online with Colab Notebooks!
|
||||||
All the following examples can be executed online using Google colab |colab|
|
All the following examples can be executed online using Google colab |colab|
|
||||||
notebooks:
|
notebooks:
|
||||||
|
|
||||||
- `Full Tutorial <https://github.com/araffin/rl-tutorial-jnrr19>`_
|
- `Full Tutorial <https://github.com/araffin/rl-tutorial-jnrr19/tree/sb3>`_
|
||||||
- `All Notebooks <https://github.com/Stable-Baselines-Team/rl-colab-notebooks/tree/sb3>`_
|
- `All Notebooks <https://github.com/Stable-Baselines-Team/rl-colab-notebooks/tree/sb3>`_
|
||||||
- `Getting Started`_
|
- `Getting Started`_
|
||||||
- `Training, Saving, Loading`_
|
- `Training, Saving, Loading`_
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,14 @@ General advice when using Reinforcement Learning
|
||||||
TL;DR
|
TL;DR
|
||||||
-----
|
-----
|
||||||
|
|
||||||
1. Read about RL and Stable Baselines
|
1. Read about RL and Stable Baselines3
|
||||||
2. Do quantitative experiments and hyperparameter tuning if needed
|
2. Do quantitative experiments and hyperparameter tuning if needed
|
||||||
3. Evaluate the performance using a separate test environment
|
3. Evaluate the performance using a separate test environment
|
||||||
4. For better performance, increase the training budget
|
4. For better performance, increase the training budget
|
||||||
|
|
||||||
|
|
||||||
Like any other subject, if you want to work with RL, you should first read about it (we have a dedicated `resource page <rl.html>`_ to get you started)
|
Like any other subject, if you want to work with RL, you should first read about it (we have a dedicated `resource page <rl.html>`_ to get you started)
|
||||||
to understand what you are using. We also recommend you read Stable Baselines (SB) documentation and do the `tutorial <https://github.com/araffin/rl-tutorial-jnrr19>`_.
|
to understand what you are using. We also recommend you read Stable Baselines3 (SB3) documentation and do the `tutorial <https://github.com/araffin/rl-tutorial-jnrr19/tree/sb3>`_.
|
||||||
It covers basic usage and guide you towards more advanced concepts of the library (e.g. callbacks and wrappers).
|
It covers basic usage and guide you towards more advanced concepts of the library (e.g. callbacks and wrappers).
|
||||||
|
|
||||||
Reinforcement Learning differs from other machine learning methods in several ways. The data used to train the agent is collected
|
Reinforcement Learning differs from other machine learning methods in several ways. The data used to train the agent is collected
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ Main Features
|
||||||
:caption: Common
|
:caption: Common
|
||||||
|
|
||||||
common/atari_wrappers
|
common/atari_wrappers
|
||||||
common/cmd_utils
|
common/cmd_util
|
||||||
common/distributions
|
common/distributions
|
||||||
common/evaluation
|
common/evaluation
|
||||||
common/env_checker
|
common/env_checker
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,9 @@
|
||||||
Changelog
|
Changelog
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Pre-Release 0.6.0a8 (WIP)
|
Pre-Release 0.6.0a10 (WIP)
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
|
|
||||||
Breaking Changes:
|
Breaking Changes:
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
@ -15,13 +14,16 @@ New Features:
|
||||||
- Added env checker (Sync with Stable Baselines)
|
- Added env checker (Sync with Stable Baselines)
|
||||||
- Added ``VecCheckNan`` and ``VecVideoRecorder`` (Sync with Stable Baselines)
|
- Added ``VecCheckNan`` and ``VecVideoRecorder`` (Sync with Stable Baselines)
|
||||||
- Added determinism tests
|
- Added determinism tests
|
||||||
- Added ``cmd_utils`` and ``atari_wrappers``
|
- Added ``cmd_util`` and ``atari_wrappers``
|
||||||
|
- Added support for ``MultiDiscrete`` and ``MultiBinary`` observation spaces (@rolandgvc)
|
||||||
|
- Added ``MultiCategorical`` and ``Bernoulli`` distributions for PPO/A2C (@rolandgvc)
|
||||||
|
|
||||||
Bug Fixes:
|
Bug Fixes:
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
- Fixed a bug that prevented model trained on cpu to be loaded on gpu
|
- Fixed a bug that prevented model trained on cpu to be loaded on gpu
|
||||||
- Fixed version number that had a new line included
|
- Fixed version number that had a new line included
|
||||||
- Fixed weird seg fault in docker image due to FakeImageEnv by reducing screen size
|
- Fixed weird seg fault in docker image due to FakeImageEnv by reducing screen size
|
||||||
|
- Fixed ``sde_sample_freq`` that was not taken into account for SAC
|
||||||
|
|
||||||
Deprecations:
|
Deprecations:
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|
@ -226,4 +228,4 @@ And all the contributors:
|
||||||
@XMaster96 @kantneel @Pastafarianist @GerardMaggiolino @PatrickWalter214 @yutingsz @sc420 @Aaahh @billtubbs
|
@XMaster96 @kantneel @Pastafarianist @GerardMaggiolino @PatrickWalter214 @yutingsz @sc420 @Aaahh @billtubbs
|
||||||
@Miffyli @dwiel @miguelrass @qxcv @jaberkow @eavelardev @ruifeng96150 @pedrohbtp @srivatsankrishnan @evilsocket
|
@Miffyli @dwiel @miguelrass @qxcv @jaberkow @eavelardev @ruifeng96150 @pedrohbtp @srivatsankrishnan @evilsocket
|
||||||
@MarvineGothic @jdossgollin @SyllogismRXS @rusu24edward @jbulow @Antymon @seheevic @justinkterry @edbeeching
|
@MarvineGothic @jdossgollin @SyllogismRXS @rusu24edward @jbulow @Antymon @seheevic @justinkterry @edbeeching
|
||||||
@flodorner @KuKuXia @NeoExtended @solliet @mmcenta @richardwu @kinalmehta
|
@flodorner @KuKuXia @NeoExtended @solliet @mmcenta @richardwu @kinalmehta @rolandgvc @tkelestemur
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,10 @@ Can I use?
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
Space Action Observation
|
Space Action Observation
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
Discrete ❌ ❌
|
Discrete ✔️ ✔️
|
||||||
Box ✔️ ✔️
|
Box ✔️ ✔️
|
||||||
MultiDiscrete ❌ ❌
|
MultiDiscrete ✔️ ✔️
|
||||||
MultiBinary ❌ ❌
|
MultiBinary ✔️ ✔️
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -46,7 +46,7 @@ Train a A2C agent on ``CartPole-v1`` using 4 environments.
|
||||||
|
|
||||||
from stable_baselines3 import A2C
|
from stable_baselines3 import A2C
|
||||||
from stable_baselines3.a2c import MlpPolicy
|
from stable_baselines3.a2c import MlpPolicy
|
||||||
from stable_baselines3.common.cmd_utils import make_vec_env
|
from stable_baselines3.common.cmd_util import make_vec_env
|
||||||
|
|
||||||
# Parallel environments
|
# Parallel environments
|
||||||
env = make_vec_env('CartPole-v1', n_envs=4)
|
env = make_vec_env('CartPole-v1', n_envs=4)
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,10 @@ Can I use?
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
Space Action Observation
|
Space Action Observation
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
Discrete ❌ ❌
|
Discrete ✔️ ✔️
|
||||||
Box ✔️ ✔️
|
Box ✔️ ✔️
|
||||||
MultiDiscrete ❌ ❌
|
MultiDiscrete ✔️ ✔️
|
||||||
MultiBinary ❌ ❌
|
MultiBinary ✔️ ✔️
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
|
|
||||||
Example
|
Example
|
||||||
|
|
@ -55,7 +55,7 @@ Train a PPO agent on ``Pendulum-v0`` using 4 environments.
|
||||||
|
|
||||||
from stable_baselines3 import A2C
|
from stable_baselines3 import A2C
|
||||||
from stable_baselines3.ppo import MlpPolicy
|
from stable_baselines3.ppo import MlpPolicy
|
||||||
from stable_baselines3.common.cmd_utils import make_vec_env
|
from stable_baselines3.common.cmd_util import make_vec_env
|
||||||
|
|
||||||
# Parallel environments
|
# Parallel environments
|
||||||
env = make_vec_env('CartPole-v1', n_envs=4)
|
env = make_vec_env('CartPole-v1', n_envs=4)
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,10 @@ Can I use?
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
Space Action Observation
|
Space Action Observation
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
Discrete ❌ ❌
|
Discrete ❌ ✔️
|
||||||
Box ✔️ ✔️
|
Box ✔️ ✔️
|
||||||
MultiDiscrete ❌ ❌
|
MultiDiscrete ❌ ✔️
|
||||||
MultiBinary ❌ ❌
|
MultiBinary ❌ ✔️
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,10 @@ Can I use?
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
Space Action Observation
|
Space Action Observation
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
Discrete ❌ ❌
|
Discrete ❌ ✔️
|
||||||
Box ✔️ ✔️
|
Box ✔️ ✔️
|
||||||
MultiDiscrete ❌ ❌
|
MultiDiscrete ❌ ✔️
|
||||||
MultiBinary ❌ ❌
|
MultiBinary ❌ ✔️
|
||||||
============= ====== ===========
|
============= ====== ===========
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -801,7 +801,7 @@ class OffPolicyRLModel(BaseRLModel):
|
||||||
|
|
||||||
while not done:
|
while not done:
|
||||||
|
|
||||||
if self.use_sde and self.sde_sample_freq > 0 and n_steps % self.sde_sample_freq == 0:
|
if self.use_sde and self.sde_sample_freq > 0 and total_steps % self.sde_sample_freq == 0:
|
||||||
# Sample a new noise matrix
|
# Sample a new noise matrix
|
||||||
self.actor.reset_noise()
|
self.actor.reset_noise()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class BaseBuffer(object):
|
||||||
to which the values will be converted
|
to which the values will be converted
|
||||||
:param n_envs: (int) Number of parallel environments
|
:param n_envs: (int) Number of parallel environments
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
buffer_size: int,
|
buffer_size: int,
|
||||||
observation_space: spaces.Space,
|
observation_space: spaces.Space,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
from typing import Optional, Tuple, Dict, Any
|
from typing import Optional, Tuple, Dict, Any, List
|
||||||
|
|
||||||
import gym
|
import gym
|
||||||
import torch as th
|
import torch as th
|
||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
from torch.distributions import Normal, Categorical
|
from torch.distributions import Normal, Categorical, Bernoulli
|
||||||
from gym import spaces
|
from gym import spaces
|
||||||
|
|
||||||
from stable_baselines3.common.preprocessing import get_action_dim
|
from stable_baselines3.common.preprocessing import get_action_dim
|
||||||
|
|
@ -88,7 +87,7 @@ def sum_independent_dims(tensor: th.Tensor) -> th.Tensor:
|
||||||
:return: (th.Tensor) shape: (n_batch,)
|
:return: (th.Tensor) shape: (n_batch,)
|
||||||
"""
|
"""
|
||||||
if len(tensor.shape) > 1:
|
if len(tensor.shape) > 1:
|
||||||
tensor = tensor.sum(axis=1)
|
tensor = tensor.sum(dim=1)
|
||||||
else:
|
else:
|
||||||
tensor = tensor.sum()
|
tensor = tensor.sum()
|
||||||
return tensor
|
return tensor
|
||||||
|
|
@ -292,6 +291,114 @@ class CategoricalDistribution(Distribution):
|
||||||
return self.distribution.log_prob(actions)
|
return self.distribution.log_prob(actions)
|
||||||
|
|
||||||
|
|
||||||
|
class MultiCategoricalDistribution(Distribution):
|
||||||
|
"""
|
||||||
|
MultiCategorical distribution for multi discrete actions.
|
||||||
|
|
||||||
|
:param action_dims: (List[int]) List of sizes of discrete action spaces
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, action_dims: List[int]):
|
||||||
|
super(MultiCategoricalDistribution, self).__init__()
|
||||||
|
self.action_dims = action_dims
|
||||||
|
self.distributions = None
|
||||||
|
|
||||||
|
def proba_distribution_net(self, latent_dim: int) -> nn.Module:
|
||||||
|
"""
|
||||||
|
Create the layer that represents the distribution:
|
||||||
|
it will be the logits (flattened) of the MultiCategorical distribution.
|
||||||
|
You can then get probabilities using a softmax on each sub-space.
|
||||||
|
|
||||||
|
:param latent_dim: (int) Dimension of the last layer
|
||||||
|
of the policy network (before the action layer)
|
||||||
|
:return: (nn.Linear)
|
||||||
|
"""
|
||||||
|
|
||||||
|
action_logits = nn.Linear(latent_dim, sum(self.action_dims))
|
||||||
|
return action_logits
|
||||||
|
|
||||||
|
def proba_distribution(self, action_logits: th.Tensor) -> 'MultiCategoricalDistribution':
|
||||||
|
self.distributions = [Categorical(logits=split) for split in th.split(action_logits, tuple(self.action_dims), dim=1)]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def mode(self) -> th.Tensor:
|
||||||
|
return th.stack([th.argmax(dist.probs, dim=1) for dist in self.distributions], dim=1)
|
||||||
|
|
||||||
|
def sample(self) -> th.Tensor:
|
||||||
|
return th.stack([dist.sample() for dist in self.distributions], dim=1)
|
||||||
|
|
||||||
|
def entropy(self) -> th.Tensor:
|
||||||
|
return th.stack([dist.entropy() for dist in self.distributions], dim=1).sum(dim=1)
|
||||||
|
|
||||||
|
def actions_from_params(self, action_logits: th.Tensor,
|
||||||
|
deterministic: bool = False) -> th.Tensor:
|
||||||
|
# Update the proba distribution
|
||||||
|
self.proba_distribution(action_logits)
|
||||||
|
return self.get_actions(deterministic=deterministic)
|
||||||
|
|
||||||
|
def log_prob_from_params(self, action_logits: th.Tensor) -> Tuple[th.Tensor, th.Tensor]:
|
||||||
|
actions = self.actions_from_params(action_logits)
|
||||||
|
log_prob = self.log_prob(actions)
|
||||||
|
return actions, log_prob
|
||||||
|
|
||||||
|
def log_prob(self, actions: th.Tensor) -> th.Tensor:
|
||||||
|
# Extract each discrete action and compute log prob for their respective distributions
|
||||||
|
return th.stack([dist.log_prob(action) for dist, action in zip(self.distributions,
|
||||||
|
th.unbind(actions, dim=1))], dim=1).sum(dim=1)
|
||||||
|
|
||||||
|
|
||||||
|
class BernoulliDistribution(Distribution):
|
||||||
|
"""
|
||||||
|
Bernoulli distribution for MultiBinary action spaces.
|
||||||
|
|
||||||
|
:param action_dim: (int) Number of binary actions
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, action_dims: int):
|
||||||
|
super(BernoulliDistribution, self).__init__()
|
||||||
|
self.distribution = None
|
||||||
|
self.action_dims = action_dims
|
||||||
|
|
||||||
|
def proba_distribution_net(self, latent_dim: int) -> nn.Module:
|
||||||
|
"""
|
||||||
|
Create the layer that represents the distribution:
|
||||||
|
it will be the logits of the Bernoulli distribution.
|
||||||
|
|
||||||
|
:param latent_dim: (int) Dimension of the last layer
|
||||||
|
of the policy network (before the action layer)
|
||||||
|
:return: (nn.Linear)
|
||||||
|
"""
|
||||||
|
action_logits = nn.Linear(latent_dim, self.action_dims)
|
||||||
|
return action_logits
|
||||||
|
|
||||||
|
def proba_distribution(self, action_logits: th.Tensor) -> 'BernoulliDistribution':
|
||||||
|
self.distribution = Bernoulli(logits=action_logits)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def mode(self) -> th.Tensor:
|
||||||
|
return th.round(self.distribution.probs)
|
||||||
|
|
||||||
|
def sample(self) -> th.Tensor:
|
||||||
|
return self.distribution.sample()
|
||||||
|
|
||||||
|
def entropy(self) -> th.Tensor:
|
||||||
|
return self.distribution.entropy().sum(dim=1)
|
||||||
|
|
||||||
|
def actions_from_params(self, action_logits: th.Tensor,
|
||||||
|
deterministic: bool = False) -> th.Tensor:
|
||||||
|
# Update the proba distribution
|
||||||
|
self.proba_distribution(action_logits)
|
||||||
|
return self.get_actions(deterministic=deterministic)
|
||||||
|
|
||||||
|
def log_prob_from_params(self, action_logits: th.Tensor) -> Tuple[th.Tensor, th.Tensor]:
|
||||||
|
actions = self.actions_from_params(action_logits)
|
||||||
|
log_prob = self.log_prob(actions)
|
||||||
|
return actions, log_prob
|
||||||
|
|
||||||
|
def log_prob(self, actions: th.Tensor) -> th.Tensor:
|
||||||
|
return self.distribution.log_prob(actions).sum(dim=1)
|
||||||
|
|
||||||
|
|
||||||
class StateDependentNoiseDistribution(Distribution):
|
class StateDependentNoiseDistribution(Distribution):
|
||||||
"""
|
"""
|
||||||
Distribution class for using generalized State Dependent Exploration (gSDE).
|
Distribution class for using generalized State Dependent Exploration (gSDE).
|
||||||
|
|
@ -551,10 +658,10 @@ def make_proba_distribution(action_space: gym.spaces.Space,
|
||||||
return DiagGaussianDistribution(get_action_dim(action_space), **dist_kwargs)
|
return DiagGaussianDistribution(get_action_dim(action_space), **dist_kwargs)
|
||||||
elif isinstance(action_space, spaces.Discrete):
|
elif isinstance(action_space, spaces.Discrete):
|
||||||
return CategoricalDistribution(action_space.n, **dist_kwargs)
|
return CategoricalDistribution(action_space.n, **dist_kwargs)
|
||||||
# elif isinstance(action_space, spaces.MultiDiscrete):
|
elif isinstance(action_space, spaces.MultiDiscrete):
|
||||||
# return MultiCategoricalDistribution(action_space.nvec, **dist_kwargs)
|
return MultiCategoricalDistribution(action_space.nvec, **dist_kwargs)
|
||||||
# elif isinstance(action_space, spaces.MultiBinary):
|
elif isinstance(action_space, spaces.MultiBinary):
|
||||||
# return BernoulliDistribution(action_space.n, **dist_kwargs)
|
return BernoulliDistribution(action_space.n, **dist_kwargs)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Error: probability distribution, not implemented for action space"
|
raise NotImplementedError("Error: probability distribution, not implemented for action space"
|
||||||
f"of type {type(action_space)}."
|
f"of type {type(action_space)}."
|
||||||
|
|
|
||||||
|
|
@ -206,8 +206,8 @@ class BasePolicy(nn.Module):
|
||||||
# Handle the different cases for images
|
# Handle the different cases for images
|
||||||
# as PyTorch use channel first format
|
# as PyTorch use channel first format
|
||||||
if is_image_space(self.observation_space):
|
if is_image_space(self.observation_space):
|
||||||
if (observation.shape == self.observation_space.shape or
|
if (observation.shape == self.observation_space.shape
|
||||||
observation.shape[1:] == self.observation_space.shape):
|
or observation.shape[1:] == self.observation_space.shape):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# Try to re-order the channels
|
# Try to re-order the channels
|
||||||
|
|
@ -279,9 +279,9 @@ class BasePolicy(nn.Module):
|
||||||
elif observation.shape[1:] == observation_space.shape:
|
elif observation.shape[1:] == observation_space.shape:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise ValueError("Error: Unexpected observation shape {} for ".format(observation.shape) +
|
raise ValueError(f"Error: Unexpected observation shape {observation.shape} for "
|
||||||
"Box environment, please use {} ".format(observation_space.shape) +
|
+ f"Box environment, please use {observation_space.shape} "
|
||||||
"or (n_env, {}) for the observation shape."
|
+ "or (n_env, {}) for the observation shape."
|
||||||
.format(", ".join(map(str, observation_space.shape))))
|
.format(", ".join(map(str, observation_space.shape))))
|
||||||
elif isinstance(observation_space, gym.spaces.Discrete):
|
elif isinstance(observation_space, gym.spaces.Discrete):
|
||||||
if observation.shape == (): # A numpy array of a number, has shape empty tuple '()'
|
if observation.shape == (): # A numpy array of a number, has shape empty tuple '()'
|
||||||
|
|
@ -289,30 +289,30 @@ class BasePolicy(nn.Module):
|
||||||
elif len(observation.shape) == 1:
|
elif len(observation.shape) == 1:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise ValueError("Error: Unexpected observation shape {} for ".format(observation.shape) +
|
raise ValueError(f"Error: Unexpected observation shape {observation.shape} for "
|
||||||
"Discrete environment, please use (1,) or (n_env, 1) for the observation shape.")
|
+ "Discrete environment, please use (1,) or (n_env, 1) for the observation shape.")
|
||||||
# TODO: add support for MultiDiscrete and MultiBinary observation spaces
|
|
||||||
# elif isinstance(observation_space, gym.spaces.MultiDiscrete):
|
elif isinstance(observation_space, gym.spaces.MultiDiscrete):
|
||||||
# if observation.shape == (len(observation_space.nvec),):
|
if observation.shape == (len(observation_space.nvec),):
|
||||||
# return False
|
return False
|
||||||
# elif len(observation.shape) == 2 and observation.shape[1] == len(observation_space.nvec):
|
elif len(observation.shape) == 2 and observation.shape[1] == len(observation_space.nvec):
|
||||||
# return True
|
return True
|
||||||
# else:
|
else:
|
||||||
# raise ValueError("Error: Unexpected observation shape {} for MultiDiscrete ".format(observation.shape) +
|
raise ValueError(f"Error: Unexpected observation shape {observation.shape} for MultiDiscrete "
|
||||||
# "environment, please use ({},) or ".format(len(observation_space.nvec)) +
|
+ f"environment, please use ({len(observation_space.nvec)},) or "
|
||||||
# "(n_env, {}) for the observation shape.".format(len(observation_space.nvec)))
|
+ f"(n_env, {len(observation_space.nvec)}) for the observation shape.")
|
||||||
# elif isinstance(observation_space, gym.spaces.MultiBinary):
|
elif isinstance(observation_space, gym.spaces.MultiBinary):
|
||||||
# if observation.shape == (observation_space.n,):
|
if observation.shape == (observation_space.n,):
|
||||||
# return False
|
return False
|
||||||
# elif len(observation.shape) == 2 and observation.shape[1] == observation_space.n:
|
elif len(observation.shape) == 2 and observation.shape[1] == observation_space.n:
|
||||||
# return True
|
return True
|
||||||
# else:
|
else:
|
||||||
# raise ValueError("Error: Unexpected observation shape {} for MultiBinary ".format(observation.shape) +
|
raise ValueError(f"Error: Unexpected observation shape {observation.shape} for MultiBinary "
|
||||||
# "environment, please use ({},) or ".format(observation_space.n) +
|
+ f"environment, please use ({observation_space.n},) or "
|
||||||
# "(n_env, {}) for the observation shape.".format(observation_space.n))
|
+ f"(n_env, {observation_space.n}) for the observation shape.")
|
||||||
else:
|
else:
|
||||||
raise ValueError("Error: Cannot determine if the observation is vectorized with the space type {}."
|
raise ValueError("Error: Cannot determine if the observation is vectorized "
|
||||||
.format(observation_space))
|
+ f" with the space type {observation_space}.")
|
||||||
|
|
||||||
def _get_data(self) -> Dict[str, Any]:
|
def _get_data(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -447,7 +447,7 @@ def get_policy_from_name(base_policy_type: Type[BasePolicy], name: str) -> Type[
|
||||||
raise ValueError(f"Error: the policy type {base_policy_type} is not registered!")
|
raise ValueError(f"Error: the policy type {base_policy_type} is not registered!")
|
||||||
if name not in _policy_registry[base_policy_type]:
|
if name not in _policy_registry[base_policy_type]:
|
||||||
raise ValueError(f"Error: unknown policy type {name},"
|
raise ValueError(f"Error: unknown policy type {name},"
|
||||||
"the only registed policy type are: {list(_policy_registry[base_policy_type].keys())}!")
|
f"the only registed policy type are: {list(_policy_registry[base_policy_type].keys())}!")
|
||||||
return _policy_registry[base_policy_type][name]
|
return _policy_registry[base_policy_type][name]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -460,14 +460,10 @@ def register_policy(name: str, policy: Type[BasePolicy]) -> None:
|
||||||
:param policy: (Type[BasePolicy]) the policy class
|
:param policy: (Type[BasePolicy]) the policy class
|
||||||
"""
|
"""
|
||||||
sub_class = None
|
sub_class = None
|
||||||
# For building the doc
|
for cls in BasePolicy.__subclasses__():
|
||||||
try:
|
if issubclass(policy, cls):
|
||||||
for cls in BasePolicy.__subclasses__():
|
sub_class = cls
|
||||||
if issubclass(policy, cls):
|
break
|
||||||
sub_class = cls
|
|
||||||
break
|
|
||||||
except AttributeError:
|
|
||||||
sub_class = str(th.random.randint(100))
|
|
||||||
if sub_class is None:
|
if sub_class is None:
|
||||||
raise ValueError(f"Error: the policy {policy} is not of any known subclasses of BasePolicy!")
|
raise ValueError(f"Error: the policy {policy} is not of any known subclasses of BasePolicy!")
|
||||||
|
|
||||||
|
|
@ -511,7 +507,6 @@ class MlpExtractor(nn.Module):
|
||||||
device: Union[th.device, str] = 'auto'):
|
device: Union[th.device, str] = 'auto'):
|
||||||
super(MlpExtractor, self).__init__()
|
super(MlpExtractor, self).__init__()
|
||||||
device = get_device(device)
|
device = get_device(device)
|
||||||
|
|
||||||
shared_net, policy_net, value_net = [], [], []
|
shared_net, policy_net, value_net = [], [], []
|
||||||
policy_only_layers = [] # Layer sizes of the network that only belongs to the policy network
|
policy_only_layers = [] # Layer sizes of the network that only belongs to the policy network
|
||||||
value_only_layers = [] # Layer sizes of the network that only belongs to the value network
|
value_only_layers = [] # Layer sizes of the network that only belongs to the value network
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import torch as th
|
import torch as th
|
||||||
import torch.nn.functional as F
|
import torch.nn.functional as F
|
||||||
from gym import spaces
|
from gym import spaces
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
def is_image_space(observation_space: spaces.Space,
|
def is_image_space(observation_space: spaces.Space,
|
||||||
|
|
@ -62,11 +62,21 @@ def preprocess_obs(obs: th.Tensor, observation_space: spaces.Space,
|
||||||
if is_image_space(observation_space) and normalize_images:
|
if is_image_space(observation_space) and normalize_images:
|
||||||
return obs.float() / 255.0
|
return obs.float() / 255.0
|
||||||
return obs.float()
|
return obs.float()
|
||||||
|
|
||||||
elif isinstance(observation_space, spaces.Discrete):
|
elif isinstance(observation_space, spaces.Discrete):
|
||||||
# One hot encoding and convert to float to avoid errors
|
# One hot encoding and convert to float to avoid errors
|
||||||
return F.one_hot(obs.long(), num_classes=observation_space.n).float()
|
return F.one_hot(obs.long(), num_classes=observation_space.n).float()
|
||||||
|
|
||||||
|
elif isinstance(observation_space, spaces.MultiDiscrete):
|
||||||
|
# Tensor concatenation of one hot encodings of each Categorical sub-space
|
||||||
|
return th.cat([F.one_hot(obs_.long(), num_classes=int(observation_space.nvec[idx])).float()
|
||||||
|
for idx, obs_ in enumerate(th.split(obs.long(), 1, dim=1))],
|
||||||
|
dim=-1).view(obs.shape[0], sum(observation_space.nvec))
|
||||||
|
|
||||||
|
elif isinstance(observation_space, spaces.MultiBinary):
|
||||||
|
return obs.float()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# TODO: Multidiscrete, Binary, MultiBinary, Tuple, Dict
|
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,8 +92,13 @@ def get_obs_shape(observation_space: spaces.Space) -> Tuple[int, ...]:
|
||||||
elif isinstance(observation_space, spaces.Discrete):
|
elif isinstance(observation_space, spaces.Discrete):
|
||||||
# Observation is an int
|
# Observation is an int
|
||||||
return 1,
|
return 1,
|
||||||
|
elif isinstance(observation_space, spaces.MultiDiscrete):
|
||||||
|
# Number of discrete features
|
||||||
|
return int(len(observation_space.nvec)),
|
||||||
|
elif isinstance(observation_space, spaces.MultiBinary):
|
||||||
|
# Number of binary features
|
||||||
|
return int(observation_space.n),
|
||||||
else:
|
else:
|
||||||
# TODO: Multidiscrete, Binary, MultiBinary, Tuple, Dict
|
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -95,8 +110,13 @@ def get_flattened_obs_dim(observation_space: spaces.Space) -> int:
|
||||||
:param observation_space: (spaces.Space)
|
:param observation_space: (spaces.Space)
|
||||||
:return: (int)
|
:return: (int)
|
||||||
"""
|
"""
|
||||||
# Use Gym internal method
|
# See issue https://github.com/openai/gym/issues/1915
|
||||||
return spaces.utils.flatdim(observation_space)
|
# it may be a problem for Dict/Tuple spaces too...
|
||||||
|
if isinstance(observation_space, spaces.MultiDiscrete):
|
||||||
|
return sum(observation_space.nvec)
|
||||||
|
else:
|
||||||
|
# Use Gym internal method
|
||||||
|
return spaces.utils.flatdim(observation_space)
|
||||||
|
|
||||||
|
|
||||||
def get_action_dim(action_space: spaces.Space) -> int:
|
def get_action_dim(action_space: spaces.Space) -> int:
|
||||||
|
|
@ -111,5 +131,11 @@ def get_action_dim(action_space: spaces.Space) -> int:
|
||||||
elif isinstance(action_space, spaces.Discrete):
|
elif isinstance(action_space, spaces.Discrete):
|
||||||
# Action is an int
|
# Action is an int
|
||||||
return 1
|
return 1
|
||||||
|
elif isinstance(action_space, spaces.MultiDiscrete):
|
||||||
|
# Number of discrete actions
|
||||||
|
return int(len(action_space.nvec))
|
||||||
|
elif isinstance(action_space, spaces.MultiBinary):
|
||||||
|
# Number of binary actions
|
||||||
|
return int(action_space.n)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from stable_baselines3.common.policies import (BasePolicy, register_policy, MlpE
|
||||||
BaseFeaturesExtractor, FlattenExtractor)
|
BaseFeaturesExtractor, FlattenExtractor)
|
||||||
from stable_baselines3.common.distributions import (make_proba_distribution, Distribution,
|
from stable_baselines3.common.distributions import (make_proba_distribution, Distribution,
|
||||||
DiagGaussianDistribution, CategoricalDistribution,
|
DiagGaussianDistribution, CategoricalDistribution,
|
||||||
|
MultiCategoricalDistribution, BernoulliDistribution,
|
||||||
StateDependentNoiseDistribution)
|
StateDependentNoiseDistribution)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -178,6 +179,10 @@ class PPOPolicy(BasePolicy):
|
||||||
log_std_init=self.log_std_init)
|
log_std_init=self.log_std_init)
|
||||||
elif isinstance(self.action_dist, CategoricalDistribution):
|
elif isinstance(self.action_dist, CategoricalDistribution):
|
||||||
self.action_net = self.action_dist.proba_distribution_net(latent_dim=latent_dim_pi)
|
self.action_net = self.action_dist.proba_distribution_net(latent_dim=latent_dim_pi)
|
||||||
|
elif isinstance(self.action_dist, MultiCategoricalDistribution):
|
||||||
|
self.action_net = self.action_dist.proba_distribution_net(latent_dim=latent_dim_pi)
|
||||||
|
elif isinstance(self.action_dist, BernoulliDistribution):
|
||||||
|
self.action_net = self.action_dist.proba_distribution_net(latent_dim=latent_dim_pi)
|
||||||
|
|
||||||
self.value_net = nn.Linear(self.mlp_extractor.latent_dim_vf, 1)
|
self.value_net = nn.Linear(self.mlp_extractor.latent_dim_vf, 1)
|
||||||
# Init weights: use orthogonal initialization
|
# Init weights: use orthogonal initialization
|
||||||
|
|
@ -226,6 +231,7 @@ class PPOPolicy(BasePolicy):
|
||||||
# Preprocess the observation if needed
|
# Preprocess the observation if needed
|
||||||
features = self.extract_features(obs)
|
features = self.extract_features(obs)
|
||||||
latent_pi, latent_vf = self.mlp_extractor(features)
|
latent_pi, latent_vf = self.mlp_extractor(features)
|
||||||
|
|
||||||
# Features for sde
|
# Features for sde
|
||||||
latent_sde = latent_pi
|
latent_sde = latent_pi
|
||||||
if self.sde_features_extractor is not None:
|
if self.sde_features_extractor is not None:
|
||||||
|
|
@ -245,11 +251,15 @@ class PPOPolicy(BasePolicy):
|
||||||
|
|
||||||
if isinstance(self.action_dist, DiagGaussianDistribution):
|
if isinstance(self.action_dist, DiagGaussianDistribution):
|
||||||
return self.action_dist.proba_distribution(mean_actions, self.log_std)
|
return self.action_dist.proba_distribution(mean_actions, self.log_std)
|
||||||
|
|
||||||
elif isinstance(self.action_dist, CategoricalDistribution):
|
elif isinstance(self.action_dist, CategoricalDistribution):
|
||||||
# Here mean_actions are the logits before the softmax
|
# Here mean_actions are the logits before the softmax
|
||||||
return self.action_dist.proba_distribution(action_logits=mean_actions)
|
return self.action_dist.proba_distribution(action_logits=mean_actions)
|
||||||
|
elif isinstance(self.action_dist, MultiCategoricalDistribution):
|
||||||
|
# Here mean_actions are the flattened logits
|
||||||
|
return self.action_dist.proba_distribution(action_logits=mean_actions)
|
||||||
|
elif isinstance(self.action_dist, BernoulliDistribution):
|
||||||
|
# Here mean_actions are the logits (before rounding to get the binary actions)
|
||||||
|
return self.action_dist.proba_distribution(action_logits=mean_actions)
|
||||||
elif isinstance(self.action_dist, StateDependentNoiseDistribution):
|
elif isinstance(self.action_dist, StateDependentNoiseDistribution):
|
||||||
return self.action_dist.proba_distribution(mean_actions, self.log_std, latent_sde)
|
return self.action_dist.proba_distribution(mean_actions, self.log_std, latent_sde)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,6 @@ class PPO(BaseRLModel):
|
||||||
callback.on_rollout_start()
|
callback.on_rollout_start()
|
||||||
|
|
||||||
while n_steps < n_rollout_steps:
|
while n_steps < n_rollout_steps:
|
||||||
|
|
||||||
if self.use_sde and self.sde_sample_freq > 0 and n_steps % self.sde_sample_freq == 0:
|
if self.use_sde and self.sde_sample_freq > 0 and n_steps % self.sde_sample_freq == 0:
|
||||||
# Sample a new noise matrix
|
# Sample a new noise matrix
|
||||||
self.policy.reset_noise(env.num_envs)
|
self.policy.reset_noise(env.num_envs)
|
||||||
|
|
@ -213,7 +212,6 @@ class PPO(BaseRLModel):
|
||||||
approx_kl_divs = []
|
approx_kl_divs = []
|
||||||
# Do a complete pass on the rollout buffer
|
# Do a complete pass on the rollout buffer
|
||||||
for rollout_data in self.rollout_buffer.get(batch_size):
|
for rollout_data in self.rollout_buffer.get(batch_size):
|
||||||
|
|
||||||
actions = rollout_data.actions
|
actions = rollout_data.actions
|
||||||
if isinstance(self.action_space, spaces.Discrete):
|
if isinstance(self.action_space, spaces.Discrete):
|
||||||
# Convert discrete action from float to long
|
# Convert discrete action from float to long
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
0.6.0a8
|
0.6.0a10
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import torch as th
|
||||||
from stable_baselines3 import A2C, PPO
|
from stable_baselines3 import A2C, PPO
|
||||||
from stable_baselines3.common.distributions import (DiagGaussianDistribution, TanhBijector,
|
from stable_baselines3.common.distributions import (DiagGaussianDistribution, TanhBijector,
|
||||||
StateDependentNoiseDistribution,
|
StateDependentNoiseDistribution,
|
||||||
CategoricalDistribution, SquashedDiagGaussianDistribution)
|
CategoricalDistribution, SquashedDiagGaussianDistribution,
|
||||||
|
MultiCategoricalDistribution, BernoulliDistribution)
|
||||||
from stable_baselines3.common.utils import set_random_seed
|
from stable_baselines3.common.utils import set_random_seed
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -85,15 +86,21 @@ def test_entropy(dist):
|
||||||
assert th.allclose(entropy.mean(), -log_prob.mean(), rtol=5e-3)
|
assert th.allclose(entropy.mean(), -log_prob.mean(), rtol=5e-3)
|
||||||
|
|
||||||
|
|
||||||
def test_categorical():
|
categorical_params = [
|
||||||
|
(CategoricalDistribution(N_ACTIONS), N_ACTIONS),
|
||||||
|
(MultiCategoricalDistribution([2, 3]), sum([2, 3])),
|
||||||
|
(BernoulliDistribution(N_ACTIONS), N_ACTIONS)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("dist, CAT_ACTIONS", categorical_params)
|
||||||
|
def test_categorical(dist, CAT_ACTIONS):
|
||||||
# The entropy can be approximated by averaging the negative log likelihood
|
# The entropy can be approximated by averaging the negative log likelihood
|
||||||
# mean negative log likelihood == entropy
|
# mean negative log likelihood == entropy
|
||||||
dist = CategoricalDistribution(N_ACTIONS)
|
|
||||||
set_random_seed(1)
|
set_random_seed(1)
|
||||||
action_logits = th.rand(N_SAMPLES, N_ACTIONS)
|
action_logits = th.rand(N_SAMPLES, CAT_ACTIONS)
|
||||||
dist = dist.proba_distribution(action_logits)
|
dist = dist.proba_distribution(action_logits)
|
||||||
|
|
||||||
actions = dist.get_actions()
|
actions = dist.get_actions()
|
||||||
entropy = dist.entropy()
|
entropy = dist.entropy()
|
||||||
log_prob = dist.log_prob(actions)
|
log_prob = dist.log_prob(actions)
|
||||||
assert th.allclose(entropy.mean(), -log_prob.mean(), rtol=2e-4)
|
assert th.allclose(entropy.mean(), -log_prob.mean(), rtol=5e-3)
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,27 @@ import numpy as np
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from stable_baselines3 import A2C, PPO, SAC, TD3
|
from stable_baselines3 import A2C, PPO, SAC, TD3
|
||||||
from stable_baselines3.common.identity_env import IdentityEnvBox, IdentityEnv
|
from stable_baselines3.common.identity_env import (IdentityEnvBox, IdentityEnv,
|
||||||
|
IdentityEnvMultiBinary, IdentityEnvMultiDiscrete)
|
||||||
|
|
||||||
|
from stable_baselines3.common.vec_env import DummyVecEnv
|
||||||
from stable_baselines3.common.evaluation import evaluate_policy
|
from stable_baselines3.common.evaluation import evaluate_policy
|
||||||
from stable_baselines3.common.noise import NormalActionNoise
|
from stable_baselines3.common.noise import NormalActionNoise
|
||||||
|
|
||||||
|
|
||||||
|
DIM = 4
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("model_class", [A2C, PPO])
|
@pytest.mark.parametrize("model_class", [A2C, PPO])
|
||||||
def test_discrete(model_class):
|
@pytest.mark.parametrize("env", [IdentityEnv(DIM), IdentityEnvMultiDiscrete(DIM), IdentityEnvMultiBinary(DIM)])
|
||||||
env = IdentityEnv(10)
|
def test_discrete(model_class, env):
|
||||||
model = model_class('MlpPolicy', env, gamma=0.5, seed=0).learn(3000)
|
env = DummyVecEnv([lambda: env])
|
||||||
|
model = model_class('MlpPolicy', env, gamma=0.5, seed=1).learn(3000)
|
||||||
|
|
||||||
evaluate_policy(model, env, n_eval_episodes=20, reward_threshold=90)
|
evaluate_policy(model, env, n_eval_episodes=20, reward_threshold=90)
|
||||||
|
obs = env.reset()
|
||||||
|
|
||||||
|
assert np.shape(model.predict(obs)[0]) == np.shape(obs)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("model_class", [A2C, PPO, SAC, TD3])
|
@pytest.mark.parametrize("model_class", [A2C, PPO, SAC, TD3])
|
||||||
|
|
|
||||||
47
tests/test_spaces.py
Normal file
47
tests/test_spaces.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
import gym
|
||||||
|
|
||||||
|
from stable_baselines3 import SAC, TD3
|
||||||
|
from stable_baselines3.common.evaluation import evaluate_policy
|
||||||
|
|
||||||
|
|
||||||
|
class DummyMultiDiscreteSpace(gym.Env):
|
||||||
|
def __init__(self, nvec):
|
||||||
|
super(DummyMultiDiscreteSpace, self).__init__()
|
||||||
|
self.observation_space = gym.spaces.MultiDiscrete(nvec)
|
||||||
|
self.action_space = gym.spaces.Box(low=-1, high=1, shape=(2,), dtype=np.float32)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
return self.observation_space.sample()
|
||||||
|
|
||||||
|
def step(self, action):
|
||||||
|
return self.observation_space.sample(), 0.0, False, {}
|
||||||
|
|
||||||
|
|
||||||
|
class DummyMultiBinary(gym.Env):
|
||||||
|
def __init__(self, n):
|
||||||
|
super(DummyMultiBinary, self).__init__()
|
||||||
|
self.observation_space = gym.spaces.MultiBinary(n)
|
||||||
|
self.action_space = gym.spaces.Box(low=-1, high=1, shape=(2,), dtype=np.float32)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
return self.observation_space.sample()
|
||||||
|
|
||||||
|
def step(self, action):
|
||||||
|
return self.observation_space.sample(), 0.0, False, {}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("model_class", [SAC, TD3])
|
||||||
|
@pytest.mark.parametrize("env", [DummyMultiDiscreteSpace([4, 3]), DummyMultiBinary(8)])
|
||||||
|
def test_identity_spaces(model_class, env):
|
||||||
|
"""
|
||||||
|
Additional tests for SAC/TD3 to check observation space support
|
||||||
|
for MultiDiscrete and MultiBinary.
|
||||||
|
"""
|
||||||
|
env = gym.wrappers.TimeLimit(env, max_episode_steps=100)
|
||||||
|
|
||||||
|
model = model_class("MlpPolicy", env, gamma=0.5, seed=1, policy_kwargs=dict(net_arch=[64]))
|
||||||
|
model.learn(total_timesteps=500)
|
||||||
|
|
||||||
|
evaluate_policy(model, env, n_eval_episodes=5)
|
||||||
Loading…
Reference in a new issue