System info helper (#613)

* Add `system_env_info`

* Add `print_system_info` to load
and store system info at save time

* Remove TODO

* Rename to `get_system_info`

* Import as sb3 for consistency

* Update changelog

* Add warning for old SB3 versions

* Use underscore litteral for more clarity
This commit is contained in:
Antonin RAFFIN 2021-10-18 10:43:56 +02:00 committed by GitHub
parent 09e9fc42eb
commit 1564a85081
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 111 additions and 18 deletions

View file

@ -50,6 +50,12 @@ Describe the characteristic of your environment:
* Gym version
* Versions of any other relevant libraries
You can use `sb3.get_system_info()` to print relevant packages info:
```python
import stable_baselines3 as sb3
sb3.get_system_info()
```
### Additional context
Add any other context about the problem here.

View file

@ -78,6 +78,12 @@ Describe the characteristic of your environment:
* Gym version
* Versions of any other relevant libraries
You can use `sb3.get_system_info()` to print relevant packages info:
```python
import stable_baselines3 as sb3
sb3.get_system_info()
```
### Additional context
Add any other context about the problem here.

View file

@ -81,6 +81,9 @@ In the following example, we will train, save and load a DQN model on the Lunar
del model # delete trained model to demonstrate loading
# Load the trained agent
# NOTE: if you have loading issue, you can pass `print_system_info=True`
# to compare the system on which the model was trained vs the current one
# model = DQN.load("dqn_lunar", env=env, print_system_info=True)
model = DQN.load("dqn_lunar", env=env)
# Evaluate the agent

View file

@ -30,8 +30,15 @@ inspecting stored objects without deserializing the object itself.
This format allows skipping elements in the file, i.e. we can skip deserializing objects that are
broken/non-serializable.
This can be done via ``custom_objects`` argument to load functions.
.. This can be done via ``custom_objects`` argument to load functions.
.. note::
If you encounter loading issue, for instance pickle issues or error after loading
(see `#171 <https://github.com/DLR-RM/stable-baselines3/issues/171>`_ or `#573 <https://github.com/DLR-RM/stable-baselines3/issues/573>`_),
you can pass ``print_system_info=True``
to compare the system on which the model was trained vs the current one
``model = PPO.load("ppo_saved", print_system_info=True)``
File structure:
@ -44,6 +51,7 @@ File structure:
├── policy.pth PyTorch state dictionary of the policy saved
├── pytorch_variables.pth Additional PyTorch variables
├── _stable_baselines3_version contains the SB3 version with which the model was saved
├── system_info.txt contains system info (os, python version, ...) on which the model was saved
Pros:

View file

@ -4,10 +4,9 @@ Changelog
==========
Release 1.2.1a3 (WIP)
Release 1.2.1a4 (WIP)
---------------------------
Breaking Changes:
^^^^^^^^^^^^^^^^^
- ``sde_net_arch`` argument in policies is deprecated and will be removed in a future version.
@ -22,6 +21,8 @@ New Features:
^^^^^^^^^^^^^
- Added methods ``get_distribution`` and ``predict_values`` for ``ActorCriticPolicy`` for A2C/PPO/TRPO (@cyprienc)
- Added methods ``forward_actor`` and ``forward_critic`` for ``MlpExtractor``
- Added ``sb3.get_system_info()`` helper function to gather version information relevant to SB3 (e.g., Python and PyTorch version)
- Saved models now store system information where agent was trained, and load functions have ``print_system_info`` parameter to help debugging load issues.
Bug Fixes:
^^^^^^^^^^

View file

@ -1,6 +1,7 @@
import os
from stable_baselines3.a2c import A2C
from stable_baselines3.common.utils import get_system_info
from stable_baselines3.ddpg import DDPG
from stable_baselines3.dqn import DQN
from stable_baselines3.her.her_replay_buffer import HerReplayBuffer

View file

@ -25,6 +25,7 @@ from stable_baselines3.common.utils import (
check_for_correct_spaces,
get_device,
get_schedule_fn,
get_system_info,
set_random_seed,
update_learning_rate,
)
@ -634,6 +635,7 @@ class BaseAlgorithm(ABC):
env: Optional[GymEnv] = None,
device: Union[th.device, str] = "auto",
custom_objects: Optional[Dict[str, Any]] = None,
print_system_info: bool = False,
**kwargs,
) -> "BaseAlgorithm":
"""
@ -650,9 +652,17 @@ class BaseAlgorithm(ABC):
will be used instead. Similar to custom_objects in
``keras.models.load_model``. Useful when you have an object in
file that can not be deserialized.
:param print_system_info: Whether to print system info from the saved model
and the current system info (useful to debug loading issues)
:param kwargs: extra arguments to change the model when loading
"""
data, params, pytorch_variables = load_from_zip_file(path, device=device, custom_objects=custom_objects)
if print_system_info:
print("== CURRENT SYSTEM INFO ==")
get_system_info()
data, params, pytorch_variables = load_from_zip_file(
path, device=device, custom_objects=custom_objects, print_system_info=print_system_info
)
# Remove stored device information and replace with ours
if "policy_kwargs" in data:

