1
Fork 0

feat(train): integrate experiment_logger and replace print statements

- Replace direct WandB calls with experiment_logger.UnifiedLogger
- Replace all print() calls with proper logging framework
- Add automatic checkpoint saving every N iterations
- Configure root logger with proper format and level
- Maintain backward compatibility with TensorBoard writer
- Save final model with metadata using unified logger
This commit is contained in:
Tibo De Peuter 2026-03-31 19:49:21 +00:00
parent bb0bb94f60
commit b70cd1c27b

View file

@ -1,3 +1,4 @@
import logging
import random import random
import time import time
from dataclasses import asdict from dataclasses import asdict
@ -18,7 +19,10 @@ from torch.utils.tensorboard import SummaryWriter
from brittle_star_project.dataclasses import PPOArgs from brittle_star_project.dataclasses import PPOArgs
from brittle_star_project.dataclasses.EpisodeStatistics import EpisodeStatistics from brittle_star_project.dataclasses.EpisodeStatistics import EpisodeStatistics
from brittle_star_project.environment.BrittleStarJaxEnvWrapper import BrittleStarJaxEnvWrapper from brittle_star_project.environment.BrittleStarJaxEnvWrapper import BrittleStarJaxEnvWrapper
from brittle_star_project.rl import Network, Actor, Critic, AgentParams, Storage from brittle_star_project.rl import Actor, AgentParams, Critic, Network, Storage
from experiment_logger import UnifiedLogger
log = logging.getLogger(__name__)
def convert_obs_dict_to_array(obs_dict: dict) -> jnp.ndarray: def convert_obs_dict_to_array(obs_dict: dict) -> jnp.ndarray:
@ -39,20 +43,19 @@ def train(args: PPOArgs):
args.minibatch_size = args.batch_size // args.num_minibatches args.minibatch_size = args.batch_size // args.num_minibatches
args.num_iterations = args.total_timesteps // args.batch_size args.num_iterations = args.total_timesteps // args.batch_size
run_name = f"{args.exp_name}__seed_{args.seed}__{int(time.time())}" run_name = f"{args.exp_name}__seed_{args.seed}__{int(time.time())}"
print(f"running name: {run_name}") log.info(f"Run name: {run_name}")
if args.track: # Initialize unified logger (replaces wandb.init and tensorboard writer)
import wandb logger = UnifiedLogger(
run_name=run_name,
wandb.init(
project=args.wandb_project_name,
entity=args.wandb_entity,
sync_tensorboard=True,
config=vars(args), config=vars(args),
name=run_name, project_name=args.wandb_project_name,
entity=args.wandb_entity,
use_wandb=args.track,
save_code=True, save_code=True,
) )
# Keep TensorBoard writer for backward compatibility
writer = SummaryWriter(f"runs/{run_name}") writer = SummaryWriter(f"runs/{run_name}")
writer.add_text( writer.add_text(
"hyperparameters", "hyperparameters",
@ -66,9 +69,10 @@ def train(args: PPOArgs):
torch.backends.cudnn.deterministic = args.torch_deterministic torch.backends.cudnn.deterministic = args.torch_deterministic
device = torch.device("cuda" if torch.cuda.is_available() and args.cuda else "cpu") device = torch.device("cuda" if torch.cuda.is_available() and args.cuda else "cpu")
print(f"Running on device: {device}") device = "cpu" # Force CPU for JAX
log.info(f"Device: {device}")
print("Creating the environment...") log.info("Creating environment...")
env = make_env(num_envs=args.num_envs)() env = make_env(num_envs=args.num_envs)()
episode_stats = EpisodeStatistics( episode_stats = EpisodeStatistics(
@ -110,7 +114,7 @@ def train(args: PPOArgs):
frac = 1.0 - (count // (args.num_minibatches * args.update_epochs)) / args.num_iterations frac = 1.0 - (count // (args.num_minibatches * args.update_epochs)) / args.num_iterations
return args.learning_rate * frac return args.learning_rate * frac
print("Initializing the models...") log.info("Initializing models...")
network = Network() network = Network()
actor = Actor(action_dim=env.single_action_space.shape[0]) # continuous actions for MJX actor = Actor(action_dim=env.single_action_space.shape[0]) # continuous actions for MJX
critic = Critic() critic = Critic()
@ -259,7 +263,7 @@ def train(args: PPOArgs):
start_time = time.time() start_time = time.time()
# Reset once to get initial state # Reset once to get initial state
print("Resetting the environment...") log.info("Resetting environment...")
next_env_state = env.reset(seed=args.seed) next_env_state = env.reset(seed=args.seed)
next_obs = convert_obs_dict_to_array(next_env_state.observations) next_obs = convert_obs_dict_to_array(next_env_state.observations)
next_done = jnp.zeros(args.num_envs, dtype=jnp.bool_) next_done = jnp.zeros(args.num_envs, dtype=jnp.bool_)
@ -301,9 +305,9 @@ def train(args: PPOArgs):
max_steps=args.num_steps, max_steps=args.num_steps,
) )
print("Starting training...") log.info("Starting training...")
iters_bar = tqdm.tqdm(range(1, args.num_iterations + 1)) iters_bar = tqdm.tqdm(range(1, args.num_iterations + 1))
for _ in iters_bar: for iteration in iters_bar:
iteration_time_start = time.time() iteration_time_start = time.time()
agent_state, episode_stats, next_obs, next_done, storage, key, next_env_state = rollout( agent_state, episode_stats, next_obs, next_done, storage, key, next_env_state = rollout(
@ -317,37 +321,76 @@ def train(args: PPOArgs):
) )
avg_episodic_return = np.mean(jax.device_get(episode_stats.returned_episode_returns)) avg_episodic_return = np.mean(jax.device_get(episode_stats.returned_episode_returns))
avg_episodic_length = np.mean(jax.device_get(episode_stats.returned_episode_lengths))
learning_rate = agent_state.opt_state[1].hyperparams["learning_rate"].item()
sps = int(global_step / (time.time() - start_time))
sps_update = int(args.num_envs * args.num_steps / (time.time() - iteration_time_start))
iters_bar.set_postfix_str( iters_bar.set_postfix_str(
f"global_step={global_step}, avg_episodic_return={avg_episodic_return}" f"global_step={global_step}, avg_episodic_return={avg_episodic_return}"
) )
# Log to unified logger
logger.log(
{
"charts/avg_episodic_return": avg_episodic_return,
"charts/avg_episodic_length": avg_episodic_length,
"charts/learning_rate": learning_rate,
"charts/SPS": sps,
"charts/SPS_update": sps_update,
"losses/value_loss": v_loss[-1, -1].item(),
"losses/policy_loss": pg_loss[-1, -1].item(),
"losses/entropy": entropy_loss[-1, -1].item(),
"losses/approx_kl": approx_kl[-1, -1].item(),
"losses/loss": loss[-1, -1].item(),
},
step=global_step,
)
# Also log to TensorBoard for backward compatibility
writer.add_scalar("charts/avg_episodic_return", avg_episodic_return, global_step) writer.add_scalar("charts/avg_episodic_return", avg_episodic_return, global_step)
writer.add_scalar( writer.add_scalar("charts/avg_episodic_length", avg_episodic_length, global_step)
"charts/avg_episodic_length", writer.add_scalar("charts/learning_rate", learning_rate, global_step)
np.mean(jax.device_get(episode_stats.returned_episode_lengths)),
global_step,
)
writer.add_scalar(
"charts/learning_rate",
agent_state.opt_state[1].hyperparams["learning_rate"].item(),
global_step,
)
writer.add_scalar("losses/value_loss", v_loss[-1, -1].item(), global_step) writer.add_scalar("losses/value_loss", v_loss[-1, -1].item(), global_step)
writer.add_scalar("losses/policy_loss", pg_loss[-1, -1].item(), global_step) writer.add_scalar("losses/policy_loss", pg_loss[-1, -1].item(), global_step)
writer.add_scalar("losses/entropy", entropy_loss[-1, -1].item(), global_step) writer.add_scalar("losses/entropy", entropy_loss[-1, -1].item(), global_step)
writer.add_scalar("losses/approx_kl", approx_kl[-1, -1].item(), global_step) writer.add_scalar("losses/approx_kl", approx_kl[-1, -1].item(), global_step)
writer.add_scalar("losses/loss", loss[-1, -1].item(), global_step) writer.add_scalar("losses/loss", loss[-1, -1].item(), global_step)
writer.add_scalar("charts/SPS", sps, global_step)
writer.add_scalar("charts/SPS_update", sps_update, global_step)
# iters_bar.set_postfix_str(f"SPS: {int(global_step / (time.time() - start_time))}") # Save periodic checkpoints
if args.checkpoint_frequency > 0 and iteration % args.checkpoint_frequency == 0:
writer.add_scalar("charts/SPS", int(global_step / (time.time() - start_time)), global_step) logger.save_checkpoint(
writer.add_scalar( params={
"charts/SPS_update", "network_params": agent_state.params["network_params"],
int(args.num_envs * args.num_steps / (time.time() - iteration_time_start)), "actor_params": agent_state.params["actor_params"],
global_step, "critic_params": agent_state.params["critic_params"],
},
step=global_step,
metadata={
"iteration": iteration,
"avg_episodic_return": float(avg_episodic_return),
"avg_episodic_length": float(avg_episodic_length),
},
) )
if args.save_model: if args.save_model:
# Save using unified logger (better organization and WandB integration)
logger.save_final_model(
params={
"network_params": agent_state.params["network_params"],
"actor_params": agent_state.params["actor_params"],
"critic_params": agent_state.params["critic_params"],
},
metadata={
"global_step": global_step,
"avg_episodic_return": float(avg_episodic_return),
"config": vars(args),
},
)
# Also save in old format for backward compatibility
model_path = f"runs/{run_name}/{args.exp_name}.cleanrl_model" model_path = f"runs/{run_name}/{args.exp_name}.cleanrl_model"
with open(model_path, "wb") as f: with open(model_path, "wb") as f:
f.write( f.write(
@ -362,13 +405,19 @@ def train(args: PPOArgs):
] ]
) )
) )
print(f"model saved to {model_path}") log.info(f"Legacy model saved to {model_path}")
# Finalize logging
logger.finish()
env.close() env.close()
writer.close() writer.close()
def main() -> None: def main() -> None:
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
args = tyro.cli(PPOArgs) args = tyro.cli(PPOArgs)
train(args) train(args)