Skip to content

symbolic_regression_part3

Manlio Morini edited this page Jun 25, 2024 · 24 revisions

Symbolic Regression - Custom Evaluator and Teams

A custom evaluator is nice but not enough. What I really need is a way to evolve multiple programs at the same time and then apply my evaluator. Can it be done?

Yes, it's possible using a team.

(Not so) toy problem

$$ \begin{pmatrix} a_1 \\ a_2 \\ \vdots \\ a_N \end{pmatrix} = \begin{pmatrix} b_{11} & b_{12} & \ldots & b_{1N} \\ b_{21} & b_{22} & \ldots & b_{2N} \\ \vdots & \vdots & \vdots & \vdots \\ b_{N1} & b_{N2} & \ldots & b_{NN} \end{pmatrix} \cdot \begin{pmatrix} \boldsymbol{f_1}(c) \\ \boldsymbol{f_2}(c) \\ \vdots \\ \boldsymbol{f_N}(c) \end{pmatrix} $$

This is similar to the previous problem but requires the evolution of multiple candidate solutions at the same time.

Setting up code

const auto a = get_vector();  // N-dimensional vector
const auto b = get_matrix();  // NxN matrix

This time a and b aren't scalar. We use two support functions to generate a random vector and a random matrix of the appropriate dimensions.


Once again, the evaluator needs various changes:

using candidate_solution = ultra::gp::team<ultra::gp::individual>;

// 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 candidate_solution is a team of individuals/programs. The members of a team are always selected, evaluated, and varied simultaneously, so they undergo 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;
  });

This time f is a vector (where we store the results of the components of the team/candidate solution).

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:

$$ model[i] = \sum_{j=0}^{N} b_{ij} \cdot f_j(c) $$

As before delta is a measure of the error based on the absolute value:

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:

$$ delta = \sum_{i=0}^{N} \left\lvert a_i - model[i] \right\rvert $$


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)


PROCEED TO PART 4→

Ultra

Highlights

Clone this wiki locally