mirror of
https://github.com/jcreek/Sarpy.git
synced 2026-07-12 18:53:44 +00:00
183 lines
7.1 KiB
Python
183 lines
7.1 KiB
Python
import os
|
|
import numpy as np
|
|
import atexit
|
|
|
|
from stable_baselines3 import PPO
|
|
from stable_baselines3.ppo import MlpPolicy
|
|
from stable_baselines3.common.callbacks import CheckpointCallback
|
|
from stable_baselines3.common.vec_env import VecMonitor, VecNormalize, VecCheckNan
|
|
|
|
from rlgym.envs import Match
|
|
|
|
from rlgym.utils.action_parsers import DiscreteAction
|
|
from rlgym.utils.reward_functions.common_rewards import (
|
|
VelocityBallToGoalReward,
|
|
LiuDistanceBallToGoalReward,
|
|
BallYCoordinateReward,
|
|
EventReward,
|
|
RewardIfClosestToBall,
|
|
LiuDistancePlayerToBallReward,
|
|
FaceBallReward,
|
|
TouchBallReward,
|
|
AlignBallGoal,
|
|
SaveBoostReward,
|
|
)
|
|
from rlgym.utils.obs_builders import AdvancedObs
|
|
from rlgym.utils.state_setters import DefaultState
|
|
from rlgym.utils.terminal_conditions.common_conditions import (
|
|
TimeoutCondition,
|
|
NoTouchTimeoutCondition,
|
|
GoalScoredCondition,
|
|
)
|
|
from rlgym.utils.reward_functions.common_rewards.player_ball_rewards import (
|
|
VelocityPlayerToBallReward,
|
|
)
|
|
from rlgym.utils.reward_functions.common_rewards.ball_goal_rewards import (
|
|
VelocityBallToGoalReward,
|
|
)
|
|
from rlgym.utils.reward_functions import CombinedReward
|
|
|
|
from rlgym_tools.sb3_utils import SB3MultipleInstanceEnv
|
|
from rlgym_tools.extra_rewards.kickoff_reward import KickoffReward
|
|
from rlgym_tools.extra_obs.advanced_padder import AdvancedObsPadder
|
|
|
|
if __name__ == "__main__": # Required for multiprocessing
|
|
# Set up the folders
|
|
models_dir = "models"
|
|
log_dir = "logs"
|
|
if not os.path.exists(models_dir):
|
|
os.makedirs(models_dir)
|
|
if not os.path.exists(log_dir):
|
|
os.makedirs(log_dir)
|
|
|
|
frame_skip = 8 # Number of ticks to repeat an action
|
|
half_life_seconds = (
|
|
5 # Easier to conceptualize, after this many seconds the reward discount is 0.5
|
|
)
|
|
|
|
fps = 120 / frame_skip
|
|
gamma = np.exp(np.log(0.5) / (fps * half_life_seconds)) # Quick mafs
|
|
print(f"fps={fps}, gamma={gamma})")
|
|
|
|
def exit_save(model):
|
|
model.save("models/exit_save")
|
|
|
|
team_size = 3
|
|
|
|
def get_match(): # Need to use a function so that each instance can call it and produce their own objects
|
|
return Match(
|
|
team_size=team_size,
|
|
tick_skip=frame_skip,
|
|
reward_function=CombinedReward(
|
|
(
|
|
VelocityPlayerToBallReward(),
|
|
VelocityBallToGoalReward(),
|
|
EventReward(
|
|
team_goal=1000.0,
|
|
concede=-100.0,
|
|
shot=10.0,
|
|
save=60.0,
|
|
demo=20.0,
|
|
),
|
|
KickoffReward(),
|
|
SaveBoostReward(),
|
|
),
|
|
(1.0, 1.0, 1.0, 1.0, 1.0),
|
|
),
|
|
# reward_function=CombinedReward(
|
|
# (
|
|
# VelocityPlayerToBallReward(),
|
|
# VelocityBallToGoalReward(),
|
|
# LiuDistanceBallToGoalReward(),
|
|
# EventReward(
|
|
# team_goal=10000.0,
|
|
# concede=-10000.0,
|
|
# shot=10.0,
|
|
# save=60.0,
|
|
# demo=20.0,
|
|
# ),
|
|
# BallYCoordinateReward(),
|
|
# RewardIfClosestToBall(LiuDistancePlayerToBallReward()),
|
|
# LiuDistancePlayerToBallReward(),
|
|
# FaceBallReward(),
|
|
# TouchBallReward(),
|
|
# AlignBallGoal(),
|
|
# KickoffReward(),
|
|
# ),
|
|
# (50.0, 10.0, 20.0, 100.0, 0.1, 10.0, 1.0, 0.2, 10.0, 20.0, 10.0),
|
|
# ),
|
|
spawn_opponents=True,
|
|
terminal_conditions=[
|
|
TimeoutCondition(fps * 300),
|
|
NoTouchTimeoutCondition(fps * 10),
|
|
GoalScoredCondition(),
|
|
],
|
|
obs_builder=AdvancedObsPadder(
|
|
team_size=team_size
|
|
), # Not that advanced, good default
|
|
state_setter=DefaultState(), # Resets to kickoff position
|
|
action_parser=DiscreteAction(), # Discrete > Continuous don't @ me
|
|
)
|
|
|
|
# Generate the environment (the Rocket League game used by RL Gym)
|
|
env = SB3MultipleInstanceEnv(
|
|
get_match, 14
|
|
) # Start 14 instances, waiting 30 seconds between each
|
|
env = VecCheckNan(env) # Optional
|
|
env = VecMonitor(env) # Recommended, logs mean reward and ep_len to Tensorboard
|
|
env = VecNormalize(
|
|
env, norm_obs=False, gamma=gamma
|
|
) # Highly recommended, normalizes rewards
|
|
|
|
# Save model every so often
|
|
# Divide by num_envs (number of agents) because callback only increments every time all agents have taken a step
|
|
# This saves to specified folder with a specified name
|
|
callback = CheckpointCallback(
|
|
round(1_000_000 / env.num_envs), save_path=models_dir, name_prefix="sarpy_model"
|
|
)
|
|
|
|
try:
|
|
while True:
|
|
# Load the model that was last trained, or start a new one if the zip file doesn't exist
|
|
try:
|
|
# Now, if one wants to load a trained model from a checkpoint, use this function
|
|
# This will contain all the attributes of the original model
|
|
# Any attribute can be overwritten by using the custom_objects parameter,
|
|
# which includes n_envs (number of agents), which has to be overwritten to use a different amount
|
|
model = PPO.load(
|
|
f"{models_dir}/exit_save.zip",
|
|
env,
|
|
custom_objects=dict(
|
|
n_envs=env.num_envs, _last_obs=None
|
|
), # Need this to change number of agents
|
|
device="auto", # Need to set device again (if using a specific one)
|
|
force_reset=True, # Make SB3 reset the env so it doesn't think we're continuing from last state
|
|
)
|
|
print("Loaded exit_save.zip model")
|
|
except:
|
|
model = PPO(
|
|
MlpPolicy,
|
|
env,
|
|
n_epochs=32, # PPO calls for multiple epochs
|
|
learning_rate=1e-5, # Around this is fairly common for PPO
|
|
ent_coef=0.01, # From PPO Atari
|
|
vf_coef=1.0, # From PPO Atari
|
|
gamma=gamma, # Gamma as calculated using half-life
|
|
verbose=3, # Print out all the info as we're going
|
|
batch_size=4096, # Batch size as high as possible within reason
|
|
n_steps=4096, # Number of steps to perform before optimizing network
|
|
tensorboard_log="out/logs", # `tensorboard --logdir out/logs` in terminal to see graphs
|
|
device="auto", # Uses GPU if available
|
|
)
|
|
print("Created new model")
|
|
|
|
atexit.register(exit_save, model)
|
|
|
|
print("Learning...")
|
|
model.learn(100_000_000, callback=callback, reset_num_timesteps=False)
|
|
model.save(f"{models_dir}/exit_save")
|
|
print("Saved exit_save model")
|
|
|
|
except Exception as e:
|
|
print(e)
|