mirror of
https://github.com/saymrwulf/stable-baselines3.git
synced 2026-05-18 21:30:19 +00:00
* Fix failing set_env test * Fix test failiing due to deprectation of env.seed * Adjust mean reward threshold in failing test * Fix her test failing due to rng * Change seed and revert reward threshold to 90 * Pin gym version * Make VecEnv compatible with gym seeding change * Revert change to VecEnv reset signature * Change subprocenv seed cmd to call reset instead * Fix type check * Add backward compat * Add `compat_gym_seed` helper * Add goal env checks in env_checker * Add docs on HER requirements for envs * Capture user warning in test with inverted box space * Update ale-py version * Fix randint * Allow noop_max to be zero * Update changelog * Update docker image * Update doc conda env and dockerfile * Custom envs should not have any warnings * Fix test for numpy >= 1.21 * Add check for vectorized compute reward * Bump to gym 0.24 * Fix gym default step docstring * Test downgrading gym * Revert "Test downgrading gym" This reverts commit 0072b77156c006ada8a1d6e26ce347ed85a83eeb. * Fix protobuf error * Fix in dependencies * Fix protobuf dep * Use newest version of cartpole * Update gym * Fix warning * Loosen required scipy version * Scipy no longer needed * Try gym 0.25 * Silence warnings from gym * Filter warnings during tests * Update doc * Update requirements * Add gym 26 compat in vec env * Fixes in envs and tests for gym 0.26+ * Enforce gym 0.26 api * format * Fix formatting * Fix dependencies * Fix syntax * Cleanup doc and warnings * Faster tests * Higher budget for HER perf test (revert prev change) * Fixes and update doc * Fix doc build * Fix breaking change * Fixes for rendering * Rename variables in monitor * update render method for gym 0.26 API backwards compatible (mode argument is allowed) while using the gym 0.26 API (render mode is determined at environment creation) * update tests and docs to new gym render API * undo removal of render modes metatadata check * set rgb_array as default render mode for gym.make * undo changes & raise warning if not 'rgb_array' * Fix type check * Remove recursion and fix type checking * Remove hacks for protobuf and gym 0.24 * Fix type annotations * reuse existing render_mode attribute * return tiled images for 'human' render mode * Allow to use opencv for human render, fix typos * Add warning when using non-zero start with Discrete (fixes #1197) * Fix type checking * Bug fixes and handle more cases * Throw proper warnings * Update test * Fix new metadata name * Ignore numpy warnings * Fixes in vec recorder * Global ignore * Filter local warning too * Monkey patch not needed for gym 26 * Add doc of VecEnv vs Gym API * Add render test * Fix return type * Update VecEnv vs Gym API doc * Fix for custom render mode * Fix return type * Fix type checking * check test env test_buffer * skip render check * check env test_dict_env * test_env test_gae * check envs in remaining tests * Update tests * Add warning for Discrete action space with non-zero (#1295) * Fix atari annotation * ignore get_action_meanings [attr-defined] * Fix mypy issues * Add patch for gym/gymnasium transition * Switch to gymnasium * Rely on signature instead of version * More patches * Type ignore because of https://github.com/Farama-Foundation/Gymnasium/pull/39 * Fix doc build * Fix pytype errors * Fix atari requirement * Update env checker due to change in dtype for Discrete * Fix type hint * Convert spaces for saved models * Ignore pytype * Remove gitlab CI * Disable pytype for convert space * Fix undefined info * Fix undefined info * Upgrade shimmy * Fix wrappers type annotation (need PR from Gymnasium) * Fix gymnasium dependency * Fix dependency declaration * Cap pygame version for python 3.7 * Point to master branch (v0.28.0) * Fix: use main not master branch * Rename done to terminated * Fix pygame dependency for python 3.7 * Rename gym to gymnasium * Update Gymnasium * Fix test * Fix tests * Forks don't have access to private variables * Fix linter warnings * Update read the doc env * Fix env checker for GoalEnv * Fix import * Update env checker (more info) and fix dtype * Use micromamab for Docker * Update dependencies * Clarify VecEnv doc * Fix Gymnasium version * Copy file only after mamba install * [ci skip] Update docker doc * Polish code * Reformat * Remove deprecated features * Ignore warning * Update doc * Update examples and changelog * Fix type annotation bundle (SAC, TD3, A2C, PPO, base class) (#1436) * Fix SAC type hints, improve DQN ones * Fix A2C and TD3 type hints * Fix PPO type hints * Fix on-policy type hints * Fix base class type annotation, do not use defaults * Update version * Disable mypy for python 3.7 * Rename Gym26StepReturn * Update continuous critic type annotation * Fix pytype complain --------- Co-authored-by: Carlos Luis <carlos.luisgonc@gmail.com> Co-authored-by: Quentin Gallouédec <45557362+qgallouedec@users.noreply.github.com> Co-authored-by: Thomas Lips <37955681+tlpss@users.noreply.github.com> Co-authored-by: tlips <thomas.lips@ugent.be> Co-authored-by: tlpss <thomas17.lips@gmail.com> Co-authored-by: Quentin GALLOUÉDEC <gallouedec.quentin@gmail.com>
354 lines
13 KiB
Python
354 lines
13 KiB
Python
import os
|
|
from copy import deepcopy
|
|
|
|
import numpy as np
|
|
import pytest
|
|
import torch as th
|
|
from gymnasium import spaces
|
|
|
|
from stable_baselines3 import A2C, DQN, PPO, SAC, TD3
|
|
from stable_baselines3.common.envs import FakeImageEnv
|
|
from stable_baselines3.common.preprocessing import is_image_space, is_image_space_channels_first
|
|
from stable_baselines3.common.utils import zip_strict
|
|
from stable_baselines3.common.vec_env import DummyVecEnv, VecFrameStack, VecNormalize, VecTransposeImage, is_vecenv_wrapped
|
|
|
|
|
|
@pytest.mark.parametrize("model_class", [A2C, PPO, SAC, TD3, DQN])
|
|
@pytest.mark.parametrize("share_features_extractor", [True, False])
|
|
def test_cnn(tmp_path, model_class, share_features_extractor):
|
|
SAVE_NAME = "cnn_model.zip"
|
|
# Fake grayscale with frameskip
|
|
# Atari after preprocessing: 84x84x1, here we are using lower resolution
|
|
# to check that the network handle it automatically
|
|
env = FakeImageEnv(screen_height=40, screen_width=40, n_channels=1, discrete=model_class not in {SAC, TD3})
|
|
if model_class in {A2C, PPO}:
|
|
kwargs = dict(
|
|
n_steps=64,
|
|
policy_kwargs=dict(
|
|
share_features_extractor=share_features_extractor,
|
|
),
|
|
)
|
|
else:
|
|
# share_features_extractor is checked later for offpolicy algorithms
|
|
if share_features_extractor:
|
|
return
|
|
|
|
# Avoid memory error when using replay buffer
|
|
# Reduce the size of the features
|
|
kwargs = dict(
|
|
buffer_size=250,
|
|
policy_kwargs=dict(features_extractor_kwargs=dict(features_dim=32)),
|
|
seed=1,
|
|
)
|
|
model = model_class("CnnPolicy", env, **kwargs).learn(250)
|
|
|
|
# FakeImageEnv is channel last by default and should be wrapped
|
|
assert is_vecenv_wrapped(model.get_env(), VecTransposeImage)
|
|
|
|
obs, _ = env.reset()
|
|
|
|
# Test stochastic predict with channel last input
|
|
if model_class == DQN:
|
|
model.exploration_rate = 0.9
|
|
|
|
for _ in range(10):
|
|
model.predict(obs, deterministic=False)
|
|
|
|
action, _ = model.predict(obs, deterministic=True)
|
|
|
|
model.save(tmp_path / SAVE_NAME)
|
|
del model
|
|
|
|
model = model_class.load(tmp_path / SAVE_NAME)
|
|
|
|
# Check that the prediction is the same
|
|
assert np.allclose(action, model.predict(obs, deterministic=True)[0])
|
|
|
|
os.remove(str(tmp_path / SAVE_NAME))
|
|
|
|
|
|
@pytest.mark.parametrize("model_class", [A2C])
|
|
def test_vec_transpose_skip(tmp_path, model_class):
|
|
# Fake grayscale with frameskip
|
|
env = FakeImageEnv(
|
|
screen_height=41, screen_width=40, n_channels=10, discrete=model_class not in {SAC, TD3}, channel_first=True
|
|
)
|
|
env = DummyVecEnv([lambda: env])
|
|
# Stack 5 frames so the observation is now (50, 40, 40) but the env is still channel first
|
|
env = VecFrameStack(env, 5, channels_order="first")
|
|
obs_shape_before = env.reset().shape
|
|
# The observation space should be different as the heuristic thinks it is channel last
|
|
assert not np.allclose(obs_shape_before, VecTransposeImage(env).reset().shape)
|
|
env = VecTransposeImage(env, skip=True)
|
|
# The observation space should be the same as we skip the VecTransposeImage
|
|
assert np.allclose(obs_shape_before, env.reset().shape)
|
|
|
|
kwargs = dict(
|
|
n_steps=64,
|
|
policy_kwargs=dict(features_extractor_kwargs=dict(features_dim=32)),
|
|
seed=1,
|
|
)
|
|
model = model_class("CnnPolicy", env, **kwargs).learn(250)
|
|
|
|
obs = env.reset()
|
|
action, _ = model.predict(obs, deterministic=True)
|
|
|
|
|
|
def patch_dqn_names_(model):
|
|
# Small hack to make the test work with DQN
|
|
if isinstance(model, DQN):
|
|
model.critic = model.q_net
|
|
model.critic_target = model.q_net_target
|
|
|
|
|
|
def params_should_match(params, other_params):
|
|
for param, other_param in zip_strict(params, other_params):
|
|
assert th.allclose(param, other_param)
|
|
|
|
|
|
def params_should_differ(params, other_params):
|
|
for param, other_param in zip_strict(params, other_params):
|
|
assert not th.allclose(param, other_param)
|
|
|
|
|
|
def check_td3_feature_extractor_match(model):
|
|
for (key, actor_param), critic_param in zip(model.actor_target.named_parameters(), model.critic_target.parameters()):
|
|
if "features_extractor" in key:
|
|
assert th.allclose(actor_param, critic_param), key
|
|
|
|
|
|
def check_td3_feature_extractor_differ(model):
|
|
for (key, actor_param), critic_param in zip(model.actor_target.named_parameters(), model.critic_target.parameters()):
|
|
if "features_extractor" in key:
|
|
assert not th.allclose(actor_param, critic_param), key
|
|
|
|
|
|
@pytest.mark.parametrize("model_class", [SAC, TD3, DQN])
|
|
@pytest.mark.parametrize("share_features_extractor", [True, False])
|
|
def test_features_extractor_target_net(model_class, share_features_extractor):
|
|
if model_class == DQN and share_features_extractor:
|
|
pytest.skip()
|
|
|
|
env = FakeImageEnv(screen_height=40, screen_width=40, n_channels=1, discrete=model_class not in {SAC, TD3})
|
|
# Avoid memory error when using replay buffer
|
|
# Reduce the size of the features
|
|
kwargs = dict(buffer_size=250, learning_starts=100, policy_kwargs=dict(features_extractor_kwargs=dict(features_dim=32)))
|
|
if model_class != DQN:
|
|
kwargs["policy_kwargs"]["share_features_extractor"] = share_features_extractor
|
|
|
|
# No delay for TD3 (changes when the actor and polyak update take place)
|
|
if model_class == TD3:
|
|
kwargs["policy_delay"] = 1
|
|
|
|
model = model_class("CnnPolicy", env, seed=0, **kwargs)
|
|
|
|
patch_dqn_names_(model)
|
|
|
|
if share_features_extractor:
|
|
# Check that the objects are the same and not just copied
|
|
assert id(model.policy.actor.features_extractor) == id(model.policy.critic.features_extractor)
|
|
if model_class == TD3:
|
|
assert id(model.policy.actor_target.features_extractor) == id(model.policy.critic_target.features_extractor)
|
|
# Actor and critic features extractor should be the same
|
|
td3_features_extractor_check = check_td3_feature_extractor_match
|
|
else:
|
|
# Actor and critic features extractor should differ same
|
|
td3_features_extractor_check = check_td3_feature_extractor_differ
|
|
# Check that the object differ
|
|
if model_class != DQN:
|
|
assert id(model.policy.actor.features_extractor) != id(model.policy.critic.features_extractor)
|
|
|
|
if model_class == TD3:
|
|
assert id(model.policy.actor_target.features_extractor) != id(model.policy.critic_target.features_extractor)
|
|
|
|
# Critic and target should be equal at the begginning of training
|
|
params_should_match(model.critic.parameters(), model.critic_target.parameters())
|
|
|
|
# TD3 has also a target actor net
|
|
if model_class == TD3:
|
|
params_should_match(model.actor.parameters(), model.actor_target.parameters())
|
|
|
|
model.learn(200)
|
|
|
|
# Critic and target should differ
|
|
params_should_differ(model.critic.parameters(), model.critic_target.parameters())
|
|
|
|
if model_class == TD3:
|
|
params_should_differ(model.actor.parameters(), model.actor_target.parameters())
|
|
td3_features_extractor_check(model)
|
|
|
|
# Re-initialize and collect some random data (without doing gradient steps,
|
|
# since 10 < learning_starts = 100)
|
|
model = model_class("CnnPolicy", env, seed=0, **kwargs).learn(10)
|
|
|
|
patch_dqn_names_(model)
|
|
|
|
original_param = deepcopy(list(model.critic.parameters()))
|
|
original_target_param = deepcopy(list(model.critic_target.parameters()))
|
|
if model_class == TD3:
|
|
original_actor_target_param = deepcopy(list(model.actor_target.parameters()))
|
|
|
|
# Deactivate copy to target
|
|
model.tau = 0.0
|
|
model.train(gradient_steps=1)
|
|
|
|
# Target should be the same
|
|
params_should_match(original_target_param, model.critic_target.parameters())
|
|
|
|
if model_class == TD3:
|
|
params_should_match(original_actor_target_param, model.actor_target.parameters())
|
|
td3_features_extractor_check(model)
|
|
|
|
# not the same for critic net (updated by gradient descent)
|
|
params_should_differ(original_param, model.critic.parameters())
|
|
|
|
# Update the reference as it should not change in the next step
|
|
original_param = deepcopy(list(model.critic.parameters()))
|
|
|
|
if model_class == TD3:
|
|
original_actor_param = deepcopy(list(model.actor.parameters()))
|
|
|
|
# Deactivate learning rate
|
|
model.lr_schedule = lambda _: 0.0
|
|
# Re-activate polyak update
|
|
model.tau = 0.01
|
|
# Special case for DQN: target net is updated in the `collect_rollouts()`
|
|
# not the `train()` method
|
|
if model_class == DQN:
|
|
model.target_update_interval = 1
|
|
model._on_step()
|
|
|
|
model.train(gradient_steps=1)
|
|
|
|
# Target should have changed now (due to polyak update)
|
|
params_should_differ(original_target_param, model.critic_target.parameters())
|
|
|
|
# Critic should be the same
|
|
params_should_match(original_param, model.critic.parameters())
|
|
|
|
if model_class == TD3:
|
|
params_should_differ(original_actor_target_param, model.actor_target.parameters())
|
|
|
|
params_should_match(original_actor_param, model.actor.parameters())
|
|
|
|
td3_features_extractor_check(model)
|
|
|
|
|
|
def test_channel_first_env(tmp_path):
|
|
# test_cnn uses environment with HxWxC setup that is transposed, but we
|
|
# also want to work with CxHxW envs directly without transposing wrapper.
|
|
SAVE_NAME = "cnn_model.zip"
|
|
|
|
# Create environment with transposed images (CxHxW).
|
|
# If underlying CNN processes the data in wrong format,
|
|
# it will raise an error of negative dimension sizes while creating convolutions
|
|
env = FakeImageEnv(screen_height=40, screen_width=40, n_channels=1, discrete=True, channel_first=True)
|
|
|
|
model = A2C("CnnPolicy", env, n_steps=100).learn(250)
|
|
|
|
assert not is_vecenv_wrapped(model.get_env(), VecTransposeImage)
|
|
|
|
obs, _ = env.reset()
|
|
|
|
action, _ = model.predict(obs, deterministic=True)
|
|
|
|
model.save(tmp_path / SAVE_NAME)
|
|
del model
|
|
|
|
model = A2C.load(tmp_path / SAVE_NAME)
|
|
|
|
# Check that the prediction is the same
|
|
assert np.allclose(action, model.predict(obs, deterministic=True)[0])
|
|
|
|
os.remove(str(tmp_path / SAVE_NAME))
|
|
|
|
|
|
def test_image_space_checks():
|
|
not_image_space = spaces.Box(0, 1, shape=(10,))
|
|
assert not is_image_space(not_image_space)
|
|
|
|
# Not uint8
|
|
not_image_space = spaces.Box(0, 255, shape=(10, 10, 3))
|
|
assert not is_image_space(not_image_space)
|
|
|
|
# Not correct shape
|
|
not_image_space = spaces.Box(0, 255, shape=(10, 10), dtype=np.uint8)
|
|
assert not is_image_space(not_image_space)
|
|
|
|
# Not correct low/high
|
|
not_image_space = spaces.Box(0, 10, shape=(10, 10, 3), dtype=np.uint8)
|
|
assert not is_image_space(not_image_space)
|
|
|
|
# Deactivate dtype and bound checking
|
|
normalized_image = spaces.Box(0, 1, shape=(10, 10, 3), dtype=np.float32)
|
|
assert is_image_space(normalized_image, normalized_image=True)
|
|
|
|
# Not correct space
|
|
not_image_space = spaces.Discrete(n=10)
|
|
assert not is_image_space(not_image_space)
|
|
|
|
an_image_space = spaces.Box(0, 255, shape=(10, 10, 3), dtype=np.uint8)
|
|
assert is_image_space(an_image_space, check_channels=False)
|
|
assert is_image_space(an_image_space, check_channels=True)
|
|
|
|
channel_first_image_space = spaces.Box(0, 255, shape=(3, 10, 10), dtype=np.uint8)
|
|
assert is_image_space(channel_first_image_space, check_channels=False)
|
|
assert is_image_space(channel_first_image_space, check_channels=True)
|
|
|
|
an_image_space_with_odd_channels = spaces.Box(0, 255, shape=(10, 10, 5), dtype=np.uint8)
|
|
assert is_image_space(an_image_space_with_odd_channels)
|
|
# Should not pass if we check if channels are valid for an image
|
|
assert not is_image_space(an_image_space_with_odd_channels, check_channels=True)
|
|
|
|
# Test if channel-check works
|
|
channel_first_space = spaces.Box(0, 255, shape=(3, 10, 10), dtype=np.uint8)
|
|
assert is_image_space_channels_first(channel_first_space)
|
|
|
|
channel_last_space = spaces.Box(0, 255, shape=(10, 10, 3), dtype=np.uint8)
|
|
assert not is_image_space_channels_first(channel_last_space)
|
|
|
|
channel_mid_space = spaces.Box(0, 255, shape=(10, 3, 10), dtype=np.uint8)
|
|
# Should raise a warning
|
|
with pytest.warns(Warning):
|
|
assert not is_image_space_channels_first(channel_mid_space)
|
|
|
|
|
|
@pytest.mark.parametrize("model_class", [A2C, PPO, DQN, SAC, TD3])
|
|
@pytest.mark.parametrize("normalize_images", [True, False])
|
|
def test_image_like_input(model_class, normalize_images):
|
|
"""
|
|
Check that we can handle image-like input (3D tensor)
|
|
when normalize_images=False
|
|
"""
|
|
# Fake grayscale with frameskip
|
|
# Atari after preprocessing: 84x84x1, here we are using lower resolution
|
|
# to check that the network handle it automatically
|
|
env = FakeImageEnv(
|
|
screen_height=36,
|
|
screen_width=36,
|
|
n_channels=1,
|
|
channel_first=True,
|
|
discrete=model_class not in {SAC, TD3},
|
|
)
|
|
vec_env = VecNormalize(DummyVecEnv([lambda: env]))
|
|
# Reduce the size of the features
|
|
# deactivate normalization
|
|
kwargs = dict(
|
|
policy_kwargs=dict(
|
|
normalize_images=normalize_images,
|
|
features_extractor_kwargs=dict(features_dim=32),
|
|
),
|
|
seed=1,
|
|
)
|
|
|
|
if model_class in {A2C, PPO}:
|
|
kwargs.update(dict(n_steps=64))
|
|
else:
|
|
# Avoid memory error when using replay buffer
|
|
# Reduce the size of the features
|
|
kwargs.update(dict(buffer_size=250))
|
|
if normalize_images:
|
|
with pytest.raises(AssertionError):
|
|
model_class("CnnPolicy", vec_env, **kwargs).learn(128)
|
|
else:
|
|
model_class("CnnPolicy", vec_env, **kwargs).learn(128)
|