Skip to main content

burn_train/learner/rl/
off_policy.rs

1use crate::{
2    AgentEnvAsyncLoop, AgentEnvLoop, AsyncAgentEnvLoopConfig, EvaluationItem,
3    EventProcessorTraining, MultiAgentEnvLoop, RLComponents, RLComponentsTypes, RLEvent,
4    RLEventProcessorType, RLStrategy,
5};
6use burn_core::{self as burn};
7use burn_core::{config::Config, data::dataloader::Progress};
8use burn_rl::{
9    AsyncPolicy, Policy, PolicyLearner, SliceAccess, ToAction, ToObservation, TransitionBuffer,
10};
11
12/// Parameters of an on policy training with multi environments and double-batching.
13#[derive(Config, Debug)]
14pub struct OffPolicyConfig {
15    /// The number of environments to run simultaneously for experience collection.
16    #[config(default = 1)]
17    pub num_envs: usize,
18    /// Number of environment state to accumulate before running one step of inference with the policy.
19    /// Must be equal or less than the number of simultaneous environments.
20    #[config(default = 1)]
21    pub autobatch_size: usize,
22    /// Max number of transitions stored in the replay buffer.
23    #[config(default = 1024)]
24    pub replay_buffer_size: usize,
25    /// The number of steps to collect between each step of training.
26    #[config(default = 1)]
27    pub train_interval: usize,
28    /// Number of optimization steps done each `train_interval`.
29    #[config(default = 1)]
30    pub train_steps: usize,
31    /// The number of steps to collect between each evaluation.
32    #[config(default = 10_000)]
33    pub eval_interval: usize,
34    /// The number of episodes to run for each evaluation.
35    #[config(default = 1)]
36    pub eval_episodes: usize,
37    /// The number of transition to train on.
38    #[config(default = 32)]
39    pub train_batch_size: usize,
40    /// Number of steps to collect before starting to train.
41    #[config(default = 0)]
42    pub warmup_steps: usize,
43}
44
45/// Off-policy reinforcement learning strategy with multi-env experience collection and double-batching.
46pub struct OffPolicyStrategy {
47    config: OffPolicyConfig,
48}
49impl OffPolicyStrategy {
50    /// Create a new off-policy base strategy.
51    pub fn new(config: OffPolicyConfig) -> Self {
52        Self { config }
53    }
54}
55
56impl<RLC> RLStrategy<RLC> for OffPolicyStrategy
57where
58    RLC: RLComponentsTypes,
59    RLC::PolicyObs: SliceAccess,
60    RLC::PolicyAction: SliceAccess,
61{
62    fn train_loop(
63        &self,
64        training_components: RLComponents<RLC>,
65        learner_agent: &mut RLC::LearningAgent,
66        starting_epoch: usize,
67        env_init: RLC::EnvInit,
68    ) -> (RLC::Policy, RLEventProcessorType<RLC>) {
69        let mut event_processor = training_components.event_processor;
70        let mut checkpointer = training_components.checkpointer;
71        let num_steps_total = training_components.num_steps;
72
73        let inference_device = training_components.inference_device;
74        let mut env_runner = MultiAgentEnvLoop::<RLC>::new(
75            self.config.num_envs,
76            env_init.clone(),
77            AsyncPolicy::new(
78                self.config.num_envs.min(self.config.autobatch_size),
79                learner_agent.policy(),
80            ),
81            false,
82            false,
83            &inference_device,
84        );
85        let runner_config = AsyncAgentEnvLoopConfig {
86            eval: true,
87            deterministic: true,
88            id: 0,
89        };
90        let mut env_runner_valid = AgentEnvAsyncLoop::<RLC>::new(
91            env_init,
92            AsyncPolicy::new(1, learner_agent.policy()),
93            runner_config,
94            &inference_device,
95            None,
96            None,
97        );
98
99        let mut transition_buffer = TransitionBuffer::<RLC::PolicyObs, RLC::PolicyAction>::new(
100            self.config.replay_buffer_size,
101            &learner_agent.device(),
102        );
103
104        let mut valid_next = self.config.eval_interval + starting_epoch - 1;
105        let mut progress = Progress {
106            items_processed: starting_epoch,
107            items_total: num_steps_total,
108            unit: Some("steps".to_string()),
109        };
110
111        let mut intermediary_update: Option<<RLC::Policy as Policy>::PolicyState> = None;
112        while progress.items_processed < num_steps_total {
113            if training_components.interrupter.should_stop() {
114                if let Some(interruption) = training_components.interrupter.interruption() {
115                    let reason = interruption.reason.as_deref().unwrap_or("reason unknown");
116                    log::info!("Training interrupted: {reason}");
117                }
118                break;
119            }
120
121            let previous_steps = progress.items_processed;
122            let items = env_runner.run_steps(
123                self.config.train_interval,
124                &mut event_processor,
125                &training_components.interrupter,
126                &mut progress,
127            );
128
129            for item in &items {
130                let t = &item.transition;
131                let state: RLC::PolicyObs = t.state.clone().to_observation(&env_runner.device());
132                let next_state: RLC::PolicyObs =
133                    t.next_state.clone().to_observation(&env_runner.device());
134                let action: RLC::PolicyAction = t.action.clone().to_action(&env_runner.device());
135                let reward = t.reward.try_to_vec_as::<f32>().unwrap()[0];
136                let done = t.done.try_to_vec_as::<f32>().unwrap()[0] > 0.5;
137                transition_buffer.push(state, next_state, action, reward, done);
138            }
139
140            if transition_buffer.len() >= self.config.train_batch_size
141                && progress.items_processed >= self.config.warmup_steps
142            {
143                if let Some(ref u) = intermediary_update {
144                    env_runner.update_policy(u.clone());
145                }
146                for _ in 0..self.config.train_steps {
147                    let batch = transition_buffer.sample(self.config.train_batch_size);
148                    let train_item = learner_agent.train(batch);
149                    intermediary_update = Some(learner_agent.policy().state());
150
151                    training_components
152                        .interrupter
153                        .fail_on_error(event_processor.process_train(RLEvent::TrainStep(
154                            EvaluationItem::new(train_item.item, progress.clone(), None),
155                        )));
156                }
157            }
158
159            if valid_next > previous_steps && valid_next <= progress.items_processed {
160                training_components
161                    .interrupter
162                    .fail_on_error(event_processor.process_valid(
163                        crate::AgentEvaluationEvent::Start(self.config.eval_episodes),
164                    ));
165
166                env_runner_valid.update_policy(learner_agent.policy().state());
167                env_runner_valid.run_episodes(
168                    self.config.eval_episodes,
169                    &mut event_processor,
170                    &training_components.interrupter,
171                    &mut progress,
172                );
173
174                if let Some(checkpointer) = &mut checkpointer {
175                    checkpointer.checkpoint(
176                        &env_runner.policy(),
177                        learner_agent,
178                        valid_next,
179                        &training_components.event_store,
180                    );
181                }
182
183                valid_next += self.config.eval_interval;
184
185                training_components
186                    .interrupter
187                    .fail_on_error(event_processor.process_valid(crate::AgentEvaluationEvent::End));
188            }
189        }
190
191        (learner_agent.policy(), event_processor)
192    }
193}