File: README.md
[!NOTE]
Tianshou version 2 is here!We have released the new major version of Tianshou on PyPI.
Version 2 is a complete overhaul of the software design of the procedural API, in which
- we establish a clear separation between learning algorithms and policies (via the separate abstractions
AlgorithmandPolicy).- we provide more well-defined, more usable interfaces with extensive documentation of all algorithm and trainer parameters,
renaming some parameters to make their names more consistent and intuitive.- the class hierarchy is fully revised, establishing a clear separation between on-policy, off-policy and offline algorithms
at the type level and ensuring that all inheritance relationships are meaningful.Because of the extent of the changes, this version is not backwards compatible with previous versions of Tianshou.
For migration information, please see the change log.
Tianshou (天授) is a reinforcement learning (RL) library based on pure PyTorch and Gymnasium. Tianshou's main features at a glance are:
- Modular low-level interfaces for algorithm developers (RL researchers) that are both flexible, hackable and type-safe.
- Convenient high-level interfaces for applications of RL (training an implemented algorithm on a custom environment).
- Large scope: online (on- and off-policy) and offline RL, experimental support for multi-agent RL (MARL), experimental support for model-based RL, and more
Unlike other reinforcement learning libraries, which may have complex codebases,
unfriendly high-level APIs, or are not optimized for speed, Tianshou provides a high-performance, modularized framework
and user-friendly interfaces for building deep reinforcement learning agents. One more aspect that sets Tianshou apart is its
generality: it supports online and offline RL, multi-agent RL, and model-based algorithms.
Tianshou aims at enabling concise implementations, both for researchers and practitioners, without sacrificing flexibility.
Supported algorithms include:
- Deep Q-Network (DQN)
- Double DQN
- Dueling DQN
- Branching DQN
- Categorical DQN (C51)
- Rainbow DQN (Rainbow)
- Quantile Regression DQN (QRDQN)
- Implicit Quantile Network (IQN)
- Fully-parameterized Quantile Function (FQF)
- Policy Gradient (PG)
- Natural Policy Gradient (NPG)
- Advantage Actor-Critic (A2C)
- Trust Region Policy Optimization (TRPO)
- Proximal Policy Optimization (PPO)
- Deep Deterministic Policy Gradient (DDPG)
- Twin Delayed DDPG (TD3)
- Soft Actor-Critic (SAC)
- Randomized Ensembled Double Q-Learning (REDQ)
- Discrete Soft Actor-Critic (SAC-Discrete)
- Vanilla Imitation Learning
- Batch-Constrained deep Q-Learning (BCQ)
- Conservative Q-Learning (CQL)
- Twin Delayed DDPG with Behavior Cloning (TD3+BC)
- Discrete Batch-Constrained deep Q-Learning (BCQ-Discrete)
- Discrete Conservative Q-Learning (CQL-Discrete)
- Discrete Critic Regularized Regression (CRR-Discrete)
- Generative Adversarial Imitation Learning (GAIL)
- Prioritized Experience Replay (PER)
- Generalized Advantage Estimator (GAE)
- Posterior Sampling Reinforcement Learning (PSRL)
- Intrinsic Curiosity Module (ICM)
- Hindsight Experience Replay (HER)
Other noteworthy features:
- Elegant framework with dual APIs:
- Tianshou's high-level API maximizes ease of use for application development while still retaining a high degree
of flexibility. - The fundamental procedural API provides a maximum of flexibility for algorithm development without being
overly verbose.
- Tianshou's high-level API maximizes ease of use for application development while still retaining a high degree
- State-of-the-art results in MuJoCo benchmarks for REINFORCE/A2C/TRPO/PPO/DDPG/TD3/SAC algorithms
- Support for vectorized environments (synchronous or asynchronous) for all algorithms (see usage)
- Support for super-fast vectorized environments based on EnvPool for all algorithms (see usage)
- Support for recurrent state representations in actor networks and critic networks (RNN-style training for POMDPs) (see usage)
- Support any type of environment state/action (e.g. a dict, a self-defined class, ...) Usage
- Support for customized training processes (see usage)
- Support n-step returns estimation and prioritized experience replay for all Q-learning based algorithms; GAE, nstep and PER are highly optimized thanks to numba's just-in-time compilation and vectorized numpy operations
- Support for multi-agent RL (see usage)
- Support for logging based on both TensorBoard and W&B
- Support for multi-GPU training (see usage)
- Comprehensive documentation, PEP8 code-style checking, type checking and thorough tests
In Chinese, Tianshou means divinely ordained, being derived to the gift of being born.
Tianshou is a reinforcement learning platform, and the nature of RL is not learn from humans.
So taking "Tianshou" means that there is no teacher to learn from, but rather to learn by oneself through constant interaction with the environment.
“天授”意指上天所授,引申为与生具有的天赋。天授是强化学习平台,而强化学习算法并不是向人类学习的,所以取“天授”意思是没有老师来教,而是自己通过跟环境不断交互来进行学习。
Installation
Tianshou is currently hosted on PyPI and conda-forge. It requires Python >= 3.11.
For installing the most recent version of Tianshou, the best way is clone the repository and install it with poetry
(which you need to install on your system first)
git clone [email protected]:thu-ml/tianshou.git
cd tianshou
poetry installYou can also install the dev requirements by adding --with dev or the extras
for say mujoco and acceleration by envpool
by adding --extras "mujoco envpool"
If you wish to install multiple extras, ensure that you include them in a single command. Sequential calls to poetry install --extras xxx will overwrite prior installations, leaving only the last specified extras installed.
Or you may install all the following extras by adding --all-extras.
Available extras are:
atari(for Atari environments)box2d(for Box2D environments)classic_control(for classic control (discrete) environments)mujoco(for MuJoCo environments)mujoco-py(for legacy mujoco-py environments[^1])pybullet(for pybullet environments)robotics(for gymnasium-robotics environments)vizdoom(for ViZDoom environments)envpool(for envpool integration)argparse(in order to be able to run the high level API examples)
[^1]:
mujoco-py is a legacy package and is not recommended for new projects.
It is only included for compatibility with older projects.
Also note that there may be compatibility issues with macOS newer than
Monterey.
Otherwise, you can install the latest release from PyPI (currently
far behind the master) with the following command:
$ pip install tianshouIf you are using Anaconda or Miniconda, you can install Tianshou from conda-forge:
$ conda install tianshou -c conda-forgeAlternatively to the poetry install, you can also install the latest source version through GitHub:
$ pip install git+https://github.com/thu-ml/tianshou.git@master --upgradeFinally, you may check the installation via your Python console as follows:
import tianshou
print(tianshou.__version__)If no errors are reported, you have successfully installed Tianshou.
Documentation
Find example scripts in the test/ and examples/ folders.
Tutorials and API documentation are hosted on tianshou.readthedocs.io.
Why Tianshou?
Comprehensive Functionality
High Software Engineering Standards
| RL Platform | Documentation | Code Coverage | Type Hints | Last Update |
|---|---|---|---|---|
| Stable-Baselines3 | :heavy_check_mark: | |||
| Ray/RLlib | :heavy_minus_sign:(1) | :heavy_check_mark: | ||
| SpinningUp | :x: | :x: | ||
| Dopamine | :x: | :x: | ||
| ACME | :heavy_minus_sign:(1) | :heavy_check_mark: | ||
| Sample Factory | :heavy_minus_sign: | :x: | ||
| Tianshou | :heavy_check_mark: |
(1): it has continuous integration but the coverage rate is not available
Reproducible, High-Quality Results
Tianshou is rigorously tested. In contrast to other RL platforms, our tests include the full agent training procedure for all of the implemented algorithms. Our tests would fail once if any of the agents failed to achieve a consistent level of performance on limited epochs.
Our tests thus ensure reproducibility.
Check out the GitHub Actions page for more detail.
Atari and MuJoCo benchmark results can be found in the examples/atari/ and examples/mujoco/ folders respectively. Our MuJoCo results reach or exceed the level of performance of most existing benchmarks.
Algorithm Abstraction
Reinforcement learning algorithms are build on abstractions for
- on-policy algorithms (
OnPolicyAlgorithm), - off-policy algorithms (
OffPolicyAlgorithm), and - offline algorithms (
OfflineAlgorithm),
all of which clearly separate the core algorithm from the training process and the respective environment interactions.
In each case, the implementation of an algorithm necessarily involves only the implementation of methods for
- pre-processing a batch of data, augmenting it with necessary information/sufficient statistics for learning (
_preprocess_batch), - updating model parameters based on an augmented batch of data (
_update_with_batch).
The implementation of these methods suffices for a new algorithm to be applicable within Tianshou,
making experimentation with new approaches particularly straightforward.
Quick Start
Tianshou provides two API levels:
- the high-level interface, which provides ease of use for end users seeking to run deep reinforcement learning applications
- the procedural interface, which provides a maximum of control, especially for very advanced users and developers of reinforcement learning algorithms.
In the following, let us consider an example application using the CartPole gymnasium environment.
We shall apply the deep Q-network (DQN) learning algorithm using both APIs.
High-Level API
In the high-level API, the basis for an RL experiment is an ExperimentBuilder
with which we can build the experiment we then seek to run.
Since we want to use DQN, we use the specialization DQNExperimentBuilder.
The high-level API provides largely declarative semantics, i.e. the code is
almost exclusively concerned with configuration that controls what to do
(rather than how to do it).
from tianshou.highlevel.config import OffPolicyTrainingConfig
from tianshou.highlevel.env import (
EnvFactoryRegistered,
VectorEnvType,
)
from tianshou.highlevel.experiment import DQNExperimentBuilder, ExperimentConfig
from tianshou.highlevel.params.algorithm_params import DQNParams
from tianshou.highlevel.trainer import (
EpochStopCallbackRewardThreshold,
)
experiment = (
DQNExperimentBuilder(
EnvFactoryRegistered(
task="CartPole-v1",
venv_type=VectorEnvType.DUMMY,
training_seed=0,
test_seed=10,
),
ExperimentConfig(
persistence_enabled=False,
watch=True,
watch_render=1 / 35,
watch_num_episodes=100,
),
OffPolicyTrainingConfig(
max_epochs=10,
epoch_num_steps=10000,
batch_size=64,
num_training_envs=10,
num_test_envs=100,
buffer_size=20000,
collection_step_num_env_steps=10,
update_step_num_gradient_steps_per_sample=1 / 10,
),
)
.with_dqn_params(
DQNParams(
lr=1e-3,
gamma=0.9,
n_step_return_horizon=3,
target_update_freq=320,
eps_training=0.3,
eps_inference=0.0,
),
)
.with_model_factory_default(hidden_sizes=(64, 64))
.with_epoch_stop_callback(EpochStopCallbackRewardThreshold(195))
.build()
)
experiment.run()The experiment builder takes three arguments:
- the environment factory for the creation of environments. In this case,
we use an existing factory implementation for gymnasium environments. - the experiment configuration, which controls persistence and the overall
experiment flow. In this case, we have configured that we want to observe
the agent's behavior after it is trained (watch=True) for a number of
episodes (watch_num_episodes=100). We have disabled persistence, because
we do not want to save training logs, the agent or its configuration for
future use. - the training configuration, which controls fundamental training parameters,
such as the total number of epochs we run the experiment for (num_epochs=10)
and the number of environment steps each epoch shall consist of
(epoch_num_steps=10000).
Every epoch consists of a series of data collection (rollout) steps and
training steps.
The parametercollection_step_num_env_stepscontrols the amount of data that is
collected in each collection step and after each collection step, we
perform a training step, applying a gradient-based update based on a sample
of data (batch_size=64) taken from the buffer of data that has been
collected. For further details, see the documentation of configuration class.
We then proceed to configure some of the parameters of the DQN algorithm itself:
For instance, we control the epsilon parameter for exploration.
We want to use random exploration during rollouts for training (eps_training),
but we don't when evaluating the agent's performance in the test environments
(eps_inference).
Furthermore, we configure model parameters of the network for the Q function,
parametrising the number of hidden layers of the default MLP factory.
Find the script in examples/discrete/discrete_dqn_hl.py.
Here's a run (with the training time cut short):
Find many further applications of the high-level API in the examples/ folder;
look for scripts ending with _hl.py.
Note that most of these examples require the extra argparse
(install it by adding --extras argparse when invoking poetry).
Procedural API
Let us now consider an analogous example in the procedural API.
Find the full script in examples/discrete/discrete_dqn.py.
First, import the relevant packages:
import gymnasium as gym
import tianshou as ts
from tianshou.algorithm.modelfree.dqn import DiscreteQLearningPolicy
from tianshou.algorithm.optim import AdamOptimizerFactory
from tianshou.data import CollectStats
from tianshou.trainer import OffPolicyTrainerParams
from tianshou.utils.net.common import Net
from tianshou.utils.space_info import SpaceInfo
from torch.utils.tensorboard import SummaryWriterDefine hyper-parameters:
task = 'CartPole-v1'
lr, epoch, batch_size = 1e-3, 10, 64
num_training_envs, num_test_envs = 10, 100
gamma, n_step, target_freq = 0.9, 3, 320
buffer_size = 20000
eps_train, eps_test = 0.1, 0.05
epoch_num_steps, collection_step_num_env_steps = 10000, 10Initialize the logger:
logger = ts.utils.TensorboardLogger(SummaryWriter('log/dqn'))Create the environments:
# You can also try SubprocVectorEnv, which will use parallelization
training_envs = ts.env.DummyVectorEnv([lambda: gym.make(task) for _ in range(num_training_envs)])
test_envs = ts.env.DummyVectorEnv([lambda: gym.make(task) for _ in range(num_test_envs)])Create the network, policy, and algorithm:
# Create the network
# Note: You can easily define other networks.
# See https://tianshou.readthedocs.io/en/master/01_tutorials/00_dqn.html#build-the-network
env = gym.make(task, render_mode="human")
assert isinstance(env.action_space, gym.spaces.Discrete)
space_info = SpaceInfo.from_env(env)
state_shape = space_info.observation_info.obs_shape
action_shape = space_info.action_info.action_shape
net = Net(state_shape=state_shape, action_shape=action_shape, hidden_sizes=[128, 128, 128])
optim = AdamOptimizerFactory(lr=lr)
# Create the policy
policy = DiscreteQLearningPolicy(
model=net,
action_space=env.action_space,
eps_training=eps_train,
eps_inference=eps_test
)
# Create the algorithm with the policy and optimizer factory
algorithm = DQN(
policy=policy,
optim=AdamOptimizerFactory(lr=lr),
gamma=gamma,
n_step_return_horizon=n_step,
target_update_freq=target_freq
)Set up the collectors:
training_collector = ts.data.Collector[CollectStats](
algorithm,
training_envs,
ts.data.VectorReplayBuffer(buffer_size, num_training_envs),
exploration_noise=True,
)
test_collector = ts.data.Collector[CollectStats](
algorithm,
test_envs,
exploration_noise=True,
)Let's train the model using the algorithm:
result = algorithm.run_training(
OffPolicyTrainerParams(
training_collector=training_collector,
test_collector=test_collector,
max_epochs=epoch,
epoch_num_steps=epoch_num_steps,
collection_step_num_env_steps=collection_step_num_env_steps,
test_step_num_episodes=num_test_envs,
batch_size=batch_size,
update_step_num_gradient_steps_per_sample=1 / collection_step_num_env_steps,
stop_fn=lambda mean_rewards: mean_rewards >= env.spec.reward_threshold,
logger=logger,
test_in_training=True,
)
)
print(f"Finished training in {result.timing.total_time} seconds")This is how you could manually save/load the trained policy (it's exactly the same as loading a torch.nn.module):
torch.save(policy.state_dict(), 'dqn.pth')
policy.load_state_dict(torch.load('dqn.pth'))Now let's watch the agent with 35 FPS:
collector = ts.data.Collector(policy, env, exploration_noise=True)
collector.collect(n_episode=1, render=1 / 35)Inspect the data saved in TensorBoard:
$ tensorboard --logdir log/dqnPlease read the documentation for advanced usage.
Contributing
Tianshou is still under development.
Further algorithms and features are continuously being added, and we always welcome contributions to help make Tianshou better.
If you would like to contribute, please check out this link.
Citing Tianshou
If you find Tianshou useful, please cite it in your publications.
@article{tianshou,
author = {Jiayi Weng and Huayu Chen and Dong Yan and Kaichao You and Alexis Duburcq and Minghao Zhang and Yi Su and Hang Su and Jun Zhu},
title = {Tianshou: A Highly Modularized Deep Reinforcement Learning Library},
journal = {Journal of Machine Learning Research},
year = {2022},
volume = {23},
number = {267},
pages = {1--6},
url = {http://jmlr.org/papers/v23/21-1127.html}
}Acknowledgments
Tianshou is supported by appliedAI Institute for Europe,
a non-profit organization committed to providing long-term support and development.
Tianshou was previously a reinforcement learning platform based on TensorFlow. You can check out the branch priv for more detail. Many thanks to Haosheng Zou's pioneering work for Tianshou before version 0.1.1.
We would like to thank TSAIL and Institute for Artificial Intelligence, Tsinghua University for providing such an excellent AI research platform.
File: CHANGELOG.md
Release 2.0.1 (2026-04-02)
This is a maintenance release.
Dependencies:
pandascompatibility was adjusted to require version< 3(incompatibility detected) #1289
Fixes:
algorithm:TRPO: Fix original policy parameters not being restored upon line search failure #1287
highlevel.experiment:- Fix
Experiment.runreturningExperimentResultwithtrainer_resultunset (None) #1288
- Fix
Release 2.0.0 (2025-12-01)
This major release of Tianshou is a big step towards cleaner design and improved usability.
Given the large extent of the changes, it was not possible to maintain compatibility with the previous version.
- Persisted agents that were created with earlier versions cannot be loaded in v2.
- Source code from v1 can, however, be migrated to v2 with minimal effort.
See migration information below. For concrete examples, you may use git to diff individual
example scripts with the corresponding ones inv1.2.0.
This release is brought to you by Applied AI Institute gGmbH.
Developers:
- Dr. Dominik Jain (@opcode81)
- Michael Panchenko (@MischaPanch)
Runtime Environment Compatibility
Tianshou v2 is now compatible with
- Python 3.12 and Python 3.13 #1274
- newer versions of gymnasium (v1+) and numpy (v2+)
Our main test environment remains Python 3.11-based for the time being (see poetry.lock).
Trainer Abstraction
The trainer logic and configuration is now properly separated between the three cases of on-policy, off-policy
and offline learning: The base class is no longer a "God" class (formerlyBaseTrainer) which does it all; logic and functionality has moved
to the respective subclasses (OnPolicyTrainer,OffPolicyTrainerandOfflineTrainer, withOnlineTrainer
being introduced as a base class for the two former specialisations).The trainers now use configuration objects with central documentation (which has been greatly improved to enhance
clarity and usability in general); every type of trainer now has a dedicated configuration class which provides
precisely the options that are applicable.The interface has been streamlined with improved naming of functions/parameters and limiting the public interface to purely
the methods and attributes a user should reasonably access.Further changes potentially affecting usage:
- We dropped the iterator semantics: Method
__next__has been replaced byexecute_epoch. #913 - We no longer report outdated statistics (e.g. on rewards/returns when a training step does not collect any full
episodes) - See also "Issues resolved" below (as issue resolution can result in usage changes)
- The default value for
test_in_trainwas changed from True to False (updating all usage sites to explicitly
set the parameter), because False is the more natural default, which does not make assumptions about
returns/score values computed for the data from a collection step being at all meaningful for early stopping - The management of epsilon-greedy exploration for discrete Q-learning algorithms has been simplified:
- All respective Policy implementations (e.g.
DQNPolicy,C51Policy, etc.) now accept two parameterseps_trainingandeps_inference, which allows the training and test collection cases to be sufficiently
differentiated and makes the use of callback functions (train_fn,test_fn) unnecessary if only
constants are to be set. - The setter method
set_epshas been replaced withset_eps_trainingandset_eps_inferenceaccordingly.
- All respective Policy implementations (e.g.
- We dropped the iterator semantics: Method
Further internal changes unlikely to affect usage:
- Module
trainer.utilswas removed and the functions therein where moved to classTrainer - The two places that collected and evaluated test episodes (
_test_in_trainand_reset) in addition to_test_stepwere unified to use_test_step(with some minor parametrisation) and now log the results
of the test step accordingly.
- Module
Issues resolved:
- Methods
runandreset: Parameterreset_prior_to_runofrunwas never respected if it was set toFalse,
because the implementation of__iter__(now removed) would callresetregardless - and callingreset
is indeed necessary, because it initializes the training. The parameter was removed and replaced byreset_collectors(such thatrunnow replicates the parameters ofreset). - Inconsistent configuration options now raise exceptions rather than silently ignoring the issue in the
hope that default behaviour will achieve what the user intended.
One condition wheretest_in_trainwas silently set toFalsewas removed and replaced by a warning. - The stop criterion
stop_fndid not consider scores as computed bycompute_score_fnbut instead always used
mean returns (i.e. it was assumed that the default implementation ofcompute_score_fnapplies).
This is an inconsistency which has been resolved. - The
gradient_stepcounter was flawed (as it made assumptions about the underlying algorithms, which were
not valid). It has been replaced with an update step counter.
Members ofInfoStatsand parameters ofLogger(and subclasses) were changed accordingly.
- Methods
Migration information at a glance:
- Training parameters are now passed via instances of configuration objects instead of directly as keyword arguments:
OnPolicyTrainerParams,OffPolicyTrainerParams,OfflineTrainerParams.- Changed parameter default: Default for
test_in_trainwas changed from True to False. - Changed parameter names to improve clarity:
max_epoch(num_epochsin high-level API) ->max_epochsstep_per_epoch->epoch_num_stepsepisode_per_test(num_test_episodesin high-level API) ->test_step_num_episodesstep_per_collect->collection_step_num_env_stepsepisode_per_collect-> collection_step_num_episodes`update_per_step->update_step_num_gradient_steps_per_samplerepeat_per_collect->update_step_num_repetitions
- Changed parameter default: Default for
- Trainer classes have been renamed:
OnpolicyTrainer->OnPolicyTrainerOffpolicyTrainer->OffPolicyTrainer
- Method
run: The parameterreset_prior_to_runwas removed and replaced byreset_collectors(see above). - Methods
runandreset: The parameterreset_bufferwas renamed toreset_collector_buffersfor clarity - Trainers are no longer iterators; manual usage (not using
run) should simply callresetfollowed by
calls ofexecute_epoch.
- Training parameters are now passed via instances of configuration objects instead of directly as keyword arguments:
Algorithms and Policies
We now conceptually differentiate between the learning algorithm and the policy being optimised:
The abstraction
BasePolicyis thus replaced byAlgorithmandPolicy, and the package was renamed
fromtianshou.policytotianshou.algorithm.Migration information: The instantiation of a policy is replaced by the instantiation of an
Algorithm,
which is passed aPolicy. In most cases, the former policy class name<Name>Policyis replaced by algorithm
class<Name>; exceptions are noted below.ImitationPolicy->OffPolicyImitationLearning,OfflineImitationLearningPGPolicy->ReinforceMultiAgentPolicyManager->MultiAgentOnPolicyAlgorithm,MultiAgentOffPolicyAlgorithmMARLRandomPolicy->MARLRandomDiscreteMaskedOffPolicyAlgorithm
For the respective subtype of
Policyto use, see the respective algorithm class' constructor.
Interface changes/improvements:
- Core methods have been renamed (and removed from the public interface; #898):
process_fn->_preprocess_batchpost_process_fn->_postprocess_batchlearn->_update_with_batch
- The updating interface has been cleaned up (#949):
- Functions
updateand_update_with_batch(formerlylearn) no longer have*argsand**kwargs. - Instead, the interfaces for the offline, off-policy and on-policy cases are properly differentiated.
- Functions
- New method
run_training: TheAlgorithmabstraction can now directly initiate the learning process via this method. Algorithmsno longer requiretorch.optim.Optimizerinstances and instead requireOptimizerFactory
instances, which create the actual optimizers internally. #959
The newOptimizerFactoryabstraction simultaneously handles the creation of learning rate schedulers
for the optimizers created (via methodwith_lr_scheduler_factoryand accompanying factory abstractionLRSchedulerFactory).
The parameterlr_schedulerhas thus been removed from all algorithm constructors.- The flag
updatinghas been removed (no internal usage, general usefulness questionable). - Removed
max_action_num, instead read it off fromaction_space - Parameter changes:
actor_step_size->trust_region_sizein NPdiscount_factor->gamma(was already used internally almost everywhere)reward_normalization->return_standardizationorreturn_scaling(more precise naming) or removed (was actually unsupported by Q-learning algorithms)return_standardizationinReinforceandDiscreteCRR(as it applies standardization of returns)return_scalingin actor-critic on-policy algorithms (A2C, PPO, GAIL, NPG, TRPO)- removed from Q-learning algorithms, where it was actually unsupported (DQN, C561, etc.)
clip_grad->max_grad_norm(for consistency)clip_loss_grad->huber_loss_delta(allowing to control not only the use of the Huber loss but also its essential parameter)estimation_step->n_step_return_horizon(more precise naming)
- Core methods have been renamed (and removed from the public interface; #898):
Internal design improvements:
Introduced an abstraction for the alpha parameter (coefficient of the entropy term)
inSAC,DiscreteSACand other algorithms.- Class hierarchy:
- Abstract base class
Alphabase class with value property and update method FixedAlphafor constant entropy coefficientsAutoAlphafor automatic entropy tuning (replaces the old tuple-based representation)
- Abstract base class
- The (auto-)updating logic is now completely encapsulated, reducing the complexity of the algorithms.
- Implementations for continuous and discrete cases now share the same abstraction,
making the codebase more consistent while preserving the original functionality.
- Class hierarchy:
Introduced a policy base class
ContinuousPolicyWithExplorationNoisewhich encapsulates noise generation
for continuous action spaces (e.g. relevant toDDPG,SACandREDQ).Multi-agent RL methods are now differentiated by the type of the sub-algorithms being employed
(MultiAgentOnPolicyAlgorithm,MultiAgentOffPolicyAlgorithm), which renders all interfaces clean.
Helper classMARLDispatcherhas been factored out to manage the dispatching of data to the respective agents.Algorithms now internally use a wrapper (
Algorithm.Optimizer) around the optimizers; creation is handled
by method_create_optimizer.- This facilitates backpropagation steps with gradient clipping.
- The optimizers of an Algorithm instance are now centrally tracked, such that we can ensure that the
optimizers' states are handled alongside the model parameters when callingstate_dictorload_state_dict
on theAlgorithminstance.
Special handling of the restoration of optimizers' state dicts was thus removed from examples and tests.
Lagged networks (target networks) are now conveniently handled via the new algorithm mixins
LaggedNetworkPolyakUpdateAlgorithmMixinandLaggedNetworkFullUpdateAlgorithmMixin.
Using these mixins,- a lagged network can simply be added by calling
_add_lagged_network - the torch method
trainmust no longer be overridden to ensure that the target networks
are never set to train mode/remain in eval mode (which was prone to errors), - a method which updates all target networks with their source networks is automatically
provided and does not need to be implemented specifically for every algorithm
(_update_lagged_network_weights).
All classes which make use of lagged networks were updated to use these mixins, simplifying
the implementations and reducing the potential for implementation errors.
(In the BCQ implementation, the VAE network was not correctly handled, but due to the way
in which examples were structured, it did not result in an error.)- a lagged network can simply be added by calling
Fixed issues in the class hierarchy (particularly critical violations of the Liskov substitution principle):
- Introduced base classes (to retain factorization without abusive inheritance):
ActorCriticOnPolicyAlgorithmActorCriticOffPolicyAlgorithmActorDualCriticsOffPolicyAlgorithm(extendsActorCriticOffPolicyAlgorithm)QLearningOffPolicyAlgorithm
A2C: Inherit fromActorCriticOnPolicyAlgorithminstead ofReinforceBDQN:- Inherit from
QLearningOffPolicyAlgorithminstead ofDQN - Remove parameter
clip_loss_grad(unused; only passed on to former base class) - Remove parameter
estimation_step, for which only one option was valid
- Inherit from
C51:- Inherit from
QLearningOffPolicyAlgorithminstead ofDQN - Remove parameters
clip_loss_gradandis_double(unused; only passed on to former base class)
- Inherit from
CQL:- Inherit directly from
OfflineAlgorithminstead ofSAC(off-policy). - Remove parameter
estimation_step(nown_step_return_horizon), which was not actually used (it was only passed it on to its
superclass).
- Inherit directly from
DiscreteBCQ:- Inherit directly from
OfflineAlgorithminstead ofDQN - Remove unused parameters
clip_loss_gradandis_double, which were only passed on to
former the base class but actually unused.
- Inherit directly from
DiscreteCQL: Remove unused parametersclip_loss_gradandis_double, which were only passed on to
base classQRDQN(and unused by it).DiscreteCRR: Inherit directly fromOfflineAlgorithminstead ofReinforce(on-policy)FQF: Remove unused parametersclip_loss_gradandis_double, which were only passed on to
base classQRDQN(and unused by it).IQN: Remove unused parametersclip_loss_gradandis_double, which were only passed on to
base classQRDQN(and unused by it).NPG: Inherit fromActorCriticOnPolicyAlgorithminstead ofA2CQRDQN:- Inherit from
QLearningOffPolicyAlgorithminstead ofDQN - Remove parameters
clip_loss_gradandis_double(unused; only passed on to former base class)
- Inherit from
REDQ: Inherit fromActorCriticOffPolicyAlgorithminstead ofDDPGSAC: Inherit fromActorDualCriticsOffPolicyAlgorithminstead ofDDPGTD3: Inherit fromActorDualCriticsOffPolicyAlgorithminstead ofDDPG
- Introduced base classes (to retain factorization without abusive inheritance):
High-Level API
Detailed optimizer configuration (analogous to the procedural API) is now possible:
- All optimizers can be configured in the respective algorithm-specific
Paramsobject by usingOptimizerFactoryFactoryinstances as parameter values (e.g.optim,actor_optim,critic_optim, etc.). - Learning rate schedulers remain separate parameters and now use
LRSchedulerFactoryFactory
instances. The respective parameter names now use the suffixlr_schedulerinstead oflr_scheduler_factory
(as the precise nature need not be reflected in the name; brevity is preferable).
- All optimizers can be configured in the respective algorithm-specific
SamplingConfigis replaced byTrainingConfigand subclasses differentiating off-policy and on-policy cases
appropriately (OnPolicyTrainingConfig,OffPolicyTrainingConfig).- The
test_in_trainparameter is now exposed (default False). - Inapplicable arguments can no longer be set in the respective subclass (e.g.
OffPolicyTrainingConfigdoes not
contain parameterrepeat_per_collect). - All parameter names have been aligned with the new names used by
TrainerParams(see above).
- The
Add option to customize the factory for the collector (
ExperimentBuilder.with_collector_factory),
adding the abstractionCollectorFactory. #1256
Peripheral Changes
- The
Actorclasses have been renamed for clarity (#1091):BaseActor->Actorcontinuous.ActorProb->ContinuousActorProbabilisticconinuous.Actor->ContinuousActorDeterministicdiscrete.Actor->DiscreteActor
- The
Criticclasses have been renamed for clarity (#1091):continuous.Critic->ContinuousCriticdiscrete.Critic->DiscreteCritic
- Moved Atari helper modules
atari_networkandatari_wrapperto the library undertianshou.env.atari. - Fix issues pertaining to the torch device assignment of network components (#810):
- Remove 'device' member (and the corresponding constructor argument) from the following classes:
BranchingNet,C51Net,ContinuousActorDeterministic,ContinuousActorProbabilistic,ContinuousCritic,DiscreteActor,DiscreteCritic,DQNet,FullQuantileFunction,ImplicitQuantileNetwork,IntrinsicCuriosityModule,MLPActor,MLP,Perturbation,QRDQNet,Rainbow,Recurrent,RecurrentActorProb,RecurrentCritic,VAE - (Peripheral change:) Require the use of keyword arguments for the constructors of all of these classes
- Remove 'device' member (and the corresponding constructor argument) from the following classes:
- Clean up handling of modules that define attribute
output_dim, introducing the explicit base classModuleWithVectorOutput- Interfaces where one could specify either a module with
output_dimor additionally provide the output
dimension as an argument were changed to useModuleWithVectorOutput. - The high-level API class
IntermediateModulecan now provide aModuleWithVectorOutputinstance
(via adaptation if necessary).
- Interfaces where one could specify either a module with
- The class hierarchy of supporting
nn.Moduleimplementations was cleaned up (#1091):- With the fundamental base classes
ActionReprNetandActionReprNetWithVectorOutput, we etablished a
well-defined interface for the most commonly usedforwardinterface in Tianshou's algorithms & policies. #948 - Some network classes were renamed:
ScaledObsInputModule->ScaledObsInputActionReprNetRainbow->RainbowNet
- With the fundamental base classes
- All modules containing base classes were renamed from
baseto a more descriptive name, rendering
file names unique.
Release 1.2.0 (2025-06-23)
This is the final release in the 1.x series before Tianshou v2.0.0.
It resolves performance regressions introduced in v1.1.0 and resolves several issues,
partly by backporting improvements from the upcoming v2.0.0 release.
This release is brought to you by Applied AI Institute gGmbH.
Core developers:
- Dr. Dominik Jain (@opcode81)
- Michael Panchenko (@MischaPanch)
Changes/Improvements
trainer:- Custom scoring now supported for selecting the best model. #1202
highlevel:DiscreteSACExperimentBuilder: Expose methodwith_actor_factory_default#1248 #1250ActorFactoryDefault: Fix parameters for hidden sizes and activation not being
passed on in the discrete case (affectswith_actor_factory_defaultmethod of experiment builders)ExperimentConfig: Do not inherit from other classes, as this breaks automatic handling byjsonargparsewhen the class is used to define interfaces (as in high-level API examples)AutoAlphaFactoryDefault: Differentiate discrete and continuous action spaces
and allow coefficient to be modified, adding an informative docstring
(previous implementation was reasonable only for continuous action spaces)- Adjust usage in
atari_sac_hlexample accordingly.
- Adjust usage in
NPGAgentFactory,TRPOAgentFactory: Fix optimizer instantiation including the actor parameters
(which was misleadingly suggested in the docstring in the respective policy classes; docstrings were fixed),
as the actor parameters are intended to be handled via natural gradients internally
data:ReplayBuffer: Fix collection of empty episodes being disallowed- Collection was slow due to
isinstancechecks on Protocols and due to Buffer integrity validation. This was solved
by no longer performingisinstanceon Protocols and by making the integrity validation disabled by default.
- Tests:
- We have introduced extensive determinism tests which allow to validate whether
training processes deterministically compute the same results across different development branches.
This is an important step towards ensuring reproducibility and consistency, which will be
instrumental in supporting Tianshou developers in their work, especially in the context of
algorithm development and evaluation.
- We have introduced extensive determinism tests which allow to validate whether
Breaking Changes
trainer:BaseTrainer.runand__iter__: Resetting was never optional prior to running the trainer,
yet the recently introduced parameterreset_prior_to_runofrunsuggested that it was optional.
Yet the parameter was ultimately not respected, because__iter__would always callreset(reset_collectors=True, reset_buffer=False)
regardless. The parameter was removed; instead, the parameters ofrunnow mirror the parameters ofreset,
and the implicitresetcall in__iter__was removed.
This aligns with upcoming changes in Tianshou v2.0.0.- NOTE: If you have been using a trainer without calling
runbut by directly iterating over it, you
will need to callreseton the trainer explicitly before iterating over the trainer. - Using a trainer as an iterator is considered deprecated and support for this will be removed in Tianshou v2.0.0.
- NOTE: If you have been using a trainer without calling
data:InfoStatshas a new non-optional fieldbest_scorewhich is used
for selecting the best model. #1202
highlevel:- Change the way in which seeding is handled: The mechanism introduced in v1.1.0
was completely revised:- The
training_seedandtest_seedattributes were removed fromSamplingConfig.
Instead, the seeds are derived from the seed defined inExperimentConfig. - Seed attributes of
EnvFactoryclasses were removed.
Instead, seeds are passed to methods ofEnvFactory.
- The
- Change the way in which seeding is handled: The mechanism introduced in v1.1.0
Release 1.1.0 (2024-08-10)
NOTE: This release introduced (potentially severe) performance regressions in data collection, please switch to a newer release for better performance.
Highlights
Evaluation Package
This release introduces a new package evaluation that integrates best
practices for running experiments (seeding test and train environmets) and for
evaluating them using the rliable
library. This should be especially useful for algorithm developers for comparing
performances and creating meaningful visualizations. This functionality is
currently in alpha state and will be further improved in the next releases.
You will need to install tianshou with the extra eval to use it.
The creation of multiple experiments with varying random seeds has been greatly
facilitated. Moreover, the ExpLauncher interface has been introduced and
implemented with several backends to support the execution of multiple
experiments in parallel.
An example for this using the high-level interfaces can be found
here, examples that use low-level
interfaces will follow soon.
Improvements in Batch
Apart from that, several important
extensions have been added to internal data structures, most notably to Batch.
Batches now implement __eq__ and can be meaningfully compared. Applying
operations in a nested fashion has been significantly simplified, and checking
for NaNs and dropping them is now possible.
One more notable change is that torch Distribution objects are now sliced when
slicing a batch. Previously, when a Batch with say 10 actions and a dist
corresponding to them was sliced to [:3], the dist in the result would still
correspond to all 10 actions. Now, the dist is also "sliced" to be the
distribution of the first 3 actions.
A detailed list of changes can be found below.
Changes/Improvements
evaluation: New package for repeating the same experiment with multiple
seeds and aggregating the results. #1074 #1141 #1183data:Batch:- Add methods
to_dictandto_list_of_dicts. #1063 #1098 - Add methods
to_numpy_andto_torch_. #1098, #1117 - Add
__eq__(semantic equality check). #1098 keys()deprecated in favor ofget_keys()(needed to make iteration
consistent with naming) #1105.- Major: new methods for applying functions to values, to check for NaNs
and drop them, and to set values. #1181 - Slicing a batch with a torch distribution now also slices the
distribution. #1181
- Add methods
data.collector:Collector:- Introduced
BaseCollectoras a base class for all collectors.
#1123 - Add method
close#1063 - Method
resetis now more granular (new flags controlling
behavior). #1063
- Introduced
CollectStats: Add convenience
constructorwith_autogenerated_stats. #1063
trainer:- Trainers can now control whether collectors should be reset prior to
training. #1063
- Trainers can now control whether collectors should be reset prior to
policy:- introduced attribute
in_training_stepthat is controlled by the trainer.
#1123 - policy automatically set to
evalmode when collecting and totrain
mode when updating. #1123 - Extended interface of
compute_actionto also support array-like inputs
#1169
- introduced attribute
highlevel:SamplingConfig:- Add support for
batch_size=None. #1077 - Add
training_seedfor explicit seeding of training and test
environments, thetest_seedis inferred fromtraining_seed. #1074
- Add support for
experiment:Experimentnow has anameattribute, which can be set
usingExperimentBuilder.with_nameand
which determines the default run name and therefore the persistence
subdirectory.
It can still be overridden inExperiment.run(), the new parameter
name beingrun_namerather thanexperiment_name(although the latter will still be interpreted
correctly). #1074 #1131- Add class
ExperimentCollectionfor the convenient execution of
multiple experiment runs #1131 - The
Worldobject, containing all low-level objects needed for
experimentation,
can now be extracted from anExperimentinstance. This enables
customizing
the experiment prior to its execution, bridging the low and high-level
interfaces. #1187 ExperimentBuilder:- Add method
build_seeded_collectionfor the sound creation of
multiple
experiments with varying random seeds #1131 - Add method
copyto facilitate the creation of multiple
experiments from a single builder #1131
- Add method
env:- Added new
VectorEnvTypecalledSUBPROC_SHARED_MEM_AUTOand used in
for Atari and Mujoco venv creation. #1141
- Added new
utils:logger:- Loggers can now restore the logged data into python by using the
newrestore_logged_datamethod. #1074 - Wandb logger extended #1183
- Loggers can now restore the logged data into python by using the
net.continuous.Critic:- Add flag
apply_preprocess_net_to_obs_onlyto allow the
preprocessing network to be applied to the observations only (without
the actions concatenated), which is essential for the case where we
want
to reuse the actor's preprocessing network #1128
- Add flag
torch_utils(new module)- Added context managers
torch_train_mode
andpolicy_within_training_step#1123
- Added context managers
printDataclassPPrintMixinnow supports outputting a string, not just
printing the pretty repr. #1141
Fixes
highlevel:CriticFactoryReuseActor: Enable the Critic
flagapply_preprocess_net_to_obs_onlyfor continuous critics,
fixing the case where we want to reuse an actor's preprocessing network
for the critic (affects usages
of the experiment builder methodwith_critic_factory_use_actorwith
continuous environments) #1128- Policy parameter
action_scalingvalue"default"was not correctly
transformed to a Boolean value for
algorithms SAC, DDPG, TD3 and REDQ. The value"default"being truthy
caused action scaling to be enabled
even for discrete action spaces. #1191
atari_network.DQN:- Fix constructor input validation #1128
- Fix
output_dimnot being set iffeatures_only=True
andoutput_dim_added_layeris not None #1128
PPOPolicy:- Fix
max_batchsizenot being used inlogp_oldcomputation
insideprocess_fn#1168
- Fix
- Fix
Batch.__eq__to allow comparing Batches with scalar array values #1185
Internal Improvements
Collectors rely less on state, the few stateful things are stored explicitly
instead of through a.dataattribute. #1063- Introduced a first iteration of a naming convention for vars in
Collectors.
#1063 - Generally improved readability of Collector code and associated tests (still
quite some way to go). #1063 - Improved typing for
exploration_noiseand within Collector. #1063 - Better variable names related to model outputs (logits, dist input etc.).
#1032 - Improved typing for actors and critics, using Tianshou classes
likeActor,ActorProb, etc.,
instead of justnn.Module. #1032 - Added interfaces for most
ActorandCriticclasses to enforce the presence
offorwardmethods. #1032 - Simplified
PGPolicyforward by unifying thedist_fninterface (see
associated breaking change). #1032 - Use
.modeof distribution instead of relying on knowledge of the
distribution type. #1032 - Exception no longer raised on
lenof emptyBatch. #1084 - tests and examples are covered by
mypy. #1077 Actoris more used, stricter typing by making it generic. #1077- Use explicit multiprocessing context for creating
Pipeinsubproc.py.
#1102
Breaking Changes
data:Collector:- Removed
.dataattribute. #1063 - Collectors no longer reset the environment on initialization.
Instead, the user might have to callresetexpicitly or
passreset_before_collect=True. #1063 - Removed
no_gradargument fromcollectmethod (was unused in
tianshou). #1123
- Removed
Batch:- Fixed
iter(Batch(...)which now behaves the same way
asBatch(...).__iter__().
Can be considered a bugfix. #1063 - The methods
to_numpyandto_torchin are not in-place anymore
(useto_numpy_orto_torch_instead). #1098, #1117 - The method
Batch.is_emptyhas been removed. Instead, the user can
simply check for emptiness of Batch by usinglenon dicts. #1144 - Stricter
cat_, only concatenation of batches with the same structure
is allowed. #1181 to_torchandto_numpyare no longer static methods.
SoBatch.to_numpy(batch)should be replaced bybatch.to_numpy().
#1200
- Fixed
utils:logger:BaseLogger.prepare_dict_for_loggingis now abstract. #1074- Removed deprecated and unused
BasicLogger(only affects users who
subclassed it). #1074
utils.net:Recurrentnow receives and returns
aRecurrentStateBatchinstead of a dict. #1077
- Modules with code that was copied from sensAI have been replaced by
imports from new dependency sensAI-utils:tianshou.utils.loggingis replaced withsensai.util.loggingtianshou.utils.stringis replaced withsensai.util.stringtianshou.utils.pickleis replaced withsensai.util.pickle
env:- All VectorEnvs now return a numpy array of info-dicts on reset instead of
a list. #1063
- All VectorEnvs now return a numpy array of info-dicts on reset instead of
policy:- Changed interface of
dist_fninPGPolicyand all subclasses to take a
single argument in both
continuous and discrete cases. #1032
- Changed interface of
AtariEnvFactoryconstructor (in examples, so not really breaking) now
requires explicit train and test seeds. #1074EnvFactoryRegisterednow requires an explicittest_seedin the
constructor. #1074highlevel:params: The parameterdist_fnhas been removed from the parameter
objects (PGParams,A2CParams,PPOParams,NPGParams,TRPOParams).
The correct distribution is now determined automatically based on the
actor factory being used, avoiding the possibility of
misspecification. Persisted configurations/policies continue to work as
expected, but code must not specify thedist_fnparameter.
#1194 #1195env:EnvFactoryRegistered: parameterseedhas been replaced by the pair
of parameterstraining_seedandtest_seed
Persisted instances will continue to work correctly.
Subclasses such asAtariEnvFactoryare also affected requires
explicit train and test seeds. #1074VectorEnvType:SUBPROC_SHARED_MEMhas been replaced
bySUBPROC_SHARED_MEM_DEFAULT. It is recommended to
useSUBPROC_SHARED_MEM_AUTOinstead. However, persisted configs will
continue working. #1141
Tests
- Fixed env seeding it
test_sac_with_il.pyso that the test doesn't fail
randomly. #1081 - Improved CI triggers and added telemetry (if requested by user) #1177
- Improved environment used in tests.
- Improved tests bach equality to check with scalar values #1185
Dependencies
- DeepDiff added to help with diffs of
batches in tests. #1098 - Bumped black, idna, pillow
- New extra "eval"
- Bumped numba to >=60.0.0, permitting installation on python 3.12 # 1177
- New dependency sensai-utils
Release 1.0.0 (2024-03-20)
This release focuses on updating and improving Tianshou internals (in particular, code quality) while creating relatively few breaking changes (apart from things like the python and dependencies' versions).
We view it as a significant step for transforming Tianshou into the go-to place both for RL researchers, as well as for RL practitioners working on industry projects.
This is the first release after the appliedAI Institute (the TransferLab division) has decided to further develop Tianshou and provide long-term support.
Breaking Changes
- dropped support of python<3.11
- dropped support of gym, from now on only Gymnasium envs are supported
- removed functions like
offpolicy_trainerin favor ofOffpolicyTrainer(...).run()(this affects all example scripts) - several breaking changes related to removing
**kwargsfrom signatures, renamings of internal attributes (likecritic1->critic) - Outputs of training methods are now dataclasses instead of dicts
Functionality Extensions
Major
- High level interfaces for experiments, demonstrated by the new example scripts with names ending in
_hl.py
Minor
- Method to compute action directly from a policy's observation, can be used for unrolling
- Support for custom keys in ReplayBuffer
- Support for CalQL as part of CQL
- Support for explicit setting of multiprocessing context for SubprocEnvWorker
critic2no longer has to be explicitly constructed and passed if it is supposed to be the same network ascritic(formerlycritic1)
Internal Improvements
Build and Docs
- Completely changed the build pipeline. Tianshou now uses poetry, black, ruff, poethepoet, nbqa and other niceties.
- Notebook tutorials are now part of the repository (previously they were in a drive). They were fixed and are executed during the build as integration tests, in addition to serving as documentation. Parts of the content have been improved.
- Documentation is now built with jupyter book. JavaScript code has been slightly improved, JS dependencies are included as part of the repository.
- Many improvements in docstrings
Typing
- Adding
BatchPrototypesto cover the fields needed and returned by methods relying on batches in a backwards compatible way - Removing
**kwargsfrom policies' constructors - Overall, much stricter and more correct typing. Removing
kwargsand replacing dicts by dataclasses in several places. - Making use of
Genericto express different kinds of stats that can be returned bylearnandupdate - Improved typing in
testsandexamples, close to passing mypy
General
- Reduced duplication, improved readability and simplified code in several places
- Use
dist.modeinstead of inferringlocorargmaxfrom thedist_fninput
Contributions
The OG creators
- @Trinkle23897 participated in almost all aspects of the coordination and reviewed most of the merged PRs
- @nuance1979 participated in several discussions
From appliedAI
The team working on this release of Tianshou consisted of @opcode81 @MischaPanch @maxhuettenrauch @carlocagnetta @bordeauxred
External contributions
- @BFAnas participated in several discussions and contributed the CalQL implementation, extending the pre-processing logic.
- @dantp-ai fixed many mypy issues and improved the tests
- @arnaujc91 improved the logic of computing deterministic actions
- Many other contributors, among them many new ones participated in this release. The Tianshou team is very grateful for your contributions!
Older Releases
File: CONTRIBUTING.md
Contributing to Tianshou
Please refer to the 'Developer Guide' on tianshou.org.
File: .pre-commit-config.yaml
default_install_hook_types: [commit-msg, pre-commit]
default_stages: [commit, manual]
fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:- id: check-added-large-files
- repo: local
hooks:- id: ruff
name: ruff
entry: poetry run ruff
require_serial: true
language: system
types: [python] - id: ruff-nb
name: ruff-nb
entry: poetry run nbqa ruff .
require_serial: true
language: system
pass_filenames: false
types: [python] - id: black
name: black
entry: poetry run black
require_serial: true
language: system
types: [python] - id: poetry-check
name: poetry check
entry: poetry check
language: system
files: pyproject.toml
pass_filenames: false - id: poetry-lock-check
name: poetry lock check
entry: poetry check
args: [--lock]
language: system
pass_filenames: false - id: mypy
name: mypy
entry: poetry run mypy tianshou examples testfilenames should not be passed as they would collide with the config in pyproject.toml
pass_filenames: false
files: '^tianshou(/[^/])/[^/]*.py$'
language: system - id: mypy-nb
name: mypy-nb
entry: poetry run nbqa mypy
language: system
- id: ruff
File: .readthedocs.yaml
.readthedocs.yaml
Read the Docs configuration file
See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
Required
version: 2
Set the version of Python and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
commands:
- mkdir -p $READTHEDOCS_OUTPUT/html
- curl -sSL https://install.python-poetry.org | python -
- ~/.local/bin/poetry config virtualenvs.create false
- ~/.local/bin/poetry install --with dev -E evalSame as poe tasks, but unfortunately poe doesn't work with poetry not creating virtualenvs
- ~/.local/bin/poetry run python docs/autogen_rst.py
- ~/.local/bin/poetry run which jupyter-book
- ~/.local/bin/poetry run python docs/create_toc.py
- ~/.local/bin/poetry run jupyter-book config sphinx docs/
- ~/.local/bin/poetry run sphinx-build -W -b html docs $READTHEDOCS_OUTPUT/htmlFile: docs/_config.yml
Book settings
Learn more at https://jupyterbook.org/customize/config.html
#######################################################################################
A default configuration that will be loaded for all jupyter books
Users are expected to override these values in their own `_config.yml` file.
This is also the "master list" of all allowed keys and values.
#######################################################################################
Book settings
title : Tianshou Documentation # The title of the book. Will be placed in the left navbar.
author : Tianshou contributors # The author of the book
copyright : "2020, Tianshou contributors." # Copyright year to be placed in the footer
logo : _static/images/tianshou-logo.png # A path to the book logo
Patterns to skip when building the book. Can be glob-style (e.g. "*skip.ipynb")
exclude_patterns : ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build', 'jupyter_execute', '.jupyter_cache', '.pytest_cache', 'docs/autogen_rst.py', 'docs/create_toc.py']
Auto-exclude files not in the toc
only_build_toc_files : false
#######################################################################################
Execution settings
execute:
execute_notebooks : cache # Whether to execute notebooks at build time. Must be one of ("auto", "force", "cache", "off")
cache : "" # A path to the jupyter cache that will be used to store execution artifacts. Defaults to _build/.jupyter_cache/
exclude_patterns : [] # A list of patterns to skip in execution (e.g. a notebook that takes a really long time)
timeout : -1 # The maximum time (in seconds) each notebook cell is allowed to run.
run_in_temp : false # If True, then a temporary directory will be created and used as the command working directory (cwd),
# otherwise the notebook's parent directory will be the cwd.
allow_errors : false # If False, when a code cell raises an error the execution is stopped, otherwise all cells are always run.
stderr_output : show # One of 'show', 'remove', 'remove-warn', 'warn', 'error', 'severe'
#######################################################################################
Parse and render settings
parse:
myst_enable_extensions: # default extensions to enable in the myst parser. See https://myst-parser.readthedocs.io/en/latest/using/syntax-optional.html
- amsmath
- colon_fence
# - deflist
- dollarmath
- html_admonition
# - html_image
- linkify
# - replacements
# - smartquotes
- substitution
- tasklist
myst_url_schemes: [ mailto, http, https ] # URI schemes that will be recognised as external URLs in Markdown links
myst_dmath_double_inline: true # Allow display math ($$) within an inline context
#######################################################################################
HTML-specific settings
html:
favicon : "_static/images/tianshou-favicon.png" # A path to a favicon image
use_edit_page_button : false # Whether to add an "edit this page" button to pages. If true, repository information in repository: must be filled in
use_repository_button : false # Whether to add a link to your repository button
use_issues_button : false # Whether to add an "open an issue" button
use_multitoc_numbering : true # Continuous numbering across parts/chapters
extra_footer : ""
google_analytics_id : "" # A GA id that can be used to track book views.
home_page_in_navbar : true # Whether to include your home page in the left Navigation Bar
baseurl : "https://tianshou.readthedocs.io/en/master/"
analytics:
comments:
hypothesis : false
utterances : false
announcement : "" # A banner announcement at the top of the site.
#######################################################################################
LaTeX-specific settings
latex:
latex_engine : pdflatex # one of 'pdflatex', 'xelatex' (recommended for unicode), 'luatex', 'platex', 'uplatex'
use_jupyterbook_latex : true # use sphinx-jupyterbook-latex for pdf builds as default
targetname : book.tex
Add a bibtex file so that we can create citations
bibtex_bibfiles:
- refs.bib
#######################################################################################
Launch button settings
launch_buttons:
notebook_interface : classic # The interface interactive links will activate ["classic", "jupyterlab"]
binderhub_url : "" # The URL of the BinderHub (e.g., https://mybinder.org)
jupyterhub_url : "" # The URL of the JupyterHub (e.g., https://datahub.berkeley.edu)
thebe : false # Add a thebe button to pages (requires the repository to run on Binder)
colab_url : "https://colab.research.google.com"
url : https://github.com/thu-ml/tianshou # The URL to your book's repository
path_to_book : docs # A path to your book's folder, relative to the repository root.
branch : master # Which branch of the repository should be used when creating links
#######################################################################################
Advanced and power-user settings
sphinx:
extra_extensions :
- sphinx.ext.autodoc
- sphinx.ext.viewcode
- sphinx_toolbox.more_autodoc.sourcelink
- sphinxcontrib.spelling
- sphinxcontrib.mermaid
local_extensions : # A list of local extensions to load by sphinx specified by "name: path" items
recursive_update : false # A boolean indicating whether to overwrite the Sphinx config (true) or recursively update (false)
config : # key-value pairs to directly over-ride the Sphinx configuration
autodoc_typehints_format: "short"
autodoc_member_order: "bysource"
autodoc_mock_imports:
# mock imports for optional dependencies (e.g. dependencies of atari/atari_wrapper)
- cv2
autoclass_content: "both"
autodoc_default_options:
show-inheritance: True
html_js_files:
We have to list them explicitly because they need to be loaded in a specific order
- js/[email protected]
- js/[email protected]
- js/[email protected]
autodoc_show_sourcelink: True
add_module_names: False
github_username: thu-ml
github_repository: tianshou
python_use_unqualified_type_names: True
nb_mime_priority_overrides: [
[ 'html', 'application/vnd.jupyter.widget-view+json', 10 ],
[ 'html', 'application/javascript', 20 ],
[ 'html', 'text/html', 30 ],
[ 'html', 'text/latex', 40 ],
[ 'html', 'image/svg+xml', 50 ],
[ 'html', 'image/png', 60 ],
[ 'html', 'image/jpeg', 70 ],
[ 'html', 'text/markdown', 80 ],
[ 'html', 'text/plain', 90 ],
[ 'spelling', 'application/vnd.jupyter.widget-view+json', 10 ],
[ 'spelling', 'application/javascript', 20 ],
[ 'spelling', 'text/html', 30 ],
[ 'spelling', 'text/latex', 40 ],
[ 'spelling', 'image/svg+xml', 50 ],
[ 'spelling', 'image/png', 60 ],
[ 'spelling', 'image/jpeg', 70 ],
[ 'spelling', 'text/markdown', 80 ],
[ 'spelling', 'text/plain', 90 ],
]
mathjax_path: https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
myst_fence_as_directive: ["mermaid"]
mathjax3_config:
loader: { load: [ '[tex]/configmacros' ] }
tex:
packages: { '[+]': [ 'configmacros' ] }
macros:
vect: ["{\\mathbf{\\boldsymbol{#1}} }", 1]
E: "{\\mathbb{E}}"
P: "{\\mathbb{P}}"
R: "{\\mathbb{R}}"
abs: ["{\\left| #1 \\right|}", 1]
simpl: ["{\\Delta^{#1} }", 1]
amax: "{\\text{argmax}}"File: docs/bibtex.json
{
"cited": {
"tutorials/dqn": [
"DQN",
"DDPG",
"PPO"
]
}
}
File: docs/index.rst
Welcome to Tianshou!
Tianshou (天授 <https://baike.baidu.com/item/%E5%A4%A9%E6%8E%88>_) is a reinforcement learning platform based on pure PyTorch. Unlike existing reinforcement learning libraries, which are mainly based on TensorFlow, have many nested classes, unfriendly API, or slow-speed, Tianshou provides a fast-speed framework and pythonic API for building the deep reinforcement learning agent. The supported interface algorithms include:
- :class:
~tianshou.algorithm.modelfree.dqn.DQNDeep Q-Network <https://storage.googleapis.com/deepmind-media/dqn/DQNNaturePaper.pdf>_ - :class:
~tianshou.algorithm.modelfree.dqn.DQNDouble DQN <https://arxiv.org/pdf/1509.06461.pdf>_ - :class:
~tianshou.algorithm.modelfree.dqn.DQNDueling DQN <https://arxiv.org/pdf/1511.06581.pdf>_ - :class:
~tianshou.algorithm.modelfree.bdqn.BDQNBranching DQN <https://arxiv.org/pdf/1711.08946.pdf>_ - :class:
~tianshou.algorithm.modelfree.c51.C51Categorical DQN <https://arxiv.org/pdf/1707.06887.pdf>_ - :class:
~tianshou.algorithm.modelfree.rainbow.RainbowDQNRainbow DQN <https://arxiv.org/pdf/1710.02298.pdf>_ - :class:
~tianshou.algorithm.modelfree.qrdqn.QRDQNQuantile Regression DQN <https://arxiv.org/pdf/1710.10044.pdf>_ - :class:
~tianshou.algorithm.modelfree.iqn.IQNImplicit Quantile Network <https://arxiv.org/pdf/1806.06923.pdf>_ - :class:
~tianshou.algorithm.modelfree.fqf.FQFFully-parameterized Quantile Function <https://arxiv.org/pdf/1911.02140.pdf>_ - :class:
~tianshou.algorithm.modelfree.reinforce.ReinforceReinforce/Vanilla Policy Gradients <https://papers.nips.cc/paper/1713-policy-gradient-methods-for-reinforcement-learning-with-function-approximation.pdf>_ - :class:
~tianshou.algorithm.modelfree.npg.NPGNatural Policy Gradient <https://proceedings.neurips.cc/paper/2001/file/4b86abe48d358ecf194c56c69108433e-Paper.pdf>_ - :class:
~tianshou.algorithm.modelfree.a2c.A2CAdvantage Actor-Critic <https://openai.com/blog/baselines-acktr-a2c/>_ - :class:
~tianshou.algorithm.modelfree.trpo.TRPOTrust Region Policy Optimization <https://arxiv.org/pdf/1502.05477.pdf>_ - :class:
~tianshou.algorithm.modelfree.ppo.PPOProximal Policy Optimization <https://arxiv.org/pdf/1707.06347.pdf>_ - :class:
~tianshou.algorithm.modelfree.ddpg.DDPGDeep Deterministic Policy Gradient <https://arxiv.org/pdf/1509.02971.pdf>_ - :class:
~tianshou.algorithm.modelfree.td3.TD3Twin Delayed DDPG <https://arxiv.org/pdf/1802.09477.pdf>_ - :class:
~tianshou.algorithm.modelfree.sac.SACSoft Actor-Critic <https://arxiv.org/pdf/1812.05905.pdf>_ - :class:
~tianshou.algorithm.modelfree.redq.REDQRandomized Ensembled Double Q-Learning <https://arxiv.org/pdf/2101.05982.pdf>_ - :class:
~tianshou.algorithm.modelfree.discrete_sac.DiscreteSACDiscrete Soft Actor-Critic <https://arxiv.org/pdf/1910.07207.pdf>_ - :class:
~tianshou.algorithm.imitation.imitation_base.ImitationPolicyImitation Learning - :class:
~tianshou.algorithm.imitation.bcq.BCQBatch-Constrained deep Q-Learning <https://arxiv.org/pdf/1812.02900.pdf>_ - :class:
~tianshou.algorithm.imitation.cql.CQLConservative Q-Learning <https://arxiv.org/pdf/2006.04779.pdf>_ - :class:
~tianshou.algorithm.imitation.td3_bc.TD3BCTwin Delayed DDPG with Behavior Cloning <https://arxiv.org/pdf/2106.06860.pdf>_ - :class:
~tianshou.algorithm.imitation.discrete_cql.DiscreteCQLDiscrete Conservative Q-Learning <https://arxiv.org/pdf/2006.04779.pdf>_ - :class:
~tianshou.algorithm.imitation.discrete_bcq.DiscreteBCQDiscrete Batch-Constrained deep Q-Learning <https://arxiv.org/pdf/1910.01708.pdf>_ - :class:
~tianshou.algorithm.imitation.discrete_crr.DiscreteCRRCritic Regularized Regression <https://arxiv.org/pdf/2006.15134.pdf>_ - :class:
~tianshou.algorithm.imitation.gail.GAILGenerative Adversarial Imitation Learning <https://arxiv.org/pdf/1606.03476.pdf>_ - :class:
~tianshou.algorithm.modelbased.psrl.PSRLPolicyPosterior Sampling Reinforcement Learning <https://www.ece.uvic.ca/~bctill/papers/learning/Strens_2000.pdf>_ - :class:
~tianshou.algorithm.modelbased.icm.ICMOffPolicyWrapper, :class:~tianshou.algorithm.modelbased.icm.ICMOnPolicyWrapperIntrinsic Curiosity Module <https://arxiv.org/pdf/1705.05363.pdf>_ - :class:
~tianshou.data.buffer.prio.PrioritizedReplayBufferPrioritized Experience Replay <https://arxiv.org/pdf/1511.05952.pdf>_ - :meth:
~tianshou.algorithm.algorithm_base.Algorithm.compute_episodic_returnGeneralized Advantage Estimator <https://arxiv.org/pdf/1506.02438.pdf>_ - :class:
~tianshou.data.buffer.her.HERReplayBufferHindsight Experience Replay <https://arxiv.org/pdf/1707.01495.pdf>_
Installation
Tianshou is available through PyPI <https://pypi.org/project/tianshou/>_.
New releases require Python >= 3.11.
Install Tianshou with the following command:
.. code-block:: bash
$ pip install tianshouAlternatively, install the current version on GitHub:
.. code-block:: bash
$ pip install git+https://github.com/thu-ml/tianshou.git@master --upgradeAfter installation, open your python console and type
::
import tianshou
print(tianshou.__version__)If no error occurs, you have successfully installed Tianshou.
Indices and tables
- :ref:
genindex - :ref:
modindex - :ref:
search
File: docs/01_user_guide/00_training_process.md
The Reinforcement Learning Process
The following diagram illustrates the key mechanisms underlying the learning process in model-free reinforcement learning algorithms.
It shows how the agent interacts with the environment, collects experiences, and periodically updates its policy based on those experiences.
⇦ Prev
Next ⇨Accordingly, the key entities involved in the learning process are:
- The environment: This is the system the agent interacts with.
It provides the agent with observable states and rewards based on the actions taken by the agent. - The agent's policy: This is the strategy used by the agent to decide which action to take in a given state.
The policy can be deterministic or stochastic and is typically represented by a neural network in deep reinforcement learning. - The replay buffer: This is a data structure used to store the agent's experiences, which consist of state transitions,
actions taken, and rewards received.
The agent learns from past experience by sampling mini-batches from the buffer during the policy update phase. - The learning algorithm: This defines how the agent updates its policy based on the experiences stored in the replay buffer.
Different algorithms have different update mechanisms, which can significantly affect the learning performance.
In some cases, the algorithm may also involve additional components (specifically neural networks), such as target networks or value
functions.
These entities have direct correspondences in Tianshou's codebase:
- The environment is represented by an instance of a class that inherits from
gymnasium.Env, which is a standard interface for
reinforcement learning environments.
In practice, environments are typically vectorized to enable parallel interactions, increasing efficiency. - The policy is encapsulated in the {class}
~tianshou.algorithm.algorithm_base.Policyclass, which provides methods for action selection. - The replay buffer is implemented in the {class}
~tianshou.data.buffer.buffer_base.ReplayBufferclass.
A {class}~tianshou.data.collector.Collectorinstance is used to manage the addition of new experiences to the replay buffer as the agent interacts with the
environment.
During the learning phase, the replay buffer may be sampled, providing an instance of {class}~tianshou.data.batch.Batchfor the policy update. - The abstraction for learning algorithms is given by the {class}
~tianshou.algorithm.algorithm_base.Algorithmclass, which defines how to update the policy using data from the
replay buffer.
(structuring-the-process)=
Structuring the Process
The learning process itself is reified in Tianshou's {class}~tianshou.trainer.trainer.Trainer class, which orchestrates the interaction between the agent and the
environment, manages the replay buffer, and coordinates the policy updates according to the specified learning algorithm.
In general, the process can be described as executing a number of epochs as follows:
- epoch:
- repeat until a sufficient number of steps is reached (for online learning, typically environment step count)
- training step:
- for online learning algorithms …
- collection step: collect state transitions in the environment by running the agent
- (optionally) conduct a test step if collected data indicates promising behaviour
- update step: apply gradient updates using the algorithm’s update logic.
The update is based on …- data from the preceding collection step only (on-policy learning)
- data from the collection step and previous data (off-policy learning)
- data from a user-provided replay buffer (offline learning)
- for online learning algorithms …
- training step:
- test step
- collect test episodes from dedicated test environments and evaluate agent performance
- (optionally) stop training early if performance is sufficiently high
- repeat until a sufficient number of steps is reached (for online learning, typically environment step count)
:class: note
The above introduces some of the key terms used throughout Tianshou.Note that the above description encompasses several modes of model-free reinforcement learning, including:
- online learning (where the agent continuously interacts with the environment in order to collect new experiences)
- on-policy learning (where the policy is updated based on data collected using the current policy only)
- off-policy learning (where the policy is updated based on data collected using the current and previous policies)
- offline learning (where the replay buffer is pre-filled and not updated during training)
In Tianshou, the {class}~tianshou.trainer.trainer.Trainer and {class}~tianshou.algorithm.algorithm_base.Algorithm classes are specialised to handle these different modes accordingly.
File: docs/01_user_guide/01_apis.md
Dual APIs
Tianshou provides two distinct APIs to serve different use cases and user preferences:
- high-level API: a declarative, configuration-based interface designed for ease of use
- procedural API: a flexible, imperative interface providing maximum control
Both APIs access the same underlying algorithm implementations, allowing you to choose the level
of abstraction that best fits your needs without sacrificing functionality.
Overview
High-Level API
The high-level API is built around the builder pattern and declarative semantics.
Instead of writing procedural code that sequentially constructs and connects components,
you declare what you want through configuration objects and let Tianshou handle how to
build and execute the experiment.
Key characteristics:
- centered around {class}
~tianshou.highlevel.experiment.ExperimentBuilderclasses (e.g., {class}~tianshou.highlevel.experiment.DQNExperimentBuilder, {class}~tianshou.highlevel.experiment.PPOExperimentBuilder, etc.) - uses configuration dataclasses and factories for all relevant parameters
- automatically handles component creation and "wiring"
- provides sensible defaults that adapt to the nature of your environment
- includes built-in persistence, logging, and experiment management
- full type hints (but object structure is not flat; a proper IDE is required for seamless user experience)
Procedural API
The procedural API provides explicit control over every component in the RL pipeline.
You manually create environments, networks, policies, algorithms, collectors, and
trainers, then wire them together.
Key characteristics:
- direct instantiation of all components
- explicit control over the training loop
- lower-level access to internal mechanisms
- minimal abstraction (closer to the implementation)
- ideal for algorithm development and research
When to Use Which API
Use the high-level API when ...
- you're applying existing algorithms to new problems
- you want to get started quickly with minimal boilerplate
- you need experiment management with persistence, logging, and reproducibility
- you prefer declarative code that focuses on configuration
- you're building applications rather than developing new algorithms
Use the procedural API when:
- you're developing new algorithms or modifying existing ones
- you need fine-grained control over the training process
- you want to understand the internal workings of Tianshou
- you're implementing custom components not supported by the high-level API
- you prefer imperative programming where each step is explicit
- you need maximum flexibility for experimental research
Comparison by Example
Let's compare both APIs by implementing the same DQN learning task on the CartPole environment.
High-Level API Example
from tianshou.highlevel.config import OffPolicyTrainingConfig
from tianshou.highlevel.env import EnvFactoryRegistered, VectorEnvType
from tianshou.highlevel.experiment import DQNExperimentBuilder, ExperimentConfig
from tianshou.highlevel.params.algorithm_params import DQNParams
from tianshou.highlevel.trainer import EpochStopCallbackRewardThreshold
# Build the experiment through configuration
experiment = (
DQNExperimentBuilder(
# Environment configuration
EnvFactoryRegistered(
task="CartPole-v1",
venv_type=VectorEnvType.DUMMY,
training_seed=0,
test_seed=10,
),
# Experiment settings
ExperimentConfig(
persistence_enabled=False,
watch=True,
watch_render=1 / 35,
watch_num_episodes=100,
),
# Training configuration
OffPolicyTrainingConfig(
max_epochs=10,
epoch_num_steps=10000,
batch_size=64,
num_training_envs=10,
num_test_envs=100,
buffer_size=20000,
collection_step_num_env_steps=10,
update_step_num_gradient_steps_per_sample=1 / 10,
),
)
# Algorithm-specific parameters
.with_dqn_params(
DQNParams(
lr=1e-3,
gamma=0.9,
n_step_return_horizon=3,
target_update_freq=320,
eps_training=0.3,
eps_inference=0.0,
),
)
# Network architecture
.with_model_factory_default(hidden_sizes=(64, 64))
# Stop condition
.with_epoch_stop_callback(EpochStopCallbackRewardThreshold(195))
.build()
)
# Run the experiment
experiment.run()What's happening here:
- We create an {class}
~tianshou.highlevel.experiment.ExperimentBuilderwith three main configuration objects - We chain builder methods to specify algorithm parameters, model architecture, and callbacks
- We call
.build()to construct the experiment - We call
.run()to execute the entire training pipeline
The high-level API handles ...
- creating and configuring environments
- building the neural network
- instantiating the policy and algorithm
- setting up collectors and replay buffer
- managing the training loop
- watching the trained agent
Procedural API Example
import gymnasium as gym
import tianshou as ts
from tianshou.algorithm.modelfree.dqn import DiscreteQLearningPolicy
from tianshou.algorithm.optim import AdamOptimizerFactory
from tianshou.data import CollectStats
from tianshou.trainer import OffPolicyTrainerParams
from tianshou.utils.net.common import Net
from tianshou.utils.space_info import SpaceInfo
from torch.utils.tensorboard import SummaryWriter
# Define hyperparameters
task = "CartPole-v1"
lr, epoch, batch_size = 1e-3, 10, 64
num_training_envs, num_test_envs = 10, 100
gamma, n_step, target_freq = 0.9, 3, 320
buffer_size = 20000
eps_train, eps_test = 0.1, 0.05
epoch_num_steps, collection_step_num_env_steps = 10000, 10
# Set up logging
logger = ts.utils.TensorboardLogger(SummaryWriter("log/dqn"))
# Create environments
training_envs = ts.env.DummyVectorEnv([lambda: gym.make(task) for _ in range(num_training_envs)])
test_envs = ts.env.DummyVectorEnv([lambda: gym.make(task) for _ in range(num_test_envs)])
# Build the network
env = gym.make(task, render_mode="human")
space_info = SpaceInfo.from_env(env)
state_shape = space_info.observation_info.obs_shape
action_shape = space_info.action_info.action_shape
net = Net(state_shape=state_shape, action_shape=action_shape, hidden_sizes=[128, 128, 128])
# Create policy and algorithm
policy = DiscreteQLearningPolicy(
model=net,
action_space=env.action_space,
eps_training=eps_train,
eps_inference=eps_test,
)
algorithm = ts.algorithm.DQN(
policy=policy,
optim=AdamOptimizerFactory(lr=lr),
gamma=gamma,
n_step_return_horizon=n_step,
target_update_freq=target_freq,
)
# Set up collectors
training_collector = ts.data.Collector[CollectStats](
algorithm,
training_envs,
ts.data.VectorReplayBuffer(buffer_size, num_training_envs),
exploration_noise=True,
)
test_collector = ts.data.Collector[CollectStats](
algorithm,
test_envs,
exploration_noise=True,
)
# Define stop condition
def stop_fn(mean_rewards: float) -> bool:
if env.spec and env.spec.reward_threshold:
return mean_rewards >= env.spec.reward_threshold
return False
# Train the algorithm
result = algorithm.run_training(
OffPolicyTrainerParams(
training_collector=training_collector,
test_collector=test_collector,
max_epochs=epoch,
epoch_num_steps=epoch_num_steps,
collection_step_num_env_steps=collection_step_num_env_steps,
test_step_num_episodes=num_test_envs,
batch_size=batch_size,
update_step_num_gradient_steps_per_sample=1 / collection_step_num_env_steps,
stop_fn=stop_fn,
logger=logger,
test_in_training=True,
)
)
print(f"Finished training in {result.timing.total_time} seconds")
# Watch the trained agent
collector = ts.data.Collector[CollectStats](algorithm, env, exploration_noise=True)
collector.collect(n_episode=100, render=1 / 35)What's happening here:
- We explicitly define all hyperparameters as variables
- We manually create the logger
- We construct training and test environments
- We build the neural network by extracting space information from the environment
- We create the policy and algorithm objects
- We set up collectors with a replay buffer
- We define callback functions
- We call
algorithm.run_training()with explicit parameters - We manually set up and run the evaluation collector
The procedural API requires ...
- explicit creation of every component
- manual extraction of environment properties
- direct specification of all connections
Key Concepts in the High-Level API
ExperimentBuilder
The {class}~tianshou.highlevel.experiment.ExperimentBuilder is the core abstraction.
Each algorithm has its own builder (e.g., {class}~tianshou.highlevel.experiment.DQNExperimentBuilder, {class}~tianshou.highlevel.experiment.PPOExperimentBuilder, {class}~tianshou.highlevel.experiment.SACExperimentBuilder).
Some methods you will find in experiment builders:
.with_<algorithm>_params()- Set algorithm-specific parameters.with_model_factory(),.with_model_factory_default()- Configure network architecture.with_critic_factory()- Configure critic network (for actor-critic methods).with_epoch_train_callback()- Add function to be called at the beginning of the training step in each epoch.with_epoch_test_callback()- Add function to be called at the beginning of the test step in each epoch.with_epoch_stop_callback()- Define stopping conditions.with_algorithm_wrapper_factory()- Add algorithm wrappers (e.g., ICM)
Configuration Objects
Three main configuration objects are required when constructing an experiment builder:
Environment Configuration ({class}
~tianshou.highlevel.env.EnvFactorysubclasses)- Defines how to create and configure environments
- Existing factories:
- {class}
~tianshou.highlevel.env.EnvFactoryRegistered- For the creation of environments registered in Gymnasium - {class}
~tianshou.highlevel.env.atari.atari_wrapper.AtariEnvFactory- For Atari environments with preprocessing
- {class}
- Custom factories for your own environments can be created by subclassing {class}
~tianshou.highlevel.env.EnvFactory
Experiment Configuration ({class}
~tianshou.highlevel.experiment.ExperimentConfig):
General settings for the experiment, particularly related to- logging
- randomization
- persistence
- watching the trained agent's performance after training
Training Configuration ({class}
~tianshou.highlevel.config.OffPolicyTrainingConfig, {class}~tianshou.highlevel.config.OnPolicyTrainingConfig):
Defines all parameters related to the training process
Parameter Classes
Algorithm parameters are defined in dataclasses specific to each algorithm (e.g., {class}~tianshou.highlevel.params.algorithm_params.DQNParams, {class}~tianshou.highlevel.params.algorithm_params.PPOParams).
The parameters are extensively documented.
Make sure to use a modern IDE to take advantage of auto-completion and inline documentation!Factories
The high-level API uses factories extensively:
- Model Factories: Create neural networks (e.g., {class}
~tianshou.highlevel.module.intermediate.IntermediateModuleFactoryAtariDQN) - Environment Factories: Create and configure environments
- Optimizer Factories: Create optimizers with specific configurations
Extensibility
The high-level API is designed to be extensible.
You can create custom factories (e.g. for your own models or your own environments) by subclassing the appropriate base classes
and then use them in the experiment builder.
If we have created a torch module in CustomNetwork, which we want to use within our policy,
we simply need to define a factory for it in order to apply it in the high-level API:
from tianshou.highlevel.env import Environments
from tianshou.highlevel.module.core import TDevice
from tianshou.highlevel.module.intermediate import IntermediateModuleFactory, IntermediateModule
class CustomNetFactory(IntermediateModuleFactory):
def __init__(self, hidden_sizes: tuple[int, ...] = (128, 128)):
self.hidden_sizes = hidden_sizes
def create_intermediate_module(self, envs: Environments, device: TDevice) -> IntermediateModule:
obs_shape = envs.get_observation_shape()
action_shape = envs.get_action_shape()
# Your custom network creation logic
net = CustomNetwork(
obs_shape=obs_shape,
action_shape=action_shape,
hidden_sizes=self.hidden_sizes,
).to(device)
return IntermediateModule(net, net.output_dim)
experiment = (
DQNExperimentBuilder(...)
.with_model_factory(CustomNetFactory(hidden_sizes=(256, 256)))
.build()
)Key Concepts in the Procedural API
Core Components
You manually create and connect ...
- environments: e.g. using
gym.make()and vectorization ({class}~tianshou.env.DummyVectorEnv, {class}~tianshou.env.SubprocVectorEnv) - networks: using {class}
~tianshou.utils.net.common.Netor other PyTorch modules - policies: using algorithm-specific policy classes (e.g., {class}
~tianshou.algorithm.modelfree.dqn.DiscreteQLearningPolicy) - algorithms: using algorithm classes (e.g., {class}
~tianshou.algorithm.modelfree.dqn.DQN, {class}~tianshou.algorithm.modelfree.ppo.PPO, {class}~tianshou.algorithm.modelfree.sac.SAC) - collectors: using {class}
~tianshou.data.Collectorto gather experience - buffers: using {class}
~tianshou.data.buffer.VectorReplayBufferor {class}~tianshou.data.buffer.ReplayBuffer - trainers: using the respective trainer class and corresponding parameter class (e.g., {class}
~tianshou.trainer.OffPolicyTrainerand {class}~tianshou.trainer.OffPolicyTrainerParams)
Training Loop
The training is executed via algorithm.run_training(), which takes a trainer parameter object.
You can alternatively implement custom training loops (or even your own trainer class) for maximum flexibility.
Additional Resources
- high-Level API examples: See
examples/directory (scripts ending in_hl.py) - procedural API examples: See
examples/directory (scripts without suffix)
File: docs/01_user_guide/02_core_abstractions.md
Core Abstractions
Tianshou's architecture is built around a number of key abstractions that work together to provide a modular and flexible reinforcement learning framework.
This document describes the conceptual foundation and functionality of each abstraction, helping you understand how they interact to enable RL agent training.
Knowing these abstractions is primarily relevant when using the procedural API – and particularly when implementing one's own learning algoriithms.
Algorithm
The {class}~tianshou.algorithm.algorithm_base.Algorithm is the central abstraction representing the core of a reinforcement learning method (such as DQN, PPO, or SAC).
It implements the key steps within the {ref}learning process <structuring-the-process>, containing a {ref}policy and defining how to update it from experience data.
Since an Algorithm contains neural networks and manages their training, the class inherits from torch.nn.Module.
Core Responsibilities
An Algorithm implements the details of an {ref}update step <structuring-the-process>:
preprocessing: Before the actual update begins, the algorithm prepares the training data.
This includes computing derived quantities that depend on temporal sequences, such as n-step returns, GAE advantages, or terminal state handling.
The {meth}~tianshou.algorithm.algorithm_base.Algorithm._preprocess_batchmethod handles this phase, often leveraging static methods like
{meth}~tianshou.algorithm.algorithm_base.Algorithm.compute_nstep_returnand
{meth}~tianshou.algorithm.algorithm_base.Algorithm.compute_episodic_returnto
efficiently compute returns using the buffer's temporal structure.network update: The algorithm performs the actual neural network updates based on its specific learning method.
Each algorithm implements its own {meth}~tianshou.algorithm.algorithm_base.Algorithm._update_with_batchlogic that defines how to update
the policy networks using the preprocessed batch data.postprocessing: After the update, the algorithm may perform cleanup operations, such as updating prioritized replay buffer weights or other
algorithm-specific bookkeeping.
Learning Orchestration
The Algorithm orchestrates the {ref}update step <structuring-the-process> through its
{meth}~tianshou.algorithm.algorithm_base.Algorithm.update method, which ensures these three phases execute in proper sequence.
It also manages optimizer state and learning rate schedulers, making them available for state persistence through
{meth}~tianshou.algorithm.algorithm_base.Algorithm.state_dict and
{meth}~tianshou.algorithm.algorithm_base.Algorithm.load_state_dict methods.
Each algorithm type (on-policy, off-policy, offline) creates its appropriate trainer through the
{meth}~tianshou.algorithm.algorithm_base.Algorithm.create_trainer method,
establishing the connection between the learning logic and the training loop.
(policy)=
Policy
The {class}~tianshou.algorithm.algorithm_base.Policy represents the agent's decision-making component, i.e. the mapping from observations to actions.
While the Algorithm defines how to learn, the Policy defines what is learned and how to act.
Like Algorithm, the class inherits from torch.nn.Module.
States of Operation
A Policy operates in two main modes:
training mode: During training, the policy may employ exploration strategies, sample from action distributions, or add noise to encourage discovery.
Training mode is further divided into:- collecting state: When gathering experience from environment interaction
- updating state: When performing network updates during learning
testing/inference mode: During evaluation, the policy typically acts deterministically or uses the mode of predicted distributions to showcase
learned behavior without exploration.
The flag is_within_training_step controls the collection strategy, distinguishing between training and inference behavior.
Key Methods
The Policy provides several essential methods:
{meth}
~tianshou.algorithm.algorithm_base.Policy.forward:
The core computation method that processes batched observations to produce action distributions or Q-values.
It takes a batch of environment data and optional hidden state (for recurrent policies),
returning a batch containing at minimum the "act" key,
and potentially "state" (hidden state) and "policy" (intermediate results to be stored in the buffer).{meth}
~tianshou.algorithm.algorithm_base.Policy.compute_action:
A convenient method for inference that takes a single observation and returns a concrete action suitable for the environment.
This method internally callsforwardwith proper batching and unbatching.{meth}
~tianshou.algorithm.algorithm_base.Policy.map_action: Transforms the raw neural network output to the environment's action space format, handling any necessary scaling or discretization.
The separation between forward (which works with batches) and
{meth}~tianshou.algorithm.algorithm_base.Policy.compute_action (which works with single observations) provides efficiency
during training and convenience during inference.
Collector
The class {class}~tianshou.data.Collector bridges the gap between the policy and the environment(s),
managing the process of gathering experience data.
It enables efficient interaction with both single environments and vectorized environments (multiple parallel environments).
Data Collection
The Collector's primary method, {meth}~tianshou.data.Collector.collect, orchestrates the environment interaction loop. It can collect either:
- a specified number of steps (
n_step): useful for maintaining consistent training batch sizes - a specified number of episodes (
n_episode): useful for evaluation or when episode-level statistics are important
During collection, the Collector ...
- obtains observations from the environment(s),
- calls the policy to compute actions,
- steps the environment(s) with these actions,
- stores the resulting transitions (observation, action, reward, next observation, termination flags, and info) in the replay buffer,
- manages episode boundaries and reset logic,
- collects statistics such as episode returns, lengths, and collection speed.
Hooks and Extensibility
The Collector supports customization through hooks that can be triggered at different points in the collection process:
- step hooks: called after each environment step
- episode done hooks: called when episodes complete
These hooks enable custom logging, curriculum learning, or other dynamic behaviors during data collection.
Vectorized Environments
The Collector seamlessly handles vectorized environments, where multiple environment instances run in parallel.
This significantly speeds up data collection while maintaining correct episode boundaries and statistics for each environment instance.
Trainer
The {class}~tianshou.trainer.Trainer orchestrates the complete training loop, coordinating data collection, policy updates, and evaluation.
It provides the high-level control flow that brings all components together.
Trainer Types
Tianshou provides three main trainer types, each suited to different algorithm families:
{class}
~tianshou.trainer.OnPolicyTrainer: for algorithms that must learn from freshly collected data (e.g., PPO, A2C).
After each collection phase, the buffer is used for updates and thereafter is cleared.{class}
~tianshou.trainer.OffPolicyTrainer: for algorithms that can learn from any past experience (e.g., DQN, SAC, DDPG).
Data accumulates in the replay buffer over time, and updates sample from this growing pool of experience.{class}
~tianshou.trainer.OfflineTrainer: for algorithms that learn exclusively from a fixed dataset without any environment interaction (e.g., BCQ, CQL).
Training Loop Structure
The training process is organized into epochs, where each epoch consists of:
- data collection: The trainer uses the train collector to gather experience according to its algorithm type's needs
- policy update: The algorithm performs one or more update steps using the collected data
- evaluation: Periodically, the trainer uses the test collector to evaluate the current policy's performance
- logging: Statistics from collection, updates, and evaluation are logged
- checkpointing: The best policy (according to a scoring function) is saved
The trainer handles the detailed choreography of these steps, including determining when to collect more data,
how many update steps to perform, when to evaluate, and when to stop training (based on maximum epochs, timesteps, or early stopping criteria).
Configuration
Trainers are configured through parameter dataclasses
({class}~tianshou.trainer.OnPolicyTrainerParams, {class}~tianshou.trainer.OffPolicyTrainerParams, {class}~tianshou.trainer.OfflineTrainerParams)
that specify in particular:
- training duration (number of epochs, steps per epoch)
- collectors for training and testing
- update frequency and batch size
- evaluation frequency
- logging and checkpointing settings
- early stopping criteria
Batch
The class {class}~tianshou.data.Batch is Tianshou's flexible data structure for passing information between components.
It serves as the lingua franca of the framework, carrying everything from raw environment observations to computed returns and policy outputs.
Design Philosophy
Batch is designed to be ...
- flexible: can contain any key-value pairs, with nested structures supported,
- numpy/torch-compatible: automatically converts lists to arrays and seamlessly works with both NumPy arrays and PyTorch tensors,
- sliceable: supports indexing and slicing operations that work across all contained data,
- composable: can be concatenated, stacked, and split to support batching operations.
Type Safety with BatchProtocol
While Batch provides a flexible, dictionary-like structure for holding arbitrary data, this flexibility can make it challenging to statically type-check which attributes are present in a batch at any given point in the code. To address this, Tianshou uses {class}~tianshou.data.batch.BatchProtocol and derived protocols to specify the expected attributes while keeping the actual runtime type as Batch.
BatchProtocol is a Python Protocol (from typing.Protocol) that defines the interface of a Batch object, specifying which operations and attributes should be available. More importantly, Tianshou provides a rich set of derived protocols in {mod}tianshou.data.types that describe batches with specific sets of attributes commonly used throughout the framework:
- {class}
~tianshou.data.types.ObsBatchProtocol: Containsobsandinfo- the minimal batch for policy forward passes - {class}
~tianshou.data.types.RolloutBatchProtocol: Addsobs_next,act,rew,terminated, andtruncated- typical data from replay buffer sampling - {class}
~tianshou.data.types.BatchWithReturnsProtocol: Extends RolloutBatchProtocol withreturnscomputed from rewards - {class}
~tianshou.data.types.BatchWithAdvantagesProtocol: Includesadv(advantages) andv_s(value estimates) for policy gradient methods - {class}
~tianshou.data.types.ActStateBatchProtocol: Containsactandstatefor policy outputs, especially with RNN support - {class}
~tianshou.data.types.ModelOutputBatchProtocol: Addslogitsto action and state information - {class}
~tianshou.data.types.DistBatchProtocol: Contains action distributions (dist) for stochastic policies - {class}
~tianshou.data.types.PrioBatchProtocol: Includesweightfor prioritized experience replay
These protocols serve as type hints in function signatures throughout Tianshou, making it explicit what attributes are expected and available. For example, a policy's forward method might accept an ObsBatchProtocol and return an ActStateBatchProtocol, clearly documenting the data contract. Despite these type annotations, the actual objects remain flexible Batch instances at runtime, preserving Tianshou's dynamic nature while improving code clarity and IDE support.
Common Use Cases
Batches flow through the system carrying different types of information:
- environment data: observations, rewards, done flags, and info from environment steps
- policy outputs: actions, hidden states, and intermediate computations
- training data: returns, advantages, and other computed quantities needed for learning
- sampling results: batches sampled from the replay buffer for training
Operations
Key operations on batches include:
- attribute access: dot notation (
batch.obs) or dictionary-style access (batch['obs']) - slicing: extract subsets with standard indexing (
batch[0:10],batch[[1,3,5]]) - stacking: combine multiple batches along a new dimension
- type conversion: convert between NumPy and PyTorch with
to_numpy()andto_torch() - null handling: detect and remove null values with
hasnull(),isnull(), anddropnull()
The first dimension of all data in a Batch represents the batch size, enabling vectorized operations.
Buffer
A buffer (i.e. class {class}~tianshou.data.buffer.ReplayBuffer and its variants) manages the storage and retrieval of experience data.
It acts as the memory of the learning system, preserving the temporal structure of episodes while providing efficient access patterns.
Storage Structure
Buffers store data in a circular queue fashion with a fixed maximum size. When the buffer fills, new data overwrites the oldest stored experiences.
All data is stored within a single underlying Batch object, with the buffer managing:
- pointer tracking: current insertion position
- episode boundaries: which transitions belong to which episodes
- temporal relationships: the sequential order of transitions
Reserved Keys
Buffers use a standard set of keys for storing transitions:
obs: Observation at time tact: Action taken at time trew: Reward received at time tterminated: True if the episode ended naturally at time ttruncated: True if the episode was cut off at time t (e.g., time limit)done: Automatically inferred asterminated or truncatedobs_next: Observation at time t+1info: Additional information from the environmentpolicy: Intermediate policy computations to be stored
Core Operations
adding data: The {meth}~tianshou.data.buffer.buffer_base.ReplayBuffer.add method stores new transitions,
automatically handling episode boundaries and computing episode statistics (return, length)
when episodes complete.
sampling: The {meth}~tianshou.data.buffer.buffer_base.ReplayBuffer.sample method retrieves batches of experiences for training,
returning both the sampled batch and the corresponding indices.
The sample size can be specified, or set to 0 to retrieve all available data.
temporal navigation: The {meth}~tianshou.data.buffer.buffer_base.ReplayBuffer.prev and {meth}~tianshou.data.buffer.ReplayBuffer.next
methods enable traversal along the temporal sequence, respecting episode boundaries.
This is essential for computing n-step returns and other time-dependent quantities.
persistence: Buffers support saving and loading via pickle or HDF5 format, enabling dataset collection and offline learning.
Buffer Variants
Tianshou provides specialized buffer types:
- {class}
~tianshou.data.buffer.buffer_base.ReplayBuffer: the standard buffer for single environments - {class}
~tianshou.data.buffer.vecbuf.VectorReplayBuffer: manages separate sub-buffers for multiple parallel environments while maintaining chronological order - {class}
~tianshou.data.buffer.prio.PrioritizedReplayBuffer: samples transitions based on their TD-error or other priority metrics, using an efficient segment tree implementation
Advanced Features
Buffers support sophisticated use cases:
- frame stacking: automatically stacks consecutive observations (useful for RNN inputs or Atari)
- memory optimization: option to skip storing next observations (useful for Atari where they can be inferred)
- multi-modal observations: handle observations with multiple components (e.g., image + vector)
Logger
The {class}~tianshou.utils.logger.logger_base.BaseLogger abstraction provides a unified interface for recording and tracking training progress, metrics, and statistics.
It decouples the training loop from the specifics of where and how data is logged.
Purpose
Loggers serve several essential functions:
- progress tracking: record timesteps, episodes, and epochs as training progresses
- metric collection: store performance indicators like rewards, losses, and success rates
- experiment organization: manage different data scopes (training, testing, updating)
- reproducibility: save training curves and hyperparameters for later analysis
Logging Scopes
The framework organizes logged data into distinct scopes:
- train data: metrics from the training collector (episode returns, steps, collection speed)
- test data: evaluation metrics from the test collector
- update data: learning statistics from the algorithm (losses, gradients, learning rates)
- info data: additional custom metrics or metadata
Each scope has a corresponding log method (log_train_data, log_test_data, log_update_data, log_info_data) that the trainer calls at appropriate times.
Implementations
Tianshou provides several logger implementations:
- {class}
~tianshou.utils.logger.tensorboard.TensorboardLogger: writes to TensorBoard format for visualization with TensorBoard - {class}
~tianshou.utils.logger.wandb.WandbLogger: integrates with Weights & Biases for cloud-based experiment tracking
All implementations inherit from {class}~tianshou.utils.logger.logger_base.BaseLogger and share a common interface,
making it easy to switch between logging backends or use multiple loggers simultaneously.
Data Preparation
Before writing, loggers prepare data through the prepare_dict_for_logging method, which can filter, transform, or aggregate metrics.
The write method then persists the prepared data to the logging backend with an associated step count.
How They Work Together
These seven abstractions collaborate to enable reinforcement learning:
- The Trainer initializes and orchestrates the training process.
- The Collector uses the Policy to gather experience from environments.
- Collected transitions are stored in the Buffer extracted as Batches.
- The Algorithm samples from the Buffer, preprocesses the data, and updates the Policy.
- The Logger records metrics throughout the process.
- The cycle repeats until training completes.
This modular design allows each component to focus on its specific responsibility while maintaining clean interfaces.
You can customize individual components (e.g., implementing a new Algorithm or Buffer) without affecting the others,
making Tianshou both powerful and flexible.
File: docs/01_user_guide/index.rst
User Guide
The user guide provides an introduction to core concepts, establishes the glossary of terms,
introduces Tianshou's dual API architecture and provides an overview of important abstractions.
File: docs/02_deep_dives/0_intro.md
Deep Dives
Our deep dives are a collection of executable tutorials on some of the internal representations used by Tianshou.
Provided as notebooks, you can run them directly in Colab or download them to run them locally.
File: docs/04_benchmarks/benchmarks.rst
Benchmarks
Tianshou's algorithm implementations lead to state-of-the-art results on standard benchmarks.
An efficient parallel implementation for
evaluating algorithms on mujoco or atari is in benchmark/run_benchmark.py. It can easily be adapted for custom
benchmarks as well. The reported results are thus completely reproducible.
The evaluation code uses Tianshou's integration with the rliable <https://github.com/google-research/rliable>_ framework,
which supports best practices for trustworthy RL evaluation.
Each experiment is conducted under 5 random seeds, we report the interquartile mean (IQM) and 95% confidence intervals over these seeds.
.. raw:: html
<select id="env-mujoco" onchange="showMujocoResults(this)"></select>File: docs/05_developer_guide/developer_guide.md
Developer Guide
The section addresses developers of Tianshou, providing information for
both casual contributors and maintainers alike.
Python Virtual Environment
Tianshou is built and managed by poetry.
The development environment uses Python 3.11.
To install all relevant requirements (as well as Tianshou itself in editable mode)
you can simply call
poetry install --with devDepending on your setup, you may need to create and activate an empty virtual environment
using the right Python version beforehand. For instance, to do this with conda, use:
conda create -n tianshou python=3.11
conda activate tianshouCode Style and Auto-Formatting
When editing code in Tianshou, strive for local consistency, i.e.
adhere to the style already present in the codebase.
Tianshou uses an auto-formatting for consistency.
To apply it, call
poe formatTo check whether your formatting is compliant without applying the
auto-formatter, call
poe lintType Checking
We use mypy to perform static type analysis.
To check typing, run
poe type-checkTests
Running the Test Suite Locally
Tianshou uses pytest. Tests are located in ./test.
To run the full set of tests locally, run
poe testDeterminism Tests
We implemented determinism tests for Tianshou's algorithms, which allow us to determine
whether algorithms still compute exactly the same results even after large refactorings.
These tests are applied by
- creating a behavior snapshot in the old code branch before the changes and then
- running the respective determinism test in the new branch to ensure that the behavior is the same.
Unfortunately, full determinism is difficult to achieve across different platforms and even different
machines using the same platform an Python environment.
Therefore, these tests are not carried out in the CI pipeline.
Instead, it is up to the developer to run them locally and check the results whenever a change
is made to the codebase that could affect algorithm behavior.
Technically, the two steps are handled by setting static flags in class AlgorithmDeterminismTest and then
running either the full test suite or a specific determinism test (test_*_determinism, e.g. test_ddpg_determinism)
in the two branches to be compared.
- On the old branch: (Temporarily) set
ENABLED=TrueandFORCE_SNAPSHOT_UPDATE=Trueand run the test(s). - On the new branch: (Temporarily) set
ENABLED=TrueandFORCE_SNAPSHOT_UPDATE=Falseand run the test(s). - Inspect the test results; find a summary in
determinism_tests.log
Tests in CI (GitHub Actions)
CI tests will extensively test Tianshou's functionality in multiple environments.
In particular, we test
- on Ubuntu (full functionality tested)
- py_pinned: using the pinned development environment (Python 3.11, known versions of all dependencies)
- py_latest: using a more recent Python version with the newest set of compatible dependencies (automatically resolved)
- on Windows and macOS (core functionality tested)
Principle of Maximum Compatibility
The idea behind testing with dynamically resolved dependencies is that we want to maximize the applicability
of Tianshou: For important dependencies that could conflict with environments used by our users, we do not restrict the version of a dependency unless there is a known incompatibility.
If incompatibilities should arise (e.g. by the "py_latest" test failing), we either
- resolve them by making the code compatible with both old and new versions OR
- add an upper bound to our dependency declarations (excluding the incompatible versions) and release a new
version of Tianshou to make these exclusions explicit.
High-Level API
The high-level API provides a declarative, user-friendly interface for setting up reinforcement learning experiments. From a library developer's perspective, it is important that this API be clearly structured and maintainable. This section explains the architectural principles and how to extend the API to support new algorithms.
Core Abstractions
The high-level API is built around a clear separation of concerns:
Parameter Classes are dataclasses (inheriting from Params) that represent algorithm-specific configuration.
They capture hyperparameters in a high-level, user-friendly form.
Because the high-level interface must abstract away from low-level details, parameters may need transformation before being passed to policy classes.
This is handled via ParamTransformer instances, which successively transform the parameter dictionary representation.
To maintain clarity and reduce coupling, parameter transformers are co-located with the parameters they affect.
The system uses inheritance and mixins extensively to reduce duplication while maintaining flexibility.
Factories embody the principle of declarative configuration.
Because object creation may depend on other objects that don't yet exist at configuration time (e.g., neural networks depend on environment properties),
the API transitions from objects to factories.
Key factory types include:
EnvFactoryfor creating training, test, and watch environmentsAgentFactoryas the central factory that creates policies, trainers, and collectors- Various specialized factories for optimizers, actors, critics, noise, distributions, learning rate schedulers, and policy wrappers
Algorithm Factories (subclasses of AlgorithmFactory) are the core components responsible for orchestrating the creation of all algorithm-specific objects.
They handle the creation of neural network architectures, apply parameter transformations, instantiate policies, and create trainers with appropriate collectors.
To support a new algorithm, this is the primary extension point.
Experiment Builders (subclasses of ExperimentBuilder) provide the user-facing interface following the builder pattern.
They contain sensible defaults while allowing customization through fluent with_* methods.
Builder mixins provide composable functionality for common patterns (e.g., actor/critic configuration), avoiding code duplication across algorithm-specific implementations.
Supporting a New Algorithm
Extending the high-level API to support a new algorithm involves creating three main components:
Parameter Class: Define a dataclass in tianshou/highlevel/params/algorithm_params.py that inherits from appropriate base classes and mixins.
The choice of base class depends on the algorithm's architecture (actor-critic, single network, etc.) and learning paradigm (on-policy, off-policy).
Override _get_param_transformers() to specify how high-level parameters should be transformed for the low-level policy API.
Common transformers handle optimizer creation, noise instantiation, and environment-dependent parameter resolution.
Algorithm Factory: Implement a subclass of AlgorithmFactory in tianshou/highlevel/algorithm.py.
In most cases, inherit from existing base factories like ActorCriticOnPolicyAlgorithmFactory, ActorCriticOffPolicyAlgorithmFactory,
or DiscreteCriticOnlyOffPolicyAlgorithmFactory, which handle common creation patterns.
The primary requirement is implementing _get_algorithm_class() to return the appropriate algorithm class.
For algorithms with non-standard requirements, override _create_algorithm(), _create_kwargs(), etc. to customize the instantiation logic.
Experiment Builder: Add a builder class in tianshou/highlevel/experiment.py that inherits from OnPolicyExperimentBuilder or OffPolicyExperimentBuilder
along with appropriate mixins. The mixins provide standard functionality for configuring actors and critics
(single critic, dual critics, critic ensembles, parameter sharing patterns, etc.).
The main responsibility is implementing _create_algorithm_factory() to instantiate the algorithm factory with appropriate parameters and network factories.
Optionally provide with_* methods for algorithm-specific configuration.
Export the new classes in tianshou/highlevel/__init__.py to make them available to users.
Design Principles
The architecture follows several key principles:
Separation of Concerns: Configuration is cleanly separated from implementation.
The transformation system bridges these layers while maintaining independence.
Declarative Configuration: Factories enable a declarative style where experiments are defined by what should be created rather than imperative steps.
This makes experiments easily serializable and reproducible.
Composition and Inheritance: Mixins and inheritance reduce code duplication.
Common functionality is factored into reusable components while maintaining flexibility for algorithm-specific requirements.
Progressive Disclosure: The API provides sensible defaults for simple use cases while allowing deep customization when needed.
Users can progress from simple configurations to advanced setups without fighting the abstractions.
Co-location: Related code is kept together. Parameter transformers are defined near the parameters they transform,
maintaining clarity about dependencies and making the codebase easier to navigate.
Type Safety: Extensive use of generics and type hints ensures that type checkers can catch configuration errors at development time rather than runtime.
Documentation
Documentation is in the docs/ directory, using Markdown (.md), ReStructuredText (.rst) and notebook files.index.rst is the main page.
API References are automatically generated by Sphinx according to the outlines under docs/api/ and should be modified when any code changes.
To compile documentation into webpage, run
poe doc-buildThe generated webpages can subsequently be found in docs/_build and can be viewed with any browser.
Verifications
We have several automated verification methods for documentation:
pydocstyle (as part of ruff): tests all docstring under
tianshou/;doc8 (as part of ruff): tests ReStructuredText format;
sphinx spelling and test: test if there is any error/warning when generating front-end html documentation.
Creating a Release
To release a new version on PyPI,
- set the version to be released in
tianshou/__init__.pyand inpyproject.toml, creating a commit - tag the commit with the version (using the format
v1.2.3) - push the commit (
git push) and the tag (git push --tags) - create a new release on GitHub based on the tag; this will trigger the release job for PyPI.
In the past, we provided releases to conda-forge as well, but this is currently not maintained.
File: docs/06_contributors/contributors.rst
Contributors
We always welcome contributions to help make Tianshou better!
Tianshou was originally created by the THU-ML Group <https://ml.cs.tsinghua.edu.cn>_ at Tsinghua University.
Today, it is backed by the appliedAI Institute for Europe <https://www.appliedai-institute.de/en/>_,
a non-profit organization committed to making Tianshou the go-to resource for reinforcement learning research and development,
guaranteeing its long-term maintenance and support.
The original creator Jiayi Weng (Trinkle23897 <https://github.com/Trinkle23897>_) continues
to be involved in Tianshou development.
The current core developers, who are behind the v1.0 and v2.0 releases of Tianshou, are:
- Dominik Jain (
opcode81 <https://github.com/opcode81>_) - Michael Panchenko (
MischaPanch <https://github.com/MischaPanch>_)
An incomplete list of early contributors is:
- Alexis Duburcq (
duburcqa <https://github.com/duburcqa>_) - Kaichao You (
youkaichao <https://github.com/youkaichao>_) - Huayu Chen (
ChenDRAG <https://github.com/ChenDRAG>_) - Yi Su (
nuance1979 <https://github.com/nuance1979>_)
You can find more information about contributors here <https://github.com/thu-ml/tianshou/graphs/contributors>_.
File: examples/atari/README.md
Atari Environment
EnvPool
We highly recommend using envpool to run the following experiments. To install, in a linux machine, type:
pip install envpoolAfter that, atari_wrapper will automatically switch to envpool's Atari env. EnvPool's implementation is much faster (
about 2~3x faster for pure execution speed, 1.5x for overall RL training pipeline) than python vectorized env
implementation, and it's behavior is consistent to that approach (OpenAI wrapper), which will describe below.
For more information, please refer to
EnvPool's GitHub, Docs,
and 3rd-party report.
ALE-py
The sample speed is ~3000 env step per second (~12000 Atari frame per second in fact since we use frame_stack=4) under
the normal mode (use a CNN policy and a collector, also storing data into the buffer).
The env wrapper is a crucial thing. Without wrappers, the agent cannot perform well enough on Atari games. Many existing
RL codebases use OpenAI wrapper,
but it is not the original DeepMind version (related issue). Dopamine
has a different wrapper but
unfortunately it cannot work very well in our codebase.
DQN (single run)
One epoch here is equal to 100,000 env step, 100 epochs stand for 10M.
| task | best reward | reward curve | parameters | time cost |
|---|---|---|---|---|
| PongNoFrameskip-v4 | 20 | python3 atari_dqn.py --task "PongNoFrameskip-v4" --batch_size 64 |
~30 min (~15 epoch) | |
| BreakoutNoFrameskip-v4 | 316 | python3 atari_dqn.py --task "BreakoutNoFrameskip-v4" --num_test_envs 100 |
3~4h (100 epoch) | |
| EnduroNoFrameskip-v4 | 670 | python3 atari_dqn.py --task "EnduroNoFrameskip-v4 " --num_test_envs 100 |
3~4h (100 epoch) | |
| QbertNoFrameskip-v4 | 7307 | python3 atari_dqn.py --task "QbertNoFrameskip-v4" --num_test_envs 100 |
3~4h (100 epoch) | |
| MsPacmanNoFrameskip-v4 | 2107 | python3 atari_dqn.py --task "MsPacmanNoFrameskip-v4" --num_test_envs 100 |
3~4h (100 epoch) | |
| SeaquestNoFrameskip-v4 | 2088 | python3 atari_dqn.py --task "SeaquestNoFrameskip-v4" --num_test_envs 100 |
3~4h (100 epoch) | |
| SpaceInvadersNoFrameskip-v4 | 812.2 | python3 atari_dqn.py --task "SpaceInvadersNoFrameskip-v4" --num_test_envs 100 |
3~4h (100 epoch) |
Note: The eps_train_final and eps_test in the original DQN paper is 0.1 and 0.01,
but some works found that smaller eps helps improve the
performance. Also, a large batchsize (say 64 instead of 32) will help faster convergence but will slow down the training
speed.
We haven't tuned this result to the best, so have fun with playing these hyperparameters!
C51 (single run)
One epoch here is equal to 100,000 env step, 100 epochs stand for 10M.
| task | best reward | reward curve | parameters |
|---|---|---|---|
| PongNoFrameskip-v4 | 20 | python3 atari_c51.py --task "PongNoFrameskip-v4" --batch_size 64 |
|
| BreakoutNoFrameskip-v4 | 536.6 | python3 atari_c51.py --task "BreakoutNoFrameskip-v4" --n-step 1 |
|
| EnduroNoFrameskip-v4 | 1032 | python3 atari_c51.py --task "EnduroNoFrameskip-v4 " |
|
| QbertNoFrameskip-v4 | 16245 | python3 atari_c51.py --task "QbertNoFrameskip-v4" |
|
| MsPacmanNoFrameskip-v4 | 3133 | python3 atari_c51.py --task "MsPacmanNoFrameskip-v4" |
|
| SeaquestNoFrameskip-v4 | 6226 | python3 atari_c51.py --task "SeaquestNoFrameskip-v4" |
|
| SpaceInvadersNoFrameskip-v4 | 988.5 | python3 atari_c51.py --task "SpaceInvadersNoFrameskip-v4" |
Note: The selection of n_step is based on Figure 6 in the Rainbow paper.
QRDQN (single run)
One epoch here is equal to 100,000 env step, 100 epochs stand for 10M.
| task | best reward | reward curve | parameters |
|---|---|---|---|
| PongNoFrameskip-v4 | 20 | python3 atari_qrdqn.py --task "PongNoFrameskip-v4" --batch_size 64 |
|
| BreakoutNoFrameskip-v4 | 409.2 | python3 atari_qrdqn.py --task "BreakoutNoFrameskip-v4" --n-step 1 |
|
| EnduroNoFrameskip-v4 | 1055.9 | python3 atari_qrdqn.py --task "EnduroNoFrameskip-v4" |
|
| QbertNoFrameskip-v4 | 14990 | python3 atari_qrdqn.py --task "QbertNoFrameskip-v4" |
|
| MsPacmanNoFrameskip-v4 | 2886 | python3 atari_qrdqn.py --task "MsPacmanNoFrameskip-v4" |
|
| SeaquestNoFrameskip-v4 | 5676 | python3 atari_qrdqn.py --task "SeaquestNoFrameskip-v4" |
|
| SpaceInvadersNoFrameskip-v4 | 938 | python3 atari_qrdqn.py --task "SpaceInvadersNoFrameskip-v4" |
IQN (single run)
One epoch here is equal to 100,000 env step, 100 epochs stand for 10M.
| task | best reward | reward curve | parameters |
|---|---|---|---|
| PongNoFrameskip-v4 | 20.3 | python3 atari_iqn.py --task "PongNoFrameskip-v4" --batch_size 64 |
|
| BreakoutNoFrameskip-v4 | 496.7 | python3 atari_iqn.py --task "BreakoutNoFrameskip-v4" --n-step 1 |
|
| EnduroNoFrameskip-v4 | 1545 | python3 atari_iqn.py --task "EnduroNoFrameskip-v4" |
|
| QbertNoFrameskip-v4 | 15342.5 | python3 atari_iqn.py --task "QbertNoFrameskip-v4" |
|
| MsPacmanNoFrameskip-v4 | 2915 | python3 atari_iqn.py --task "MsPacmanNoFrameskip-v4" |
|
| SeaquestNoFrameskip-v4 | 4874 | python3 atari_iqn.py --task "SeaquestNoFrameskip-v4" |
|
| SpaceInvadersNoFrameskip-v4 | 1498.5 | python3 atari_iqn.py --task "SpaceInvadersNoFrameskip-v4" |
FQF (single run)
One epoch here is equal to 100,000 env step, 100 epochs stand for 10M.
| task | best reward | reward curve | parameters |
|---|---|---|---|
| PongNoFrameskip-v4 | 20.7 | python3 atari_fqf.py --task "PongNoFrameskip-v4" --batch_size 64 |
|
| BreakoutNoFrameskip-v4 | 517.3 | python3 atari_fqf.py --task "BreakoutNoFrameskip-v4" --n-step 1 |
|
| EnduroNoFrameskip-v4 | 2240.5 | python3 atari_fqf.py --task "EnduroNoFrameskip-v4" |
|
| QbertNoFrameskip-v4 | 16172.5 | python3 atari_fqf.py --task "QbertNoFrameskip-v4" |
|
| MsPacmanNoFrameskip-v4 | 2429 | python3 atari_fqf.py --task "MsPacmanNoFrameskip-v4" |
|
| SeaquestNoFrameskip-v4 | 10775 | python3 atari_fqf.py --task "SeaquestNoFrameskip-v4" |
|
| SpaceInvadersNoFrameskip-v4 | 2482 | python3 atari_fqf.py --task "SpaceInvadersNoFrameskip-v4" |
Rainbow (single run)
One epoch here is equal to 100,000 env step, 100 epochs stand for 10M.
| task | best reward | reward curve | parameters |
|---|---|---|---|
| PongNoFrameskip-v4 | 21 | python3 atari_rainbow.py --task "PongNoFrameskip-v4" --batch_size 64 |
|
| BreakoutNoFrameskip-v4 | 684.6 | python3 atari_rainbow.py --task "BreakoutNoFrameskip-v4" --n-step 1 |
|
| EnduroNoFrameskip-v4 | 1625.9 | python3 atari_rainbow.py --task "EnduroNoFrameskip-v4" |
|
| QbertNoFrameskip-v4 | 16192.5 | python3 atari_rainbow.py --task "QbertNoFrameskip-v4" |
|
| MsPacmanNoFrameskip-v4 | 3101 | python3 atari_rainbow.py --task "MsPacmanNoFrameskip-v4" |
|
| SeaquestNoFrameskip-v4 | 2126 | python3 atari_rainbow.py --task "SeaquestNoFrameskip-v4" |
|
| SpaceInvadersNoFrameskip-v4 | 1794.5 | python3 atari_rainbow.py --task "SpaceInvadersNoFrameskip-v4" |
PPO (single run)
One epoch here is equal to 100,000 env step, 100 epochs stand for 10M.
| task | best reward | reward curve | parameters |
|---|---|---|---|
| PongNoFrameskip-v4 | 20.2 | python3 atari_ppo.py --task "PongNoFrameskip-v4" |
|
| BreakoutNoFrameskip-v4 | 441.8 | python3 atari_ppo.py --task "BreakoutNoFrameskip-v4" |
|
| EnduroNoFrameskip-v4 | 1245.4 | python3 atari_ppo.py --task "EnduroNoFrameskip-v4" |
|
| QbertNoFrameskip-v4 | 17395 | python3 atari_ppo.py --task "QbertNoFrameskip-v4" |
|
| MsPacmanNoFrameskip-v4 | 2098 | python3 atari_ppo.py --task "MsPacmanNoFrameskip-v4" |
|
| SeaquestNoFrameskip-v4 | 882 | python3 atari_ppo.py --task "SeaquestNoFrameskip-v4" --lr 1e-4 |
|
| SpaceInvadersNoFrameskip-v4 | 1340.5 | python3 atari_ppo.py --task "SpaceInvadersNoFrameskip-v4" |
SAC (single run)
One epoch here is equal to 100,000 env step, 100 epochs stand for 10M.
| task | best reward | reward curve | parameters |
|---|---|---|---|
| PongNoFrameskip-v4 | 20.1 | python3 atari_sac.py --task "PongNoFrameskip-v4" |
|
| BreakoutNoFrameskip-v4 | 211.2 | python3 atari_sac.py --task "BreakoutNoFrameskip-v4" --n-step 1 --actor-lr 1e-4 --critic-lr 1e-4 |
|
| EnduroNoFrameskip-v4 | 1290.7 | python3 atari_sac.py --task "EnduroNoFrameskip-v4" |
|
| QbertNoFrameskip-v4 | 13157.5 | python3 atari_sac.py --task "QbertNoFrameskip-v4" |
|
| MsPacmanNoFrameskip-v4 | 3836 | python3 atari_sac.py --task "MsPacmanNoFrameskip-v4" |
|
| SeaquestNoFrameskip-v4 | 1772 | python3 atari_sac.py --task "SeaquestNoFrameskip-v4" |
|
| SpaceInvadersNoFrameskip-v4 | 649 | python3 atari_sac.py --task "SpaceInvadersNoFrameskip-v4" |
File: examples/box2d/README.md
Bipedal-Hardcore-SAC
- Our default choice: remove the done flag penalty, will soon converge to ~280 reward within 100 epochs (10M env steps,
3~4 hours, see the image below) - If the done penalty is not removed, it converges much slower than before, about 200 epochs (20M env steps) to reach
the same performance (~200 reward)
BipedalWalker-BDQ
- To demonstrate the cpabilities of the BDQ to scale up to big discrete action spaces, we run it on a discretized
version of the BipedalWalker-v3 environment, where the number of possible actions in each dimension is 25, for a total
of 25^4 = 390 625 possible actions. A usaual DQN architecture would use 25^4 output neurons for the Q-network, thus
scaling exponentially with the number of action space dimensions, while the Branching architecture scales linearly and
uses only 25*4 output neurons.
File: examples/inverse/README.md
Inverse Reinforcement Learning
In inverse reinforcement learning setting, the agent learns a policy from interaction with an environment without reward
and a fixed dataset which is collected with an expert policy.
Continuous control
Once the dataset is collected, it will not be changed during training. We
use d4rl datasets to train agent for continuous control. You can refer
to d4rl to see how to use d4rl datasets.
We provide implementation of GAIL algorithm for continuous control.
Train
You can parse d4rl datasets into a ReplayBuffer , and set it as the parameter expert_buffer of GAILPolicy.irl_gail.py is an example of inverse RL using the d4rl dataset.
To train an agent with BCQ algorithm:
python irl_gail.py --task HalfCheetah-v2 --expert-data-task halfcheetah-expert-v2GAIL (single run)
| task | best reward | reward curve | parameters |
|---|---|---|---|
| HalfCheetah-v2 | 5177.07 | python3 irl_gail.py --task "HalfCheetah-v2" --expert-data-task "halfcheetah-expert-v2" |
|
| Hopper-v2 | 1761.44 | python3 irl_gail.py --task "Hopper-v2" --expert-data-task "hopper-expert-v2" |
|
| Walker2d-v2 | 2020.77 | python3 irl_gail.py --task "Walker2d-v2" --expert-data-task "walker2d-expert-v2" |
File: examples/modelbased/README.md
PSRL
NChain-v0: python3 psrl.py --task NChain-v0 --epoch_num_steps 10 --rew-mean-prior 0 --rew-std-prior 1
FrozenLake-v0:python3 psrl.py --task FrozenLake-v0 --epoch_num_steps 1000 --rew-mean-prior 0 --rew-std-prior 1 --add-done-loop --epoch 20
Taxi-v3: python3 psrl.py --task Taxi-v3 --epoch_num_steps 1000 --rew-mean-prior 0 --rew-std-prior 2 --epoch 20
File: examples/mujoco/README.md
Tianshou's Mujoco Benchmark
We benchmarked Tianshou algorithm implementations in 9 out of 13 environments from the MuJoCo Gym task
suite[1].
For each supported algorithm and supported mujoco environments, we provide:
- Default hyperparameters used for benchmark and scripts to reproduce the benchmark;
- A comparison of performance (or code level details) with other open source implementations or classic papers;
- Graphs and raw data that can be used for research purposes[2];
- Log details obtained during training[2];
- Pretrained agents[2];
- Some hints on how to tune the algorithm.
Supported algorithms are listed below:
- Deep Deterministic Policy Gradient (DDPG), commit id
- Twin Delayed DDPG (TD3), commit id
- Soft Actor-Critic (SAC), commit id
- REINFORCE algorithm, commit id
- Natural Policy Gradient, commit id
- Advantage Actor-Critic (A2C), commit id
- Proximal Policy Optimization (PPO), commit id
- Trust Region Policy Optimization (TRPO), commit id
- Hindsight Experience Replay (HER)
EnvPool
We highly recommend using envpool to run the following experiments. To install, in a linux machine, type:
pip install envpoolAfter that, make_mujoco_env will automatically switch to envpool's Mujoco env. EnvPool's implementation is much
faster (about 2~3x faster for pure execution speed, 1.5x for overall RL training pipeline in average) than python
vectorized env implementation, and it's behavior is consistent to gym's Mujoco env.
For more information, please refer to EnvPool's GitHub
and Docs.
Usage
Run
$ python mujoco_sac.py --task Ant-v3Logs is saved in ./log/ and can be monitored with tensorboard.
$ tensorboard --logdir logYou can also reproduce the benchmark (e.g. SAC in Ant-v3) with the example script we provide under examples/mujoco/:
$ ./run_experiments.sh Ant-v3 sacThis will start 10 experiments with different seeds.
Now that all the experiments are finished, we can convert all tfevent files into csv files and then try plotting the
results.
# generate csv
$ ./tools.py --root-dir ./results/Ant-v3/sac
# generate figures
$ ./plotter.py --root-dir ./results/Ant-v3 --shaded-std --legend-pattern "\\w+"
# generate numerical result (support multiple groups: `--root-dir ./` instead of single dir)
$ ./analysis.py --root-dir ./results --normExample benchmark
Other graphs can be found under examples/mujuco/benchmark/
For pretrained agents, detailed graphs (single agent, single game) and log details, please refer
to https://cloud.tsinghua.edu.cn/d/f45fcfc5016043bc8fbc/.
Offpolicy algorithms
Notes
- In offpolicy algorithms (DDPG, TD3, SAC), the shared hyperparameters are almost the same, and unless otherwise
stated, hyperparameters are consistent with those used for benchmark in SpinningUp's implementations (e.g. we use
batchsize 256 in DDPG/TD3/SAC while SpinningUp use 100. Minor difference also lies withstart-timesteps, data loop
methodcollection_step_num_env_steps, method to deal with/bootstrap truncated steps because of timelimit and
unfinished/collecting episodes (contribute to performance improvement), etc.). - By comparison to both classic literature and open source implementations (e.g.,
SpinningUp)[1][2], Tianshou's implementations of DDPG, TD3, and SAC
are roughly at-parity with or better than the best reported results for these algorithms, so you can definitely use
Tianshou's benchmark for research purposes. - We didn't compare offpolicy algorithms to OpenAI
baselines benchmark, because for now it
seems that they haven't provided benchmark for offpolicy algorithms, but
in SpinningUp docs they stated that "SpinningUp
implementations of DDPG, TD3, and SAC are roughly at-parity with the best-reported results for these algorithms", so
we think lack of comparisons with OpenAI baselines is okay.
DDPG
| Environment | Tianshou (1M) | Spinning Up (PyTorch) | TD3 paper (DDPG) | TD3 paper (OurDDPG) |
|---|---|---|---|---|
| Ant | 990.4±4.3 | ~840 | 1005.3 | 888.8 |
| HalfCheetah | 11718.7±465.6 | ~11000 | 3305.6 | 8577.3 |
| Hopper | 2197.0±971.6 | ~1800 | 2020.5 | 1860.0 |
| Walker2d | 1400.6±905.0 | ~1950 | 1843.6 | 3098.1 |
| Swimmer | 144.1±6.5 | ~137 | N | N |
| Humanoid | 177.3±77.6 | N | N | N |
| Reacher | -3.3±0.3 | N | -6.51 | -4.01 |
| InvertedPendulum | 1000.0±0.0 | N | 1000.0 | 1000.0 |
| InvertedDoublePendulum | 8364.3±2778.9 | N | 9355.5 | 8370.0 |
TD3
| Environment | Tianshou (1M) | Spinning Up (PyTorch) | TD3 paper |
|---|---|---|---|
| Ant | 5116.4±799.9 | ~3800 | 4372.4±1000.3 |
| HalfCheetah | 10201.2±772.8 | ~9750 | 9637.0±859.1 |
| Hopper | 3472.2±116.8 | ~2860 | 3564.1±114.7 |
| Walker2d | 3982.4±274.5 | ~4000 | 4682.8±539.6 |
| Swimmer | 104.2±34.2 | ~78 | N |
| Humanoid | 5189.5±178.5 | N | N |
| Reacher | -2.7±0.2 | N | -3.6±0.6 |
| InvertedPendulum | 1000.0±0.0 | N | 1000.0±0.0 |
| InvertedDoublePendulum | 9349.2±14.3 | N | 9337.5±15.0 |
Hints for TD3
- TD3's learning rate is set to 3e-4 while it is 1e-3 for DDPG/SAC. However, there is NO enough evidence to support our
choice of such hyperparameters (we simply choose them because SpinningUp do so) and you can try playing with those
hyperparameters to see if you can improve performance. Do tell us if you can!
SAC
| Environment | Tianshou (1M) | Spinning Up (PyTorch) | SAC paper |
|---|---|---|---|
| Ant | 5850.2±475.7 | ~3980 | ~3720 |
| HalfCheetah | 12138.8±1049.3 | ~11520 | ~10400 |
| Hopper | 3542.2±51.5 | ~3150 | ~3370 |
| Walker2d | 5007.0±251.5 | ~4250 | ~3740 |
| Swimmer | 44.4±0.5 | ~41.7 | N |
| Humanoid | 5488.5±81.2 | N | ~5200 |
| Reacher | -2.6±0.2 | N | N |
| InvertedPendulum | 1000.0±0.0 | N | N |
| InvertedDoublePendulum | 9359.5±0.4 | N | N |
Hints for SAC
- SAC's start-timesteps is set to 10000 by default while it is 25000 is DDPG/TD3. However, there is NO enough evidence
to support our choice of such hyperparameters (we simply choose them because SpinningUp do so) and you can try
playing with those hyperparameters to see if you can improve performance. Do tell us if you can! - DO NOT share the same network with two critic networks.
- The sigma (of the Gaussian policy) should be conditioned on input.
- The deterministic evaluation helps a lot :)
Onpolicy Algorithms
Notes
- In A2C and PPO, unless otherwise stated, most hyperparameters are consistent with those used for benchmark
in ikostrikov/pytorch-a2c-ppo-acktr-gail. - Gernally speaking, by comparison to both classic literature and open source implementations (e.g., OPENAI
Baselines)[1][2], Tianshou's implementations of REINFORCE, A2C, PPO
are better than the best reported results for these algorithms, so you can definitely use Tianshou's benchmark for
research purposes.
REINFORCE
| Environment | Tianshou (10M) |
|---|---|
| Ant | 1108.1±323.1 |
| HalfCheetah | 1138.8±104.7 |
| Hopper | 416.0±104.7 |
| Walker2d | 440.9±148.2 |
| Swimmer | 35.6±2.6 |
| Humanoid | 464.3±58.4 |
| Reacher | -5.5±0.2 |
| InvertedPendulum | 1000.0±0.0 |
| InvertedDoublePendulum | 7726.2±1287.3 |
| Environment | Tianshou (3M) | Spinning Up (VPG PyTorch)[7] |
|---|---|---|
| Ant | 474.9+-133.5 | ~5 |
| HalfCheetah | 884.0+-41.0 | ~600 |
| Hopper | 395.8+-64.5* | ~800 |
| Walker2d | 412.0+-52.4 | ~460 |
| Swimmer | 35.3+-1.4 | ~51 |
| Humanoid | 438.2+-47.8 | N |
| Reacher | -10.5+-0.7 | N |
| InvertedPendulum | 999.2+-2.4 | N |
| InvertedDoublePendulum | 1059.7+-307.7 | N |
Hints for REINFORCE
- Following Andrychowicz, Marcin, et al, we downscale last layer of policy network
by a factor of 0.01 after orthogonal initialization. - We choose "tanh" function to squash sampled action from range (-inf, inf) to (-1, 1) rather than usually used
clipping method (As in StableBaselines3). We did full scale ablation studies and results show that tanh squashing
performs a tiny little bit better than clipping overall, and is much better than no action bounding. However, "clip"
method is still a very good method, considering its simplicity. - We use global observation normalization and global rew-to-go (value) normalization by default. Both are crucial to
good performance of REINFORCE algorithm. Since we minus mean when doing rew-to-go normalization, you can treat global
mean of rew-to-go as a naive version of "baseline". - Since we do not have a value estimator, we use global rew-to-go mean to bootstrap truncated steps because of
timelimit and unfinished collecting, while most other implementations use 0. We feel this would help because mean is
more likely a better estimate than 0 (no ablation study has been done). - We have done full scale ablation study on learning rate and lr decay strategy. We experiment with lr of 3e-4, 5e-4,
1e-3, each have 2 options: no lr decay or linear decay to 0. Experiments show that 3e-4 learning rate will cause
slowly learning and make agent step in local optima easily for certain environments like InvertedDoublePendulum, Ant,
HalfCheetah, and 1e-3 lr helps a lot. However, after training agents with lr 1e-3 for 5M steps or so, agents in
certain environments like InvertedPendulum will become unstable. Conclusion is that we should start with a large
learning rate and linearly decay it, but for a small initial learning rate or if you only train agents for limited
timesteps, DO NOT decay it. - We didn't tune
step-per-collectoption andtraining-numoption. Default values are finetuned with PPO algorithm
so we assume they are also good for REINFORCE. You can play with them if you want, but remember thatbuffer-size
should always be larger thanstep-per-collect, and ifstep-per-collectis too small andtraining-numtoo large,
episodes will be truncated and bootstrapped very often, which will harm performance. Iftraining-numis too small (
e.g., less than 8), speed will go down. - Sigma of action is not fixed (normally seen in other implementation) or conditioned on observation, but is an
independent parameter which can be updated by gradient descent. We choose this setting because it works well in PPO,
and is recommended by Andrychowicz, Marcin, et al. See Fig. 23.
A2C
| Environment | Tianshou (3M) | Spinning Up (PyTorch) |
|---|---|---|
| Ant | 5236.8+-236.7 | ~5 |
| HalfCheetah | 2377.3+-1363.7 | ~600 |
| Hopper | 1608.6+-529.5 | ~800 |
| Walker2d | 1805.4+-1055.9 | ~460 |
| Swimmer | 40.2+-1.8 | ~51 |
| Humanoid | 5316.6+-554.8 | N |
| Reacher | -5.2+-0.5 | N |
| InvertedPendulum | 1000.0+-0.0 | N |
| InvertedDoublePendulum | 9351.3+-12.8 | N |
| Environment | Tianshou (1M) | PPO paper A2C | PPO paper A2C + Trust Region |
|---|---|---|---|
| Ant | 3485.4+-433.1 | N | N |
| HalfCheetah | 1829.9+-1068.3 | ~1000 | ~930 |
| Hopper | 1253.2+-458.0 | ~900 | ~1220 |
| Walker2d | 1091.6+-709.2 | ~850 | ~700 |
| Swimmer | 36.6+-2.1 | ~31 | ~36 |
| Humanoid | 1726.0+-1070.1 | N | N |
| Reacher | -6.7+-2.3 | ~-24 | ~-27 |
| InvertedPendulum | 1000.0+-0.0 | ~1000 | ~1000 |
| InvertedDoublePendulum | 9257.7+-277.4 | ~7100 | ~8100 |
Hints for A2C
- We choose
clipaction method in A2C instead oftanhoption as used in REINFORCE simply to be consistent with
original implementation.tanhmay be better or equally well but we didn't have a try. - (Initial) learning rate, lr_decay,
step-per-collectandtraining-numaffect the performance of A2C to a great
extend. These 4 hyperparameters also affect each other and should be tuned together. We have done full scale ablation
studies on these 4 hyperparameters (more than 800 agents have been trained). Below are our findings. step-per-collect/training-numare equal tobootstrap-lenghth, which is the max length of an "episode" used in
GAE estimator and 80/16=5 in default settings. Whenbootstrap-lenghthis small, (maybe) because GAE can look
forward at most 5 steps and use bootstrap strategy very often, the critic is less well-trained leading the actor to a
not very high score. However, if we increasestep-per-collectto increasebootstrap-lenghth(e.g. 256/16=16),
actor/critic will be updated less often, resulting in low sample efficiency and slow training process. To conclude,
If you don't restrict env timesteps, you can try using largerbootstrap-lenghthand train with more steps to get a
better converged score. Train slower, achieve higher.- The learning rate 7e-4 with decay strategy is appropriate for
step-per-collect=80andtraining-num=16. But if you
use a largerstep-per-collect(e.g. 256 - 2048), 7e-4 is a little bit small forlrbecause each update will have
more data, less noise and thus smaller deviation in this case. So it is more appropriate to use a higher learning
rate (e.g. 1e-3) to boost performance in this setting. If plotting results arise fast in early stages and become
unstable later, consider lr decay first before decreasing lr. max-grad-normdidn't really help in our experiments. We simply keep it for consistency with other open-source
implementations (e.g. SB3).- Although original paper of A3C uses RMSprop optimizer, we found that Adam with the same learning rate worked equally
well. We use RMSprop anyway. Again, for consistency. - We noticed that the implementation of A2C in SB3 sets
gae-lambdato 1 by default for no reason, and our experiments
showed better results overall whengae-lambdawas set to 0.95. - We found out that
step-per-collect=256andtraining-num=8are also good settings. You can have a try.
PPO
| Environment | Tianshou (1M) | ikostrikov/pytorch-a2c-ppo-acktr-gail | PPO paper | OpenAI Baselines | Spinning Up (PyTorch) |
|---|---|---|---|---|---|
| Ant | 3258.4+-1079.3 | N | N | N | ~650 |
| HalfCheetah | 5783.9+-1244.0 | ~3120 | ~1800 | ~1700 | ~1670 |
| Hopper | 2609.3+-700.8 | ~2300 | ~2330 | ~2400 | ~1850 |
| Walker2d | 3588.5+-756.6 | ~4000 | ~3460 | ~3510 | ~1230 |
| Swimmer | 66.7+-99.1 | N | ~108 | ~111 | ~120 |
| Humanoid | 787.1+-193.5 | N | N | N | N |
| Reacher | -4.1+-0.3 | ~-5 | ~-7 | ~-6 | N |
| InvertedPendulum | 1000.0+-0.0 | N | ~1000 | ~940 | N |
| InvertedDoublePendulum | 9231.3+-270.4 | N | ~8000 | ~7350 | N |
| Environment | Tianshou (3M) | Spinning Up (PyTorch) |
|---|---|---|
| Ant | 4079.3+-880.2 | ~3000 |
| HalfCheetah | 7337.4+-1508.2 | ~3130 |
| Hopper | 3127.7+-413.0 | ~2460 |
| Walker2d | 4895.6+-704.3 | ~2600 |
| Swimmer | 81.4+-96.0 | ~120 |
| Humanoid | 1359.7+-572.7 | N |
| Reacher | -3.7+-0.3 | N |
| InvertedPendulum | 1000.0+-0.0 | N |
| InvertedDoublePendulum | 9231.3+-270.4 | N |
Hints for PPO
- Following Andrychowicz, Marcin, et al Sec 3.5, we use "recompute advantage"
strategy, which contributes a lot to our SOTA benchmark. However, I personally don't quite agree with their
explanation about why "recompute advantage" helps. They stated that it's because old strategy "makes it impossible to
compute advantages as the temporal structure is broken", but PPO's update equation is designed to learn from
slightly-outdated advantages. I think the only reason "recompute advantage" works is that it update the critic
several times rather than just one time per update, which leads to a better value function estimation. - We have done full scale ablation studies of PPO algorithm's hyperparameters. Here are our findings: In Mujoco
settings,value-clipandnorm-advmay help a litte bit in some games (e.g.norm-advhelps stabilize training in
InvertedPendulum-v2), but they make no difference to overall performance. So in our benchmark we do not use such
tricks. We validate that settingent-coefto 0.0 rather than 0.01 will increase overall performance in mujoco
environments.max-grad-normstill offers no help for PPO algorithm, but we still keep it for consistency. - Andrychowicz, Marcin, et al's work indicates that using
gae-lambda0.9 and
changing policy network's width based on which game you play (e.g. use [16, 16]hidden-sizesforactornetwork in
HalfCheetah and [256, 256] for Ant) may help boost performance. Our ablation studies say otherwise: both options may
lead to equal or lower performance overall in our experiments. We are not confident about this claim because we
didn't change learning rate and other maybe-correlated factors in our experiments. So if you want, you can still have
a try. batch-size128 and 64 (default) work equally well. Changingtraining-numalone slightly (maybe in range [8, 128])
won't affect performance. For bound action method, bothclipandtanhwork quite well.- In OPENAI implementations of PPO, they multiply value loss with a factor of 0.5 for no good reason (see
this issue). We do not do so and therefore
make ourvf-coef0.25 (half of standard 0.5). However, since value loss is only used to optimizecriticnetwork,
setting differentvf-coefshould in theory make no difference if using Adam optimizer.
TRPO
| Environment | Tianshou (1M) | ACKTR paper | PPO paper | OpenAI Baselines | Spinning Up (Tensorflow) |
|---|---|---|---|---|---|
| Ant | 2866.7±707.9 | ~0 | N | N | ~150 |
| HalfCheetah | 4471.2±804.9 | ~400 | ~0 | ~1350 | ~850 |
| Hopper | 2046.0±1037.9 | ~1400 | ~2100 | ~2200 | ~1200 |
| Walker2d | 3826.7±782.7 | ~550 | ~1100 | ~2350 | ~600 |
| Swimmer | 40.9±19.6 | ~40 | ~121 | ~95 | ~85 |
| Humanoid | 810.1±126.1 | N | N | N | N |
| Reacher | -5.1±0.8 | -8 | ~-115 | ~-5 | N |
| InvertedPendulum | 1000.0±0.0 | ~1000 | ~1000 | ~910 | N |
| InvertedDoublePendulum | 8435.2±1073.3 | ~800 | ~200 | ~7000 | N |
Hints for TRPO
- We have tried
step-per-collectin (80, 1024, 2048, 4096), andtraining-numin (4, 16, 32, 64), and found out 1024
forstep-per-collect(same as OpenAI Baselines) and smallertraining-num(below 16) are good choices. Settraining-numto 4 is actually better but we still use 16 considering the boost of training speed. - Advantage normalization is a standard trick in TRPO, but we found it of minor help, just like in PPO.
- Larger
optim-critic-iters(than 5, as used in OpenAI Baselines) helps in most environments. Smaller lr and lr_decay
strategy also help a tiny little bit for performance. gae-lambda0.98 and 0.95 work equally well.- We use GAE returns (GAE advantage + value) as the target of critic network when updating, while people usually tend
to use reward to go (lambda = 0.) as target. We found that they work equally well although using GAE returns is a
little bit inaccurate (biased) by math. - Empirically, Swimmer-v3 usually requires larger bootstrap lengths and learning rate. Humanoid-v3 and
InvertedPendulum-v2, however, are on the opposite. - In contrast, with the statement made in TRPO paper, we found that backtracking in line search is rarely used at least
in Mujoco settings, which is actually unimportant. This makes TRPO algorithm actually the same as TNPG algorithm (
described in this paper). This also explains why TNPG and TRPO's
plotting results look so similar in that paper. - "recompute advantage" is helpful in PPO but doesn't help in TRPO.
NPG
| Environment | Tianshou (1M) |
|---|---|
| Ant | 2358.0±517.5 |
| HalfCheetah | 3485.2±716.6 |
| Hopper | 1915.2±550.5 |
| Walker2d | 2503.2±963.3 |
| Swimmer | 31.5±8.0 |
| Humanoid | 765.1±91.3 |
| Reacher | -4.5±0.5 |
| InvertedPendulum | 1000.0±0.0 |
| InvertedDoublePendulum | 9243.2±276.0 |
Hints for NPG
- All shared hyperparameters are exactly the same as TRPO, regarding how similar these two algorithms are.
- We found different games in Mujoco may require quite different
actor-step-size: Reacher/Swimmer are insensitive to
step-size in range (0.11.0), while InvertedDoublePendulum / InvertedPendulum / Humanoid are quite sensitive to step0.4), but aren't that
size, and even 0.1 is too large. Other games may requireactor-step-sizein range (0.1
sensitive in general.
Others
HER
| Environment | DDPG without HER | DDPG with HER |
|---|---|---|
| FetchReach | -49.9±0.2. | -17.6±21.7 |
Hints for HER
- The HER technique is proposed for solving task-based environments, so it cannot be compared with non-task-based
mujoco benchmarks. The environment used in this evaluation isFetchReach-v3which requires an
extra installation. - Simple hyperparameters optimizations are done for both settings, DDPG with and without HER. However, since DDPG
without HER failed in every experiment, the best hyperparameters for DDPG with HER are used in the evaluation of
both settings. - The scores are the mean reward ± 1 standard deviation of 16 seeds. The minimum reward for
FetchReach-v3is -50
which we can imply that DDPG without HER performs as good as a random policy. DDPG with HER although has a better
mean reward, the standard deviation is quite high. This is because in this setting, the agent will either fail
completely (-50 reward) or successfully learn the task (close to 0 reward). This means that the agent successfully
learned in about 70% of the 16 seeds.
Note
[1] Supported environments include HalfCheetah-v3, Hopper-v3, Swimmer-v3, Walker2d-v3, Ant-v3,
Humanoid-v3, Reacher-v2, InvertedPendulum-v2 and InvertedDoublePendulum-v2. Pusher, Thrower, Striker and HumanoidStandup
are not supported because they are not commonly seen in literatures.
[2] Pretrained agents, detailed graphs (single agent, single game) and log details can all be
found at Google Drive.
[3] We used the latest version of all mujoco environments in gym (0.17.3 with mujoco==2.0.2.13),
but it's not often the case with other benchmarks. Please check for details yourself in the original paper. (Different
version's outcomes are usually similar, though)
[4] ~ means the number is approximated from the graph because accurate numbers is not provided
in the paper. N means graphs not provided.
[5] Reward metric: The meaning of the table value is the max average return over 10 trails (
different seeds) ± a single standard deviation over trails. Each trial is averaged on another 10 test seeds. Only the
first 1M steps data will be considered, if not otherwise stated. The shaded region on the graph also represents a single
standard deviation. It is the same as TD3 evaluation method.
[6] In TD3 paper, shaded region represents only half of standard deviation.
[7] Comparing Tianshou's REINFORCE algorithm with SpinningUp's VPG is quite unfair because
SpinningUp's VPG uses a generative advantage estimator (GAE) which requires a dnn value predictor (critic network),
which makes so called "VPG" more like A2C (advantage actor critic) algorithm. Even so, you can see that we are roughly
at-parity with each other even if tianshou's REINFORCE do not use a critic or GAE.
File: examples/offline/README.md
Offline
In offline reinforcement learning setting, the agent learns a policy from a fixed dataset which is collected once with
any policy. And the agent does not interact with environment anymore.
Continuous control
Once the dataset is collected, it will not be changed during training. We
use d4rl datasets to train offline agent for continuous control. You can refer
to d4rl to see how to use d4rl datasets.
We provide implementation of BCQ and CQL algorithm for continuous control.
Train
Tianshou provides an offline_trainer for offline reinforcement learning. You can parse d4rl datasets into aReplayBuffer , and set it as the parameter buffer of offline_trainer. d4rl_bcq.py is an example of offline RL
using the d4rl dataset.
Results
IL (Imitation Learning, aka, Behavior Cloning)
| Environment | Dataset | IL | Parameters |
|---|---|---|---|
| HalfCheetah-v2 | halfcheetah-expert-v2 | 11355.31 | python3 d4rl_il.py --task HalfCheetah-v2 --expert-data-task halfcheetah-expert-v2 |
| HalfCheetah-v2 | halfcheetah-medium-v2 | 5098.16 | python3 d4rl_il.py --task HalfCheetah-v2 --expert-data-task halfcheetah-medium-v2 |
BCQ
| Environment | Dataset | BCQ | Parameters |
|---|---|---|---|
| HalfCheetah-v2 | halfcheetah-expert-v2 | 11509.95 | python3 d4rl_bcq.py --task HalfCheetah-v2 --expert-data-task halfcheetah-expert-v2 |
| HalfCheetah-v2 | halfcheetah-medium-v2 | 5147.43 | python3 d4rl_bcq.py --task HalfCheetah-v2 --expert-data-task halfcheetah-medium-v2 |
CQL
| Environment | Dataset | CQL | Parameters |
|---|---|---|---|
| HalfCheetah-v2 | halfcheetah-expert-v2 | 2864.37 | python3 d4rl_cql.py --task HalfCheetah-v2 --expert-data-task halfcheetah-expert-v2 |
| HalfCheetah-v2 | halfcheetah-medium-v2 | 6505.41 | python3 d4rl_cql.py --task HalfCheetah-v2 --expert-data-task halfcheetah-medium-v2 |
TD3+BC
| Environment | Dataset | CQL | Parameters |
|---|---|---|---|
| HalfCheetah-v2 | halfcheetah-expert-v2 | 11788.25 | python3 d4rl_td3_bc.py --task HalfCheetah-v2 --expert-data-task halfcheetah-expert-v2 |
| HalfCheetah-v2 | halfcheetah-medium-v2 | 5741.13 | python3 d4rl_td3_bc.py --task HalfCheetah-v2 --expert-data-task halfcheetah-medium-v2 |
Observation normalization
Following the original paper, we use observation normalization by default. You can turn it off by setting--norm-obs 0. The difference are small but consistent.
| Dataset | w/ norm-obs | w/o norm-obs |
|---|---|---|
| halfcheeta-medium-v2 | 5741.13 | 5724.41 |
| halfcheeta-expert-v2 | 11788.25 | 11665.77 |
| walker2d-medium-v2 | 4051.76 | 3985.59 |
| walker2d-expert-v2 | 5068.15 | 5027.75 |
Discrete control
For discrete control, we currently use ad hoc Atari data generated from a trained QRDQN agent.
Gather Data
To running CQL algorithm on Atari, you need to do the following things:
- Train an expert, by using the command listed in the QRDQN section of Atari examples:
python3 atari_qrdqn.py --task {your_task} - Generate buffer with noise:
python3 atari_qrdqn.py --task {your_task} --watch --resume-path log/{your_task}/qrdqn/policy.pth --eps-test 0.2 --buffer-size 1000000 --save-buffer-name expert.hdf5(
note that 1M Atari buffer cannot be saved as.pklformat because it is too large and will cause error); - Train offline model:
python3 atari_{bcq,cql,crr}.py --task {your_task} --load-buffer-name expert.hdf5.
IL
We test our IL implementation on two example tasks (different from author's version, we use v4 instead of v0; one epoch
means 10k gradient step):
| Task | Online QRDQN | Behavioral | IL | parameters |
|---|---|---|---|---|
| PongNoFrameskip-v4 | 20.5 | 6.8 | 20.0 (epoch 5) | python3 atari_il.py --task PongNoFrameskip-v4 --load-buffer-name log/PongNoFrameskip-v4/qrdqn/expert.hdf5 --epoch 5 |
| BreakoutNoFrameskip-v4 | 394.3 | 46.9 | 121.9 (epoch 12, could be higher) | python3 atari_il.py --task BreakoutNoFrameskip-v4 --load-buffer-name log/BreakoutNoFrameskip-v4/qrdqn/expert.hdf5 --epoch 12 |
BCQ
We test our BCQ implementation on two example tasks (different from author's version, we use v4 instead of v0; one epoch
means 10k gradient step):
| Task | Online QRDQN | Behavioral | BCQ | parameters |
|---|---|---|---|---|
| PongNoFrameskip-v4 | 20.5 | 6.8 | 20.1 (epoch 5) | python3 atari_bcq.py --task PongNoFrameskip-v4 --load-buffer-name log/PongNoFrameskip-v4/qrdqn/expert.hdf5 --epoch 5 |
| BreakoutNoFrameskip-v4 | 394.3 | 46.9 | 64.6 (epoch 12, could be higher) | python3 atari_bcq.py --task BreakoutNoFrameskip-v4 --load-buffer-name log/BreakoutNoFrameskip-v4/qrdqn/expert.hdf5 --epoch 12 |
CQL
We test our CQL implementation on two example tasks (different from author's version, we use v4 instead of v0; one epoch
means 10k gradient step):
| Task | Online QRDQN | Behavioral | CQL | parameters |
|---|---|---|---|---|
| PongNoFrameskip-v4 | 20.5 | 6.8 | 20.4 (epoch 5) | python3 atari_cql.py --task PongNoFrameskip-v4 --load-buffer-name log/PongNoFrameskip-v4/qrdqn/expert.hdf5 --epoch 5 |
| BreakoutNoFrameskip-v4 | 394.3 | 46.9 | 129.4 (epoch 12) | python3 atari_cql.py --task BreakoutNoFrameskip-v4 --load-buffer-name log/BreakoutNoFrameskip-v4/qrdqn/expert.hdf5 --epoch 12 --min-q-weight 50 |
We reduce the size of the offline data to 10% and 1% of the above and get:
Buffer size 100000:
| Task | Online QRDQN | Behavioral | CQL | parameters |
|---|---|---|---|---|
| PongNoFrameskip-v4 | 20.5 | 5.8 | 21 (epoch 5) | python3 atari_cql.py --task PongNoFrameskip-v4 --load-buffer-name log/PongNoFrameskip-v4/qrdqn/expert.size_1e5.hdf5 --epoch 5 |
| BreakoutNoFrameskip-v4 | 394.3 | 41.4 | 40.8 (epoch 12) | python3 atari_cql.py --task BreakoutNoFrameskip-v4 --load-buffer-name log/BreakoutNoFrameskip-v4/qrdqn/expert.size_1e5.hdf5 --epoch 12 --min-q-weight 20 |
Buffer size 10000:
| Task | Online QRDQN | Behavioral | CQL | parameters |
|---|---|---|---|---|
| PongNoFrameskip-v4 | 20.5 | nan | 1.8 (epoch 5) | python3 atari_cql.py --task PongNoFrameskip-v4 --load-buffer-name log/PongNoFrameskip-v4/qrdqn/expert.size_1e4.hdf5 --epoch 5 --min-q-weight 1 |
| BreakoutNoFrameskip-v4 | 394.3 | 31.7 | 22.5 (epoch 12) | python3 atari_cql.py --task BreakoutNoFrameskip-v4 --load-buffer-name log/BreakoutNoFrameskip-v4/qrdqn/expert.size_1e4.hdf5 --epoch 12 --min-q-weight 10 |
CRR
We test our CRR implementation on two example tasks (different from author's version, we use v4 instead of v0; one epoch
means 10k gradient step):
| Task | Online QRDQN | Behavioral | CRR | CRR w/ CQL | parameters |
|---|---|---|---|---|---|
| PongNoFrameskip-v4 | 20.5 | 6.8 | -21 (epoch 5) | 17.7 (epoch 5) | python3 atari_crr.py --task PongNoFrameskip-v4 --load-buffer-name log/PongNoFrameskip-v4/qrdqn/expert.hdf5 --epoch 5 |
| BreakoutNoFrameskip-v4 | 394.3 | 46.9 | 23.3 (epoch 12) | 76.9 (epoch 12) | python3 atari_crr.py --task BreakoutNoFrameskip-v4 --load-buffer-name log/BreakoutNoFrameskip-v4/qrdqn/expert.hdf5 --epoch 12 --min-q-weight 50 |
Note that CRR itself does not work well in Atari tasks but adding CQL loss/regularizer helps.
RL Unplugged Data
We provide a script to convert the Atari datasets
of RL Unplugged to Tianshou ReplayBuffer.
For example, the following command will download the first shard of the first run of Breakout game to~/.rl_unplugged/datasets/Breakout/run_1-00001-of-00100 then convert it to a tianshou.data.ReplayBuffer and save it
to ~/.rl_unplugged/buffers/Breakout/run_1-00001-of-00100.hdf5 (use --dataset-dir and --buffer-dir to change the
default directories):
python3 convert_rl_unplugged_atari.py --task Breakout --run-id 1 --shard-id 1Then you can use it to train an agent by:
python3 atari_bcq.py --task BreakoutNoFrameskip-v4 --load-buffer-name ~/.rl_unplugged/datasets/Breakout/run_1-00001-of-00100.hdf5 --buffer-from-rl-unplugged --epoch 12Note:
- Each shard contains about 500k transitions.
- This conversion script depends on Tensorflow.
- It takes about 1 hour to process one shard on my machine. YMMV.
File: examples/vizdoom/README.md
ViZDoom
ViZDoom is a popular RL env for a famous first-person shooting game Doom. Here we
provide some results and intuitions for this scenario.
EnvPool
We highly recommend using envpool to run the following experiments. To install, in a linux machine, type:
pip install envpoolAfter that, make_vizdoom_env will automatically switch to envpool's ViZDoom env. EnvPool's implementation is much
faster (about 2~3x faster for pure execution speed, 1.5x for overall RL training pipeline) than python vectorized env
implementation.
For more information, please refer to EnvPool's GitHub
and Docs.
Train
To train an agent:
python3 vizdoom_c51.py --task {D1_basic|D2_navigation|D3_battle|D4_battle2}D1 (health gathering) should finish training (no death) in less than 500k env step (5 epochs);
D3 can reach 1600+ reward (75+ killcount in 5 minutes);
D4 can reach 700+ reward. Here is the result:
(episode length, the maximum length is 2625 because we use frameskip=4, that is 10500/4=2625)
(episode reward)
To evaluate an agent's performance:
python3 vizdoom_c51.py --num_test_envs 100 --resume-path policy.pth --watch --task {D1_basic|D3_battle|D4_battle2}To save .lmp files for recording:
python3 vizdoom_c51.py --save-lmp --num_test_envs 100 --resume-path policy.pth --watch --task {D1_basic|D3_battle|D4_battle2}it will store lmp file in lmps/ directory. To watch these lmp files (for example, d3 lmp):
python3 replay.py maps/D3_battle.cfg episode_8_25.lmpWe provide two lmp files (d3 best and d4 best) under results/c51, you can use the following command to enjoy:
python3 replay.py maps/D3_battle.cfg results/c51/d3.lmp
python3 replay.py maps/D4_battle2.cfg results/c51/d4.lmpMaps
See maps/README.md
Reward
- living reward is bad
- combo-action is really important
- negative reward for health and ammo2 is really helpful for d3/d4
- only with positive reward for health is really helpful for d1
- remove MOVE_BACKWARD may converge faster but the final performance may be lower
Algorithms
The setting is exactly the same as Atari. You can definitely try more algorithms listed in Atari example.
C51 (single run)
| task | best reward | reward curve | parameters |
|---|---|---|---|
| D2_navigation | 747.52 | python3 vizdoom_c51.py --task "D2_navigation" |
|
| D3_battle | 1855.29 | python3 vizdoom_c51.py --task "D3_battle" |
PPO (single run)
| task | best reward | reward curve | parameters |
|---|---|---|---|
| D2_navigation | 770.75 | python3 vizdoom_ppo.py --task "D2_navigation" |
|
| D3_battle | 320.59 | python3 vizdoom_ppo.py --task "D3_battle" |
PPO with ICM (single run)
| task | best reward | reward curve | parameters |
|---|---|---|---|
| D2_navigation | 844.99 | python3 vizdoom_ppo.py --task "D2_navigation" --icm-lr-scale 10 |
|
| D3_battle | 547.08 | python3 vizdoom_ppo.py --task "D3_battle" --icm-lr-scale 10 |
File: examples/vizdoom/maps/README.md
D1-D4 maps are from https://github.com/intel-isl/DirectFuturePrediction/
More maps and cfgs: https://github.com/mwydmuch/ViZDoom/tree/master/scenarios
File: .devcontainer/devcontainer.json
{
"name": "Tianshou",
"dockerFile": "../Dockerfile",
"workspaceFolder": "/workspaces/tianshou",
"runArgs": ["--shm-size=1g"],
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/local/bin/python"
},
"extensions": [
"ms-python.python",
"ms-toolsai.jupyter",
"ms-python.vscode-pylance"
]
}
},
"forwardPorts": [],
"postCreateCommand": "poetry install --with dev",
"remoteUser": "root"
}
File: .github/ISSUE_TEMPLATE.md
- I have marked all applicable categories:
- exception-raising bug
- RL algorithm bug
- documentation request (i.e. "X is missing from the documentation.")
- new feature request
- design request (i.e. "X should be changed to Y.")
- I have visited the source website
- I have searched through the issue tracker for duplicates
- I have mentioned version numbers, operating system and environment, where applicable:python
import tianshou, gymnasium as gym, torch, numpy, sys print(tianshou.__version__, gym.__version__, torch.__version__, numpy.__version__, sys.version, sys.platform)
File: .github/PULL_REQUEST_TEMPLATE.md
- I have added the correct label(s) to this Pull Request or linked the relevant issue(s)
- I have provided a description of the changes in this Pull Request
- I have added documentation for my changes and have listed relevant changes in CHANGELOG.md
- If applicable, I have added tests to cover my changes.
- If applicable, I have made sure that the determinism tests run through, meaning that my changes haven't influenced any aspect of training. See info in the contributing documentation.
- I have reformatted the code using
poe format - I have checked style and types with
poe lintandpoe type-check - (Optional) I ran tests locally with
poe test
(or a subset of them withpoe test-reduced) ,and they pass - (Optional) I have tested that documentation builds correctly with
poe doc-build
File: .github/workflows/extra_sys.yml
name: Windows/MacOS
on:
pull_request:
branches:
- master
push:
branches:
- master
workflow_dispatch:
inputs:
debug_enabled:
type: boolean
required: false
default: false
jobs:
cpu-extra:
runs-on: ${{ matrix.os }}
if: "!contains(github.event.head_commit.message, 'ci skip')"
strategy:
matrix:
os: [macos-latest, windows-latest]
python-version: [3.11]
steps:
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
- name: Cancel previous run
uses: styfle/[email protected]
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
# use poetry and cache installed packages, see https://github.com/marketplace/actions/python-poetry-action
- name: Install poetry
uses: abatilo/actions-poetry@v2
- name: Setup a local virtual environment (if no poetry.toml file)
run: |
poetry config virtualenvs.create true --local
poetry config virtualenvs.in-project true --local
- uses: actions/cache@v3
name: Define a cache for the virtual environment based on the dependencies lock file
with:
path: ./.venv
key: venv-${{ hashFiles('poetry.lock') }}
- name: Install the project dependencies
# ugly as hell, but well...
# see https://github.com/python-poetry/poetry/issues/7611
run: poetry install --with dev || poetry install --with dev || poetry install --with dev
- name: wandb login
run: poetry run wandb login e2366d661b89f2bee877c40bee15502d67b7abef
- name: Test with pytest
run: poetry run poe test-reduced
File: .github/workflows/gputest.yml
name: Ubuntu GPU
on:
pull_request:
branches:
- master
push:
branches:
- master
workflow_dispatch:
inputs:
debug_enabled:
type: boolean
required: false
default: false
jobs:
gpu:
runs-on: [self-hosted, Linux, X64]
if: "!contains(github.event.head_commit.message, 'ci skip')"
steps:
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
- name: Cancel previous run
uses: styfle/[email protected]
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v3
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: "3.11"
# use poetry and cache installed packages, see https://github.com/marketplace/actions/python-poetry-action
- name: Install poetry
uses: abatilo/actions-poetry@v2
- name: Setup a local virtual environment (if no poetry.toml file)
run: |
poetry config virtualenvs.create true --local
poetry config virtualenvs.in-project true --local
- uses: actions/cache@v3
name: Define a cache for the virtual environment based on the dependencies lock file
with:
path: ./.venv
key: venv-${{ hashFiles('poetry.lock') }}
- name: Install the project dependencies
run: |
poetry install --with dev --extras "envpool"
- name: wandb login
run: |
poetry run wandb login e2366d661b89f2bee877c40bee15502d67b7abef
- name: Test with pytest
run: |
poetry run poe test
File: .github/workflows/lint_and_docs.yml
name: Check Formatting/Typing and Build Docs
on:
pull_request:
branches:
- master
push:
branches:
- master
workflow_dispatch:
inputs:
debug_enabled:
type: boolean
required: false
default: false
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
- name: Cancel previous run
uses: styfle/[email protected]
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v3
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: 3.11
# use poetry and cache installed packages, see https://github.com/marketplace/actions/python-poetry-action
- name: Install poetry
uses: abatilo/actions-poetry@v2
- name: Setup a local virtual environment (if no poetry.toml file)
run: |
poetry config virtualenvs.create true --local
poetry config virtualenvs.in-project true --local
- uses: actions/cache@v3
name: Define a cache for the virtual environment based on the dependencies lock file
with:
path: ./.venv
key: venv-${{ hashFiles('poetry.lock') }}
- name: Install the project dependencies
run: |
poetry install --with dev --extras "eval"
- name: Check formatting
run: poetry run poe lint
- name: Check typing
run: poetry run poe type-check
- name: Build docs
run: MYSTNB_DEBUG=1 poetry run poe doc-build
- name: Show errors (if any)
if: failure()
run: find docs/_build/reports -name "*.err.log" -exec echo "--- {} ---" ; -exec cat {} ;
File: .github/workflows/publish.yaml
name: Upload Python Package
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.11
# use poetry and cache installed packages, see https://github.com/marketplace/actions/python-poetry-action
- name: Install poetry
uses: abatilo/actions-poetry@v2
- name: Setup a local virtual environment (if no poetry.toml file)
run: |
poetry config virtualenvs.create true --local
poetry config virtualenvs.in-project true --local
- name: Build and publish
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}
run: |
if [ -z "${POETRY_PYPI_TOKEN_PYPI}" ]; then echo "Set the PYPI_TOKEN variable in your repository secrets"; exit 1; fi
poetry publish --build
File: .github/workflows/pytest.yml
name: Ubuntu
on:
pull_request:
branches:
- master
push:
branches:
- master
workflow_dispatch:
inputs:
debug_enabled:
type: boolean
required: false
default: false
This job runs the test suite in two environments:
- py_pinned: uses Python 3.11 with the existing poetry.lock file (our stable, pinned dev environment)
- py_latest: latest Python version we want to support, without the lock file to furthermore install the newest dependency versions
This ensures compatibility with both our controlled dev setup and the latest upstream packages,
helping catch issues introduced by dependency updates or newer Python versions.
jobs:
cpu:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"
strategy:
fail-fast: false
matrix:
include:
- env_name: py_pinned
python-version: "3.11"
use_lock: true
- env_name: py_latest
python-version: "3.13"
use_lock: false
steps:
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
- name: Cancel previous run
uses: styfle/[email protected]
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install poetry
uses: abatilo/actions-poetry@v2
- name: Setup a local virtual environment (if no poetry.toml file)
run: |
poetry config virtualenvs.create true --local
poetry config virtualenvs.in-project true --local
- name: Remove poetry.lock for latest dependency test
if: ${{ !matrix.use_lock }}
run: rm -f poetry.lock
- name: Define a cache for the virtual environment based on the dependencies lock file
if: matrix.use_lock
uses: actions/cache@v3
with:
path: ./.venv
key: venv-${{ matrix.env_name }}-${{ hashFiles('poetry.lock') }}
restore-keys: |
venv-${{ matrix.env_name }}-
- name: Install the project dependencies
run: |
if [ "${{ matrix.env_name }}" = "py_latest" ]; then
poetry install --with dev --extras "eval"
else
poetry install --with dev --extras "envpool eval"
fi
- name: List installed packages
run: |
poetry run pip list
- name: wandb login
run: |
poetry run wandb login e2366d661b89f2bee877c40bee15502d67b7abef
- name: Test with pytest
run: |
if [ "${{ matrix.env_name }}" = "py_pinned" ]; then
poetry run poe test
else
poetry run poe test-nocov
fi
- name: Upload coverage to Codecov
if: matrix.env_name == 'py_pinned'
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV }}
file: ./coverage.xml
flags: ${{ matrix.env_name }}
name: codecov-${{ matrix.env_name }}
fail_ci_if_error: false