class TreeMutation(object): def __init__(self, config, **kwargs): self.config = config self.recorder = kwargs.get("recorder", None) self.generator = TreeGenerator(self.config) # mutation stats self.method = None self.index = None self.mutation_probability = None self.random_probability = None self.mutated = False self.before_mutation = None self.after_mutation = None def generate_new_node(self, details): if details is None: return None elif details["type"] == NodeType.FUNCTION: return Node( NodeType.FUNCTION, name=details["name"], arity=details["arity"], branches=[] ) elif details["type"] == NodeType.CLASS_FUNCTION: return Node( NodeType.CLASS_FUNCTION, name=details["name"], arity=details["arity"], branches=[] ) elif details["type"] == NodeType.INPUT: return Node( NodeType.INPUT, name=details["name"] ) elif details["type"] == NodeType.CONSTANT: return Node( NodeType.CONSTANT, name=details.get("name", None), value=details["value"] ) elif details["type"] == NodeType.RANDOM_CONSTANT: resolved_details = self.generator.resolve_random_constant(details) return Node( NodeType.CONSTANT, name=resolved_details.get("name", None), value=resolved_details["value"] ) def mutate_new_node_details(self, old_node): # determine what kind of old_node it is node_pool = [] if old_node.is_function() or old_node.is_class_function(): tmp = list(self.config["function_nodes"]) tmp = [n for n in tmp if n["arity"] == old_node.arity] node_pool.extend(tmp) elif old_node.is_terminal(): node_pool.extend(self.config["terminal_nodes"]) # check the node and return retry = 0 retry_limit = 100 while True: if retry == retry_limit: return None else: retry += 1 n_details = sample(node_pool, 1)[0] if n_details["type"] == NodeType.RANDOM_CONSTANT: n_details = self.generator.resolve_random_constant(n_details) elif n_details["type"] == NodeType.CLASS_FUNCTION: n_details = self.generator.resolve_class_function(n_details) new_node = self.generate_new_node(n_details) if old_node.equals(new_node) is False: return n_details def point_mutation(self, tree, mutation_index=None): # mutate node self.index = randint(0, len(tree.program) - 1) node = tree.program[self.index] new_node = self.mutate_new_node_details(node) if new_node is None: return elif node.is_function(): node.name = new_node["name"] elif node.is_terminal(): node.node_type = new_node.get("type") node.name = new_node.get("name", None) node.value = new_node.get("value", None) tree.update() self.mutated = True def hoist_mutation(self, tree, mutation_index=None): # new indivdiaul generated from subtree node = None if mutation_index is None: self.index = randint(0, len(tree.program) - 2) node = tree.program[self.index] else: self.index = mutation_index node = tree.program[mutation_index] tree.root = node tree.update() self.mutated = True def subtree_mutation(self, tree, mutation_index=None): # subtree exchanged against external random subtree node = None if mutation_index is None: self.index = randint(0, len(tree.program) - 1) node = tree.program[self.index] else: self.index = mutation_index node = tree.program[mutation_index] self.generator.max_depth = randint(1, 3) sub_tree = self.generator.generate_tree() if node is not tree.root: tree.replace_node(node, sub_tree.root) else: tree.root = sub_tree.root tree.update() self.mutated = True def shrink_mutation(self, tree, mutation_index=None): # replace subtree with terminal if len(tree.func_nodes): node = None if mutation_index is None: self.index = randint(0, len(tree.func_nodes) - 1) node = tree.func_nodes[self.index] while node is tree.root: self.index = randint(0, len(tree.func_nodes) - 1) node = tree.func_nodes[self.index] else: self.index = mutation_index node = tree.program[mutation_index] candidate_nodes = tree.term_nodes candidate_nodes.extend(tree.input_nodes) new_node_detail = sample(candidate_nodes, 1)[0] node_details = self.mutate_new_node_details(new_node_detail) new_node = self.generate_new_node(node_details) if new_node: tree.replace_node(node, new_node) tree.update() self.mutated = True def expansion_mutation(self, tree, mutation_index=None): # terminal exchanged against external random subtree node = None if mutation_index is None: prob = random() if tree.size == 1: return elif prob > 0.5 and len(tree.term_nodes) > 0: self.index = randint(0, len(tree.term_nodes) - 1) node = tree.term_nodes[self.index] elif prob < 0.5 and len(tree.input_nodes) > 0: self.index = randint(0, len(tree.input_nodes) - 1) node = tree.input_nodes[self.index] elif len(tree.term_nodes) > 0: self.index = randint(0, len(tree.term_nodes) - 1) node = tree.term_nodes[self.index] elif len(tree.input_nodes) > 0: self.index = randint(0, len(tree.input_nodes) - 1) node = tree.input_nodes[self.index] else: self.index = mutation_index node = tree.program[mutation_index] sub_tree = self.generator.generate_tree() tree.replace_node(node, sub_tree.root) tree.update() self.mutated = True def mutate(self, tree): mutation_methods = { "POINT_MUTATION": self.point_mutation, "HOIST_MUTATION": self.hoist_mutation, "SUBTREE_MUTATION": self.subtree_mutation, "SHRINK_MUTATION": self.shrink_mutation, "EXPAND_MUTATION": self.expansion_mutation } self.method = sample(self.config["mutation"]["methods"], 1)[0] self.index = None self.mutation_probability = self.config["mutation"]["probability"] self.random_probability = random() self.mutated = False self.before_mutation = None self.after_mutation = None # record before mutation self.before_mutation = tree.to_dict()["program"] # mutate if self.mutation_probability >= self.random_probability: mutation_func = mutation_methods[self.method] mutation_func(tree) # record after mutation self.after_mutation = tree.to_dict()["program"] # record if self.recorder is not None: self.recorder.record(RecordType.MUTATION, self) def to_dict(self): self_dict = { "method": self.method, "mutation_index": self.index, "mutation_probability": self.mutation_probability, "random_probability": self.random_probability, "mutated": self.mutated, "before_mutation": self.before_mutation, "after_mutation": self.after_mutation } return self_dict
class TreeGeneratorTests(unittest.TestCase): def setUp(self): self.config = { "max_population": 10, "tree_generation": { "tree_type": "SYMBOLIC_REGRESSION", "method": "RAMPED_HALF_AND_HALF_METHOD", "initial_max_depth": 3 }, "function_nodes": [ {"type": "FUNCTION", "arity": 2, "name": "ADD"}, {"type": "FUNCTION", "arity": 2, "name": "SUB"}, {"type": "FUNCTION", "arity": 2, "name": "MUL"}, {"type": "FUNCTION", "arity": 2, "name": "DIV"}, {"type": "FUNCTION", "arity": 1, "name": "COS"}, {"type": "FUNCTION", "arity": 1, "name": "SIN"} ], "terminal_nodes": [ {"type": "CONSTANT", "value": 1.0}, {"type": "INPUT", "name": "x"}, {"type": "INPUT", "name": "y"}, { "type": "RANDOM_CONSTANT", "data_range": { "upper_bound": 10.0, "lower_bound": -10.0, "decimal_places": 1 } } ], "input_variables": [ {"name": "x"}, {"name": "y"} ] } self.functions = GPFunctionRegistry("SYMBOLIC_REGRESSION") self.generator = TreeGenerator(self.config) self.parser = TreeParser() def tearDown(self): del self.config del self.generator del self.parser def test_generate_func_node(self): # SYMBOLIC REGRESSION TREES for i in range(100): node = self.generator.generate_func_node() self.assertEquals(node.node_type, NodeType.FUNCTION) # CLASSIFICATION TREES self.config["tree_generation"]["tree_type"] = "CLASSIFICATION_TREE" self.config["function_nodes"] = [ { "type": "CLASS_FUNCTION", "name": "GREATER_THAN", "arity": 2, "data_range": { "lower_bound": 0.0, "upper_bound": 10.0, "decimal_places": 1 } } ] self.config["class_attributes"] = [ "attrubte_1", "attrubte_2", "attrubte_3" ] generator = TreeGenerator(self.config) for i in range(100): node = generator.generate_func_node() class_attribute = node.class_attribute self.assertEquals(node.node_type, NodeType.CLASS_FUNCTION) self.assertTrue(class_attribute in self.config["class_attributes"]) def test_resolve_random_constant(self): upper_bound = 10.0 lower_bound = -10.0 decimal_places = 0 for i in range(100): n_details = { "type": "RANDOM_CONSTANT", "data_range": { "lower_bound": lower_bound, "upper_bound": upper_bound, "decimal_places": decimal_places } } new_n_details = self.generator.resolve_random_constant(n_details) node_type = new_n_details["type"] node_value = new_n_details["value"] self.assertEquals(node_type, "CONSTANT") self.assertTrue(upper_bound >= node_value) self.assertTrue(lower_bound <= node_value) self.assertEquals(node_value, int(node_value)) upper_bound = 100.0 lower_bound = -100.0 decimal_places = 1 for i in range(100): n_details = { "type": "RANDOM_CONSTANT", "data_range": { "lower_bound": lower_bound, "upper_bound": upper_bound, "decimal_places": decimal_places } } new_n_details = self.generator.resolve_random_constant(n_details) node_type = new_n_details["type"] node_value = new_n_details["value"] self.assertEquals(node_type, "CONSTANT") self.assertTrue(upper_bound >= node_value) self.assertTrue(lower_bound <= node_value) node_value = decimal.Decimal(str(node_value)) node_decimal_places = abs(node_value.as_tuple().exponent) self.assertEquals(decimal_places, node_decimal_places) def test_generate_term_node(self): for i in range(100): node = self.generator.generate_term_node() self.assertTrue( node.node_type == NodeType.CONSTANT or NodeType.INPUT ) def test_full_method(self): tests = 1 for i in xrange(tests): tree = self.generator.full_method() # asserts init_max = self.config["tree_generation"]["initial_max_depth"] self.assertEquals(tree.depth, init_max) self.assertTrue(tree.size > init_max) def test_grow_method(self): tests = 1000 for i in xrange(tests): tree = self.generator.grow_method() # asserts init_max = self.config["tree_generation"]["initial_max_depth"] self.assertEquals(tree.depth, init_max) self.assertTrue(tree.size > init_max) def test_generate_tree_from_dict(self): population = self.generator.init() tree = population.individuals[0] tree_dict = self.parser.tree_to_dict(tree, tree.root) tree_generated = self.generator.generate_tree_from_dict(tree_dict) program_str = "" for i in tree.program: if i.name is not None: program_str += i.name else: program_str += str(i.value) generated_str = "" for i in tree_generated.program: if i.name is not None: generated_str += i.name else: generated_str += str(i.value) self.assertEquals(program_str, generated_str) def test_init(self): population = self.generator.init() self.assertEquals(len(population.individuals), 10)
class TreeGeneratorTests(unittest.TestCase): def setUp(self): self.config = { "max_population": 10, "tree_generation": { "tree_type": "SYMBOLIC_REGRESSION", "method": "RAMPED_HALF_AND_HALF_METHOD", "initial_max_depth": 3 }, "function_nodes": [{ "type": "FUNCTION", "arity": 2, "name": "ADD" }, { "type": "FUNCTION", "arity": 2, "name": "SUB" }, { "type": "FUNCTION", "arity": 2, "name": "MUL" }, { "type": "FUNCTION", "arity": 2, "name": "DIV" }, { "type": "FUNCTION", "arity": 1, "name": "COS" }, { "type": "FUNCTION", "arity": 1, "name": "SIN" }], "terminal_nodes": [{ "type": "CONSTANT", "value": 1.0 }, { "type": "INPUT", "name": "x" }, { "type": "INPUT", "name": "y" }, { "type": "RANDOM_CONSTANT", "data_range": { "upper_bound": 10.0, "lower_bound": -10.0, "decimal_places": 1 } }], "input_variables": [{ "name": "x" }, { "name": "y" }] } self.functions = GPFunctionRegistry("SYMBOLIC_REGRESSION") self.generator = TreeGenerator(self.config) self.parser = TreeParser() def tearDown(self): del self.config del self.generator del self.parser def test_generate_func_node(self): # SYMBOLIC REGRESSION TREES for i in range(100): node = self.generator.generate_func_node() self.assertEquals(node.node_type, NodeType.FUNCTION) # CLASSIFICATION TREES self.config["tree_generation"]["tree_type"] = "CLASSIFICATION_TREE" self.config["function_nodes"] = [{ "type": "CLASS_FUNCTION", "name": "GREATER_THAN", "arity": 2, "data_range": { "lower_bound": 0.0, "upper_bound": 10.0, "decimal_places": 1 } }] self.config["class_attributes"] = [ "attrubte_1", "attrubte_2", "attrubte_3" ] generator = TreeGenerator(self.config) for i in range(100): node = generator.generate_func_node() class_attribute = node.class_attribute self.assertEquals(node.node_type, NodeType.CLASS_FUNCTION) self.assertTrue(class_attribute in self.config["class_attributes"]) def test_resolve_random_constant(self): upper_bound = 10.0 lower_bound = -10.0 decimal_places = 0 for i in range(100): n_details = { "type": "RANDOM_CONSTANT", "data_range": { "lower_bound": lower_bound, "upper_bound": upper_bound, "decimal_places": decimal_places } } new_n_details = self.generator.resolve_random_constant(n_details) node_type = new_n_details["type"] node_value = new_n_details["value"] self.assertEquals(node_type, "CONSTANT") self.assertTrue(upper_bound >= node_value) self.assertTrue(lower_bound <= node_value) self.assertEquals(node_value, int(node_value)) upper_bound = 100.0 lower_bound = -100.0 decimal_places = 1 for i in range(100): n_details = { "type": "RANDOM_CONSTANT", "data_range": { "lower_bound": lower_bound, "upper_bound": upper_bound, "decimal_places": decimal_places } } new_n_details = self.generator.resolve_random_constant(n_details) node_type = new_n_details["type"] node_value = new_n_details["value"] self.assertEquals(node_type, "CONSTANT") self.assertTrue(upper_bound >= node_value) self.assertTrue(lower_bound <= node_value) node_value = decimal.Decimal(str(node_value)) node_decimal_places = abs(node_value.as_tuple().exponent) self.assertEquals(decimal_places, node_decimal_places) def test_generate_term_node(self): for i in range(100): node = self.generator.generate_term_node() self.assertTrue(node.node_type == NodeType.CONSTANT or NodeType.INPUT) def test_full_method(self): tests = 1 for i in xrange(tests): tree = self.generator.full_method() # asserts init_max = self.config["tree_generation"]["initial_max_depth"] self.assertEquals(tree.depth, init_max) self.assertTrue(tree.size > init_max) def test_grow_method(self): tests = 1000 for i in xrange(tests): tree = self.generator.grow_method() # asserts init_max = self.config["tree_generation"]["initial_max_depth"] self.assertEquals(tree.depth, init_max) self.assertTrue(tree.size > init_max) def test_generate_tree_from_dict(self): population = self.generator.init() tree = population.individuals[0] tree_dict = self.parser.tree_to_dict(tree, tree.root) tree_generated = self.generator.generate_tree_from_dict(tree_dict) program_str = "" for i in tree.program: if i.name is not None: program_str += i.name else: program_str += str(i.value) generated_str = "" for i in tree_generated.program: if i.name is not None: generated_str += i.name else: generated_str += str(i.value) self.assertEquals(program_str, generated_str) def test_init(self): population = self.generator.init() self.assertEquals(len(population.individuals), 10)
class TreeMutation(object): def __init__(self, config, **kwargs): self.config = config self.recorder = kwargs.get("recorder", None) self.generator = TreeGenerator(self.config) # mutation stats self.method = None self.index = None self.mutation_probability = None self.random_probability = None self.mutated = False self.before_mutation = None self.after_mutation = None def generate_new_node(self, details): if details is None: return None elif details["type"] == NodeType.FUNCTION: return Node(NodeType.FUNCTION, name=details["name"], arity=details["arity"], branches=[]) elif details["type"] == NodeType.CLASS_FUNCTION: return Node(NodeType.CLASS_FUNCTION, name=details["name"], arity=details["arity"], branches=[]) elif details["type"] == NodeType.INPUT: return Node(NodeType.INPUT, name=details["name"]) elif details["type"] == NodeType.CONSTANT: return Node(NodeType.CONSTANT, name=details.get("name", None), value=details["value"]) elif details["type"] == NodeType.RANDOM_CONSTANT: resolved_details = self.generator.resolve_random_constant(details) return Node(NodeType.CONSTANT, name=resolved_details.get("name", None), value=resolved_details["value"]) def mutate_new_node_details(self, old_node): # determine what kind of old_node it is node_pool = [] if old_node.is_function() or old_node.is_class_function(): tmp = list(self.config["function_nodes"]) tmp = [n for n in tmp if n["arity"] == old_node.arity] node_pool.extend(tmp) elif old_node.is_terminal(): node_pool.extend(self.config["terminal_nodes"]) # check the node and return retry = 0 retry_limit = 100 while True: if retry == retry_limit: return None else: retry += 1 n_details = sample(node_pool, 1)[0] if n_details["type"] == NodeType.RANDOM_CONSTANT: n_details = self.generator.resolve_random_constant(n_details) elif n_details["type"] == NodeType.CLASS_FUNCTION: n_details = self.generator.resolve_class_function(n_details) new_node = self.generate_new_node(n_details) if old_node.equals(new_node) is False: return n_details def point_mutation(self, tree, mutation_index=None): # mutate node self.index = randint(0, len(tree.program) - 1) node = tree.program[self.index] new_node = self.mutate_new_node_details(node) if new_node is None: return elif node.is_function(): node.name = new_node["name"] elif node.is_terminal(): node.node_type = new_node.get("type") node.name = new_node.get("name", None) node.value = new_node.get("value", None) tree.update() self.mutated = True def hoist_mutation(self, tree, mutation_index=None): # new indivdiaul generated from subtree node = None if mutation_index is None: self.index = randint(0, len(tree.program) - 2) node = tree.program[self.index] else: self.index = mutation_index node = tree.program[mutation_index] tree.root = node tree.update() self.mutated = True def subtree_mutation(self, tree, mutation_index=None): # subtree exchanged against external random subtree node = None if mutation_index is None: self.index = randint(0, len(tree.program) - 1) node = tree.program[self.index] else: self.index = mutation_index node = tree.program[mutation_index] self.generator.max_depth = randint(1, 3) sub_tree = self.generator.generate_tree() if node is not tree.root: tree.replace_node(node, sub_tree.root) else: tree.root = sub_tree.root tree.update() self.mutated = True def shrink_mutation(self, tree, mutation_index=None): # replace subtree with terminal if len(tree.func_nodes): node = None if mutation_index is None: self.index = randint(0, len(tree.func_nodes) - 1) node = tree.func_nodes[self.index] while node is tree.root: self.index = randint(0, len(tree.func_nodes) - 1) node = tree.func_nodes[self.index] else: self.index = mutation_index node = tree.program[mutation_index] candidate_nodes = tree.term_nodes candidate_nodes.extend(tree.input_nodes) new_node_detail = sample(candidate_nodes, 1)[0] node_details = self.mutate_new_node_details(new_node_detail) new_node = self.generate_new_node(node_details) if new_node: tree.replace_node(node, new_node) tree.update() self.mutated = True def expansion_mutation(self, tree, mutation_index=None): # terminal exchanged against external random subtree node = None if mutation_index is None: prob = random() if tree.size == 1: return elif prob > 0.5 and len(tree.term_nodes) > 0: self.index = randint(0, len(tree.term_nodes) - 1) node = tree.term_nodes[self.index] elif prob < 0.5 and len(tree.input_nodes) > 0: self.index = randint(0, len(tree.input_nodes) - 1) node = tree.input_nodes[self.index] elif len(tree.term_nodes) > 0: self.index = randint(0, len(tree.term_nodes) - 1) node = tree.term_nodes[self.index] elif len(tree.input_nodes) > 0: self.index = randint(0, len(tree.input_nodes) - 1) node = tree.input_nodes[self.index] else: self.index = mutation_index node = tree.program[mutation_index] sub_tree = self.generator.generate_tree() tree.replace_node(node, sub_tree.root) tree.update() self.mutated = True def mutate(self, tree): mutation_methods = { "POINT_MUTATION": self.point_mutation, "HOIST_MUTATION": self.hoist_mutation, "SUBTREE_MUTATION": self.subtree_mutation, "SHRINK_MUTATION": self.shrink_mutation, "EXPAND_MUTATION": self.expansion_mutation } self.method = sample(self.config["mutation"]["methods"], 1)[0] self.index = None self.mutation_probability = self.config["mutation"]["probability"] self.random_probability = random() self.mutated = False self.before_mutation = None self.after_mutation = None # record before mutation self.before_mutation = tree.to_dict()["program"] # mutate if self.mutation_probability >= self.random_probability: mutation_func = mutation_methods[self.method] mutation_func(tree) # record after mutation self.after_mutation = tree.to_dict()["program"] # record if self.recorder is not None: self.recorder.record(RecordType.MUTATION, self) def to_dict(self): self_dict = { "method": self.method, "mutation_index": self.index, "mutation_probability": self.mutation_probability, "random_probability": self.random_probability, "mutated": self.mutated, "before_mutation": self.before_mutation, "after_mutation": self.after_mutation } return self_dict