Reinforcement Learning
stable-baselines3
English
Korean
ppo
continuous-control
mujoco
humanoid-v5
robotics
robot
bipedal-robot
neuromotion
Instructions to use hwihwalab/neuromotion-humanoid-v5-ppo with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- stable-baselines3
How to use hwihwalab/neuromotion-humanoid-v5-ppo with stable-baselines3:
from huggingface_sb3 import load_from_hub checkpoint = load_from_hub( repo_id="hwihwalab/neuromotion-humanoid-v5-ppo", filename="{MODEL FILENAME}.zip", ) - Notebooks
- Google Colab
- Kaggle
| import os | |
| import sys | |
| import time | |
| import argparse | |
| import warnings | |
| warnings.filterwarnings("ignore") | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import gymnasium as gym | |
| from stable_baselines3 import PPO | |
| from stable_baselines3.common.callbacks import BaseCallback | |
| # ========================================================== | |
| # 1. μ€μκ° κ·Έλν μκ°ν μ½λ°± | |
| # ========================================================== | |
| class RealtimePlotCallback(BaseCallback): | |
| """νμ΅ λμ€ μ€μκ°μΌλ‘ μνΌμλ 보μ λ° μνΌμλ κΈΈμ΄λ₯Ό κ·Έλνλ‘ μ λ°μ΄νΈν©λλ€.""" | |
| def __init__(self, plot_freq_episodes: int = 1, verbose: int = 0): | |
| super().__init__(verbose) | |
| self.plot_freq = plot_freq_episodes | |
| self.episode_rewards = [] | |
| self.episode_lengths = [] | |
| self.moving_avg_rewards = [] | |
| self.current_ep_reward = 0.0 | |
| self.current_ep_length = 0 | |
| self.fig = None | |
| self.ax1 = None | |
| self.ax2 = None | |
| def _on_training_start(self) -> None: | |
| plt.ion() | |
| self.fig, (self.ax1, self.ax2) = plt.subplots(2, 1, figsize=(8, 6)) | |
| self.fig.canvas.manager.set_window_title("Humanoid-v5 νμ΅ μ€μκ° ν΅κ³") | |
| self.fig.tight_layout(pad=3.0) | |
| plt.show(block=False) | |
| def _on_step(self) -> bool: | |
| rewards = self.locals.get("rewards") | |
| dones = self.locals.get("dones") | |
| if rewards is not None: | |
| self.current_ep_reward += float(rewards[0]) | |
| self.current_ep_length += 1 | |
| if dones is not None and dones[0]: | |
| self.episode_rewards.append(self.current_ep_reward) | |
| self.episode_lengths.append(self.current_ep_length) | |
| window = min(10, len(self.episode_rewards)) | |
| moving_avg = np.mean(self.episode_rewards[-window:]) | |
| self.moving_avg_rewards.append(moving_avg) | |
| if len(self.episode_rewards) % self.plot_freq == 0: | |
| self._update_plot() | |
| self.current_ep_reward = 0.0 | |
| self.current_ep_length = 0 | |
| return True | |
| def _update_plot(self): | |
| if not plt.fignum_exists(self.fig.number): | |
| return | |
| self.ax1.clear() | |
| self.ax2.clear() | |
| eps = range(1, len(self.episode_rewards) + 1) | |
| self.ax1.plot(eps, self.episode_rewards, label="Episode Reward", color="#4CAF50", alpha=0.5) | |
| self.ax1.plot(eps, self.moving_avg_rewards, label="10-Ep Moving Avg", color="#1E88E5", linewidth=2) | |
| self.ax1.set_title("Episode Reward Trend") | |
| self.ax1.set_xlabel("Episode") | |
| self.ax1.set_ylabel("Total Reward") | |
| self.ax1.legend(loc="upper left") | |
| self.ax1.grid(True, linestyle="--", alpha=0.6) | |
| self.ax2.plot(eps, self.episode_lengths, label="Survival Steps", color="#FF9800", linewidth=1.5) | |
| self.ax2.set_title("Survival Timesteps per Episode") | |
| self.ax2.set_xlabel("Episode") | |
| self.ax2.set_ylabel("Steps") | |
| self.ax2.legend(loc="upper left") | |
| self.ax2.grid(True, linestyle="--", alpha=0.6) | |
| self.fig.canvas.draw() | |
| self.fig.canvas.flush_events() | |
| plt.pause(0.001) | |
| def _on_training_end(self) -> None: | |
| if self.fig is not None and plt.fignum_exists(self.fig.number): | |
| plt.ioff() | |
| plt.show(block=False) | |
| # ========================================================== | |
| # 2. μ£ΌκΈ°μ 3D λ λλ§ μμ° μ½λ°± | |
| # ========================================================== | |
| class PeriodicVisualEvalCallback(BaseCallback): | |
| """νμ΅ λμ€ μΌμ μ€ν λ§λ€ 3D λ λλ§ μ°½μ λμ νμ¬ μ μ± μ 보ν μ±λ₯μ μκ°μ μΌλ‘ 보μ¬μ€λλ€.""" | |
| def __init__(self, eval_freq: int = 10000, n_eval_episodes: int = 1, verbose: int = 1): | |
| super().__init__(verbose) | |
| self.eval_freq = eval_freq | |
| self.n_eval_episodes = n_eval_episodes | |
| def _on_step(self) -> bool: | |
| if self.n_calls % self.eval_freq == 0: | |
| if self.verbose > 0: | |
| print(f"\n[μκ°ν νκ°] {self.n_calls} μ€ν λλ¬! νμ¬ μ μ± μ 보ν λͺ¨μ΅μ 3D μλμ°λ‘ μμ°ν©λλ€...") | |
| eval_env = gym.make("Humanoid-v5", render_mode="human") | |
| for ep in range(self.n_eval_episodes): | |
| obs, _ = eval_env.reset() | |
| done = False | |
| total_reward = 0.0 | |
| step_count = 0 | |
| while not done: | |
| action, _ = self.model.predict(obs, deterministic=True) | |
| obs, reward, terminated, truncated, _ = eval_env.step(action) | |
| total_reward += reward | |
| step_count += 1 | |
| done = terminated or truncated | |
| time.sleep(0.01) | |
| if self.verbose > 0: | |
| print(f" > νκ° μνΌμλ {ep + 1}: μ΄ λ³΄μ = {total_reward:.2f}, μμ‘΄ μ€ν = {step_count}") | |
| eval_env.close() | |
| return True | |
| # ========================================================== | |
| # 3. νκ²½ μμ± ν¬νΌ ν¨μ | |
| # ========================================================== | |
| def make_humanoid_env(render_mode=None): | |
| """Humanoid-v5 νκ²½μ μμ±ν©λλ€.""" | |
| return gym.make("Humanoid-v5", render_mode=render_mode) | |
| # ========================================================== | |
| # 4. κ° λͺ¨λλ³ μ€ν λ‘μ§ | |
| # ========================================================== | |
| def run_random_demo(n_steps=500): | |
| """νμ΅ μ κΈ°λ³Έ 무μμ νλ μκ°ν (μ΄κΈ° μν κ΄μ°°)""" | |
| print("\n" + "=" * 60) | |
| print(" [λͺ¨λ 4] 무μμ(Random) νλ 3D μκ°ν μμ°") | |
| print(" λ‘λ΄μ΄ νμ΅λμ§ μμ μνμμ μ΄λ»κ² λμ΄μ§λμ§ κ΄μ°°ν©λλ€.") | |
| print("=" * 60) | |
| env = gym.make("Humanoid-v5", render_mode="human") | |
| obs, info = env.reset(seed=42) | |
| total_reward = 0.0 | |
| ep_count = 1 | |
| for step in range(n_steps): | |
| action = env.action_space.sample() | |
| obs, reward, terminated, truncated, info = env.step(action) | |
| total_reward += reward | |
| time.sleep(0.015) | |
| if terminated or truncated: | |
| print(f" [μνΌμλ {ep_count}] {step + 1}λ²μ§Έ μ€ν μμ μ’ λ£ (λμ 보μ: {total_reward:.2f})") | |
| obs, info = env.reset() | |
| total_reward = 0.0 | |
| ep_count += 1 | |
| env.close() | |
| print("무μμ νλ μμ°μ΄ μλ£λμμ΅λλ€.\n") | |
| def run_live_training(total_timesteps=50000, model_save_path="humanoid_ppo_model.zip"): | |
| """μ€μκ° 3D λ λλ§ μλμ°μ κ·Έλνλ₯Ό 보면μ μ§μ νμ΅ μ§ν""" | |
| print("\n" + "=" * 60) | |
| print(" [λͺ¨λ 1] μ€μκ° 3D μκ°ν + κ°ννμ΅ (Live Visual Training)") | |
| print(" 3D νλ©΄μΌλ‘ λ‘λ΄μ΄ λμ΄μ§κ³ μΌμ΄μλ©° νμ΅νλ λͺ¨μ΅κ³Ό μ€μκ° κ·Έλνλ₯Ό κ΄μ°°ν©λλ€.") | |
| print(f" λͺ©ν νμ΅ νμμ€ν : {total_timesteps:,} steps") | |
| print("=" * 60) | |
| env = make_humanoid_env(render_mode="human") | |
| model = PPO( | |
| policy="MlpPolicy", | |
| env=env, | |
| learning_rate=3e-4, | |
| n_steps=1024, | |
| batch_size=64, | |
| n_epochs=10, | |
| gamma=0.99, | |
| gae_lambda=0.95, | |
| clip_range=0.2, | |
| ent_coef=0.0, | |
| verbose=1, | |
| ) | |
| plot_cb = RealtimePlotCallback(plot_freq_episodes=1) | |
| try: | |
| model.learn(total_timesteps=total_timesteps, callback=[plot_cb]) | |
| print(f"\nνμ΅ μλ£! λͺ¨λΈμ μ μ₯ν©λλ€ -> {model_save_path}") | |
| model.save(model_save_path) | |
| except KeyboardInterrupt: | |
| print("\nμ¬μ©μμ μν΄ νμ΅μ΄ μ€λ¨λμμ΅λλ€. νμ¬κΉμ§μ λͺ¨λΈμ μ μ₯ν©λλ€.") | |
| model.save(model_save_path) | |
| finally: | |
| env.close() | |
| def run_fast_training(total_timesteps=100000, eval_freq=10000, model_save_path="humanoid_ppo_model.zip"): | |
| """κ³ μ λ°±κ·ΈλΌμ΄λ νμ΅ + μ£ΌκΈ°μ 3D νκ° μμ° + μ€μκ° ν΅κ³ κ·Έλν""" | |
| print("\n" + "=" * 60) | |
| print(" [λͺ¨λ 2] κ³ μ νμ΅ + μ£ΌκΈ°μ 3D μμ° (Fast Training + Periodic Eval)") | |
| print(f" λΉ λ₯Έ μλλ‘ νμ΅νλ©΄μ λ§€ {eval_freq:,} μ€ν λ§λ€ 3D μ°½μΌλ‘ νμ΅ μ±κ³Όλ₯Ό μμ°ν©λλ€.") | |
| print(f" λͺ©ν νμ΅ νμμ€ν : {total_timesteps:,} steps") | |
| print("=" * 60) | |
| env = make_humanoid_env(render_mode=None) | |
| model = PPO( | |
| policy="MlpPolicy", | |
| env=env, | |
| learning_rate=3e-4, | |
| n_steps=2048, | |
| batch_size=64, | |
| n_epochs=10, | |
| gamma=0.99, | |
| gae_lambda=0.95, | |
| clip_range=0.2, | |
| verbose=1, | |
| ) | |
| plot_cb = RealtimePlotCallback(plot_freq_episodes=2) | |
| visual_cb = PeriodicVisualEvalCallback(eval_freq=eval_freq, n_eval_episodes=1) | |
| try: | |
| model.learn(total_timesteps=total_timesteps, callback=[plot_cb, visual_cb]) | |
| print(f"\nνμ΅ μλ£! λͺ¨λΈμ μ μ₯ν©λλ€ -> {model_save_path}") | |
| model.save(model_save_path) | |
| except KeyboardInterrupt: | |
| print("\nμ¬μ©μμ μν΄ νμ΅μ΄ μ€λ¨λμμ΅λλ€. νμ¬κΉμ§μ λͺ¨λΈμ μ μ₯ν©λλ€.") | |
| model.save(model_save_path) | |
| finally: | |
| env.close() | |
| def run_watch_trained_model(model_save_path="humanoid_ppo_model.zip", n_episodes=5): | |
| """μ μ₯λ λͺ¨λΈμ λΆλ¬μ 3D νλ©΄μΌλ‘ ν΄λ¨Έλ Έμ΄λ 보ν λͺ¨μ κ°μ""" | |
| print("\n" + "=" * 60) | |
| print(" [λͺ¨λ 3] νμ΅λ λͺ¨λΈ 3D μμ° (Watch Trained Model)") | |
| print(f" λͺ¨λΈ κ²½λ‘: {model_save_path}") | |
| print("=" * 60) | |
| if not os.path.exists(model_save_path): | |
| print(f"μ€λ₯: '{model_save_path}' νμΌμ΄ μ‘΄μ¬νμ§ μμ΅λλ€.") | |
| print("λ¨Όμ [λͺ¨λ 1] λλ [λͺ¨λ 2]λ‘ νμ΅μ μ§ννμ¬ λͺ¨λΈμ μμ±ν΄μ£ΌμΈμ.") | |
| return | |
| env = gym.make("Humanoid-v5", render_mode="human") | |
| model = PPO.load(model_save_path, env=env) | |
| print(f"μ΄ {n_episodes}κ° μνΌμλ λμ νμ΅λ λͺ¨λΈμ 보νμ μμ°ν©λλ€.\n") | |
| for ep in range(n_episodes): | |
| obs, info = env.reset() | |
| done = False | |
| total_reward = 0.0 | |
| step_count = 0 | |
| while not done: | |
| action, _states = model.predict(obs, deterministic=True) | |
| obs, reward, terminated, truncated, info = env.step(action) | |
| total_reward += reward | |
| step_count += 1 | |
| done = terminated or truncated | |
| time.sleep(0.015) | |
| print(f" [μνΌμλ {ep + 1}/{n_episodes}] μλ£ - μμ‘΄ μ€ν : {step_count}, μ΄ λ³΄μ: {total_reward:.2f}") | |
| env.close() | |
| print("\nμμ°μ΄ μλ£λμμ΅λλ€.") | |
| # ========================================================== | |
| # 5. λ©μΈ μ§μ μ λ° λνν λ©λ΄ | |
| # ========================================================== | |
| def main(): | |
| parser = argparse.ArgumentParser(description="MuJoCo Humanoid-v5 μ€μκ° μκ°ν κ°ννμ΅") | |
| parser.add_argument( | |
| "--mode", | |
| type=str, | |
| choices=["train_live", "train_fast", "play", "random"], | |
| default=None, | |
| help="μ€ν λͺ¨λ: train_live (μ€μκ° μκ°ν νμ΅), train_fast (κ³ μ νμ΅+μ£ΌκΈ°μ μμ°), play (μ μ₯λ λͺ¨λΈ μμ°), random (무μμ λμ)", | |
| ) | |
| parser.add_argument("--timesteps", type=int, default=50000, help="μ΄ νμ΅ μ€ν μ (κΈ°λ³Έ: 50,000)") | |
| parser.add_argument("--eval_freq", type=int, default=10000, help="κ³ μ νμ΅ μ 3D μκ°ν νκ° μ£ΌκΈ° μ€ν (κΈ°λ³Έ: 10,000)") | |
| parser.add_argument("--model_path", type=str, default="humanoid_ppo_model.zip", help="λͺ¨λΈ μ μ₯/λ‘λ κ²½λ‘") | |
| args = parser.parse_args() | |
| if args.mode is not None: | |
| if args.mode == "train_live": | |
| run_live_training(total_timesteps=args.timesteps, model_save_path=args.model_path) | |
| elif args.mode == "train_fast": | |
| run_fast_training(total_timesteps=args.timesteps, eval_freq=args.eval_freq, model_save_path=args.model_path) | |
| elif args.mode == "play": | |
| run_watch_trained_model(model_save_path=args.model_path) | |
| elif args.mode == "random": | |
| run_random_demo(n_steps=args.timesteps if args.timesteps != 50000 else 500) | |
| return | |
| while True: | |
| print("\n" + "=" * 65) | |
| print(" π€ MuJoCo Humanoid-v5 μ€μκ° μκ°ν κ°ννμ΅ μμ€ν π€") | |
| print("=" * 65) | |
| print(" 1. [μ€μκ° 3D μκ°ν νμ΅] - 3D νλ©΄ & κ·Έλνλ₯Ό 보면μ μ€μκ° νμ΅") | |
| print(" 2. [κ³ μ νμ΅ + μ£ΌκΈ°μ μμ°] - λΉ λ₯Έ νμ΅ + N μ€ν λ§λ€ 3D μ°½ μμ°") | |
| print(" 3. [νμ΅λ λͺ¨λΈ μμ°] - μ μ₯λ λͺ¨λΈλ‘ ν΄λ¨Έλ Έμ΄λ 보ν κ°μ") | |
| print(" 4. [무μμ λμ μμ°] - νμ΅ μ κΈ°λ³Έ 무μμ μν κ΄μ°°") | |
| print(" q. [νλ‘κ·Έλ¨ μ’ λ£]") | |
| print("=" * 65) | |
| choice = input("μ€νν λ²νΈλ₯Ό μ λ ₯νμΈμ (1-4, q=μ’ λ£): ").strip().lower() | |
| if choice == "1": | |
| run_live_training(total_timesteps=args.timesteps, model_save_path=args.model_path) | |
| elif choice == "2": | |
| run_fast_training(total_timesteps=args.timesteps, eval_freq=args.eval_freq, model_save_path=args.model_path) | |
| elif choice == "3": | |
| run_watch_trained_model(model_save_path=args.model_path) | |
| elif choice == "4": | |
| run_random_demo(n_steps=500) | |
| elif choice in ["q", "quit", "exit"]: | |
| print("\nνλ‘κ·Έλ¨μ μ’ λ£ν©λλ€. κ°μ¬ν©λλ€!") | |
| break | |
| else: | |
| print("\nμ¬λ°λ₯Έ λ²νΈ(1~4 λλ q)λ₯Ό μ λ ₯ν΄μ£ΌμΈμ.") | |
| if __name__ == "__main__": | |
| main() | |