This is the official repository for the paper titled Thinker: Learning to Plan and Act. Please refer to the project website for details of the algorithm.
- Prerequisites
- Installation
- Training in Thinker-augmented MDPs
- Basic Usage
- Configuration
- Available Environments
- Available Wrappers
- Resource Management
- Resuming from a Checkpoint
- API
- Miscellaneous
Ensure that Pytorch is installed (versions v2.0.0 and v1.13.0 have been tested).
- Update essential packages and install Cython:
sudo apt-get update
sudo apt-get install zip python-opencv build-essential -y
pip install Cython- Install the C++ version of Sokoban (skip this step if you're not running experiments on Sokoban):
cd sokoban
pip install -e .- Compile and install Thinker:
cd thinker
pip install -e .To train actor-critic (IMPALA) on the Thinker-augmented MDP, run the following commands in the thinker directory:
Sokoban default run:
python train.pyAtari default run (change the environment if needed; by default the standard atari wrapper such as 4-frame-stacking will be applied):
python train.py --name BreakoutNoFrameskip-v4 --reward_clip 1 --model_size_nn 2 --discounting 0.99- To understand how the Thinker-augmented MDP can be used with an actor-critic network, please refer to this example notebook.
- The above runs are used to generate the results of the Thinker-augmented MDP in Figure 5 and Figure 9 of the paper, but some hyper-parameters have been optimized further in the current version. To use the same hyper-parameters as in the paper, add
--actor_learning_rate 0.0006. - For Atari, we also support EnvPool for faster computation. To use EnvPool, first install the modified version of EnvPool from here (please download the correct version and run
pip install $FILE_NAME.whl; you need to compile the repo manually if your Python version / OS cannot be found.) Then run (Breakout-v5in EnvPool is the same asBreakoutNoFrameskip-v4in Gym):
python train.py --name Breakout-v5 --reward_clip 1 --model_size_nn 2 --discounting 0.99 --envpool TrueTroubleshooting
- If running out of GPU memory, try using mixed precision by adding
--float16 true. - If you encounter errors related to Ray memory, try setting
--ray_mem -1to allow Ray to allocate memory automatically. - The number of GPUs will be detected automatically. A single RTX3090 is sufficient for the Sokoban default run, while two RTX3090s are required for the Atari default run. The number of CPUs and GPUs allocated can be controlled with the
--ray_cpuand--ray_gpuoptions, e.g.,--ray_cpu 16 --ray_gpu 1limits usage to 16 CPUs and 1 GPU. - To enable logging to wandb, add
--use_wandb truewith the desired experiment ID--xpid $XPID. The global variable$WANDB_USERshould be set to the wandb username. To log visualized episodes, add--policy_vis_freq 1000000, which creates a GIF of an animated episode every 1M steps.
Some common baselines include:
- IMPALA on the raw MDP:
python train.py --wrapper_type 1 --has_model false --actor_unroll_len 20 --actor_learning_rate 3e-4 --see_real_state true- MCTS with 99 imagainary steps instead of RL agents (this is equivalent to MuZero but with the Thinker world model):
python train.py --mcts true --tree_carry false --rec_t 100 --actor_unroll_len 200 --max_depth -1 --auto_res False --env_n 256 - DRC:
python train.py --drc true --actor_unroll_len 20 --reg_cost 0.01 --actor_learning_rate 4e-4 --entropy_cost 1e-2 --v_trace_lamb 0.97 --actor_adam_eps 1e-4 --has_model falseSome common ablation includes:
- Thinker-augmentation with environment simluators:
python train.py --wrapper_type 2- No RNN for actor:
python train.py --tree_rep_rnn false- No auxillary statistics:
python train.py --stat_mask_type 1- No planning rewards:
python train.py --im_cost 0.To save computational cost, you can use only 10 imaginary steps (instead of the default 19), no RNN for the actor, and don't let the actor see the hidden state of the model. On Sokoban, these modifications have minimal impact on performance:
python train.py --rec_t 11 --tree_rep_rnn false --see_h falseTo visualize a specific run, follow the steps below:
- Identify the experiment ID of the run you want to visualize. By default, it is in the format:
Thinker-{DATE}-{TIME}. This ID should be displayed in the standard output during your experiment run. - Use the following command, replacing
$XPIDwith the experiment ID:
python visual.py --xpid $XPIDThe Thinker-augmented MDP provides the same interface as OpanAI's Gym. To test if the installation is successful, run the following:
import thinker
import numpy as np
env_n = 16 # batch size
env = thinker.make("Sokoban-v0", env_n=env_n, gpu=False) # or atari games like "BreakoutNoFrameskip-v4"
initial_state = env.reset()
for _ in range(20):
primary_action = np.random.randint(5, size=env_n) # 5 possible actions in Sokoban
reset_action = np.random.randint(2, size=env_n) # 2 possible reset actions
state, reward, done, info = env.step(primary_action, reset_action)
print(state["tree_reps"].shape, state["real_states"].shape)
env.close()which should output torch.Size([16, 79]) torch.Size([16, 3, 80, 80]).
The default configuration of the Thinker-augmented MDP can be found in thinker/thinker/config/default_thinker.yaml, which contains a list of parameters. Important parameters include:
rec_t: the stage length K, which is20by defaultmax_depth: the maximum search depth L, which is5by defaultmodel_unroll_len: model unroll length when training the model, which is5by defaultwrapper_type: the type of the augmented MDP;0: default Thinker;1: raw env;2: Thinker with a perfect state-reward network (i.e. using the true environment dynamic instead). Using type2can significantly increase learning speed, at the expense of more calls to the underlying real environment. Detault:0model_size_nn: integer multipler to the model size. Increase it for a larger model. Default:1xpid: experiment id, which is used for checkpoint resumption. Default:thinker-{DATE}-{TIME}
Please refer to default_thinker.yaml for other parameters.
There are two methods to change the default parameters. The first method is to pass the parameters when calling Thinker.make, such as:
env = thinker.make("Sokoban-v0", env_n=16, rec_t=10, max_depth=10)The second method is to create a custom configuration file which contains the parameters that need to be changed, such as:
# custom.yaml
rec_t: 10
max_depth: 10
and pass it to Thinker.make:
env = Thinker.make("Sokoban-v0", env_n=16, config='custom.yaml')Note that the first method will take precedence over the second method.
By default, environments like Sokoban-v0, as well as other Atari environments such as BreakoutNoFrameskip-v4 and SeaquestNoFrameskip-v4, can be passed to the Thinker.make method. The Thinker.make method internally calls gym.make using the provided environment name to instantiate the environment.
By default, the following Atari wrapper is applied (except for Sokoban environment):
- 4-frame stacking,
- up to 30 random no-ops at the start,
- resizing the image to 3x84x84,
- truncating at 108,000 steps,
- treating end-of-life as end-of-episode.
Example:
env = Thinker.make("BreakoutNoFrameskip-v4", env_n=16, config='custom.yaml')You can also provide custom environments. To do this, supply an env_fn function that, when invoked as env_fn(), returns a Gym environment:
thinker.make(env_fn=env_fn, env_n=16)The only requirement is that the observation_space of the returned environment must be a Box with dimensions C, H, W. The low and high properties should be properly set (they will be used to normalize the input during model training). Additionally, the action_space should be of type Discrete. Thinker will not apply any additional wrappers.
Notes:
- If frame stacking is used in a custom environment, set the property
frame_stack_nof the returned environment fromenv_fn()to the counts of stacking. This will ensure that the model only predicts the most recent state and not the previously stacked states. If frame stacking is detected, the log will output:Detected frame stacking with {n} counts. - The C++ version of the Sokoban gym environment can be used as follows:
import gym, gym_sokoban
env = gym.make("Sokoban-v0")There are five different wrapper types available since v1.2, which can be configured via the wrapper_type argument:
0(Default): The Thinker-augmentation discussed in the paper.1: The raw MDP without any Thinker-augmentation. The return state will only contain real states, as other statistics such as tree representation won't be available. This is mainly for baseline purposes.2: The Thinker-augmentation with an environment simulator. Useful for debugging purposes.3: Thinker-v2 augmentation (work in progress).4: Thinker-v2 augmentation with an environment simulator (work in progress).
The environment simulator means that we have access to the dynamics of the environment, so the state-reward network does not need to be learned, and only the value-prediction network is learned. In such cases, the value-policy network will no longer be an RNN but a simple deep convolution network that predicts the value and policy.
The environment simulator version works with both Sokoban and Atari games. For custom environments, please ensure that the environment has the following two methods:
quick_save(): Save the current state of the environment. No return is needed.quick_load(): Restore to the state saved inquick_save(); will only be called afterquick_save(). No return is needed.
For an illustration, see the AtariSaveLoad method in thinker/thinker/gym_add/wrapper.py, which wraps Gym's Atari environments to provide these two methods.
By default, a GPU will be utilized in the Thinker-augmented MDP. To disable GPU usage, set gpu=False when calling thinker.make. The Thinker-augmented MDP offers three operational modes.
This is the default mode. In this mode, model training occurs within the env.step method. Only a single environment class is permitted. Each env instance returned by thinker.make utilizes a distinct model, so it is advisable not to invoke thinker.make multiple times.
This mode employs Ray to establish a parallel thread that trains the model, as opposed to training within the env.step method. The env.step method will periodically synchronize with the model training thread to ensure the models are updated. To activate this mode, set parallel=True and assign gpu_learn to the GPU fraction allocated for the model training thread. For example,
env = thinker.make("Sokoban-v0", env_n=16, parallel=True, gpu_learn=0.5)This mode resembles Mode 2 but permits multiple environment classes that share a common model. To implement this, first generate a shared Ray resource. This resource can then be provided to other Ray actors to initialize new environments:
ray_obj = ray_init(gpu_learn=0.5, rec_t=10) # pass the configurations here, rather than in thinker.make
# in ray actor 1
env_1 = thinker.make("Sokoban-v0", env_n=16, ray_obj=ray_obj) # only name, env_fn, env_n, and gpu can be passed here
# in ray actor 2
env_2 = thinker.make("Sokoban-v0", env_n=16, ray_obj=ray_obj)See thinker/train.py and thinker/thinker/self_play.py for an example of using mode 3 to implement IMPALA on the Thinker-augmented MDP.
Note:
- In Modes 2 and 3, the memory, GPU, and CPU allocations for Ray can be configured using the
ray_mem,ray_GPU, andray_CPUparameters, respectively. If Ray returns an error, consider setting these parameters manually.
To resume from a previously saved checkpoint:
- Identify the
XPID(Experiment ID) of the run you want to resume. By default, it is in the format:Thinker-{DATE}-{TIME}. This ID should be displayed in the standard output during your experiment run. - Set the
ckpparameter toTruewhen invokingthinker.make. For those using mode 3, you should set these parameters in theray_initfunction.
Example:
env = thinker.make("Sokoban-v0", env_n=16, xpid=XPID, ckp=True)This command will attempt to load the checkpoint from the savedir/XPID directory.
The thinker.make method creates a Gym-class environment.
Definition:
env = thinker.make(name=None, env_fn=None, ray_obj=None, env_n=1, gpu=True, **kwargs)Parameters:
name: Name of the environment, which will be passed internally toGym.make.env_fn: Custom environment function that, when called, returns a Gym-class environment. Eithernameorenv_fnmust be provided.ray_obj: Ray shared resources, only required for mode 3 (see Resource Management above).env_n: Integer representing the batch size of the environment.gpu: Boolean indicating whether a GPU will be used.**kwargs: All other configurations present inthinker/thinker/config/default_thinker.yaml. Additionally, acceptsconfigthat points to a customyamlfile. For mode 3, pass**kwargstothinker.ray_initinstead of here.
Returns:
env: A Gym-class environment that supports the methodsreset,step, andclose.
The env.reset method resets the current environment, and shall be called only once after thinker.make. Note that environment will be automatically reset upon episode termination and one does not need to call this.
Definition:
initial_state = env.reset()Returns:
initial_state: The initial state, which is in the same format as thestatereturned inenv.stepmethod.
The env.step method advances the environment's state by one time step based on the provided primary action and reset action. The function returns the next state, reward, termination indicator, and additional information.
Definition:
state, reward, done, info = env.step(primary_action, reset_action, action_prob=None)Parameters:
-
primary_action: Imaginary/real action to be taken in the environment.- Type: Torch tensor or numpy array.
- Shape:
(env_n,)
-
reset_action: Reset action to be taken in the environment state.- Type: Torch tensor or numpy array.
- Shape:
(env_n,) - Each element must be either 0 or 1.
-
action_prob(Optional): Probability distribution over actions. This is required whenrequire_prob=Trueis set for the environment. Passing the action probability provides a better training target for the model. Ifrequire_prob=False, thisaction_probwill not be used.- Type: Torch tensor or numpy array.
- Shape:
(env_n, num_actions) - Default:
None
Returns:
state: A dictionary containing:tree_reps: Tree representation of the state.- Shape:
(env_n, N)
- Shape:
real_states: The real state of the environment, which remains unchanged throughout a stage.- Shape:
(env_n, C, H, W), where(C, H, W)represents the shape of the real observation space.
- Shape:
hs(optional): Model's hidden state of the current node.- Shape:
(env_n, hC, hH, hW), where(hC, hH, hW)represents the shape of the model's hidden state.
- Shape:
xs(optional): Predicted state of the current node.- Shape:
(env_n, C, H, W), where(C, H, W)represents the shape of the real observation space.
- Shape:
reward: Float tensor representing the reward obtained after executing the action. This is clipped byreward_clipifreward_clipis enabled (default disabled).- Shape:
(env_n,)
- Shape:
done: Boolean tensor that indicates whether the episode has concluded for each environment instance.- Shape:
(env_n,)
- Shape:
info: A dictionary containing:real_done: Boolean tensor indicating the genuine end of the episode. It may not matchdonein Atari games, which is set toTruewhenever a life is lost.truncated_done: Boolean tensor indicating whether the termination occurred due to truncation.step_status: Integer tensor representing the current step's status. Values can be:0: A real action was just taken, marking the beginning of a stage.1: An imaginary action was just taken, and the next action is an imaginary action.2: An imaginary action was just taken, and the next action is a real action. Typically, a stage has astep_statusof[0, 1, 1, ..., 1, 2], where the count of1s equals the stage lengthKminus 2.3: A real action was just taken, and the next action is a real action. This only occurs when there is no imagaination steps.
max_rollout_depth: Integer tensor representing the greatest depth achieved in all rollouts during the current stage; primarily used for logging.baseline: Float tensor representing the mean rollout return at the root node. It is updated only at the end of a stage and can be utilized as a value estimation of the real state underlying the stage.im_reward: Float tensor representing the planning rewards.episode_step: Integer tensor representing the episode steps within the augmented MDP.episode_return: Float tensor representing the undiscounted sum of unclipped reward in the current episode (as defined byreal_done); primarily used for performance evaluation.im_episode_return: Float tensor representing the undiscounted sum of planning rewards in the current stage.model_status: A dictionary providing the model's training status, including:processed_n: Integer representing the number of real transitions processed by the replay buffer. It should approximately match the count of real transitions performed.model_warm_up_n: Integer representing the number of real transitions processed by the replay buffer before initiating the model's training.running: Boolean that denotes if the model is under training. By default, the model only begins its training afterprocessed_n >= model_warm_up_n.finish: Boolean indicating if the model has completed its training. The model ceases training onceprocessed_n >= total_steps.
Unless stated otherwise, all output elements in info are Torch tensors of the data type torch.float32 with shape (env_n,).
Note
- The specific shape of each element in
statecan be checked withenv.observation_space. hsinstateis only returned whenreturn_h=True(default value isTrue) andxsinstateis returned only whenreturn_x=True(default value isFalse).- If
return_double=True(default value isFalse), then bothhsandxswill also return their respective statistics at the root node. These statistics will be stacked on top of the respective statistics at the current node in theCdimension. - To interpret the information in
tree_reps, one can usethinker.util.decode_tree_reps:
thinker.util.decode_tree_reps(state['tree_reps'], num_actions)where num_actions denotes the number of action available in the real environment. This will return a dictionary that maps each elements in the tree representation to interpretable keys such as root_logits, root_qs_mean, root_ns etc. (see the function decode_tree_reps for explaination of each key).
The env.close method closes the environment, freeing the memory of the model.
Definition:
env.close()If you are editing the Cython files, run the following in the sokoban or thinker folder to recompile the code:
python setup.py build_ext### License
This project is licensed under the MIT License - see the LICENSE file for details.
### Contact
For any questions or discussions, please contact me at mhc48@cam.ac.uk.