From acedd6761744f78ffab7a1019cf0c0070340a4ff Mon Sep 17 00:00:00 2001 From: wls2002 Date: Thu, 15 Jun 2023 11:05:26 +0800 Subject: [PATCH] change project structure and using .ini as config file --- algorithms/hyper_neat/__init__.py | 0 algorithms/neat/__init__.py | 1 - algorithms/neat/genome/debug/__init__.py | 0 examples/enhane_xor.py | 5 +- examples/final_design_experiement.py | 56 -------------- examples/final_design_experiment2.py | 52 ------------- examples/function_tests.py | 7 +- examples/jax_playground.py | 3 + examples/xor.py | 13 ++-- neat/__init__.py | 2 + {algorithms/neat => neat}/function_factory.py | 0 {algorithms/neat => neat}/genome/__init__.py | 0 .../neat => neat}/genome/activations.py | 0 .../neat => neat}/genome/aggregations.py | 0 {algorithms/neat => neat}/genome/crossover.py | 0 {algorithms => neat/genome/debug}/__init__.py | 0 .../neat => neat}/genome/debug/tools.py | 0 {algorithms/neat => neat}/genome/distance.py | 0 {algorithms/neat => neat}/genome/forward.py | 0 {algorithms/neat => neat}/genome/genome.py | 0 {algorithms/neat => neat}/genome/graph.py | 2 +- {algorithms/neat => neat}/genome/mutate.py | 0 {algorithms/neat => neat}/genome/utils.py | 0 {algorithms/neat => neat}/pipeline.py | 0 {algorithms/neat => neat}/population.py | 0 {algorithms/neat => neat}/species.py | 0 utils/config.py | 55 ++++---------- utils/default_config.ini | 65 ++++++++++++++++ utils/default_config.json | 76 ------------------- utils/dotdict.py | 61 --------------- 30 files changed, 97 insertions(+), 301 deletions(-) delete mode 100644 algorithms/hyper_neat/__init__.py delete mode 100644 algorithms/neat/__init__.py delete mode 100644 algorithms/neat/genome/debug/__init__.py delete mode 100644 examples/final_design_experiement.py delete mode 100644 examples/final_design_experiment2.py create mode 100644 neat/__init__.py rename {algorithms/neat => neat}/function_factory.py (100%) rename {algorithms/neat => neat}/genome/__init__.py (100%) rename {algorithms/neat => neat}/genome/activations.py (100%) rename {algorithms/neat => neat}/genome/aggregations.py (100%) rename {algorithms/neat => neat}/genome/crossover.py (100%) rename {algorithms => neat/genome/debug}/__init__.py (100%) rename {algorithms/neat => neat}/genome/debug/tools.py (100%) rename {algorithms/neat => neat}/genome/distance.py (100%) rename {algorithms/neat => neat}/genome/forward.py (100%) rename {algorithms/neat => neat}/genome/genome.py (100%) rename {algorithms/neat => neat}/genome/graph.py (98%) rename {algorithms/neat => neat}/genome/mutate.py (100%) rename {algorithms/neat => neat}/genome/utils.py (100%) rename {algorithms/neat => neat}/pipeline.py (100%) rename {algorithms/neat => neat}/population.py (100%) rename {algorithms/neat => neat}/species.py (100%) create mode 100644 utils/default_config.ini delete mode 100644 utils/default_config.json delete mode 100644 utils/dotdict.py diff --git a/algorithms/hyper_neat/__init__.py b/algorithms/hyper_neat/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/algorithms/neat/__init__.py b/algorithms/neat/__init__.py deleted file mode 100644 index f842286..0000000 --- a/algorithms/neat/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .pipeline import Pipeline \ No newline at end of file diff --git a/algorithms/neat/genome/debug/__init__.py b/algorithms/neat/genome/debug/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/examples/enhane_xor.py b/examples/enhane_xor.py index 3e37296..a0738e2 100644 --- a/examples/enhane_xor.py +++ b/examples/enhane_xor.py @@ -1,9 +1,8 @@ import numpy as np import jax from utils import Configer -from algorithms.neat import Pipeline -from time_utils import using_cprofile -from algorithms.neat.function_factory import FunctionFactory +from neat import Pipeline +from neat import FunctionFactory from problems import EnhanceLogic import time diff --git a/examples/final_design_experiement.py b/examples/final_design_experiement.py deleted file mode 100644 index d6a7c91..0000000 --- a/examples/final_design_experiement.py +++ /dev/null @@ -1,56 +0,0 @@ -import numpy as np -import jax -from utils import Configer -from algorithms.neat import Pipeline -from time_utils import using_cprofile -from algorithms.neat.function_factory import FunctionFactory -from problems import EnhanceLogic -import time - - -def evaluate(problem, func): - outs = func(problem.inputs) - outs = jax.device_get(outs) - fitnesses = -np.mean((problem.outputs - outs) ** 2, axis=(1, 2)) - return fitnesses - - -def main(): - config = Configer.load_config() - problem = EnhanceLogic("xor", n=3) - problem.refactor_config(config) - function_factory = FunctionFactory(config) - evaluate_func = lambda func: evaluate(problem, func) - - # precompile - pipeline = Pipeline(config, function_factory, seed=114514) - pipeline.auto_run(evaluate_func) - - for r in range(10): - print(f"running: {r}/{10}") - tic = time.time() - - pipeline = Pipeline(config, function_factory, seed=r) - pipeline.auto_run(evaluate_func) - - total_time = time.time() - tic - evaluate_time = pipeline.evaluate_time - total_it = pipeline.generation - print(f"total time: {total_time:.2f}s, evaluate time: {evaluate_time:.2f}s, total_it: {total_it}") - - if total_it >= 500: - res = "fail" - else: - res = "success" - - with open("log", "ab") as f: - f.write(f"{res}, total time: {total_time:.2f}s, evaluate time: {evaluate_time:.2f}s, total_it: {total_it}\n".encode("utf-8")) - f.write(str(pipeline.generation_time_list).encode("utf-8")) - - compile_time = function_factory.compile_time - - print("total_compile_time:", compile_time) - - -if __name__ == '__main__': - main() diff --git a/examples/final_design_experiment2.py b/examples/final_design_experiment2.py deleted file mode 100644 index 84bf770..0000000 --- a/examples/final_design_experiment2.py +++ /dev/null @@ -1,52 +0,0 @@ - -import numpy as np -import jax -from utils import Configer -from algorithms.neat import Pipeline -from time_utils import using_cprofile -from algorithms.neat.function_factory import FunctionFactory -from problems import EnhanceLogic -import time - - -def evaluate(problem, func): - outs = func(problem.inputs) - outs = jax.device_get(outs) - fitnesses = -np.mean((problem.outputs - outs) ** 2, axis=(1, 2)) - return fitnesses - - -def main(): - config = Configer.load_config() - problem = EnhanceLogic("xor", n=3) - problem.refactor_config(config) - - evaluate_func = lambda func: evaluate(problem, func) - - for p in [100, 200, 500, 1000, 2000, 5000, 10000, 20000]: - config.neat.population.pop_size = p - tic = time.time() - function_factory = FunctionFactory(config) - print(f"running: {p}") - - pipeline = Pipeline(config, function_factory, seed=2) - pipeline.auto_run(evaluate_func) - - total_time = time.time() - tic - evaluate_time = pipeline.evaluate_time - total_it = pipeline.generation - print(f"total time: {total_time:.2f}s, evaluate time: {evaluate_time:.2f}s, total_it: {total_it}") - - with open("2060_log2", "ab") as f: - f.write \ - (f"{p}, total time: {total_time:.2f}s, compile time: {function_factory.compile_time:.2f}s, total_it: {total_it}\n".encode - ("utf-8")) - f.write(f"{str(pipeline.generation_time_list)}\n".encode("utf-8")) - - compile_time = function_factory.compile_time - - print("total_compile_time:", compile_time) - - -if __name__ == '__main__': - main() diff --git a/examples/function_tests.py b/examples/function_tests.py index 5426837..7c6dea6 100644 --- a/examples/function_tests.py +++ b/examples/function_tests.py @@ -1,12 +1,9 @@ import jax import numpy as np -from algorithms.neat.function_factory import FunctionFactory -from algorithms.neat.genome.debug.tools import check_array_valid +from neat import FunctionFactory +from neat.genome.debug.tools import check_array_valid from utils import Configer -from algorithms.neat.genome.crossover import crossover - - if __name__ == '__main__': config = Configer.load_config() function_factory = FunctionFactory(config, debug=True) diff --git a/examples/jax_playground.py b/examples/jax_playground.py index 32ce960..4fc61cb 100644 --- a/examples/jax_playground.py +++ b/examples/jax_playground.py @@ -25,10 +25,12 @@ def jax_mutate_population(seed, pop_x): def numpy_mutate_population(pop_x): return np.stack([numpy_mutate(x) for x in pop_x]) + def numpy_mutate_population_vmap(pop_x): noise = np.random.normal(size=pop_x.shape) * 0.1 return pop_x + noise + def main(): seed = jax.random.PRNGKey(0) i = 10 @@ -53,5 +55,6 @@ def main(): i = int(i * 1.3) + if __name__ == '__main__': main() diff --git a/examples/xor.py b/examples/xor.py index b61f5ba..3c57307 100644 --- a/examples/xor.py +++ b/examples/xor.py @@ -1,7 +1,7 @@ +from neat import FunctionFactory from utils import Configer -from algorithms.neat import Pipeline -from time_utils import using_cprofile -from problems import Sin, Xor, DIY +from neat import Pipeline +from problems import Xor import time @@ -10,11 +10,14 @@ import time def main(): tic = time.time() config = Configer.load_config() + print(config) + assert False problem = Xor() problem.refactor_config(config) - pipeline = Pipeline(config, seed=6) + function_factory = FunctionFactory(config) + pipeline = Pipeline(config, function_factory, seed=6) nodes, cons = pipeline.auto_run(problem.evaluate) - # print(nodes, cons) + print(nodes, cons) total_time = time.time() - tic compile_time = pipeline.function_factory.compile_time total_it = pipeline.generation diff --git a/neat/__init__.py b/neat/__init__.py new file mode 100644 index 0000000..cfdcd55 --- /dev/null +++ b/neat/__init__.py @@ -0,0 +1,2 @@ +from .pipeline import Pipeline +from .function_factory import FunctionFactory diff --git a/algorithms/neat/function_factory.py b/neat/function_factory.py similarity index 100% rename from algorithms/neat/function_factory.py rename to neat/function_factory.py diff --git a/algorithms/neat/genome/__init__.py b/neat/genome/__init__.py similarity index 100% rename from algorithms/neat/genome/__init__.py rename to neat/genome/__init__.py diff --git a/algorithms/neat/genome/activations.py b/neat/genome/activations.py similarity index 100% rename from algorithms/neat/genome/activations.py rename to neat/genome/activations.py diff --git a/algorithms/neat/genome/aggregations.py b/neat/genome/aggregations.py similarity index 100% rename from algorithms/neat/genome/aggregations.py rename to neat/genome/aggregations.py diff --git a/algorithms/neat/genome/crossover.py b/neat/genome/crossover.py similarity index 100% rename from algorithms/neat/genome/crossover.py rename to neat/genome/crossover.py diff --git a/algorithms/__init__.py b/neat/genome/debug/__init__.py similarity index 100% rename from algorithms/__init__.py rename to neat/genome/debug/__init__.py diff --git a/algorithms/neat/genome/debug/tools.py b/neat/genome/debug/tools.py similarity index 100% rename from algorithms/neat/genome/debug/tools.py rename to neat/genome/debug/tools.py diff --git a/algorithms/neat/genome/distance.py b/neat/genome/distance.py similarity index 100% rename from algorithms/neat/genome/distance.py rename to neat/genome/distance.py diff --git a/algorithms/neat/genome/forward.py b/neat/genome/forward.py similarity index 100% rename from algorithms/neat/genome/forward.py rename to neat/genome/forward.py diff --git a/algorithms/neat/genome/genome.py b/neat/genome/genome.py similarity index 100% rename from algorithms/neat/genome/genome.py rename to neat/genome/genome.py diff --git a/algorithms/neat/genome/graph.py b/neat/genome/graph.py similarity index 98% rename from algorithms/neat/genome/graph.py rename to neat/genome/graph.py index bfe38d8..6e5e973 100644 --- a/algorithms/neat/genome/graph.py +++ b/neat/genome/graph.py @@ -8,7 +8,7 @@ from jax import jit, vmap, Array from jax import numpy as jnp # from .utils import fetch_first, I_INT -from algorithms.neat.genome.utils import fetch_first, I_INT +from neat.genome.utils import fetch_first, I_INT @jit diff --git a/algorithms/neat/genome/mutate.py b/neat/genome/mutate.py similarity index 100% rename from algorithms/neat/genome/mutate.py rename to neat/genome/mutate.py diff --git a/algorithms/neat/genome/utils.py b/neat/genome/utils.py similarity index 100% rename from algorithms/neat/genome/utils.py rename to neat/genome/utils.py diff --git a/algorithms/neat/pipeline.py b/neat/pipeline.py similarity index 100% rename from algorithms/neat/pipeline.py rename to neat/pipeline.py diff --git a/algorithms/neat/population.py b/neat/population.py similarity index 100% rename from algorithms/neat/population.py rename to neat/population.py diff --git a/algorithms/neat/species.py b/neat/species.py similarity index 100% rename from algorithms/neat/species.py rename to neat/species.py diff --git a/utils/config.py b/utils/config.py index 2d1dbda..7b0ddf7 100644 --- a/utils/config.py +++ b/utils/config.py @@ -1,78 +1,51 @@ -import json import os import warnings - -from .dotdict import DotDict +import configparser class Configer: @classmethod def __load_default_config(cls): par_dir = os.path.dirname(os.path.abspath(__file__)) - default_config_path = os.path.join(par_dir, "./default_config.json") + default_config_path = os.path.join(par_dir, "default_config.ini") return cls.__load_config(default_config_path) @classmethod def __load_config(cls, config_path): - with open(config_path, "r") as f: - text = "".join(f.readlines()) - try: - j = json.loads(text) - except ValueError: - raise Exception("Invalid config") - return DotDict.from_dict(j, "root") + c = configparser.ConfigParser() + c.read(config_path) + config = {} + + for section in c.sections(): + for key, value in c.items(section): + config[key] = eval(value) + + return config @classmethod def __check_redundant_config(cls, default_config, config): for key in config: if key not in default_config: warnings.warn(f"Redundant config: {key} in {config.name}") - continue - if isinstance(default_config[key], DotDict): - cls.__check_redundant_config(default_config[key], config[key]) @classmethod def __complete_config(cls, default_config, config): for key in default_config: if key not in config: config[key] = default_config[key] - continue - if isinstance(default_config[key], DotDict): - cls.__complete_config(default_config[key], config[key]) - - @classmethod - def __decorate_config(cls, config): - if config.neat.gene.activation.options == 'all': - config.neat.gene.activation.options = [ - "sigmoid", "tanh", "sin", "gauss", "relu", "elu", "lelu", "selu", "softplus", "identity", "clamped", - "inv", "log", "exp", "abs", "hat", "square", "cube" - ] - if isinstance(config.neat.gene.activation.options, str): - config.neat.gene.activation.options = [config.neat.gene.activation.options] - - if config.neat.gene.aggregation.options == 'all': - config.neat.gene.aggregation.options = ["product", "sum", "max", "min", "median", "mean"] - if isinstance(config.neat.gene.aggregation.options, str): - config.neat.gene.aggregation.options = [config.neat.gene.aggregation.options] @classmethod def load_config(cls, config_path=None): default_config = cls.__load_default_config() if config_path is None: - config = DotDict("root") + config = {} elif not os.path.exists(config_path): warnings.warn(f"config file {config_path} not exist!") - config = DotDict("root") + config = {} else: config = cls.__load_config(config_path) cls.__check_redundant_config(default_config, config) cls.__complete_config(default_config, config) - cls.__decorate_config(config) + # cls.__decorate_config(config) return config - - @classmethod - def write_config(cls, config, write_path): - text = json.dumps(config, indent=2) - with open(write_path, "w") as f: - f.write(text) diff --git a/utils/default_config.ini b/utils/default_config.ini new file mode 100644 index 0000000..cd22e14 --- /dev/null +++ b/utils/default_config.ini @@ -0,0 +1,65 @@ +[basic] +num_inputs = 2 +num_outputs = 1 +init_maximum_nodes = 20 +init_maximum_connections = 20 +init_maximum_species = 10 +expands_coe = 2 +forward_way = "pop_batch" + +[population] +fitness_threshold = 100000 +generation_limit = 100 +fitness_criterion = "max" +pop_size = 150 + +[genome] +compatibility_disjoint = 1.0 +compatibility_weight = 0.5 +conn_add_prob = 0.5 +conn_add_trials = 1 +conn_delete_prob = 0 +node_add_prob = 0.2 +node_delete_prob = 0 + +[species] +compatibility_threshold = 3.0 +species_elitism = 2 +species_max_stagnation = 15 +genome_elitism = 2 +survival_threshold = 0.2 +min_species_size = 1 + +[gene-bias] +bias_init_mean = 0.0 +bias_init_stdev = 1.0 +bias_mutate_power = 0.5 +bias_mutate_rate = 0.7 +bias_replace_rate = 0.1 + +[gene-response] +response_init_mean = 1.0 +response_init_stdev = 0.0 +response_mutate_power = 0.0 +response_mutate_rate = 0.0 +response_replace_rate = 0.0 + +[gene-activation] +activation_default = "sigmoid" +activation_options = ["sigmoid"] +activation_replace_rate = 0.0 + +[gene-aggregation] +aggregation_default = "sum" +aggregation_options = ["sum"] +aggregation_replace_rate = 0.0 + +[gene-weight] +weight_init_mean = 0.0 +weight_init_stdev = 1.0 +weight_mutate_power = 0.5 +weight_mutate_rate = 0.8 +weight_replace_rate = 0.1 + +[gene-enable] +enable_mutate_rate = 0.01 diff --git a/utils/default_config.json b/utils/default_config.json deleted file mode 100644 index 0071d57..0000000 --- a/utils/default_config.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "basic": { - "num_inputs": 2, - "num_outputs": 1, - "problem_batch": 4, - "init_maximum_nodes": 20, - "init_maximum_connections": 20, - "init_maximum_species": 10, - "expands_coe": 2, - "pre_compile_times": 3, - "forward_way": "pop_batch" - }, - "neat": { - "population": { - "fitness_criterion": "max", - "fitness_threshold": 1e-2, - "generation_limit": 100, - "pop_size": 1000, - "reset_on_extinction": "False" - }, - "gene": { - "bias": { - "init_mean": 0.0, - "init_stdev": 1.0, - "mutate_power": 0.5, - "mutate_rate": 0.7, - "replace_rate": 0.1 - }, - "response": { - "init_mean": 1.0, - "init_stdev": 0.0, - "mutate_power": 0.0, - "mutate_rate": 0.0, - "replace_rate": 0.0 - }, - "activation": { - "default": "sigmoid", - "options": ["sigmoid"], - "mutate_rate": 0.1 - }, - "aggregation": { - "default": "sum", - "options": "sum", - "mutate_rate": 0.1 - }, - "weight": { - "init_mean": 0.0, - "init_stdev": 1.0, - "mutate_power": 0.5, - "mutate_rate": 0.8, - "replace_rate": 0.1 - }, - "enabled": { - "mutate_rate": 0.01 - } - }, - "genome": { - "compatibility_disjoint_coefficient": 1.0, - "compatibility_weight_coefficient": 0.5, - "single_structural_mutation": "False", - "conn_add_prob": 0.6, - "conn_delete_prob": 0, - "node_add_prob": 0.3, - "node_delete_prob": 0 - }, - "species": { - "compatibility_threshold": 2.5, - "species_fitness_func": "max", - "max_stagnation": 20, - "species_elitism": 2, - "genome_elitism": 2, - "survival_threshold": 0.2, - "min_species_size": 1 - } - } -} \ No newline at end of file diff --git a/utils/dotdict.py b/utils/dotdict.py deleted file mode 100644 index 713c2b1..0000000 --- a/utils/dotdict.py +++ /dev/null @@ -1,61 +0,0 @@ -# DotDict For Config. Case Insensitive. - -class DotDict(dict): - def __init__(self, name, *args, **kwargs): - super().__init__(*args, **kwargs) - self["name"] = name - - def __getattr__(self, attr): - attr = attr.lower() # case insensitive - if attr in self: - return self[attr] - else: - raise AttributeError(f"'{self.__class__.__name__}-{self.name}' has no attribute '{attr}'") - - def __setattr__(self, attr, value): - attr = attr.lower() # case insensitive - if attr not in self: - raise AttributeError(f"'{self.__class__.__name__}-{self.name}' has no attribute '{attr}'") - self[attr] = value - - def __delattr__(self, attr): - attr = attr.lower() # case insensitive - if attr in self: - del self[attr] - else: - raise AttributeError(f"{self.__class__.__name__}-{self.name} object has no attribute '{attr}'") - - @classmethod - def from_dict(cls, d, name): - if not isinstance(d, dict): - return d - - dot_dict = cls(name) - for key, value in d.items(): - key = key.lower() # case insensitive - if isinstance(value, dict): - dot_dict[key] = cls.from_dict(value, key) - else: - dot_dict[key] = value - if dot_dict[key] == "True": # Fuck! Json has no bool type! - dot_dict[key] = True - if dot_dict[key] == "False": - dot_dict[key] = False - if dot_dict[key] == "None": - dot_dict[key] = None - return dot_dict - - -if __name__ == '__main__': - nested_dict = { - "a": 1, - "b": { - "c": 2, - "ACDeef": { - "e": 3 - } - } - } - - dd = DotDict.from_dict(nested_dict, "root") - print(dd.b.acdeef.e) # 输出:3