def prioritize_resume_init(self) -> None: """Prioritize explicit command line resume/init over conflicting yaml options. if both resume/init are set at one place use resume""" _non_default_args = DetectDefault.non_default_args if "resume" in _non_default_args: if self.initialize_from is not None: logger.warning( f"Both 'resume' and 'initialize_from={self.initialize_from}' are set!" f" Current run will be resumed ignoring initialization." ) self.initialize_from = parser.get_default("initialize_from") elif "initialize_from" in _non_default_args: if self.resume: logger.warning( f"Both 'resume' and 'initialize_from={self.initialize_from}' are set!" f" {self.run_id} is initialized_from {self.initialize_from} and resume will be ignored." ) self.resume = parser.get_default("resume") elif self.resume and self.initialize_from is not None: # no cli args but both are set in yaml file logger.warning( f"Both 'resume' and 'initialize_from={self.initialize_from}' are set in yaml file!" f" Current run will be resumed ignoring initialization." ) self.initialize_from = parser.get_default("initialize_from")
class EnvironmentSettings: env_path: Optional[str] = parser.get_default("env_path") env_args: Optional[List[str]] = parser.get_default("env_args") base_port: int = parser.get_default("base_port") num_envs: int = attr.ib(default=parser.get_default("num_envs")) seed: int = parser.get_default("seed") @num_envs.validator def validate_num_envs(self, attribute, value): if value > 1 and self.env_path is None: raise ValueError("num_envs must be 1 if env_path is not set.")
class CheckpointSettings: run_id: str = parser.get_default("run_id") initialize_from: str = parser.get_default("initialize_from") load_model: bool = parser.get_default("load_model") resume: bool = parser.get_default("resume") force: bool = parser.get_default("force") train_model: bool = parser.get_default("train_model") inference: bool = parser.get_default("inference") enable_meta_learning: bool = parser.get_default("enable_meta_learning") meta_lr: float = parser.get_default("meta_lr")
class CheckpointSettings: save_freq: int = parser.get_default("save_freq") run_id: str = parser.get_default("run_id") initialize_from: str = parser.get_default("initialize_from") load_model: bool = parser.get_default("load_model") resume: bool = parser.get_default("resume") force: bool = parser.get_default("force") train_model: bool = parser.get_default("train_model") inference: bool = parser.get_default("inference") lesson: int = parser.get_default("lesson")
class CheckpointSettings: run_id: str = parser.get_default("run_id") initialize_from: Optional[str] = parser.get_default("initialize_from") load_model: bool = parser.get_default("load_model") resume: bool = parser.get_default("resume") force: bool = parser.get_default("force") train_model: bool = parser.get_default("train_model") inference: bool = parser.get_default("inference") results_dir: str = parser.get_default("results_dir") @property def write_path(self) -> str: return os.path.join(self.results_dir, self.run_id) @property def maybe_init_path(self) -> Optional[str]: return ( os.path.join(self.results_dir, self.initialize_from) if self.initialize_from is not None else None ) @property def run_logs_dir(self) -> str: return os.path.join(self.write_path, "run_logs")
class ParameterRandomizationSettings(abc.ABC): seed: int = parser.get_default("seed") @staticmethod def structure(d: Union[Mapping, float], t: type) -> "ParameterRandomizationSettings": """ Helper method to a ParameterRandomizationSettings class. Meant to be registered with cattr.register_structure_hook() and called with cattr.structure(). This is needed to handle the special Enum selection of ParameterRandomizationSettings classes. """ if isinstance(d, (float, int)): return ConstantSettings(value=d) if not isinstance(d, Mapping): raise TrainerConfigError( f"Unsupported parameter randomization configuration {d}.") if "sampler_type" not in d: raise TrainerConfigError( f"Sampler configuration does not contain sampler_type : {d}.") if "sampler_parameters" not in d: raise TrainerConfigError( f"Sampler configuration does not contain sampler_parameters : {d}." ) enum_key = ParameterRandomizationType(d["sampler_type"]) t = enum_key.to_settings() return strict_to_cls(d["sampler_parameters"], t) @staticmethod def unstructure(d: "ParameterRandomizationSettings") -> Mapping: """ Helper method to a ParameterRandomizationSettings class. Meant to be registered with cattr.register_unstructure_hook() and called with cattr.unstructure(). """ _reversed_mapping = { UniformSettings: ParameterRandomizationType.UNIFORM, GaussianSettings: ParameterRandomizationType.GAUSSIAN, MultiRangeUniformSettings: ParameterRandomizationType.MULTIRANGEUNIFORM, ConstantSettings: ParameterRandomizationType.CONSTANT, } sampler_type: Optional[str] = None for t, name in _reversed_mapping.items(): if isinstance(d, t): sampler_type = name.value sampler_parameters = attr.asdict(d) return { "sampler_type": sampler_type, "sampler_parameters": sampler_parameters } @abc.abstractmethod def apply(self, key: str, env_channel: EnvironmentParametersChannel) -> None: """ Helper method to send sampler settings over EnvironmentParametersChannel Calls the appropriate sampler type set method. :param key: environment parameter to be sampled :param env_channel: The EnvironmentParametersChannel to communicate sampler settings to environment """ pass
class EngineSettings: width: int = parser.get_default("width") height: int = parser.get_default("height") quality_level: int = parser.get_default("quality_level") time_scale: float = parser.get_default("time_scale") target_frame_rate: int = parser.get_default("target_frame_rate") capture_frame_rate: int = parser.get_default("capture_frame_rate") no_graphics: bool = parser.get_default("no_graphics")
class CheckpointSettings: run_id: str = parser.get_default("run_id") initialize_from: Optional[str] = parser.get_default("initialize_from") load_model: bool = parser.get_default("load_model") resume: bool = parser.get_default("resume") force: bool = parser.get_default("force") train_model: bool = parser.get_default("train_model") inference: bool = parser.get_default("inference")
class CheckpointSettings: run_id: str = parser.get_default("run_id") initialize_from: Optional[str] = parser.get_default("initialize_from") load_model: bool = parser.get_default("load_model") resume: bool = parser.get_default("resume") force: bool = parser.get_default("force") train_model: bool = parser.get_default("train_model") inference: bool = parser.get_default("inference") results_dir: str = parser.get_default("results_dir") @property def write_path(self) -> str: return os.path.join(self.results_dir, self.run_id) @property def maybe_init_path(self) -> Optional[str]: return ( os.path.join(self.results_dir, self.initialize_from) if self.initialize_from is not None else None ) @property def run_logs_dir(self) -> str: return os.path.join(self.write_path, "run_logs") def prioritize_resume_init(self) -> None: """Prioritize explicit command line resume/init over conflicting yaml options. if both resume/init are set at one place use resume""" _non_default_args = DetectDefault.non_default_args if "resume" in _non_default_args: if self.initialize_from is not None: logger.warning( f"Both 'resume' and 'initialize_from={self.initialize_from}' are set!" f" Current run will be resumed ignoring initialization." ) self.initialize_from = parser.get_default("initialize_from") elif "initialize_from" in _non_default_args: if self.resume: logger.warning( f"Both 'resume' and 'initialize_from={self.initialize_from}' are set!" f" {self.run_id} is initialized_from {self.initialize_from} and resume will be ignored." ) self.resume = parser.get_default("resume") elif self.resume and self.initialize_from is not None: # no cli args but both are set in yaml file logger.warning( f"Both 'resume' and 'initialize_from={self.initialize_from}' are set in yaml file!" f" Current run will be resumed ignoring initialization." ) self.initialize_from = parser.get_default("initialize_from")
class ParameterRandomizationSettings(abc.ABC): seed: int = parser.get_default("seed") @staticmethod def structure(d: Mapping, t: type) -> Any: """ Helper method to structure a Dict of ParameterRandomizationSettings class. Meant to be registered with cattr.register_structure_hook() and called with cattr.structure(). This is needed to handle the special Enum selection of ParameterRandomizationSettings classes. """ if not isinstance(d, Mapping): raise TrainerConfigError( f"Unsupported parameter randomization configuration {d}." ) d_final: Dict[str, List[float]] = {} for environment_parameter, environment_parameter_config in d.items(): if environment_parameter == "resampling-interval": logger.warning( "The resampling-interval is no longer necessary for parameter randomization. It is being ignored." ) continue if "sampler_type" not in environment_parameter_config: raise TrainerConfigError( f"Sampler configuration for {environment_parameter} does not contain sampler_type." ) if "sampler_parameters" not in environment_parameter_config: raise TrainerConfigError( f"Sampler configuration for {environment_parameter} does not contain sampler_parameters." ) enum_key = ParameterRandomizationType( environment_parameter_config["sampler_type"] ) t = enum_key.to_settings() d_final[environment_parameter] = strict_to_cls( environment_parameter_config["sampler_parameters"], t ) return d_final @abc.abstractmethod def apply(self, key: str, env_channel: EnvironmentParametersChannel) -> None: """ Helper method to send sampler settings over EnvironmentParametersChannel Calls the appropriate sampler type set method. :param key: environment parameter to be sampled :param env_channel: The EnvironmentParametersChannel to communicate sampler settings to environment """ pass
class EnvironmentSettings: env_path: Optional[str] = parser.get_default("env_path") env_args: Optional[List[str]] = parser.get_default("env_args") base_port: int = parser.get_default("base_port") num_envs: int = attr.ib(default=parser.get_default("num_envs")) num_areas: int = attr.ib(default=parser.get_default("num_areas")) seed: int = parser.get_default("seed") max_lifetime_restarts: int = parser.get_default("max_lifetime_restarts") restarts_rate_limit_n: int = parser.get_default("restarts_rate_limit_n") restarts_rate_limit_period_s: int = parser.get_default( "restarts_rate_limit_period_s" ) @num_envs.validator def validate_num_envs(self, attribute, value): if value > 1 and self.env_path is None: raise ValueError("num_envs must be 1 if env_path is not set.") @num_areas.validator def validate_num_area(self, attribute, value): if value <= 0: raise ValueError("num_areas must be set to a positive number >= 1.")
class NetworkSettings: @attr.s class MemorySettings: sequence_length: int = attr.ib(default=64) memory_size: int = attr.ib(default=128) @memory_size.validator def _check_valid_memory_size(self, attribute, value): if value <= 0: raise TrainerConfigError( "When using a recurrent network, memory size must be greater than 0." ) elif value % 2 != 0: raise TrainerConfigError( "When using a recurrent network, memory size must be divisible by 2." ) normalize: bool = False hidden_units: int = 128 num_layers: int = 2 vis_encode_type: EncoderType = EncoderType.SIMPLE memory: Optional[MemorySettings] = None goal_conditioning_type: ConditioningType = ConditioningType.HYPER deterministic: bool = parser.get_default("deterministic")
class RunOptions(ExportableSettings): behaviors: DefaultDict[str, TrainerSettings] = attr.ib( factory=lambda: collections.defaultdict(TrainerSettings) ) env_settings: EnvironmentSettings = attr.ib(factory=EnvironmentSettings) engine_settings: EngineSettings = attr.ib(factory=EngineSettings) parameter_randomization: Optional[Dict] = None curriculum: Optional[Dict[str, CurriculumSettings]] = None checkpoint_settings: CheckpointSettings = attr.ib(factory=CheckpointSettings) # These are options that are relevant to the run itself, and not the engine or environment. # They will be left here. debug: bool = parser.get_default("debug") # Strict conversion cattr.register_structure_hook(EnvironmentSettings, strict_to_cls) cattr.register_structure_hook(EngineSettings, strict_to_cls) cattr.register_structure_hook(CheckpointSettings, strict_to_cls) cattr.register_structure_hook(CurriculumSettings, strict_to_cls) cattr.register_structure_hook(TrainerSettings, TrainerSettings.structure) cattr.register_structure_hook( DefaultDict[str, TrainerSettings], TrainerSettings.dict_to_defaultdict ) cattr.register_unstructure_hook(collections.defaultdict, defaultdict_to_dict) @staticmethod def from_argparse(args: argparse.Namespace) -> "RunOptions": """ Takes an argparse.Namespace as specified in `parse_command_line`, loads input configuration files from file paths, and converts to a RunOptions instance. :param args: collection of command-line parameters passed to mlagents-learn :return: RunOptions representing the passed in arguments, with trainer config, curriculum and sampler configs loaded from files. """ argparse_args = vars(args) config_path = StoreConfigFile.trainer_config_path # Load YAML configured_dict: Dict[str, Any] = { "checkpoint_settings": {}, "env_settings": {}, "engine_settings": {}, } if config_path is not None: configured_dict.update(load_config(config_path)) # Use the YAML file values for all values not specified in the CLI. for key in configured_dict.keys(): # Detect bad config options if key not in attr.fields_dict(RunOptions): raise TrainerConfigError( "The option {} was specified in your YAML file, but is invalid.".format( key ) ) # Override with CLI args # Keep deprecated --load working, TODO: remove argparse_args["resume"] = argparse_args["resume"] or argparse_args["load_model"] for key, val in argparse_args.items(): if key in DetectDefault.non_default_args: if key in attr.fields_dict(CheckpointSettings): configured_dict["checkpoint_settings"][key] = val elif key in attr.fields_dict(EnvironmentSettings): configured_dict["env_settings"][key] = val elif key in attr.fields_dict(EngineSettings): configured_dict["engine_settings"][key] = val else: # Base options configured_dict[key] = val return RunOptions.from_dict(configured_dict) @staticmethod def from_dict(options_dict: Dict[str, Any]) -> "RunOptions": return cattr.structure(options_dict, RunOptions)
class EnvironmentSettings: env_path: Optional[str] = parser.get_default("env_path") env_args: Optional[List[str]] = parser.get_default("env_args") base_port: int = parser.get_default("base_port") num_envs: int = parser.get_default("num_envs") seed: int = parser.get_default("seed")
class RunOptions(ExportableSettings): default_settings: Optional[TrainerSettings] = None behaviors: TrainerSettings.DefaultTrainerDict = attr.ib( factory=TrainerSettings.DefaultTrainerDict ) env_settings: EnvironmentSettings = attr.ib(factory=EnvironmentSettings) engine_settings: EngineSettings = attr.ib(factory=EngineSettings) environment_parameters: Optional[Dict[str, EnvironmentParameterSettings]] = None checkpoint_settings: CheckpointSettings = attr.ib(factory=CheckpointSettings) torch_settings: TorchSettings = attr.ib(factory=TorchSettings) # These are options that are relevant to the run itself, and not the engine or environment. # They will be left here. debug: bool = parser.get_default("debug") # Convert to settings while making sure all fields are valid cattr.register_structure_hook(EnvironmentSettings, strict_to_cls) cattr.register_structure_hook(EngineSettings, strict_to_cls) cattr.register_structure_hook(CheckpointSettings, strict_to_cls) cattr.register_structure_hook( Dict[str, EnvironmentParameterSettings], EnvironmentParameterSettings.structure ) cattr.register_structure_hook(Lesson, strict_to_cls) cattr.register_structure_hook( ParameterRandomizationSettings, ParameterRandomizationSettings.structure ) cattr.register_unstructure_hook( ParameterRandomizationSettings, ParameterRandomizationSettings.unstructure ) cattr.register_structure_hook(TrainerSettings, TrainerSettings.structure) cattr.register_structure_hook( TrainerSettings.DefaultTrainerDict, TrainerSettings.dict_to_trainerdict ) cattr.register_unstructure_hook(collections.defaultdict, defaultdict_to_dict) @staticmethod def from_argparse(args: argparse.Namespace) -> "RunOptions": """ Takes an argparse.Namespace as specified in `parse_command_line`, loads input configuration files from file paths, and converts to a RunOptions instance. :param args: collection of command-line parameters passed to mlagents-learn :return: RunOptions representing the passed in arguments, with trainer config, curriculum and sampler configs loaded from files. """ argparse_args = vars(args) config_path = StoreConfigFile.trainer_config_path # Load YAML configured_dict: Dict[str, Any] = { "checkpoint_settings": {}, "env_settings": {}, "engine_settings": {}, "torch_settings": {}, } _require_all_behaviors = True if config_path is not None: configured_dict.update(load_config(config_path)) else: # If we're not loading from a file, we don't require all behavior names to be specified. _require_all_behaviors = False # Use the YAML file values for all values not specified in the CLI. for key in configured_dict.keys(): # Detect bad config options if key not in attr.fields_dict(RunOptions): raise TrainerConfigError( "The option {} was specified in your YAML file, but is invalid.".format( key ) ) # Override with CLI args # Keep deprecated --load working, TODO: remove argparse_args["resume"] = argparse_args["resume"] or argparse_args["load_model"] for key, val in argparse_args.items(): if key in DetectDefault.non_default_args: if key in attr.fields_dict(CheckpointSettings): configured_dict["checkpoint_settings"][key] = val elif key in attr.fields_dict(EnvironmentSettings): configured_dict["env_settings"][key] = val elif key in attr.fields_dict(EngineSettings): configured_dict["engine_settings"][key] = val elif key in attr.fields_dict(TorchSettings): configured_dict["torch_settings"][key] = val else: # Base options configured_dict[key] = val final_runoptions = RunOptions.from_dict(configured_dict) final_runoptions.checkpoint_settings.prioritize_resume_init() # Need check to bypass type checking but keep structure on dict working if isinstance(final_runoptions.behaviors, TrainerSettings.DefaultTrainerDict): # configure whether or not we should require all behavior names to be found in the config YAML final_runoptions.behaviors.set_config_specified(_require_all_behaviors) _non_default_args = DetectDefault.non_default_args # Prioritize the deterministic mode from the cli for deterministic actions. if "deterministic" in _non_default_args: for behaviour in final_runoptions.behaviors.keys(): final_runoptions.behaviors[ behaviour ].network_settings.deterministic = argparse_args["deterministic"] return final_runoptions @staticmethod def from_dict(options_dict: Dict[str, Any]) -> "RunOptions": # If a default settings was specified, set the TrainerSettings class override if ( "default_settings" in options_dict.keys() and options_dict["default_settings"] is not None ): TrainerSettings.default_override = cattr.structure( options_dict["default_settings"], TrainerSettings ) return cattr.structure(options_dict, RunOptions)
class TorchSettings: device: Optional[str] = parser.get_default("device")