Beispiel #1
0
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
Beispiel #2
0
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 ClassifierEvaluationTests(unittest.TestCase):
    def setUp(self):
        self.config = {
            "max_population": 5,

            "tree_generation": {
                "tree_type": "CLASSIFICATION_TREE",
                "method": "FULL_METHOD",
                "initial_max_depth": 2
            },

            "evaluator": {
                "use_cache": True
            },

            "function_nodes": [
                {
                    "type": "CLASS_FUNCTION",
                    "name": "GREATER_THAN",
                    "arity": 2,

                    "data_range": {
                        "lower_bound": 0.0,
                        "upper_bound": 10.0,
                        "decimal_places": 0,
                    }
                },
                {
                    "type": "CLASS_FUNCTION",
                    "name": "LESS_THAN",
                    "arity": 2,

                    "data_range": {
                        "lower_bound": 0.0,
                        "upper_bound": 10.0,
                        "decimal_places": 0,
                    }
                },
                {
                    "type": "CLASS_FUNCTION",
                    "name": "EQUALS",
                    "arity": 2,
                    "decimal_precision": 2,
                    "data_range": {
                        "lower_bound": 0.0,
                        "upper_bound": 10.0,
                        "decimal_places": 0,
                    }
                }
            ],

            "terminal_nodes": [
                {
                    "type": "RANDOM_CONSTANT",
                    "name": "species",
                    "range": [
                        1.0,
                        2.0,
                        3.0
                    ]
                },
            ],

            "input_variables": [
                {"name": "sepal_length"},
                {"name": "sepal_width"},
                {"name": "petal_length"},
                {"name": "petal_width"}
            ],

            "class_attributes": [
                "sepal_length",
                "sepal_width",
                "petal_length",
                "petal_width"
            ],

            "data_file": "tests/data/iris.dat",

            "response_variables": [{"name": "species"}]
        }
        config.load_data(self.config)

        self.functions = GPFunctionRegistry("CLASSIFICATION")
        self.generator = TreeGenerator(self.config)
        self.population = self.generator.init()

    def test_evaluate_tree(self):
        for i in range(100):
            tree = self.generator.generate_tree()
            score, output = evaluate_tree(tree, self.functions, self.config)

            # self.assertTrue(score < 1.0)
            self.assertEquals(len(output), self.config["data"]["rows"])

    def test_evaluate(self):
        results = []
        evaluate(self.population.individuals, self.functions, self.config, results)
        self.assertTrue(len(results), len(self.population.individuals))
Beispiel #4
0
class ClassifierEvaluationTests(unittest.TestCase):
    def setUp(self):
        self.config = {
            "max_population":
            5,
            "tree_generation": {
                "tree_type": "CLASSIFICATION_TREE",
                "method": "FULL_METHOD",
                "initial_max_depth": 2
            },
            "evaluator": {
                "use_cache": True
            },
            "function_nodes": [{
                "type": "CLASS_FUNCTION",
                "name": "GREATER_THAN",
                "arity": 2,
                "data_range": {
                    "lower_bound": 0.0,
                    "upper_bound": 10.0,
                    "decimal_places": 0,
                }
            }, {
                "type": "CLASS_FUNCTION",
                "name": "LESS_THAN",
                "arity": 2,
                "data_range": {
                    "lower_bound": 0.0,
                    "upper_bound": 10.0,
                    "decimal_places": 0,
                }
            }, {
                "type": "CLASS_FUNCTION",
                "name": "EQUALS",
                "arity": 2,
                "decimal_precision": 2,
                "data_range": {
                    "lower_bound": 0.0,
                    "upper_bound": 10.0,
                    "decimal_places": 0,
                }
            }],
            "terminal_nodes": [
                {
                    "type": "RANDOM_CONSTANT",
                    "name": "species",
                    "range": [1.0, 2.0, 3.0]
                },
            ],
            "input_variables": [{
                "name": "sepal_length"
            }, {
                "name": "sepal_width"
            }, {
                "name": "petal_length"
            }, {
                "name": "petal_width"
            }],
            "class_attributes":
            ["sepal_length", "sepal_width", "petal_length", "petal_width"],
            "data_file":
            "tests/data/iris.dat",
            "response_variables": [{
                "name": "species"
            }]
        }
        config.load_data(self.config)

        self.functions = GPFunctionRegistry("CLASSIFICATION")
        self.generator = TreeGenerator(self.config)
        self.population = self.generator.init()

    def test_evaluate_tree(self):
        for i in range(100):
            tree = self.generator.generate_tree()
            score, output = evaluate_tree(tree, self.functions, self.config)

            # self.assertTrue(score < 1.0)
            self.assertEquals(len(output), self.config["data"]["rows"])

    def test_evaluate(self):
        results = []
        evaluate(self.population.individuals, self.functions, self.config,
                 results)
        self.assertTrue(len(results), len(self.population.individuals))