|
|
|
|
|
|
|
|
|
import attr |
|
import cattr |
|
import yaml |
|
from typing import Dict, Any, Optional |
|
import argparse |
|
from mlagents.trainers.settings import TrainerSettings, NetworkSettings |
|
from mlagents.trainers.cli_utils import load_config |
|
from mlagents.trainers.exception import TrainerConfigError |
|
from mlagents.plugins import all_trainer_settings |
|
|
|
|
|
|
|
def convert_behaviors(old_trainer_config: Dict[str, Any]) -> Dict[str, Any]: |
|
all_behavior_config_dict = {} |
|
default_config = old_trainer_config.get("default", {}) |
|
for behavior_name, config in old_trainer_config.items(): |
|
if behavior_name != "default": |
|
config = default_config.copy() |
|
config.update(old_trainer_config[behavior_name]) |
|
|
|
|
|
|
|
try: |
|
trainer_type = config["trainer"] |
|
except KeyError: |
|
raise TrainerConfigError( |
|
"Config doesn't specify a trainer type. " |
|
"Please specify trainer: in your config." |
|
) |
|
new_config = {} |
|
new_config["trainer_type"] = trainer_type |
|
hyperparam_cls = all_trainer_settings[trainer_type] |
|
|
|
new_config["hyperparameters"] = cattr.structure(config, hyperparam_cls) |
|
|
|
|
|
new_config["network_settings"] = cattr.structure(config, NetworkSettings) |
|
|
|
try: |
|
if config["use_recurrent"]: |
|
new_config[ |
|
"network_settings" |
|
].memory = NetworkSettings.MemorySettings( |
|
sequence_length=config["sequence_length"], |
|
memory_size=config["memory_size"], |
|
) |
|
except KeyError: |
|
raise TrainerConfigError( |
|
"Config doesn't specify use_recurrent. " |
|
"Please specify true or false for use_recurrent in your config." |
|
) |
|
|
|
for key, val in config.items(): |
|
if key in attr.fields_dict(TrainerSettings): |
|
new_config[key] = val |
|
|
|
|
|
all_behavior_config_dict[behavior_name] = cattr.structure( |
|
new_config, TrainerSettings |
|
) |
|
return all_behavior_config_dict |
|
|
|
|
|
def write_to_yaml_file(unstructed_config: Dict[str, Any], output_config: str) -> None: |
|
with open(output_config, "w") as f: |
|
try: |
|
yaml.dump(unstructed_config, f, sort_keys=False) |
|
except TypeError: |
|
yaml.dump(unstructed_config, f) |
|
|
|
|
|
def remove_nones(config: Dict[Any, Any]) -> Dict[str, Any]: |
|
new_config = {} |
|
for key, val in config.items(): |
|
if isinstance(val, dict): |
|
new_config[key] = remove_nones(val) |
|
elif val is not None: |
|
new_config[key] = val |
|
return new_config |
|
|
|
|
|
|
|
def convert_samplers(old_sampler_config: Dict[str, Any]) -> Dict[str, Any]: |
|
new_sampler_config: Dict[str, Any] = {} |
|
for parameter, parameter_config in old_sampler_config.items(): |
|
if parameter == "resampling-interval": |
|
print( |
|
"resampling-interval is no longer necessary for parameter randomization and is being ignored." |
|
) |
|
continue |
|
new_sampler_config[parameter] = {} |
|
new_sampler_config[parameter]["sampler_type"] = parameter_config["sampler-type"] |
|
new_samp_parameters = dict(parameter_config) |
|
new_samp_parameters.pop("sampler-type") |
|
new_sampler_config[parameter]["sampler_parameters"] = new_samp_parameters |
|
return new_sampler_config |
|
|
|
|
|
def convert_samplers_and_curriculum( |
|
parameter_dict: Dict[str, Any], curriculum: Dict[str, Any] |
|
) -> Dict[str, Any]: |
|
for key, sampler in parameter_dict.items(): |
|
if "sampler_parameters" not in sampler: |
|
parameter_dict[key]["sampler_parameters"] = {} |
|
for argument in [ |
|
"seed", |
|
"min_value", |
|
"max_value", |
|
"mean", |
|
"st_dev", |
|
"intervals", |
|
]: |
|
if argument in sampler: |
|
parameter_dict[key]["sampler_parameters"][argument] = sampler[argument] |
|
parameter_dict[key].pop(argument) |
|
param_set = set(parameter_dict.keys()) |
|
for behavior_name, behavior_dict in curriculum.items(): |
|
measure = behavior_dict["measure"] |
|
min_lesson_length = behavior_dict.get("min_lesson_length", 1) |
|
signal_smoothing = behavior_dict.get("signal_smoothing", False) |
|
thresholds = behavior_dict["thresholds"] |
|
num_lessons = len(thresholds) + 1 |
|
parameters = behavior_dict["parameters"] |
|
for param_name in parameters.keys(): |
|
if param_name in param_set: |
|
print( |
|
f"The parameter {param_name} has both a sampler and a curriculum. Will ignore curriculum" |
|
) |
|
else: |
|
param_set.add(param_name) |
|
parameter_dict[param_name] = {"curriculum": []} |
|
for lesson_index in range(num_lessons - 1): |
|
parameter_dict[param_name]["curriculum"].append( |
|
{ |
|
f"Lesson{lesson_index}": { |
|
"completion_criteria": { |
|
"measure": measure, |
|
"behavior": behavior_name, |
|
"signal_smoothing": signal_smoothing, |
|
"min_lesson_length": min_lesson_length, |
|
"threshold": thresholds[lesson_index], |
|
}, |
|
"value": parameters[param_name][lesson_index], |
|
} |
|
} |
|
) |
|
lesson_index += 1 |
|
parameter_dict[param_name]["curriculum"].append( |
|
{ |
|
f"Lesson{lesson_index}": { |
|
"value": parameters[param_name][lesson_index] |
|
} |
|
} |
|
) |
|
return parameter_dict |
|
|
|
|
|
def parse_args(): |
|
argparser = argparse.ArgumentParser( |
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter |
|
) |
|
argparser.add_argument( |
|
"trainer_config_path", |
|
help="Path to old format (<=0.18.X) trainer configuration YAML.", |
|
) |
|
argparser.add_argument( |
|
"--curriculum", |
|
help="Path to old format (<=0.16.X) curriculum configuration YAML.", |
|
default=None, |
|
) |
|
argparser.add_argument( |
|
"--sampler", |
|
help="Path to old format (<=0.16.X) parameter randomization configuration YAML.", |
|
default=None, |
|
) |
|
argparser.add_argument( |
|
"output_config_path", help="Path to write converted YAML file." |
|
) |
|
args = argparser.parse_args() |
|
return args |
|
|
|
|
|
def convert( |
|
config: Dict[str, Any], |
|
old_curriculum: Optional[Dict[str, Any]], |
|
old_param_random: Optional[Dict[str, Any]], |
|
) -> Dict[str, Any]: |
|
if "behaviors" not in config: |
|
print("Config file format version : version <= 0.16.X") |
|
behavior_config_dict = convert_behaviors(config) |
|
full_config = {"behaviors": behavior_config_dict} |
|
|
|
|
|
|
|
if old_curriculum is not None: |
|
full_config["curriculum"] = old_curriculum |
|
|
|
if old_param_random is not None: |
|
sampler_config_dict = convert_samplers(old_param_random) |
|
full_config["parameter_randomization"] = sampler_config_dict |
|
|
|
|
|
config = cattr.unstructure(full_config) |
|
if "curriculum" in config or "parameter_randomization" in config: |
|
print("Config file format version : 0.16.X < version <= 0.18.X") |
|
full_config = {"behaviors": config["behaviors"]} |
|
|
|
param_randomization = config.get("parameter_randomization", {}) |
|
if "resampling-interval" in param_randomization: |
|
param_randomization.pop("resampling-interval") |
|
if len(param_randomization) > 0: |
|
|
|
if ( |
|
"sampler-type" |
|
in param_randomization[list(param_randomization.keys())[0]] |
|
): |
|
param_randomization = convert_samplers(param_randomization) |
|
|
|
full_config["environment_parameters"] = convert_samplers_and_curriculum( |
|
param_randomization, config.get("curriculum", {}) |
|
) |
|
|
|
|
|
config = cattr.unstructure(full_config) |
|
return config |
|
|
|
|
|
def main() -> None: |
|
args = parse_args() |
|
print( |
|
f"Converting {args.trainer_config_path} and saving to {args.output_config_path}." |
|
) |
|
|
|
old_config = load_config(args.trainer_config_path) |
|
curriculum_config_dict = None |
|
old_sampler_config_dict = None |
|
if args.curriculum is not None: |
|
curriculum_config_dict = load_config(args.curriculum) |
|
if args.sampler is not None: |
|
old_sampler_config_dict = load_config(args.sampler) |
|
new_config = convert(old_config, curriculum_config_dict, old_sampler_config_dict) |
|
unstructed_config = remove_nones(new_config) |
|
write_to_yaml_file(unstructed_config, args.output_config_path) |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |
|
|