The procedural API requires manual orchestration of the RL pipeline.
Workflow:
- Setup Logging: Use
ts.utils.TensorboardLogger. - Create Environments: Use
ts.env.DummyVectorEnv or similar to wrap Gymnasium environments. - Build Network: Extract
SpaceInfo from the environment to define state_shape and action_shape, then instantiate a network (e.g., ts.utils.net.common.Net). - Create Policy & Algorithm: Instantiate a policy (e.g.,
DiscreteQLearningPolicy) and pass it to an algorithm (e.g., ts.algorithm.DQN). - Setup Collectors: Create
ts.data.Collector instances for both training (with a VectorReplayBuffer) and testing. - Train: Call
algorithm.run_training() passing an OffPolicyTrainerParams object containing collectors, hyperparameters, and a stop_fn. - Evaluate: Manually run a collector on a single environment to watch the agent.
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)