add no gui, no realtime mode, and train bash script
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import subprocess
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
class Server():
|
||||
def __init__(self, first_server_p, first_monitor_p, n_servers) -> None:
|
||||
def __init__(self, first_server_p, first_monitor_p, n_servers, no_render=True, no_realtime=True) -> None:
|
||||
try:
|
||||
import psutil
|
||||
self.check_running_servers(psutil, first_server_p, first_monitor_p, n_servers)
|
||||
@@ -17,21 +18,32 @@ class Server():
|
||||
|
||||
# makes it easier to kill test servers without affecting train servers
|
||||
cmd = "rcssservermj"
|
||||
render_arg = "--no-render" if no_render else ""
|
||||
realtime_arg = "--no-realtime" if no_realtime else ""
|
||||
for i in range(n_servers):
|
||||
port = first_server_p + i
|
||||
mport = first_monitor_p + i
|
||||
|
||||
server_cmd = f"{cmd} -c {port} -m {mport} "
|
||||
server_cmd = f"{cmd} -c {port} -m {mport} {render_arg} {realtime_arg}".strip()
|
||||
|
||||
self.rcss_processes.append(
|
||||
subprocess.Popen(
|
||||
server_cmd.split(),
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.STDOUT,
|
||||
start_new_session=True
|
||||
)
|
||||
proc = subprocess.Popen(
|
||||
server_cmd.split(),
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.STDOUT,
|
||||
start_new_session=True
|
||||
)
|
||||
|
||||
# Avoid startup storm when launching many servers at once.
|
||||
time.sleep(0.03)
|
||||
|
||||
rc = proc.poll()
|
||||
if rc is not None:
|
||||
raise RuntimeError(
|
||||
f"rcssservermj exited early (code={rc}) on server port {port}, monitor port {mport}"
|
||||
)
|
||||
|
||||
self.rcss_processes.append(proc)
|
||||
|
||||
def check_running_servers(self, psutil, first_server_p, first_monitor_p, n_servers):
|
||||
''' Check if any server is running on chosen ports '''
|
||||
found = False
|
||||
|
||||
99
scripts/gyms/Walk.py
Normal file → Executable file
99
scripts/gyms/Walk.py
Normal file → Executable file
@@ -7,7 +7,8 @@ from random import random
|
||||
from random import uniform
|
||||
|
||||
from stable_baselines3 import PPO
|
||||
from stable_baselines3.common.vec_env import SubprocVecEnv
|
||||
from stable_baselines3.common.monitor import Monitor
|
||||
from stable_baselines3.common.vec_env import SubprocVecEnv, DummyVecEnv
|
||||
|
||||
import gymnasium as gym
|
||||
from gymnasium import spaces
|
||||
@@ -164,6 +165,32 @@ class WalkEnv(gym.Env):
|
||||
)
|
||||
self.start_time = time.time()
|
||||
|
||||
def _reconnect_server(self):
|
||||
try:
|
||||
self.Player.server.shutdown()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.Player.server.connect()
|
||||
self.Player.server.send_immediate(
|
||||
f"(init {self.Player.robot.name} {self.Player.world.team_name} {self.Player.world.number})"
|
||||
)
|
||||
|
||||
def _safe_receive_world_update(self, retries=1):
|
||||
last_exc = None
|
||||
for attempt in range(retries + 1):
|
||||
try:
|
||||
self.Player.server.receive()
|
||||
self.Player.world.update()
|
||||
return
|
||||
except (ConnectionResetError, OSError) as exc:
|
||||
last_exc = exc
|
||||
if attempt >= retries:
|
||||
raise
|
||||
self._reconnect_server()
|
||||
if last_exc is not None:
|
||||
raise last_exc
|
||||
|
||||
def debug_log(self, message):
|
||||
print(message)
|
||||
try:
|
||||
@@ -231,8 +258,7 @@ class WalkEnv(gym.Env):
|
||||
|
||||
def sync(self):
|
||||
''' Run a single simulation step '''
|
||||
self.Player.server.receive()
|
||||
self.Player.world.update()
|
||||
self._safe_receive_world_update(retries=1)
|
||||
self.Player.robot.commit_motor_targets_pd()
|
||||
self.Player.server.send()
|
||||
if self._target_dt > 0.0:
|
||||
@@ -302,8 +328,7 @@ class WalkEnv(gym.Env):
|
||||
beam_yaw = uniform(-self.reset_beam_yaw_range_deg, self.reset_beam_yaw_range_deg)
|
||||
|
||||
for _ in range(5):
|
||||
self.Player.server.receive()
|
||||
self.Player.world.update()
|
||||
self._safe_receive_world_update(retries=2)
|
||||
self.Player.robot.commit_motor_targets_pd()
|
||||
self.Player.server.commit_beam(pos2d=(beam_x, beam_y), rotation=beam_yaw)
|
||||
self.Player.server.send()
|
||||
@@ -510,13 +535,14 @@ class Train(Train_Base):
|
||||
def train(self, args):
|
||||
|
||||
# --------------------------------------- Learning parameters
|
||||
n_envs = 20 # Reduced from 8 to decrease CPU/network pressure during init
|
||||
n_envs = int(os.environ.get("GYM_CPU_N_ENVS", "20"))
|
||||
if n_envs < 1:
|
||||
raise ValueError("GYM_CPU_N_ENVS must be >= 1")
|
||||
n_steps_per_env = 256 # RolloutBuffer is of size (n_steps_per_env * n_envs)
|
||||
minibatch_size = 512 # should be a factor of (n_steps_per_env * n_envs)
|
||||
server_warmup_sec = float(os.environ.get("GYM_CPU_SERVER_WARMUP_SEC", "3.0"))
|
||||
n_steps_per_env = int(os.environ.get("GYM_CPU_TRAIN_STEPS_PER_ENV", "256")) # RolloutBuffer is of size (n_steps_per_env * n_envs)
|
||||
minibatch_size = int(os.environ.get("GYM_CPU_TRAIN_BATCH_SIZE", "512")) # should be a factor of (n_steps_per_env * n_envs)
|
||||
total_steps = 30000000
|
||||
learning_rate = 1e-4
|
||||
learning_rate = float(os.environ.get("GYM_CPU_TRAIN_LR", "3e-4"))
|
||||
folder_name = f'Walk_R{self.robot_type}'
|
||||
model_path = f'./scripts/gyms/logs/{folder_name}/'
|
||||
|
||||
@@ -524,22 +550,29 @@ class Train(Train_Base):
|
||||
print(f"Using {n_envs} parallel environments")
|
||||
|
||||
# --------------------------------------- Run algorithm
|
||||
def init_env(i_env):
|
||||
def init_env(i_env, monitor=False):
|
||||
def thunk():
|
||||
return WalkEnv(self.ip, self.server_p + i_env)
|
||||
env = WalkEnv(self.ip, self.server_p + i_env)
|
||||
if monitor:
|
||||
env = Monitor(env)
|
||||
return env
|
||||
|
||||
return thunk
|
||||
|
||||
server_log_dir = os.path.join(model_path, "server_logs")
|
||||
os.makedirs(server_log_dir, exist_ok=True)
|
||||
servers = Train_Server(self.server_p, self.monitor_p_1000, n_envs + 1) # include 1 extra server for testing
|
||||
servers = Train_Server(self.server_p, self.monitor_p_1000, n_envs + 1, no_render=True, no_realtime=True) # include 1 extra server for testing
|
||||
|
||||
# Wait for servers to start
|
||||
print(f"Starting {n_envs + 1} rcssservermj servers...")
|
||||
if server_warmup_sec > 0:
|
||||
print(f"Waiting {server_warmup_sec:.1f}s for server warmup...")
|
||||
sleep(server_warmup_sec)
|
||||
print("Servers started, creating environments...")
|
||||
|
||||
env = SubprocVecEnv([init_env(i) for i in range(n_envs)])
|
||||
eval_env = SubprocVecEnv([init_env(n_envs)])
|
||||
env = SubprocVecEnv([init_env(i, monitor=True) for i in range(n_envs)])
|
||||
# Use single-process eval env to avoid extra subprocess fragility during callback evaluation.
|
||||
eval_env = DummyVecEnv([init_env(n_envs, monitor=True)])
|
||||
|
||||
try:
|
||||
# Custom policy network architecture
|
||||
@@ -564,16 +597,17 @@ class Train(Train_Base):
|
||||
learning_rate=learning_rate,
|
||||
device="cpu",
|
||||
policy_kwargs=policy_kwargs,
|
||||
ent_coef=0.03, # Entropy coefficient for exploration
|
||||
clip_range=0.13, # PPO clipping parameter
|
||||
ent_coef=float(os.environ.get("GYM_CPU_TRAIN_ENT_COEF", "0.05")), # Entropy coefficient for exploration
|
||||
clip_range=float(os.environ.get("GYM_CPU_TRAIN_CLIP_RANGE", "0.2")), # PPO clipping parameter
|
||||
gae_lambda=0.95, # GAE lambda
|
||||
gamma=0.95 , # Discount factor
|
||||
gamma=float(os.environ.get("GYM_CPU_TRAIN_GAMMA", "0.95")), # Discount factor
|
||||
target_kl=0.03,
|
||||
n_epochs=5
|
||||
n_epochs=int(os.environ.get("GYM_CPU_TRAIN_EPOCHS", "5")),
|
||||
# tensorboard_log=f"./scripts/gyms/logs/{folder_name}/tensorboard/"
|
||||
)
|
||||
|
||||
model_path = self.learn_model(model, total_steps, model_path, eval_env=eval_env,
|
||||
eval_freq=n_steps_per_env * 10, save_freq=n_steps_per_env * 10,
|
||||
eval_freq=n_steps_per_env * 20, save_freq=n_steps_per_env * 20,
|
||||
backup_env_file=__file__)
|
||||
except KeyboardInterrupt:
|
||||
sleep(1) # wait for child processes
|
||||
@@ -590,7 +624,16 @@ class Train(Train_Base):
|
||||
# Uses different server and monitor ports
|
||||
server_log_dir = os.path.join(args["folder_dir"], "server_logs")
|
||||
os.makedirs(server_log_dir, exist_ok=True)
|
||||
server = Train_Server(self.server_p - 1, self.monitor_p, 1)
|
||||
test_no_render = os.environ.get("GYM_CPU_TEST_NO_RENDER", "0") == "1"
|
||||
test_no_realtime = os.environ.get("GYM_CPU_TEST_NO_REALTIME", "0") == "1"
|
||||
|
||||
server = Train_Server(
|
||||
self.server_p - 1,
|
||||
self.monitor_p,
|
||||
1,
|
||||
no_render=test_no_render,
|
||||
no_realtime=test_no_realtime,
|
||||
)
|
||||
env = WalkEnv(self.ip, self.server_p - 1)
|
||||
model = PPO.load(args["model_file"], env=env)
|
||||
|
||||
@@ -621,6 +664,16 @@ if __name__ == "__main__":
|
||||
)
|
||||
|
||||
trainer = Train(script_args)
|
||||
trainer.train({"model_file": "scripts/gyms/logs/Walk_R0_004/best_model.zip"})
|
||||
# trainer.test({"model_file": "scripts/gyms/logs/Walk_R0_004/best_model.zip",
|
||||
# "folder_dir": "scripts/gyms/logs/Walk_R0_004/",})
|
||||
|
||||
run_mode = os.environ.get("GYM_CPU_MODE", "train").strip().lower()
|
||||
|
||||
if run_mode == "test":
|
||||
test_model_file = os.environ.get("GYM_CPU_TEST_MODEL", "scripts/gyms/logs/Walk_R0_004/best_model.zip")
|
||||
test_folder = os.environ.get("GYM_CPU_TEST_FOLDER", "scripts/gyms/logs/Walk_R0_004/")
|
||||
trainer.test({"model_file": test_model_file, "folder_dir": test_folder})
|
||||
else:
|
||||
retrain_model = os.environ.get("GYM_CPU_TRAIN_MODEL", "").strip()
|
||||
if retrain_model:
|
||||
trainer.train({"model_file": retrain_model})
|
||||
else:
|
||||
trainer.train({})
|
||||
Reference in New Issue
Block a user