crossover changes
parent
3e0fe06017
commit
5356be6649
|
@ -27,7 +27,7 @@ macro(compile_options target_name)
|
|||
sanitizers(${target_name})
|
||||
endmacro()
|
||||
|
||||
project(blt-gp VERSION 0.2.5)
|
||||
project(blt-gp VERSION 0.2.6)
|
||||
|
||||
include(CTest)
|
||||
|
||||
|
|
|
@ -30,11 +30,12 @@ namespace blt::gp
|
|||
{
|
||||
struct prog_config_t
|
||||
{
|
||||
blt::size_t population_size = 500;
|
||||
blt::size_t max_generations = 50;
|
||||
blt::size_t initial_min_tree_size = 3;
|
||||
blt::size_t initial_max_tree_size = 10;
|
||||
|
||||
size_t population_size = 500;
|
||||
size_t max_generations = 50;
|
||||
size_t initial_min_tree_size = 2;
|
||||
size_t initial_max_tree_size = 6;
|
||||
size_t max_tree_depth = 17;
|
||||
|
||||
// percent chance that we will do crossover
|
||||
double crossover_chance = 0.8;
|
||||
// percent chance that we will do mutation
|
||||
|
@ -42,96 +43,96 @@ namespace blt::gp
|
|||
// percent chance we will do reproduction (copy individual)
|
||||
double reproduction_chance = 0.1;
|
||||
// everything else will just be selected
|
||||
|
||||
blt::size_t elites = 0;
|
||||
|
||||
|
||||
size_t elites = 0;
|
||||
|
||||
bool try_mutation_on_crossover_failure = true;
|
||||
|
||||
|
||||
std::reference_wrapper<mutation_t> mutator;
|
||||
std::reference_wrapper<crossover_t> crossover;
|
||||
std::reference_wrapper<population_initializer_t> pop_initializer;
|
||||
|
||||
|
||||
blt::size_t threads = std::thread::hardware_concurrency();
|
||||
// number of elements each thread should pull per execution. this is for granularity performance and can be optimized for better results!
|
||||
blt::size_t evaluation_size = 4;
|
||||
|
||||
|
||||
// default config (ramped half-and-half init) or for buildering
|
||||
prog_config_t();
|
||||
|
||||
|
||||
// default config with a user specified initializer
|
||||
prog_config_t(const std::reference_wrapper<population_initializer_t>& popInitializer); // NOLINT
|
||||
|
||||
|
||||
prog_config_t(size_t populationSize, const std::reference_wrapper<population_initializer_t>& popInitializer);
|
||||
|
||||
|
||||
prog_config_t(size_t populationSize); // NOLINT
|
||||
|
||||
|
||||
prog_config_t& set_pop_size(blt::size_t pop)
|
||||
{
|
||||
population_size = pop;
|
||||
//evaluation_size = (population_size / threads) / 2;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_initial_min_tree_size(blt::size_t size)
|
||||
{
|
||||
initial_min_tree_size = size;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_initial_max_tree_size(blt::size_t size)
|
||||
{
|
||||
initial_max_tree_size = size;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_crossover(crossover_t& ref)
|
||||
{
|
||||
crossover = {ref};
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_mutation(mutation_t& ref)
|
||||
{
|
||||
mutator = {ref};
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_initializer(population_initializer_t& ref)
|
||||
{
|
||||
pop_initializer = ref;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_elite_count(blt::size_t new_elites)
|
||||
{
|
||||
elites = new_elites;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_crossover_chance(double new_crossover_chance)
|
||||
{
|
||||
crossover_chance = new_crossover_chance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_mutation_chance(double new_mutation_chance)
|
||||
{
|
||||
mutation_chance = new_mutation_chance;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_max_generations(blt::size_t new_max_generations)
|
||||
{
|
||||
max_generations = new_max_generations;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_try_mutation_on_crossover_failure(bool new_try_mutation_on_crossover_failure)
|
||||
{
|
||||
try_mutation_on_crossover_failure = new_try_mutation_on_crossover_failure;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_thread_count(blt::size_t t)
|
||||
{
|
||||
if (t == 0)
|
||||
|
@ -140,13 +141,19 @@ namespace blt::gp
|
|||
//evaluation_size = (population_size / threads) / 2;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_evaluation_size(blt::size_t s)
|
||||
{
|
||||
evaluation_size = s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
prog_config_t& set_max_tree_depth(const size_t depth)
|
||||
{
|
||||
max_tree_depth = depth;
|
||||
return *this;
|
||||
}
|
||||
|
||||
prog_config_t& set_reproduction_chance(double chance)
|
||||
{
|
||||
reproduction_chance = chance;
|
||||
|
|
|
@ -60,32 +60,30 @@ namespace blt::gp
|
|||
public:
|
||||
struct point_info_t
|
||||
{
|
||||
blt::ptrdiff_t point;
|
||||
ptrdiff_t point;
|
||||
operator_info_t& type_operator_info;
|
||||
};
|
||||
struct crossover_point_t
|
||||
{
|
||||
blt::ptrdiff_t p1_crossover_point;
|
||||
blt::ptrdiff_t p2_crossover_point;
|
||||
ptrdiff_t p1_crossover_point;
|
||||
ptrdiff_t p2_crossover_point;
|
||||
};
|
||||
struct config_t
|
||||
{
|
||||
// number of times crossover will try to pick a valid point in the tree. this is purely based on the return type of the operators
|
||||
u32 max_crossover_tries = 5;
|
||||
// if tree have fewer nodes than this number, they will not be considered for crossover
|
||||
u32 min_tree_size = 3;
|
||||
// should be at least 5 as crossover will not select the root node.
|
||||
u32 min_tree_size = 5;
|
||||
// used by the traverse version of get_crossover_point
|
||||
// at each depth level, what chance do we have to exit with this as our point? or in other words what's the chance we continue traversing
|
||||
// this is what this option configures.
|
||||
f32 traverse_chance = 0.5;
|
||||
|
||||
|
||||
// legacy settings:
|
||||
|
||||
// if we fail to find a point in the tree, should we search forward from the last point to the end of the operators?
|
||||
bool should_crossover_try_forward = false;
|
||||
// avoid selecting terminals when doing crossover
|
||||
bool avoid_terminals = false;
|
||||
// how often should we select terminals over functions. By default, we only allow selection of terminals 10% of the time
|
||||
// this applies to both types of crossover point functions. Traversal will use the parent if it should not pick a terminal.
|
||||
f32 terminal_chance = 0.1;
|
||||
// use traversal to select point instead of random selection
|
||||
bool traverse = false;
|
||||
};
|
||||
|
||||
crossover_t() = default;
|
||||
|
@ -97,9 +95,7 @@ namespace blt::gp
|
|||
|
||||
std::optional<crossover_point_t> get_crossover_point_traverse(gp_program& program, const tree_t& c1, const tree_t& c2) const;
|
||||
|
||||
std::optional<point_info_t> get_point_traverse(gp_program& program, const tree_t& t, std::optional<type_id> type) const;
|
||||
|
||||
static std::optional<point_info_t> random_place_of_type(gp_program& program, const tree_t& t, type_id type);
|
||||
std::optional<point_info_t> get_point_from_traverse_raw(gp_program& program, const tree_t& t, std::optional<type_id> type) const;
|
||||
|
||||
/**
|
||||
* child1 and child2 are copies of the parents, the result of selecting a crossover point and performing standard subtree crossover.
|
||||
|
@ -143,7 +139,7 @@ namespace blt::gp
|
|||
virtual bool apply(gp_program& program, const tree_t& p, tree_t& c);
|
||||
|
||||
// returns the point after the mutation
|
||||
blt::size_t mutate_point(gp_program& program, tree_t& c, blt::size_t node);
|
||||
blt::size_t mutate_point(gp_program& program, tree_t& c, blt::size_t node) const;
|
||||
|
||||
virtual ~mutation_t() = default;
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ namespace blt::gp
|
|||
return operations;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline blt::gp::stack_allocator& get_values()
|
||||
[[nodiscard]] inline stack_allocator& get_values()
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ namespace blt::gp
|
|||
return (*func)(*this, nullptr);
|
||||
}
|
||||
|
||||
blt::size_t get_depth(gp_program& program);
|
||||
blt::size_t get_depth(gp_program& program) const;
|
||||
|
||||
/**
|
||||
* Helper template for returning the result of the last evaluation
|
||||
|
|
|
@ -67,7 +67,12 @@ namespace blt::gp
|
|||
auto& c1_ops = c1.get_operations();
|
||||
auto& c2_ops = c2.get_operations();
|
||||
|
||||
const auto point = get_crossover_point(program, p1, p2);
|
||||
std::optional<crossover_point_t> point;
|
||||
|
||||
if (config.traverse)
|
||||
point = get_crossover_point_traverse(program, p1, p2);
|
||||
else
|
||||
point = get_crossover_point(program, p1, p2);
|
||||
|
||||
if (!point)
|
||||
return false;
|
||||
|
@ -88,28 +93,28 @@ namespace blt::gp
|
|||
case 1:
|
||||
{
|
||||
// basic crossover
|
||||
auto crossover_point_begin_itr = c1_ops.begin() + point->p1_crossover_point;
|
||||
auto crossover_point_end_itr = c1_ops.begin() + c1.find_endpoint(program, point->p1_crossover_point);
|
||||
const auto crossover_point_begin_itr = c1_ops.begin() + point->p1_crossover_point;
|
||||
const auto crossover_point_end_itr = c1_ops.begin() + c1.find_endpoint(program, point->p1_crossover_point);
|
||||
|
||||
auto found_point_begin_itr = c2_ops.begin() + point->p2_crossover_point;
|
||||
auto found_point_end_itr = c2_ops.begin() + c2.find_endpoint(program, point->p2_crossover_point);
|
||||
const auto found_point_begin_itr = c2_ops.begin() + point->p2_crossover_point;
|
||||
const auto found_point_end_itr = c2_ops.begin() + c2.find_endpoint(program, point->p2_crossover_point);
|
||||
|
||||
stack_allocator& c1_stack = c1.get_values();
|
||||
stack_allocator& c2_stack = c2.get_values();
|
||||
|
||||
for (const auto& op : blt::iterate(crossover_point_begin_itr, crossover_point_end_itr))
|
||||
for (const auto& op : iterate(crossover_point_begin_itr, crossover_point_end_itr))
|
||||
c1_operators.push_back(op);
|
||||
for (const auto& op : blt::iterate(found_point_begin_itr, found_point_end_itr))
|
||||
for (const auto& op : iterate(found_point_begin_itr, found_point_end_itr))
|
||||
c2_operators.push_back(op);
|
||||
|
||||
blt::size_t c1_stack_after_bytes = accumulate_type_sizes(crossover_point_end_itr, c1_ops.end());
|
||||
blt::size_t c1_stack_for_bytes = accumulate_type_sizes(crossover_point_begin_itr, crossover_point_end_itr);
|
||||
blt::size_t c2_stack_after_bytes = accumulate_type_sizes(found_point_end_itr, c2_ops.end());
|
||||
blt::size_t c2_stack_for_bytes = accumulate_type_sizes(found_point_begin_itr, found_point_end_itr);
|
||||
auto c1_total = static_cast<blt::ptrdiff_t>(c1_stack_after_bytes + c1_stack_for_bytes);
|
||||
auto c2_total = static_cast<blt::ptrdiff_t>(c2_stack_after_bytes + c2_stack_for_bytes);
|
||||
auto copy_ptr_c1 = get_thread_pointer_for_size<struct c1_t>(c1_total);
|
||||
auto copy_ptr_c2 = get_thread_pointer_for_size<struct c2_t>(c2_total);
|
||||
const size_t c1_stack_after_bytes = accumulate_type_sizes(crossover_point_end_itr, c1_ops.end());
|
||||
const size_t c1_stack_for_bytes = accumulate_type_sizes(crossover_point_begin_itr, crossover_point_end_itr);
|
||||
const size_t c2_stack_after_bytes = accumulate_type_sizes(found_point_end_itr, c2_ops.end());
|
||||
const size_t c2_stack_for_bytes = accumulate_type_sizes(found_point_begin_itr, found_point_end_itr);
|
||||
const auto c1_total = static_cast<blt::ptrdiff_t>(c1_stack_after_bytes + c1_stack_for_bytes);
|
||||
const auto c2_total = static_cast<blt::ptrdiff_t>(c2_stack_after_bytes + c2_stack_for_bytes);
|
||||
const auto copy_ptr_c1 = get_thread_pointer_for_size<struct c1_t>(c1_total);
|
||||
const auto copy_ptr_c2 = get_thread_pointer_for_size<struct c2_t>(c2_total);
|
||||
|
||||
c1_stack.reserve(c1_stack.bytes_in_head() - c1_stack_for_bytes + c2_stack_for_bytes);
|
||||
c2_stack.reserve(c2_stack.bytes_in_head() - c2_stack_for_bytes + c1_stack_for_bytes);
|
||||
|
@ -170,6 +175,8 @@ namespace blt::gp
|
|||
}
|
||||
#endif
|
||||
|
||||
c1.get_depth(program);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -181,52 +188,32 @@ namespace blt::gp
|
|||
|
||||
size_t crossover_point = program.get_random().get_size_t(1ul, c1_ops.size());
|
||||
|
||||
while (config.avoid_terminals && program.get_operator_info(c1_ops[crossover_point].id).argc.is_terminal())
|
||||
const bool allow_terminal_selection = program.get_random().choice(config.terminal_chance);
|
||||
|
||||
blt::size_t counter = 0;
|
||||
while (!allow_terminal_selection && program.get_operator_info(c1_ops[crossover_point].id).argc.is_terminal())
|
||||
{
|
||||
if (counter >= config.max_crossover_tries)
|
||||
return {};
|
||||
crossover_point = program.get_random().get_size_t(1ul, c1_ops.size());
|
||||
counter++;
|
||||
}
|
||||
|
||||
size_t attempted_point = 0;
|
||||
|
||||
const auto& crossover_point_type = program.get_operator_info(c1_ops[crossover_point].id);
|
||||
operator_info_t* attempted_point_type = nullptr;
|
||||
const operator_info_t* attempted_point_type = nullptr;
|
||||
|
||||
blt::size_t counter = 0;
|
||||
do
|
||||
for (counter = 0; counter < config.max_crossover_tries; counter++)
|
||||
{
|
||||
if (counter >= config.max_crossover_tries)
|
||||
{
|
||||
if (config.should_crossover_try_forward)
|
||||
{
|
||||
bool found = false;
|
||||
for (auto i = attempted_point + 1; i < c2_ops.size(); i++)
|
||||
{
|
||||
auto* info = &program.get_operator_info(c2_ops[i].id);
|
||||
if (info->return_type == crossover_point_type.return_type)
|
||||
{
|
||||
if (config.avoid_terminals && info->argc.is_terminal())
|
||||
continue;
|
||||
attempted_point = i;
|
||||
attempted_point_type = info;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return {};
|
||||
}
|
||||
// should we try again over the whole tree? probably not.
|
||||
return {};
|
||||
}
|
||||
attempted_point = program.get_random().get_size_t(1ul, c2_ops.size());
|
||||
attempted_point_type = &program.get_operator_info(c2_ops[attempted_point].id);
|
||||
if (config.avoid_terminals && attempted_point_type->argc.is_terminal())
|
||||
if (!allow_terminal_selection && attempted_point_type->argc.is_terminal())
|
||||
continue;
|
||||
if (crossover_point_type.return_type == attempted_point_type->return_type)
|
||||
break;
|
||||
counter++;
|
||||
return crossover_point_t{static_cast<blt::ptrdiff_t>(crossover_point), static_cast<blt::ptrdiff_t>(attempted_point)};
|
||||
}
|
||||
while (true);
|
||||
|
||||
return crossover_point_t{static_cast<blt::ptrdiff_t>(crossover_point), static_cast<blt::ptrdiff_t>(attempted_point)};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<crossover_t::crossover_point_t> crossover_t::get_crossover_point_traverse(gp_program& program, const tree_t& c1,
|
||||
|
@ -241,25 +228,29 @@ namespace blt::gp
|
|||
return {{c1_point_o->point, c2_point_o->point}};
|
||||
}
|
||||
|
||||
std::optional<crossover_t::point_info_t> crossover_t::random_place_of_type(gp_program& program, const tree_t& t, type_id type)
|
||||
{
|
||||
auto attempted_point = program.get_random().get_i64(1ul, t.get_operations().size());
|
||||
auto& attempted_point_type = program.get_operator_info(t.get_operations()[attempted_point].id);
|
||||
if (type == attempted_point_type.return_type)
|
||||
return {{attempted_point, attempted_point_type}};
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<crossover_t::point_info_t> crossover_t::get_point_traverse(gp_program& program, const tree_t& t, std::optional<type_id> type) const
|
||||
std::optional<crossover_t::point_info_t> crossover_t::get_point_from_traverse_raw(gp_program& program, const tree_t& t,
|
||||
std::optional<type_id> type) const
|
||||
{
|
||||
auto& random = program.get_random();
|
||||
|
||||
bool should_select_terminal = program.get_random().choice(config.terminal_chance);
|
||||
|
||||
ptrdiff_t parent = -1;
|
||||
ptrdiff_t point = 0;
|
||||
while (true)
|
||||
{
|
||||
auto& current_op_type = program.get_operator_info(t.get_operations()[point].id);
|
||||
if (current_op_type.argc.is_terminal())
|
||||
{
|
||||
if (!should_select_terminal)
|
||||
{
|
||||
if (parent == -1)
|
||||
return {};
|
||||
auto& parent_type = program.get_operator_info(t.get_operations()[parent].id);
|
||||
if (type && *type != parent_type.return_type)
|
||||
return {};
|
||||
return {{parent, parent_type}};
|
||||
}
|
||||
if (type && *type != current_op_type.return_type)
|
||||
return {};
|
||||
return {{point, current_op_type}};
|
||||
|
@ -270,6 +261,7 @@ namespace blt::gp
|
|||
const auto args = current_op_type.argc.argc;
|
||||
const auto argument = random.get_size_t(0, args);
|
||||
|
||||
parent = point;
|
||||
// move to the first child
|
||||
point += 1;
|
||||
// loop through all the children we wish to skip. The result will be the first node of the next child, becoming the new parent
|
||||
|
@ -288,7 +280,7 @@ namespace blt::gp
|
|||
{
|
||||
for (size_t i = 0; i < config.max_crossover_tries; i++)
|
||||
{
|
||||
if (auto found = get_point_traverse(program, t, type))
|
||||
if (auto found = get_point_from_traverse_raw(program, t, type))
|
||||
return found;
|
||||
}
|
||||
return {};
|
||||
|
@ -300,7 +292,7 @@ namespace blt::gp
|
|||
return true;
|
||||
}
|
||||
|
||||
blt::size_t mutation_t::mutate_point(gp_program& program, tree_t& c, blt::size_t node)
|
||||
blt::size_t mutation_t::mutate_point(gp_program& program, tree_t& c, blt::size_t node) const
|
||||
{
|
||||
auto& ops_r = c.get_operations();
|
||||
auto& vals_r = c.get_values();
|
||||
|
|
17
src/tree.cpp
17
src/tree.cpp
|
@ -140,14 +140,17 @@ namespace blt::gp
|
|||
out << '\n';
|
||||
}
|
||||
|
||||
blt::size_t tree_t::get_depth(gp_program& program)
|
||||
size_t tree_t::get_depth(gp_program& program) const
|
||||
{
|
||||
blt::size_t depth = 0;
|
||||
size_t depth = 0;
|
||||
|
||||
auto operations_stack = operations;
|
||||
tracked_vector<blt::size_t> values_process;
|
||||
tracked_vector<blt::size_t> value_stack;
|
||||
|
||||
thread_local tracked_vector<size_t> values_process;
|
||||
thread_local tracked_vector<size_t> value_stack;
|
||||
|
||||
values_process.clear();
|
||||
value_stack.clear();
|
||||
|
||||
for (const auto& op : operations_stack)
|
||||
{
|
||||
if (op.is_value)
|
||||
|
@ -167,8 +170,8 @@ namespace blt::gp
|
|||
value_stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
blt::size_t local_depth = 0;
|
||||
for (blt::size_t i = 0; i < program.get_operator_info(operation.id).argc.argc; i++)
|
||||
size_t local_depth = 0;
|
||||
for (size_t i = 0; i < program.get_operator_info(operation.id).argc.argc; i++)
|
||||
{
|
||||
local_depth = std::max(local_depth, values_process.back());
|
||||
values_process.pop_back();
|
||||
|
|
Loading…
Reference in New Issue