From 5f1a2f9eaacf700c1dbdb9bd66f56febfa242e63 Mon Sep 17 00:00:00 2001 From: kywch Date: Tue, 14 May 2024 23:08:14 -0700 Subject: [PATCH] add agent training with curriculum --- curriculum/neurips_curriculum.py | 471 ++++++++++++++++++ .../neurips_curriculum_with_embedding.pkl | Bin 0 -> 187088 bytes curriculum/task_encoder.py | 35 +- curriculum/team_curriculum.py | 61 --- curriculum/team_curriculum_with_embedding.pkl | Bin 4614 -> 0 bytes reinforcement_learning/environment.py | 43 +- reinforcement_learning/stat_wrapper.py | 6 +- train.py | 10 +- 8 files changed, 552 insertions(+), 74 deletions(-) create mode 100644 curriculum/neurips_curriculum.py create mode 100644 curriculum/neurips_curriculum_with_embedding.pkl delete mode 100644 curriculum/team_curriculum.py delete mode 100644 curriculum/team_curriculum_with_embedding.pkl diff --git a/curriculum/neurips_curriculum.py b/curriculum/neurips_curriculum.py new file mode 100644 index 0000000..96e2137 --- /dev/null +++ b/curriculum/neurips_curriculum.py @@ -0,0 +1,471 @@ +"""2023 NeurIPS competition training and eval tasks""" + +from typing import List + +import nmmo.lib.material as m +import nmmo.systems.item as Item +import nmmo.systems.skill as Skill +from nmmo.task.base_predicates import ( + AttainSkill, + BuyItem, + CanSeeAgent, + CanSeeGroup, + CanSeeTile, + ConsumeItem, + CountEvent, + DefeatEntity, + EarnGold, + EquipItem, + FullyArmed, + GainExperience, + HarvestItem, + HoardGold, + InventorySpaceGE, + ListItem, + MakeProfit, + OccupyTile, + OwnItem, + ScoreHit, + SpendGold, + TickGE, +) +from nmmo.task.task_spec import TaskSpec, check_task_spec + +SKILLS = Skill.COMBAT_SKILL + Skill.HARVEST_SKILL +COMBAT_STYLE = Skill.COMBAT_SKILL +ALL_ITEM = Item.ALL_ITEM +EQUIP_ITEM = Item.ARMOR + Item.WEAPON + Item.TOOL + Item.AMMUNITION +HARVEST_ITEM = Item.WEAPON + Item.AMMUNITION + Item.CONSUMABLE +TOOL_FOR_SKILL = { + Skill.Melee: Item.Spear, + Skill.Range: Item.Bow, + Skill.Mage: Item.Wand, + Skill.Fishing: Item.Rod, + Skill.Herbalism: Item.Gloves, + Skill.Carving: Item.Axe, + Skill.Prospecting: Item.Pickaxe, + Skill.Alchemy: Item.Chisel, +} + +# Constants for the training tasks +EVENT_NUMBER_GOAL = [1, 2, 3, 5, 7, 9, 12, 15, 20, 30, 50] +INFREQUENT_GOAL = list(range(1, 10)) +STAY_ALIVE_GOAL = EXP_GOAL = [50, 100, 150, 200, 300, 500, 700] +LEVEL_GOAL = list(range(2, 10)) # TODO: get config +AGENT_NUM_GOAL = ITEM_NUM_GOAL = [1, 2, 3, 4, 5] # competition team size: 8 + +# Constants for the eval tasks +EVAL_EVENT_GOAL = 20 +EVAL_LEVEL_GOAL = [1, 3] +EVAL_GOLD_GOAL = 100 + + +curriculum: List[TaskSpec] = [] + +"""Training tasks""" + +# The default task: stay alive for the whole episode +curriculum.append(TaskSpec(eval_fn=TickGE, eval_fn_kwargs={"num_tick": 1024})) + +# explore, eat, drink, attack any agent, harvest any item, level up any skill +# which can happen frequently +most_essentials = [ + "EAT_FOOD", + "DRINK_WATER", +] +for event_code in most_essentials: + for cnt in range(1, 10): + curriculum.append( + TaskSpec( + eval_fn=CountEvent, + eval_fn_kwargs={"event": event_code, "N": cnt}, + sampling_weight=100, + ) + ) + +essential_skills = [ + "SCORE_HIT", + "PLAYER_KILL", + "HARVEST_ITEM", + "EQUIP_ITEM", + "CONSUME_ITEM", + "LEVEL_UP", + "EARN_GOLD", + "LIST_ITEM", + "BUY_ITEM", +] +for event_code in essential_skills: + for cnt in EVENT_NUMBER_GOAL: + curriculum.append( + TaskSpec( + eval_fn=CountEvent, + eval_fn_kwargs={"event": event_code, "N": cnt}, + sampling_weight=20, + ) + ) + +# item/market skills, which happen less frequently or should not do too much +item_skills = [ + "GIVE_ITEM", + "DESTROY_ITEM", + "GIVE_GOLD", +] +for event_code in item_skills: + curriculum += [ + TaskSpec(eval_fn=CountEvent, eval_fn_kwargs={"event": event_code, "N": cnt}) + for cnt in INFREQUENT_GOAL + ] # less than 10 + +# find resource tiles +for resource in m.Harvestable: + curriculum.append( + TaskSpec( + eval_fn=CanSeeTile, + eval_fn_kwargs={"tile_type": resource}, + sampling_weight=10, + ) + ) + + +# practice particular skill with a tool +def PracticeSkillWithTool(gs, subject, skill, exp): + return 0.3 * EquipItem( + gs, subject, item=TOOL_FOR_SKILL[skill], level=1, num_agent=1 + ) + 0.7 * GainExperience(gs, subject, skill, exp, num_agent=1) + + +for skill in SKILLS: + # level up a skill + for level in LEVEL_GOAL[1:]: + # since this is an agent task, num_agent must be 1 + curriculum.append( + TaskSpec( + eval_fn=AttainSkill, + eval_fn_kwargs={"skill": skill, "level": level, "num_agent": 1}, + sampling_weight=10 * (6 - level) if level < 6 else 5, + ) + ) + + # gain experience on particular skill + for exp in EXP_GOAL: + curriculum.append( + TaskSpec( + eval_fn=PracticeSkillWithTool, + eval_fn_kwargs={"skill": skill, "exp": exp}, + sampling_weight=50, + ) + ) + +# stay alive ... like ... for 300 ticks +# i.e., getting incremental reward for each tick alive as an individual or a team +for num_tick in STAY_ALIVE_GOAL: + curriculum.append(TaskSpec(eval_fn=TickGE, eval_fn_kwargs={"num_tick": num_tick})) + +# occupy the center tile, assuming the Medium map size +# TODO: it'd be better to have some intermediate targets toward the center +curriculum.append(TaskSpec(eval_fn=OccupyTile, eval_fn_kwargs={"row": 80, "col": 80})) + +# find the other team leader +for target in ["left_team_leader", "right_team_leader"]: + curriculum.append(TaskSpec(eval_fn=CanSeeAgent, eval_fn_kwargs={"target": target})) + +# find the other team (any agent) +for target in ["left_team", "right_team"]: + curriculum.append(TaskSpec(eval_fn=CanSeeGroup, eval_fn_kwargs={"target": target})) + +# practice specific combat style +for style in COMBAT_STYLE: + for cnt in EVENT_NUMBER_GOAL: + curriculum.append( + TaskSpec( + eval_fn=ScoreHit, + eval_fn_kwargs={"combat_style": style, "N": cnt}, + sampling_weight=5, + ) + ) + +# hoarding gold -- evaluated on the current gold +for amount in EVENT_NUMBER_GOAL: + curriculum.append( + TaskSpec(eval_fn=HoardGold, eval_fn_kwargs={"amount": amount}, sampling_weight=10) + ) + +# earning gold -- evaluated on the total gold earned by selling items +for amount in EVENT_NUMBER_GOAL: + curriculum.append( + TaskSpec(eval_fn=EarnGold, eval_fn_kwargs={"amount": amount}, sampling_weight=10) + ) + +# spending gold, by buying items +for amount in EVENT_NUMBER_GOAL: + curriculum.append( + TaskSpec(eval_fn=SpendGold, eval_fn_kwargs={"amount": amount}, sampling_weight=5) + ) + +# making profits by trading -- only buying and selling are counted +for amount in EVENT_NUMBER_GOAL: + curriculum.append( + TaskSpec(eval_fn=MakeProfit, eval_fn_kwargs={"amount": amount}, sampling_weight=3) + ) + + +# managing inventory space +def PracticeInventoryManagement(gs, subject, space, num_tick): + return InventorySpaceGE(gs, subject, space) * TickGE(gs, subject, num_tick) + + +for space in [2, 4, 8]: + curriculum += [ + TaskSpec( + eval_fn=PracticeInventoryManagement, + eval_fn_kwargs={"space": space, "num_tick": num_tick}, + ) + for num_tick in STAY_ALIVE_GOAL + ] + +# own item, evaluated on the current inventory +for item in ALL_ITEM: + for level in LEVEL_GOAL: + # agent task + for quantity in ITEM_NUM_GOAL: + if level + quantity <= 6 or quantity == 1: # heuristic prune + curriculum.append( + TaskSpec( + eval_fn=OwnItem, + eval_fn_kwargs={ + "item": item, + "level": level, + "quantity": quantity, + }, + sampling_weight=4 - level if level < 4 else 1, + ) + ) + +# equip item, evaluated on the current inventory and equipment status +for item in EQUIP_ITEM: + for level in LEVEL_GOAL: + # agent task + curriculum.append( + TaskSpec( + eval_fn=EquipItem, + eval_fn_kwargs={"item": item, "level": level, "num_agent": 1}, + sampling_weight=4 - level if level < 4 else 1, + ) + ) + +# consume items (ration, potion), evaluated based on the event log +for item in Item.CONSUMABLE: + for level in LEVEL_GOAL: + # agent task + for quantity in ITEM_NUM_GOAL: + if level + quantity <= 6 or quantity == 1: # heuristic prune + curriculum.append( + TaskSpec( + eval_fn=ConsumeItem, + eval_fn_kwargs={ + "item": item, + "level": level, + "quantity": quantity, + }, + sampling_weight=4 - level if level < 4 else 1, + ) + ) + +# harvest items, evaluated based on the event log +for item in HARVEST_ITEM: + for level in LEVEL_GOAL: + # agent task + for quantity in ITEM_NUM_GOAL: + if level + quantity <= 6 or quantity == 1: # heuristic prune + curriculum.append( + TaskSpec( + eval_fn=HarvestItem, + eval_fn_kwargs={ + "item": item, + "level": level, + "quantity": quantity, + }, + sampling_weight=4 - level if level < 4 else 1, + ) + ) + +# list items, evaluated based on the event log +for item in ALL_ITEM: + for level in LEVEL_GOAL: + # agent task + for quantity in ITEM_NUM_GOAL: + if level + quantity <= 6 or quantity == 1: # heuristic prune + curriculum.append( + TaskSpec( + eval_fn=ListItem, + eval_fn_kwargs={ + "item": item, + "level": level, + "quantity": quantity, + }, + sampling_weight=4 - level if level < 4 else 1, + ) + ) + +# buy items, evaluated based on the event log +for item in ALL_ITEM: + for level in LEVEL_GOAL: + # agent task + for quantity in ITEM_NUM_GOAL: + if level + quantity <= 6 or quantity == 1: # heuristic prune + curriculum.append( + TaskSpec( + eval_fn=BuyItem, + eval_fn_kwargs={ + "item": item, + "level": level, + "quantity": quantity, + }, + sampling_weight=4 - level if level < 4 else 1, + ) + ) + +"""Eval tasks""" + +# Survive to the end +curriculum.append(TaskSpec(eval_fn=TickGE, eval_fn_kwargs={"num_tick": 1024})) + +# Kill 20 players/npcs +curriculum.append( + TaskSpec( + eval_fn=CountEvent, + eval_fn_kwargs={"event": "PLAYER_KILL", "N": EVAL_EVENT_GOAL}, + tags=["eval"], + ) +) + +# Kill npcs of level 1+, 3+ +for level in EVAL_LEVEL_GOAL: + curriculum.append( + TaskSpec( + eval_fn=DefeatEntity, + eval_fn_kwargs={"agent_type": "npc", "level": level, "num_agent": EVAL_EVENT_GOAL}, + tags=["eval"], + ) + ) + +# Explore and reach the center (80, 80) +curriculum.append( + TaskSpec(eval_fn=CountEvent, eval_fn_kwargs={"event": "GO_FARTHEST", "N": 64}, tags=["eval"]) +) + +curriculum.append( + TaskSpec(eval_fn=OccupyTile, eval_fn_kwargs={"row": 80, "col": 80}, tags=["eval"]) +) + +# Reach skill level 10 +for skill in SKILLS: + curriculum.append( + TaskSpec( + eval_fn=AttainSkill, + eval_fn_kwargs={"skill": skill, "level": 10, "num_agent": 1}, + tags=["eval"], + ) + ) + +# Harvest 20 ammos of level 1+ or 3+ +for ammo in Item.AMMUNITION: + for level in EVAL_LEVEL_GOAL: + curriculum.append( + TaskSpec( + eval_fn=HarvestItem, + eval_fn_kwargs={"item": ammo, "level": level, "quantity": EVAL_EVENT_GOAL}, + tags=["eval"], + ) + ) + +# Consume 10 ration/potions of level 1+ or 3+ +for item in Item.CONSUMABLE: + for level in EVAL_LEVEL_GOAL: + curriculum.append( + TaskSpec( + eval_fn=ConsumeItem, + eval_fn_kwargs={"item": item, "level": level, "quantity": EVAL_EVENT_GOAL}, + tags=["eval"], + ) + ) + +# Equip armour, weapons, tools, and ammos +for item in EQUIP_ITEM: + for level in EVAL_LEVEL_GOAL: + curriculum.append( + TaskSpec( + eval_fn=EquipItem, + eval_fn_kwargs={"item": item, "level": level, "num_agent": 1}, + tags=["eval"], + ) + ) + +# Fully armed, level 1+ or 3+ +for skill in Skill.COMBAT_SKILL: + for level in EVAL_LEVEL_GOAL: + curriculum.append( + TaskSpec( + eval_fn=FullyArmed, + eval_fn_kwargs={"combat_style": skill, "level": level, "num_agent": 1}, + tags=["eval"], + ) + ) + +# Buy and Sell 10 items (of any kind) +curriculum.append( + TaskSpec( + eval_fn=CountEvent, + eval_fn_kwargs={"event": "EARN_GOLD", "N": EVAL_EVENT_GOAL}, # item sold + tags=["eval"], + ) +) + +curriculum.append( + TaskSpec( + eval_fn=CountEvent, + eval_fn_kwargs={"event": "BUY_ITEM", "N": EVAL_EVENT_GOAL}, # item bought + tags=["eval"], + ) +) + +# Earn 100 gold (revenue), just by trading +curriculum.append( + TaskSpec(eval_fn=EarnGold, eval_fn_kwargs={"amount": EVAL_GOLD_GOAL}, tags=["eval"]) +) + +# Own and protect 100 gold by any means (looting or trading) +curriculum.append( + TaskSpec(eval_fn=HoardGold, eval_fn_kwargs={"amount": EVAL_GOLD_GOAL}, tags=["eval"]) +) + +# Make profit of 100 gold by any means +curriculum.append( + TaskSpec(eval_fn=MakeProfit, eval_fn_kwargs={"amount": EVAL_GOLD_GOAL}, tags=["eval"]) +) + + +if __name__ == "__main__": + import multiprocessing as mp + from contextlib import contextmanager + + import dill + import numpy as np + import psutil + + @contextmanager + def create_pool(num_proc): + pool = mp.Pool(processes=num_proc) + yield pool + pool.close() + pool.join() + + # divide the specs into chunks + num_workers = round(psutil.cpu_count(logical=False) * 0.7) + spec_chunks = np.array_split(curriculum, num_workers) + with create_pool(num_workers) as pool: + pool.map(check_task_spec, spec_chunks) + + # test if the task spec is pickalable + with open("pickle_test.pkl", "wb") as f: + dill.dump(curriculum, f) diff --git a/curriculum/neurips_curriculum_with_embedding.pkl b/curriculum/neurips_curriculum_with_embedding.pkl new file mode 100644 index 0000000000000000000000000000000000000000..30fe43e9ab1ddc9cd7f827a91539ce0277d59346 GIT binary patch literal 187088 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
    hUa8PyW7Ju0Ki-!yGPBRn~O)%?QWgX6E@v2o6 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 diff --git a/reinforcement_learning/environment.py b/reinforcement_learning/environment.py index 743a155..68f0a1c 100644 --- a/reinforcement_learning/environment.py +++ b/reinforcement_learning/environment.py @@ -1,5 +1,8 @@ from argparse import Namespace +import dill +import numpy as np + import pufferlib import pufferlib.emulation from pettingzoo.utils.wrappers.base_parallel import BaseParallelWrapper @@ -57,6 +60,41 @@ def _set_config(self): self.config.set_for_episode("NPC_N", npc_num) +class AgentTraining(ng.AgentTraining): + def _get_candidate_tasks(self, eval_mode=False): + with open(self.config.CURRICULUM_FILE_PATH, "rb") as f: + # curriculum file may have been changed, so read the file when sampling + curriculum = dill.load(f) # a list of TaskSpec + + cand_specs = [spec for spec in curriculum if spec.reward_to == "agent"] + if eval_mode: + cand_specs = [spec for spec in cand_specs if "eval" in spec.tags] + else: + cand_specs = [spec for spec in cand_specs if "eval" not in spec.tags] + + assert len(cand_specs) > 0, "There is no agent task to be sampled" + return cand_specs + + def _make_agent_tasks(self, cand_specs): + sampling_weights = [spec.sampling_weight for spec in cand_specs] + sampled_spec = self._np_random.choice( + cand_specs, size=self.config.PLAYER_N, p=sampling_weights / np.sum(sampling_weights) + ) + return make_task_from_spec(self.config.POSSIBLE_AGENTS, sampled_spec) + + def _define_tasks(self): + # NOTE: Some tasks may not be achievable at all when the necessary game system is off + cand_specs = self._get_candidate_tasks(eval_mode=False) + return self._make_agent_tasks(cand_specs) + + +class AgentTaskEval(AgentTraining): + def _define_tasks(self): + # NOTE: Some tasks may not be achievable at all when the necessary game system is off + cand_specs = self._get_candidate_tasks(eval_mode=True) + return self._make_agent_tasks(cand_specs) + + class AmmoTraining(ng.AgentTraining): def is_compatible(self): return self.config.are_systems_enabled(["COMBAT", "EQUIPMENT", "PROFESSION"]) @@ -171,8 +209,7 @@ def __init__(self, env_args: Namespace): self.set("RESOURCE_RESILIENT_POPULATION", env_args.resilient_population) self.set("COMBAT_SPAWN_IMMUNITY", env_args.spawn_immunity) - # NOTE: Disabling curriculum file for now - # self.set("CURRICULUM_FILE_PATH", env_args.curriculum_file_path) + self.set("CURRICULUM_FILE_PATH", env_args.curriculum_file_path) class FullGameConfig( @@ -210,6 +247,8 @@ def make_env_creator( game_packs = [(TeamBattle, 1), (EasyKingoftheHill, 1), (Sandwich, 1)] elif train_flag == "tb_ammo": game_packs = [(TeamBattle, 5), (AmmoTraining, 1)] + elif train_flag == "tb_curr": + game_packs = [(TeamBattle, 1), (AgentTraining, 1)] else: raise ValueError(f"Invalid train_flag: {train_flag}") diff --git a/reinforcement_learning/stat_wrapper.py b/reinforcement_learning/stat_wrapper.py index 9b4bc42..9ca8bbc 100644 --- a/reinforcement_learning/stat_wrapper.py +++ b/reinforcement_learning/stat_wrapper.py @@ -7,7 +7,7 @@ import nmmo.systems.item as Item from nmmo.minigames import RacetoCenter, KingoftheHill, Sandwich, RadioRaid -from reinforcement_learning.environment import TeamBattle +from reinforcement_learning.environment import TeamBattle, AgentTraining class BaseStatWrapper(BaseParallelWrapper): @@ -231,8 +231,8 @@ def _process_stats_and_early_stop(self, agent_id, reward, terminated, truncated, # 'return' is used for ranking in the eval mode, so put the task progress here info["return"] = task._max_progress # this is 1 if done - # Log the below stats ONLY for the team battle - if isinstance(self.env.game, TeamBattle): + # Log the below stats ONLY for the team battle & agent training + if isinstance(self.env.game, TeamBattle) or isinstance(self.env.game, AgentTraining): # Max combat/harvest level achieved info["stats"]["achieved/max_combat_level"] = agent.attack_level info["stats"]["achieved/max_harvest_skill_ammo"] = max( diff --git a/train.py b/train.py index 3d1dbc6..5a2845e 100644 --- a/train.py +++ b/train.py @@ -13,7 +13,7 @@ from reinforcement_learning import environment from train_helper import init_wandb, train, sweep, generate_replay -BASELINE_CURRICULUM = "curriculum/team_curriculum_with_embedding.pkl" +BASELINE_CURRICULUM = "curriculum/neurips_curriculum_with_embedding.pkl" def load_from_config(agent, debug=False): @@ -120,7 +120,7 @@ def update_args(args, mode=None): args = pufferlib.namespace(**args) args.track = not args.no_track - # args.env.curriculum_file_path = args.curriculum + args.env.curriculum_file_path = args.curriculum vec = args.vectorization if vec == "serial" or args.debug: @@ -187,9 +187,9 @@ def update_args(args, mode=None): choices="battle race koh sandwich radio".split(), help="Game to evaluate/replay", ) - # parser.add_argument( - # "-c", "--curriculum", type=str, default=BASELINE_CURRICULUM, help="Path to curriculum file" - # ) + parser.add_argument( + "-c", "--curriculum", type=str, default=BASELINE_CURRICULUM, help="Path to curriculum file" + ) # parser.add_argument( # "-t", # "--task-to-assign",