Skip to content

Commit

Permalink
Improved variation attempts. Fixed segfault in subtree mutation.
Browse files Browse the repository at this point in the history
Zero rewards if the mutation fails.
  • Loading branch information
gAldeia committed Sep 23, 2024
1 parent 6d6bc87 commit b31d310
Show file tree
Hide file tree
Showing 14 changed files with 94 additions and 67 deletions.
3 changes: 2 additions & 1 deletion pybrush/EstimatorInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ class EstimatorInterface():
weights_init : bool, default True
Whether the search space should initialize the sampling weights of terminal nodes
based on the correlation with the output y. If `False`, then all terminal nodes
will have the same probability of 1.0.
will have the same probability of 1.0. This parameter is ignored if the bandit
strategy is used, and weights will be learned dynamically during the run.
validation_size : float, default 0.0
Percentage of samples to use as a hold-out partition. These samples are used
to calculate statistics during evolution, but not used to train the models.
Expand Down
4 changes: 2 additions & 2 deletions src/bandit/bandit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ map<T, float> Bandit<T>::sample_probs(bool update) {
}

template <typename T>
T Bandit<T>::choose(tree<Node>& tree, Fitness& f) {
return this->pbandit->choose(tree, f);
T Bandit<T>::choose(VectorXf& context) {
return this->pbandit->choose(context);
}

template <typename T>
Expand Down
2 changes: 1 addition & 1 deletion src/bandit/bandit.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ struct Bandit
* @param f The fitness function used to evaluate and select nodes.
* @return T The selected arm from the tree.
*/
T choose(tree<Node>& tree, Fitness& f);
T choose(VectorXf& context);

/**
* @brief Updates the bandit's state based on the chosen arm and the received reward.
Expand Down
2 changes: 1 addition & 1 deletion src/bandit/bandit_operator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ std::map<T, float> BanditOperator<T>::sample_probs(bool update)
}

template<typename T>
T BanditOperator<T>::choose(tree<Node>& tree, Fitness& f)
T BanditOperator<T>::choose(VectorXf& context)
{
// TODO: Implement the logic for sampling probabilities
// based on the bandit operator's strategy
Expand Down
2 changes: 1 addition & 1 deletion src/bandit/bandit_operator.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class BanditOperator
* @param f The fitness value used to influence the choice.
* @return The arm with highest probability.
*/
virtual T choose(tree<Node>& tree, Fitness& f);
virtual T choose(VectorXf& context);

/**
* @brief Updates the reward for a specific arm.
Expand Down
2 changes: 1 addition & 1 deletion src/bandit/dummy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ std::map<T, float> DummyBandit<T>::sample_probs(bool update) {
}

template <typename T>
T DummyBandit<T>::choose(tree<Node>& tree, Fitness& f) {
T DummyBandit<T>::choose(VectorXf& context) {
// std::map<T, float> probs = this->sample_probs(false);

return r.random_choice(this->probabilities);
Expand Down
2 changes: 1 addition & 1 deletion src/bandit/dummy.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class DummyBandit : public BanditOperator<T>
~DummyBandit(){};

std::map<T, float> sample_probs(bool update);
T choose(tree<Node>& tree, Fitness& f);
T choose(VectorXf& context);
void update(T arm, float reward, VectorXf& context);

private:
Expand Down
2 changes: 1 addition & 1 deletion src/bandit/linear_thompson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ std::map<T, float> LinearThompsonSamplingBandit<T>::sample_probs(bool update) {
}

template <typename T>
T LinearThompsonSamplingBandit<T>::choose(tree<Node>& tree, Fitness& f) {
T LinearThompsonSamplingBandit<T>::choose(VectorXf& context) {
// TODO: use context here

std::map<T, float> probs = this->sample_probs(true);
Expand Down
2 changes: 1 addition & 1 deletion src/bandit/linear_thompson.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class LinearThompsonSamplingBandit : public BanditOperator<T>
~LinearThompsonSamplingBandit(){};

std::map<T, float> sample_probs(bool update);
T choose(tree<Node>& tree, Fitness& f);
T choose(VectorXf& context);
void update(T arm, float reward, VectorXf& context);
private:
};
Expand Down
2 changes: 1 addition & 1 deletion src/bandit/thompson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ std::map<T, float> ThompsonSamplingBandit<T>::sample_probs(bool update) {
}

template <typename T>
T ThompsonSamplingBandit<T>::choose(tree<Node>& tree, Fitness& f) {
T ThompsonSamplingBandit<T>::choose(VectorXf& context) {
std::map<T, float> probs = this->sample_probs(true);

return r.random_choice(probs);
Expand Down
2 changes: 1 addition & 1 deletion src/bandit/thompson.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ThompsonSamplingBandit : public BanditOperator<T>
~ThompsonSamplingBandit(){};

std::map<T, float> sample_probs(bool update);
T choose(tree<Node>& tree, Fitness& f);
T choose(VectorXf& context);
void update(T arm, float reward, VectorXf& context);
private:
bool dynamic_update;
Expand Down
97 changes: 54 additions & 43 deletions src/vary/variation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -537,57 +537,63 @@ std::tuple<std::optional<Individual<T>>, VectorXf> Variation<T>::cross(
* @return `std::optional` that may contain the child program of type `T`
*/
template<Brush::ProgramType T>
std::tuple<std::optional<Individual<T>>, VectorXf> Variation<T>::mutate(const Individual<T>& parent)
std::tuple<std::optional<Individual<T>>, VectorXf> Variation<T>::mutate(const Individual<T>& parent, string choice)
{
auto options = parameters.mutation_probs;
if (choice.empty())
{
std::cout << "Will sample a mut choice" << std::endl;
auto options = parameters.mutation_probs;

bool all_zero = true;
for (auto &it : parameters.mutation_probs) {
if (it.second > 0.0) {
all_zero = false;
break;
bool all_zero = true;
for (auto &it : parameters.mutation_probs) {
if (it.second > 0.0) {
all_zero = false;
break;
}
}

if (all_zero) { // No mutation can be successfully applied to this solution
return std::make_tuple(std::nullopt, VectorXf());
}

// picking a valid mutation option
choice = r.random_choice(parameters.mutation_probs);
}

// std::cout << "Mutation choice: " << choice << std::endl;

Program<T> copy(parent.program);

vector<float> weights; // choose location by weighted sampling of program
if (choice == "point") // TODO: use enum here to optimize
weights = PointMutation::find_spots(copy.Tree, search_space, parameters);
else if (choice == "insert")
weights = InsertMutation::find_spots(copy.Tree, search_space, parameters);
else if (choice == "delete")
weights = DeleteMutation::find_spots(copy.Tree, search_space, parameters);
else if (choice == "subtree")
weights = SubtreeMutation::find_spots(copy.Tree, search_space, parameters);
else if (choice == "toggle_weight_on")
weights = ToggleWeightOnMutation::find_spots(copy.Tree, search_space, parameters);
else if (choice == "toggle_weight_off")
weights = ToggleWeightOffMutation::find_spots(copy.Tree, search_space, parameters);
else {
std::string msg = fmt::format("{} not a valid mutation choice", choice);
HANDLE_ERROR_THROW(msg);
}

if (all_zero)
{ // No mutation can be successfully applied to this solution
if (std::all_of(weights.begin(), weights.end(), [](const auto& w) {
return w<=0.0;
}))
{ // There is no spot that has a probability to be selected
return std::make_tuple(std::nullopt, VectorXf());
}

Program<T> child(parent.program);

int attempts = 0;
while(++attempts <= 3)
{
// choose a valid mutation option
string choice = r.random_choice(parameters.mutation_probs);

vector<float> weights;

// choose location by weighted sampling of program
if (choice == "point") // TODO: use enum here to optimize
weights = PointMutation::find_spots(child.Tree, search_space, parameters);
else if (choice == "insert")
weights = InsertMutation::find_spots(child.Tree, search_space, parameters);
else if (choice == "delete")
weights = DeleteMutation::find_spots(child.Tree, search_space, parameters);
else if (choice == "subtree")
weights = SubtreeMutation::find_spots(child.Tree, search_space, parameters);
else if (choice == "toggle_weight_on")
weights = ToggleWeightOnMutation::find_spots(child.Tree, search_space, parameters);
else if (choice == "toggle_weight_off")
weights = ToggleWeightOffMutation::find_spots(child.Tree, search_space, parameters);
else {
std::string msg = fmt::format("{} not a valid mutation choice", choice);
HANDLE_ERROR_THROW(msg);
}

if (std::all_of(weights.begin(), weights.end(), [](const auto& w) {
return w<=0.0;
}))
{ // There is no spot that has a probability to be selected
continue;
}
// std::cout << "Attempt: " << attempts << std::endl;
Program<T> child(parent.program);

// apply the mutation and check if it succeeded
auto spot = r.select_randomly(child.Tree.begin(), child.Tree.end(),
Expand All @@ -614,7 +620,8 @@ std::tuple<std::optional<Individual<T>>, VectorXf> Variation<T>::mutate(const In
if (success
&& ( (child.size() <= parameters.max_size)
&& (child.depth() <= parameters.max_depth) )){

// std::cout << "Mutation succeeded on attempt " << attempts << std::endl;

Individual<T> ind(child);
ind.set_variation(choice);

Expand All @@ -626,8 +633,9 @@ std::tuple<std::optional<Individual<T>>, VectorXf> Variation<T>::mutate(const In
VectorXf context = this->variation_bandit.get_context(parent.program.Tree, spot);

return std::make_tuple(ind, context);
} else {
continue;
}
else { // reseting
// std::cout << "Mutation failed on attempt " << attempts << std::endl;
}
}

Expand Down Expand Up @@ -668,9 +676,12 @@ void Variation<T>::vary(Population<T>& pop, int island,
}
else
{
std::cout << "Performing mutation " << std::endl;
auto variation_result = mutate(mom);
cout << "finished mutation" << endl;
ind_parents = {mom};
tie(opt, context) = variation_result;
cout << "unpacked" << endl;
}

// this assumes that islands do not share indexes before doing variation
Expand Down
37 changes: 26 additions & 11 deletions src/vary/variation.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class Variation {
* successful, or an empty optional otherwise.
*/
std::tuple<std::optional<Individual<T>>, VectorXf> mutate(
const Individual<T>& parent);
const Individual<T>& parent, string choice="");

/**
* @brief Handles variation of a population.
Expand Down Expand Up @@ -234,10 +234,11 @@ class Variation {
*r.select_randomly(parents.begin(), parents.end())];

vector<Individual<T>> ind_parents;
VectorXf context = {};
VectorXf context = this->variation_bandit.get_context(mom.program.Tree, mom.program.Tree.begin());

bool crossover = (r() < parameters.cx_prob);
if (crossover)
string choice = this->variation_bandit.choose(context);

if (choice == "cx")
{
const Individual<T>& dad = pop[
*r.select_randomly(parents.begin(), parents.end())];
Expand All @@ -249,10 +250,12 @@ class Variation {
}
else
{
// std::cout << "Performing mutation" << std::endl;
auto variation_result = mutate(mom);
// std::cout << "Performing mutation " << choice << std::endl;
auto variation_result = mutate(mom, choice);
// cout << "finished mutation" << endl;
ind_parents = {mom};
tie(opt, context) = variation_result;
// cout << "unpacked" << endl;
}

// this assumes that islands do not share indexes before doing variation
Expand Down Expand Up @@ -327,10 +330,17 @@ class Variation {
r = 1.0;

// std::cout << "Updating variation bandit with reward: " << r << std::endl;
this->variation_bandit.update(ind.get_variation(), r, context);

if (ind.get_variation() != "born" && ind.get_variation() != "cx"
&& ind.get_variation() != "subtree")
if (ind.get_variation() != "born")
{
this->variation_bandit.update(ind.get_variation(), r, context);
}
else
{ // giving zero reward if the variation failed
this->variation_bandit.update(choice, 0.0, context);
}

if (ind.get_variation() != "born" && ind.get_variation() != "cx")
{
if (ind.get_sampled_nodes().size() > 0) {
const auto& changed_nodes = ind.get_sampled_nodes();
Expand Down Expand Up @@ -366,9 +376,14 @@ class Variation {
map<DataType, Bandit<string>> terminal_bandits;
map<DataType, Bandit<string>> op_bandits;

// these functions will extract context and use it to choose the nodes to replace
// these functions below will extract context and use it to choose the nodes to replace

// bandit_get_node_like
//bandit_sample_op_with_arg
//bandit_sample_terminal
//bandit_sample_op
// bandit_sample_op
//bandit_sample_subtree

//etc.
};

Expand Down
2 changes: 1 addition & 1 deletion tests/cpp/test_variation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ TEST(Variation, FixedRootDoesntChange)
Variation variator = Variation<ProgramType::BinaryClassifier>(params, SS);

int successes = 0;
for (int attempt = 0; attempt < 10; ++attempt)
for (int attempt = 0; attempt < 50; ++attempt)
{
// different program types changes how predict works (and the rettype of predict)
ClassifierProgram PRG = SS.make_classifier(0, 0, params);
Expand Down

0 comments on commit b31d310

Please sign in to comment.