View file

@ -79,7 +79,7 @@ class OffPolicyAlgorithm(BaseAlgorithm):
env: Union[GymEnv, str],
policy_base: Type[BasePolicy],
learning_rate: Union[float, Schedule],
buffer_size: int = 1000000, # 1e6
buffer_size: int = 1_000_000, # 1e6
learning_starts: int = 100,
batch_size: int = 256,
tau: float = 0.005,

View file

@ -16,9 +16,9 @@ from typing import Any, Dict, Optional, Tuple, Union
import cloudpickle
import torch as th
import stable_baselines3
import stable_baselines3 as sb3
from stable_baselines3.common.type_aliases import TensorDict
from stable_baselines3.common.utils import get_device
from stable_baselines3.common.utils import get_device, get_system_info
def recursive_getattr(obj: Any, attr: str, *args) -> Any:
@ -321,7 +321,9 @@ def save_to_zip_file(
with archive.open(file_name + ".pth", mode="w") as param_file:
th.save(dict_, param_file)
# Save metadata: library version when file was saved
archive.writestr("_stable_baselines3_version", stable_baselines3.__version__)
archive.writestr("_stable_baselines3_version", sb3.__version__)
# Save system info about the current python env
archive.writestr("system_info.txt", get_system_info(print_info=False)[1])
def save_to_pkl(path: Union[str, pathlib.Path, io.BufferedIOBase], obj: Any, verbose: int = 0) -> None:
@ -362,6 +364,7 @@ def load_from_zip_file(
custom_objects: Optional[Dict[str, Any]] = None,
device: Union[th.device, str] = "auto",
verbose: int = 0,
print_system_info: bool = False,
) -> (Tuple[Optional[Dict[str, Any]], Optional[TensorDict], Optional[TensorDict]]):
"""
Load model data from a .zip archive
@ -376,6 +379,9 @@ def load_from_zip_file(
``keras.models.load_model``. Useful when you have an object in
file that can not be deserialized.
:param device: Device on which the code should run.
:param verbose: Verbosity level, 0 means only warnings, 2 means debug information.
:param print_system_info: Whether to print or not the system info
about the saved model.
:return: Class parameters, model state_dicts (aka "params", dict of state_dict)
and dict of pytorch variables
"""
@ -395,6 +401,17 @@ def load_from_zip_file(
pytorch_variables = None
params = {}
# Debug system info first
if print_system_info:
if "system_info.txt" in namelist:
print("== SAVED MODEL SYSTEM INFO ==")
print(archive.read("system_info.txt").decode())
else:
warnings.warn(
"The model was saved with SB3 <= 1.2.0 and thus cannot print system information.",
UserWarning,
)
if "data" in namelist and load_data:
# Load class parameters that are stored
# with either JSON or pickle (not PyTorch variables).

View file

@ -1,14 +1,17 @@
import glob
import os
import platform
import random
from collections import deque
from itertools import zip_longest
from typing import Dict, Iterable, Optional, Union
from typing import Dict, Iterable, Optional, Tuple, Union
import gym
import numpy as np
import torch as th
import stable_baselines3 as sb3
# Check if tensorboard is available for pytorch
try:
from torch.utils.tensorboard import SummaryWriter
@ -460,3 +463,28 @@ def should_collect_more_steps(
"The unit of the `train_freq` must be either TrainFrequencyUnit.STEP "
f"or TrainFrequencyUnit.EPISODE not '{train_freq.unit}'!"
)
def get_system_info(print_info: bool = True) -> Tuple[Dict[str, str], str]:
"""
Retrieve system and python env info for the current system.
:param print_info: Whether to print or not those infos
:return: Dictionary summing up the version for each relevant package
and a formatted string.
"""
env_info = {
"OS": f"{platform.platform()} {platform.version()}",
"Python": platform.python_version(),
"Stable-Baselines3": sb3.__version__,
"PyTorch": th.__version__,
"GPU Enabled": str(th.cuda.is_available()),
"Numpy": np.__version__,
"Gym": gym.__version__,
}
env_info_str = ""
for key, value in env_info.items():
env_info_str += f"{key}: {value}\n"
if print_info:
print(env_info_str)
return env_info, env_info_str

View file

@ -58,7 +58,7 @@ class DDPG(TD3):
policy: Union[str, Type[TD3Policy]],
env: Union[GymEnv, str],
learning_rate: Union[float, Schedule] = 1e-3,
buffer_size: int = 1000000, # 1e6
buffer_size: int = 1_000_000, # 1e6
learning_starts: int = 100,
batch_size: int = 100,
tau: float = 0.005,

View file

@ -63,7 +63,7 @@ class DQN(OffPolicyAlgorithm):
policy: Union[str, Type[DQNPolicy]],
env: Union[GymEnv, str],
learning_rate: Union[float, Schedule] = 1e-4,
buffer_size: int = 1000000, # 1e6
buffer_size: int = 1_000_000, # 1e6
learning_starts: int = 50000,
batch_size: Optional[int] = 32,
tau: float = 1.0,

View file

@ -193,8 +193,6 @@ class PPO(OnPolicyAlgorithm):
actions = rollout_data.actions.long().flatten()
# Re-sample the noise matrix because the log_std has changed
# TODO: investigate why there is no issue with the gradient
# if that line is commented (as in SAC)
if self.use_sde:
self.policy.reset_noise(self.batch_size)

View file

@ -77,7 +77,7 @@ class SAC(OffPolicyAlgorithm):
policy: Union[str, Type[SACPolicy]],
env: Union[GymEnv, str],
learning_rate: Union[float, Schedule] = 3e-4,
buffer_size: int = 1000000, # 1e6
buffer_size: int = 1_000_000, # 1e6
learning_starts: int = 100,
batch_size: int = 256,
tau: float = 0.005,

View file

@ -65,7 +65,7 @@ class TD3(OffPolicyAlgorithm):
policy: Union[str, Type[TD3Policy]],
env: Union[GymEnv, str],
learning_rate: Union[float, Schedule] = 1e-3,
buffer_size: int = 1000000, # 1e6
buffer_size: int = 1_000_000, # 1e6
learning_starts: int = 100,
batch_size: int = 100,
tau: float = 0.005,

View file

@ -1 +1 @@
1.2.1a3
1.2.1a4

View file

@ -222,7 +222,11 @@ def test_exclude_include_saved_params(tmp_path, model_class):
del model
# Load with custom objects
custom_objects = dict(learning_rate=2e-5, dummy=1.0)
model = model_class.load(str(tmp_path / "test_save.zip"), custom_objects=custom_objects)
model = model_class.load(
str(tmp_path / "test_save.zip"),
custom_objects=custom_objects,
print_system_info=True,
)
assert model.verbose == 2
# Check that the custom object was taken into account
assert model.learning_rate == custom_objects["learning_rate"]

View file

@ -6,13 +6,14 @@ import numpy as np
import pytest
import torch as th
import stable_baselines3 as sb3
from stable_baselines3 import A2C, PPO
from stable_baselines3.common.atari_wrappers import ClipRewardEnv, MaxAndSkipEnv
from stable_baselines3.common.env_util import is_wrapped, make_atari_env, make_vec_env, unwrap_wrapper
from stable_baselines3.common.evaluation import evaluate_policy
from stable_baselines3.common.monitor import Monitor
from stable_baselines3.common.noise import ActionNoise, OrnsteinUhlenbeckActionNoise, VectorizedActionNoise
from stable_baselines3.common.utils import polyak_update, zip_strict
from stable_baselines3.common.utils import get_system_info, polyak_update, zip_strict
from stable_baselines3.common.vec_env import DummyVecEnv, SubprocVecEnv
@ -376,3 +377,13 @@ def test_ppo_warnings():
# Truncated mini-batch
with pytest.warns(UserWarning):
PPO("MlpPolicy", "Pendulum-v0", n_steps=6, batch_size=8)
def test_get_system_info():
info, info_str = get_system_info(print_info=True)
assert info["Stable-Baselines3"] == str(sb3.__version__)
assert "Python" in info_str
assert "PyTorch" in info_str
assert "GPU Enabled" in info_str
assert "Numpy" in info_str
assert "Gym" in info_str