symbolic_regression_part3 - morinim/ultra GitHub Wiki
A custom evaluator is often not enough. In some problems, we need to evolve multiple programs simultaneously and evaluate them as a whole.
Can this be done?
Yes, it's possible using a team.
This extends the previous problem: instead of evolving a single function, we now evolve a set of functions that are evaluated jointly.
const auto a = get_vector(); // N-dimensional vector
const auto b = get_matrix(); // NxN matrixThis time, a and b are no longer scalars. We use helper functions to generate a random vector and a matrix of compatible dimensions.
using candidate_solution = ultra::gp::team<ultra::gp::individual>;The candidate solution is now a team of individuals (i.e. multiple programs evolved together). Conceptually, each individual in the team learns a component of the solution, and the final output emerges from their combination.
Once again, the evaluator needs various changes:
// Given a team (i.e. a candidate solution of the problem), returns a score
// measuring how good it is.
[[nodiscard]] double my_evaluator(const candidate_solution &x)
{
using namespace ultra;
std::vector<double> f(N);
std::ranges::transform(
x, f.begin(),
[](const auto &prg)
{
const auto ret(run(prg));
return has_value(ret) ? std::get<D_DOUBLE>(ret) : 0.0;
});
std::vector<double> model(N, 0.0);
for (unsigned i(0); i < N; ++i)
for (unsigned j(0); j < N; ++j)
model[i] += b(i, j) * f[j];
double delta(std::inner_product(a.begin(), a.end(), model.begin(), 0.0,
std::plus{},
[](auto v1, auto v2)
{
return std::fabs(v1 - v2);
}));
return -delta;
}The members of a team are always selected, evaluated, and varied together, effectively undergoing a co-evolutionary process.
A line-by-line description of the evaluation process follows:
std::vector<double> f(N);
std::ranges::transform(
x, f.begin(),
[](const auto &prg)
{
const auto ret(run(prg));
return has_value(ret) ? std::get<D_DOUBLE>(ret) : 0.0;
});Here, f is a vector storing the outputs of each program in the team.
std::vector<double> model(N, 0.0);
for (unsigned i(0); i < N; ++i)
for (unsigned j(0); j < N; ++j)
model[i] += b(i, j) * f[j];Mathematically the code is equivalent to:
This corresponds to a standard matrix–vector multiplication.
As before, delta measures the error using the absolute difference:
double delta(std::inner_product(a.begin(), a.end(), model.begin(), 0.0,
std::plus{},
[](auto v1, auto v2)
{
return std::fabs(v1 - v2);
}));std::inner_product performs an ordered map/reduce operation on a and model. Mathematically:
Only one line of the main() function varies:
prob.params.team.individuals = N;to inform the search engine of the team size.
(for your ease, all the code is in the examples/symbolic_regression04.cc file)