neuromotion-humanoid-v5-ppo / mujoko_humanoid.py
hwihwalab's picture
feat: upload mujoko_humanoid.py
06f5218 verified
Raw
History Blame
13.6 kB
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()