pulsatrix
Loading...
Searching...
No Matches
evolution_strategies.hpp
Go to the documentation of this file.
1
13#pragma once
14
15#include <cstddef>
16#include <random>
17#include <stdexcept>
18#include <vector>
19
20namespace pulsatrix {
21
28inline std::vector<double> ESUpdateGivenPerturbations(const std::vector<double>& theta,
29 const std::vector<std::vector<double>>& epsilons,
30 const std::vector<double>& fitnesses, double alpha,
31 double sigma) {
32 if (epsilons.empty()) {
33 throw std::invalid_argument("ESUpdateGivenPerturbations: epsilons must not be empty");
34 }
35 if (epsilons.size() != fitnesses.size()) {
36 throw std::invalid_argument("ESUpdateGivenPerturbations: epsilons and fitnesses must have the same size");
37 }
38 if (sigma <= 0.0) {
39 throw std::invalid_argument("ESUpdateGivenPerturbations: sigma must be positive");
40 }
41 size_t dim = theta.size();
42 for (const auto& eps : epsilons) {
43 if (eps.size() != dim) {
44 throw std::invalid_argument("ESUpdateGivenPerturbations: every epsilon must match theta's dimension");
45 }
46 }
47
48 std::vector<double> gradient_estimate(dim, 0.0);
49 for (size_t i = 0; i < epsilons.size(); ++i) {
50 for (size_t d = 0; d < dim; ++d) {
51 gradient_estimate[d] += fitnesses[i] * epsilons[i][d];
52 }
53 }
54
55 double scale = alpha / (static_cast<double>(epsilons.size()) * sigma);
56 std::vector<double> updated(dim);
57 for (size_t d = 0; d < dim; ++d) {
58 updated[d] = theta[d] + scale * gradient_estimate[d];
59 }
60 return updated;
61}
62
70template <typename FitnessFn, typename RNG>
71std::vector<double> ESStep(const std::vector<double>& theta, FitnessFn fitness_fn, int population_size, double sigma,
72 double alpha, RNG& rng) {
73 if (population_size <= 0 || population_size % 2 != 0) {
74 throw std::invalid_argument("ESStep: population_size must be a positive even number (mirrored sampling)");
75 }
76 if (sigma <= 0.0) {
77 throw std::invalid_argument("ESStep: sigma must be positive");
78 }
79
80 size_t dim = theta.size();
81 std::normal_distribution<double> noise(0.0, 1.0);
82 std::vector<std::vector<double>> epsilons;
83 std::vector<double> fitnesses;
84 epsilons.reserve(static_cast<size_t>(population_size));
85 fitnesses.reserve(static_cast<size_t>(population_size));
86
87 for (int i = 0; i < population_size / 2; ++i) {
88 std::vector<double> eps(dim);
89 for (size_t d = 0; d < dim; ++d) {
90 eps[d] = noise(rng);
91 }
92 std::vector<double> neg_eps(dim);
93 for (size_t d = 0; d < dim; ++d) {
94 neg_eps[d] = -eps[d];
95 }
96
97 std::vector<double> theta_plus(dim);
98 std::vector<double> theta_minus(dim);
99 for (size_t d = 0; d < dim; ++d) {
100 theta_plus[d] = theta[d] + sigma * eps[d];
101 theta_minus[d] = theta[d] + sigma * neg_eps[d];
102 }
103
104 epsilons.push_back(eps);
105 fitnesses.push_back(fitness_fn(theta_plus));
106 epsilons.push_back(std::move(neg_eps));
107 fitnesses.push_back(fitness_fn(theta_minus));
108 }
109
110 return ESUpdateGivenPerturbations(theta, epsilons, fitnesses, alpha, sigma);
111}
112
114struct ESResult {
115 std::vector<double> best_theta;
118};
119
130template <typename FitnessFn, typename RNG>
131ESResult RunEvolutionStrategies(std::vector<double> theta, FitnessFn fitness_fn, int num_iterations,
132 int population_size, double sigma, double alpha, RNG& rng) {
133 if (theta.empty()) {
134 throw std::invalid_argument("RunEvolutionStrategies: initial theta must not be empty");
135 }
136 if (num_iterations <= 0) {
137 throw std::invalid_argument("RunEvolutionStrategies: num_iterations must be positive");
138 }
139
140 std::vector<double> best_theta = theta;
141 double best_fitness = fitness_fn(theta);
142
143 for (int iter = 0; iter < num_iterations; ++iter) {
144 double current_fitness = fitness_fn(theta);
145 if (current_fitness > best_fitness) {
146 best_fitness = current_fitness;
147 best_theta = theta;
148 }
149 theta = ESStep(theta, fitness_fn, population_size, sigma, alpha, rng);
150 }
151
152 double final_fitness = fitness_fn(theta);
153 if (final_fitness > best_fitness) {
154 best_fitness = final_fitness;
155 best_theta = std::move(theta);
156 }
157
158 return ESResult{std::move(best_theta), best_fitness, num_iterations};
159}
160
161} // namespace pulsatrix
Definition acquisition_functions.hpp:16
std::vector< double > ESUpdateGivenPerturbations(const std::vector< double > &theta, const std::vector< std::vector< double > > &epsilons, const std::vector< double > &fitnesses, double alpha, double sigma)
Pure core: the exact ES parameter update given already-sampled perturbations and their fitness scores...
Definition evolution_strategies.hpp:28
ESResult RunEvolutionStrategies(std::vector< double > theta, FitnessFn fitness_fn, int num_iterations, int population_size, double sigma, double alpha, RNG &rng)
Runs num_iterations of Evolution Strategies starting from theta, tracking the best (theta,...
Definition evolution_strategies.hpp:131
std::vector< double > ESStep(const std::vector< double > &theta, FitnessFn fitness_fn, int population_size, double sigma, double alpha, RNG &rng)
RNG-driven wrapper: samples population_size/2 standard-normal perturbation vectors,...
Definition evolution_strategies.hpp:71
Result of a full Evolution Strategies run.
Definition evolution_strategies.hpp:114
double best_fitness
Definition evolution_strategies.hpp:116
int iterations_run
Definition evolution_strategies.hpp:117
std::vector< double > best_theta
Definition evolution_strategies.hpp:115