pulsatrix
Loading...
Searching...
No Matches
cma_es.hpp
Go to the documentation of this file.
1
23#pragma once
24
25#include <algorithm>
26#include <cmath>
27#include <numeric>
28#include <random>
29#include <stdexcept>
30#include <vector>
31
32namespace pulsatrix {
33
36struct CMAESState {
37 std::vector<double> mean;
38 double sigma;
39 std::vector<double> variances;
40};
41
47inline std::vector<std::vector<double>> AskGivenSamples(const CMAESState& state,
48 const std::vector<std::vector<double>>& z_samples) {
49 const size_t n = state.mean.size();
50 std::vector<std::vector<double>> offspring;
51 offspring.reserve(z_samples.size());
52 for (const auto& z : z_samples) {
53 if (z.size() != n) {
54 throw std::invalid_argument("AskGivenSamples: every z_sample must match state.mean's dimension");
55 }
56 std::vector<double> x(n);
57 for (size_t j = 0; j < n; ++j) {
58 x[j] = state.mean[j] + state.sigma * std::sqrt(state.variances[j]) * z[j];
59 }
60 offspring.push_back(std::move(x));
61 }
62 return offspring;
63}
64
74inline CMAESState TellGivenSamples(const CMAESState& state, const std::vector<std::vector<double>>& z_samples,
75 const std::vector<std::vector<double>>& offspring,
76 const std::vector<double>& fitness, double step_size_learning_rate,
77 double scale_learning_rate) {
78 if (z_samples.size() != offspring.size() || z_samples.size() != fitness.size()) {
79 throw std::invalid_argument("TellGivenSamples: z_samples, offspring, and fitness must be the same size");
80 }
81 if (z_samples.size() < 2) {
82 throw std::invalid_argument("TellGivenSamples: need at least 2 samples");
83 }
84
85 const size_t n = state.mean.size();
86 const size_t lambda = z_samples.size();
87 const size_t mu = std::max<size_t>(1, lambda / 2);
88
89 std::vector<size_t> order(lambda);
90 std::iota(order.begin(), order.end(), size_t{0});
91 std::sort(order.begin(), order.end(), [&](size_t a, size_t b) { return fitness[a] > fitness[b]; });
92
93 std::vector<double> y(n, 0.0); // mean of the top-mu displacements y_i = sqrt(var)*z_i
94 std::vector<double> y_squared(n, 0.0); // mean of the top-mu displacements' squares
95 for (size_t rank = 0; rank < mu; ++rank) {
96 const auto& z = z_samples[order[rank]];
97 for (size_t j = 0; j < n; ++j) {
98 double y_ij = std::sqrt(state.variances[j]) * z[j];
99 y[j] += y_ij;
100 y_squared[j] += y_ij * y_ij;
101 }
102 }
103 for (size_t j = 0; j < n; ++j) {
104 y[j] /= static_cast<double>(mu);
105 y_squared[j] /= static_cast<double>(mu);
106 }
107
108 CMAESState next;
109 next.mean.resize(n);
110 for (size_t j = 0; j < n; ++j) {
111 next.mean[j] = state.mean[j] + state.sigma * y[j];
112 }
113
114 double y_norm = 0.0;
115 for (size_t j = 0; j < n; ++j) {
116 y_norm += y[j] * y[j];
117 }
118 y_norm = std::sqrt(y_norm);
119 double expected_norm = std::sqrt(static_cast<double>(n));
120 next.sigma = state.sigma * std::exp(step_size_learning_rate * (y_norm / expected_norm - 1.0));
121
122 next.variances.resize(n);
123 for (size_t j = 0; j < n; ++j) {
124 next.variances[j] = (1.0 - scale_learning_rate) * state.variances[j] + scale_learning_rate * y_squared[j];
125 }
126
127 return next;
128}
129
134class CMAES {
135public:
140 CMAES(std::vector<double> initial_mean, double initial_sigma, size_t lambda,
141 double step_size_learning_rate = 0.3, double scale_learning_rate = 0.3)
142 : lambda_(lambda),
143 step_size_learning_rate_(step_size_learning_rate),
144 scale_learning_rate_(scale_learning_rate) {
145 if (initial_mean.empty()) {
146 throw std::invalid_argument("CMAES: initial_mean must not be empty");
147 }
148 if (initial_sigma <= 0.0) {
149 throw std::invalid_argument("CMAES: initial_sigma must be positive");
150 }
151 if (lambda < 2) {
152 throw std::invalid_argument("CMAES: lambda must be >= 2");
153 }
154 state_.mean = std::move(initial_mean);
155 state_.sigma = initial_sigma;
156 state_.variances.assign(state_.mean.size(), 1.0);
157 }
158
161 template <typename RNG>
162 std::vector<std::vector<double>> Ask(RNG& rng) {
163 std::normal_distribution<double> dist(0.0, 1.0);
164 last_z_samples_.assign(lambda_, std::vector<double>(state_.mean.size()));
165 for (auto& z : last_z_samples_) {
166 for (double& v : z) {
167 v = dist(rng);
168 }
169 }
170 return AskGivenSamples(state_, last_z_samples_);
171 }
172
179 void Tell(const std::vector<std::vector<double>>& offspring, const std::vector<double>& fitness) {
180 if (last_z_samples_.empty()) {
181 throw std::invalid_argument("CMAES::Tell: called before any Ask()");
182 }
183 state_ = TellGivenSamples(state_, last_z_samples_, offspring, fitness, step_size_learning_rate_,
184 scale_learning_rate_);
185 }
186
187 [[nodiscard]] const std::vector<double>& mean() const { return state_.mean; }
188 [[nodiscard]] double sigma() const { return state_.sigma; }
189 [[nodiscard]] const std::vector<double>& variances() const { return state_.variances; }
190
191private:
192 CMAESState state_;
193 size_t lambda_;
194 double step_size_learning_rate_;
195 double scale_learning_rate_;
196 std::vector<std::vector<double>> last_z_samples_;
197};
198
199} // namespace pulsatrix
RNG-driven ask-tell wrapper: caches the z-samples an Ask() call draws so a matching Tell() call can r...
Definition cma_es.hpp:134
void Tell(const std::vector< std::vector< double > > &offspring, const std::vector< double > &fitness)
Updates the internal state from the most recent Ask() call's offspring and their fitness values (maxi...
Definition cma_es.hpp:179
CMAES(std::vector< double > initial_mean, double initial_sigma, size_t lambda, double step_size_learning_rate=0.3, double scale_learning_rate=0.3)
Definition cma_es.hpp:140
const std::vector< double > & variances() const
Definition cma_es.hpp:189
std::vector< std::vector< double > > Ask(RNG &rng)
Draws lambda fresh offspring from the current state, caching the standard- normal samples used for th...
Definition cma_es.hpp:162
const std::vector< double > & mean() const
Definition cma_es.hpp:187
double sigma() const
Definition cma_es.hpp:188
Definition acquisition_functions.hpp:16
std::vector< std::vector< double > > AskGivenSamples(const CMAESState &state, const std::vector< std::vector< double > > &z_samples)
Pure core: decodes an explicit set of standard-normal sample vectors into offspring points,...
Definition cma_es.hpp:47
CMAESState TellGivenSamples(const CMAESState &state, const std::vector< std::vector< double > > &z_samples, const std::vector< std::vector< double > > &offspring, const std::vector< double > &fitness, double step_size_learning_rate, double scale_learning_rate)
Pure core: given the offspring AskGivenSamples produced (same order), their maximization-convention f...
Definition cma_es.hpp:74
This algorithm's full adaptive state: the search mean, the global step size, and each dimension's own...
Definition cma_es.hpp:36
std::vector< double > variances
Definition cma_es.hpp:39
double sigma
Definition cma_es.hpp:38
std::vector< double > mean
Definition cma_es.hpp:37