Skip to content

Commit

Permalink
Merge pull request #47 from cavalab/toggle_weight_on_off
Browse files Browse the repository at this point in the history
Toggle weight on off
  • Loading branch information
gAldeia authored Aug 15, 2023
2 parents 84fabf4 + 8f930bf commit c10d158
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 39 deletions.
24 changes: 13 additions & 11 deletions src/brush/deap_api/nsga2.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

def nsga2(toolbox, NGEN, MU, CXPB, use_batch, verbosity, rnd_flt):
# NGEN = 250
# MU = 100
# MU = 100
# CXPB = 0.9
# rnd_flt: random number generator to sample crossover prob

Expand All @@ -18,15 +18,18 @@ def calculate_statistics(ind):

stats = tools.Statistics(calculate_statistics)

stats.register("ave", np.mean, axis=0)
stats.register("avg", np.mean, axis=0)
stats.register("med", np.median, axis=0)
stats.register("std", np.std, axis=0)
stats.register("min", np.min, axis=0)
stats.register("max", np.max, axis=0)

logbook = tools.Logbook()
logbook.header = "gen", "evals", "ave (O1 train, O2 train, O1 val, O2 val)", \
logbook.header = "gen", "evals", "avg (O1 train, O2 train, O1 val, O2 val)", \
"med (O1 train, O2 train, O1 val, O2 val)", \
"std (O1 train, O2 train, O1 val, O2 val)", \
"min (O1 train, O2 train, O1 val, O2 val)"
"min (O1 train, O2 train, O1 val, O2 val)", \
"max (O1 train, O2 train, O1 val, O2 val)"

pop = toolbox.population(n=MU)

Expand Down Expand Up @@ -65,17 +68,16 @@ def calculate_statistics(ind):
offspring = []

for ind1, ind2 in zip(parents[::2], parents[1::2]):
off1, off2 = None, None
if rnd_flt() < CXPB:
off1, off2 = toolbox.mate(ind1, ind2)
else:
off1, off2 = ind1, ind2

off1 = toolbox.mutate(ind1)
off2 = toolbox.mutate(ind2)

# avoid inserting empty solutions
if off1 != None: off1 = toolbox.mutate(off1)
if off1 != None: offspring.extend([off1])

if off2 != None: off2 = toolbox.mutate(off2)
if off2 != None: offspring.extend([off2])
if off1 is not None: offspring.extend([off1])
if off2 is not None: offspring.extend([off2])

# archive.update(offspring)
# Evaluate the individuals with an invalid fitness
Expand Down
24 changes: 17 additions & 7 deletions src/brush/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,20 @@ class BrushEstimator(BaseEstimator):
Maximum depth of GP trees in the GP program. Use 0 for no limit.
max_size : int, default 0
Maximum number of nodes in a tree. Use 0 for no limit.
cx_prob : float, default 0.9
Probability of applying the crossover variation when generating the offspring
mutation_options : dict, default {"point":0.2, "insert":0.2, "delete":0.2, "subtree":0.2, "toggle_weight":0.2}
cx_prob : float, default 1/7
Probability of applying the crossover variation when generating the offspring,
must be between 0 and 1.
Given that there are `n` mutations, and either crossover or mutation is
used to generate each individual in the offspring (but not both at the
same time), we want to have by default an uniform probability between
crossover and every possible mutation. By setting `cx_prob=1/(n+1)`, and
`1/n` for each mutation, we can achieve an uniform distribution.
mutation_options : dict, default {"point":1/6, "insert":1/6, "delete":1/6, "subtree":1/6, "toggle_weight_on":1/6, "toggle_weight_off":1/6}
A dictionary with keys naming the types of mutation and floating point
values specifying the fraction of total mutations to do with that method.
values specifying the fraction of total mutations to do with that method.
The probability of having a mutation is `(1-cx_prob)` and, in case the mutation
is applied, then each mutation option is sampled based on the probabilities
defined in `mutation_options`. The set of probabilities should add up to 1.0.
functions: dict[str,float] or list[str], default {}
A dictionary with keys naming the function set and values giving the probability
of sampling them, or a list of functions which will be weighted uniformly.
Expand Down Expand Up @@ -95,8 +104,9 @@ def __init__(
verbosity=0,
max_depth=3,
max_size=20,
cx_prob=0.9,
mutation_options = {"point":0.2, "insert":0.2, "delete":0.2, "subtree":0.2, "toggle_weight":0.2},
cx_prob= 1/7,
mutation_options = {"point":1/6, "insert":1/6, "delete":1/6, "subtree":1/6,
"toggle_weight_on":1/6, "toggle_weight_off":1/6},
functions: list[str]|dict[str,float] = {},
initialization="grow",
random_state=None,
Expand Down Expand Up @@ -189,7 +199,7 @@ def fit(self, X, y):
"""
_brush.set_params(self.get_params())

if self.random_state != None:
if self.random_state is not None:
_brush.set_random_state(self.random_state)

self.data_ = self._make_data(X,y, validation_size=self.validation_size)
Expand Down
41 changes: 30 additions & 11 deletions src/variation.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,32 @@ inline bool delete_mutation(tree<Node>& Tree, Iter spot, const SearchSpace& SS)
return true;
};

/// @brief toggle the node's weight on or off.
/// @brief toggle the node's weight ON.
/// @param Tree the program tree
/// @param spot an iterator to the node that is being mutated
/// @param SS the search space (unused)
/// @return boolean indicating the success (true) or fail (false) of the operation
inline bool toggle_weight_mutation(tree<Node>& Tree, Iter spot, const SearchSpace& SS)
inline bool toggle_weight_on_mutation(tree<Node>& Tree, Iter spot, const SearchSpace& SS)
{
spot.node->data.set_is_weighted(!spot.node->data.get_is_weighted());
if (spot.node->data.get_is_weighted()==true // cant turn on whats already on
|| !IsWeighable(spot.node->data.ret_type)) // does not accept weights (e.g. boolean)
return false; // false indicates that mutation failed and should return std::nullopt

spot.node->data.set_is_weighted(true);
return true;
}

/// @brief toggle the node's weight OFF.
/// @param Tree the program tree
/// @param spot an iterator to the node that is being mutated
/// @param SS the search space (unused)
/// @return boolean indicating the success (true) or fail (false) of the operation
inline bool toggle_weight_off_mutation(tree<Node>& Tree, Iter spot, const SearchSpace& SS)
{
if (spot.node->data.get_is_weighted()==false)
return false;

spot.node->data.set_is_weighted(false);
return true;
}

Expand Down Expand Up @@ -176,8 +193,10 @@ inline bool subtree_mutation(tree<Node>& Tree, Iter spot, const SearchSpace& SS)
*
* - point mutation changes a single node.
* - insertion mutation inserts a node as the parent of an existing node, and fills in the other arguments.
* - deletion mutation deletes a node
* - toggle_weight mutation turns a node's weight on or off.
* - deletion mutation deletes a node.
* - subtree mutation inserts a new subtree into the program.
* - toggle_weight_on mutation turns a node's weight ON.
* - toggle_weight_off mutation turns a node's weight OFF.
*
* Every mutation has a probability (weight) based on global parameters. The
* spot where the mutation will take place is sampled based on attribute
Expand Down Expand Up @@ -247,11 +266,12 @@ std::optional<Program<T>> mutate(const Program<T>& parent, const SearchSpace& SS
using MutationFunc = std::function<bool(tree<Node>&, Iter, const SearchSpace&)>;

std::map<std::string, MutationFunc> mutations{
{"insert", insert_mutation},
{"delete", delete_mutation},
{"point", point_mutation},
{"subtree", subtree_mutation},
{"toggle_weight", toggle_weight_mutation}
{"insert", insert_mutation},
{"delete", delete_mutation},
{"point", point_mutation},
{"subtree", subtree_mutation},
{"toggle_weight_on", toggle_weight_on_mutation},
{"toggle_weight_off", toggle_weight_off_mutation}
};

// Try to find the mutation function based on the choice
Expand All @@ -270,7 +290,6 @@ std::optional<Program<T>> mutate(const Program<T>& parent, const SearchSpace& SS

return child;
} else {

return std::nullopt;
}
};
Expand Down
2 changes: 1 addition & 1 deletion tests/cpp/test_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ TEST(Data, MixedVariableTypes)
// We need to set at least the mutation options (and respective
// probabilities) in order to call PRG.predict()
PARAMS["mutation_options"] = {
{"point",0.25}, {"insert", 0.25}, {"delete", 0.25}, {"toggle_weight", 0.25}
{"point",0.25}, {"insert", 0.25}, {"delete", 0.25}, {"toggle_weight_on", 0.125}, {"toggle_weight_off", 0.125}
};

MatrixXf X(5,3);
Expand Down
6 changes: 3 additions & 3 deletions tests/cpp/test_variation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ TEST(Operators, InsertMutationWorks)
// To understand design implementation of this test, check Mutation test

PARAMS["mutation_options"] = {
{"point", 0.0}, {"insert", 1.0}, {"delete", 0.0}, {"subtree", 0.0}, {"toggle_weight", 0.0}
{"point", 0.0}, {"insert", 1.0}, {"delete", 0.0}, {"subtree", 0.0}, {"toggle_weight_on", 0.0}, {"toggle_weight_off", 0.0}
};

// retrieving the options to check if everything was set right
Expand Down Expand Up @@ -117,7 +117,7 @@ TEST(Operators, Mutation)
// TODO: set random seed

PARAMS["mutation_options"] = {
{"point",0.25}, {"insert", 0.25}, {"delete", 0.25}, {"subtree", 0.0}, {"toggle_weight", 0.25}
{"point",0.25}, {"insert", 0.25}, {"delete", 0.25}, {"subtree", 0.0}, {"toggle_weight_on", 0.125}, {"toggle_weight_off", 0.125}
};

MatrixXf X(10,2);
Expand Down Expand Up @@ -193,7 +193,7 @@ TEST(Operators, Mutation)
TEST(Operators, MutationSizeAndDepthLimit)
{
PARAMS["mutation_options"] = {
{"point",0.25}, {"insert", 0.25}, {"delete", 0.25}, {"subtree", 0.0}, {"toggle_weight", 0.25}
{"point",0.25}, {"insert", 0.25}, {"delete", 0.25}, {"subtree", 0.0}, {"toggle_weight_on", 0.125}, {"toggle_weight_off", 0.125}
};

MatrixXf X(10,2);
Expand Down
13 changes: 7 additions & 6 deletions tests/python/test_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,16 @@ def _change_and_wait(config):
'max_gen' : 100,
'max_depth': 5,
'max_size' : 50,
'mutation_options': {'point' : 0.0,
'insert' : 0.0,
'delete' : 0.0,
'subtree' : 0.0,
'toggle_weight': 0.0}
'mutation_options': {'point' : 0.0,
'insert' : 0.0,
'delete' : 0.0,
'subtree' : 0.0,
'toggle_weight_on' : 0.0,
'toggle_weight_off': 0.0}
}

# We need to guarantee order to use the index correctly
mutations = ['point', 'insert', 'delete', 'subtree', 'toggle_weight']
mutations = ['point', 'insert', 'delete', 'subtree', 'toggle_weight_on', 'toggle_weight_off']

for i, m in enumerate(mutations):
params['mutation_options'][m] = 0 if i != index else 1.0
Expand Down

0 comments on commit c10d158

Please sign in to comment.