pulsatrix
Loading...
Searching...
No Matches
tpe.hpp
Go to the documentation of this file.
1
21#pragma once
22
23#include <algorithm>
24#include <cmath>
25#include <limits>
26#include <stdexcept>
27#include <string>
28#include <vector>
29
32#include "pulsatrix/trial.hpp"
33
34namespace pulsatrix {
35
41inline double GaussianKdeDensity(const std::vector<double>& observations, double bandwidth, double x) {
42 if (observations.empty()) {
43 throw std::invalid_argument("GaussianKdeDensity: observations must not be empty");
44 }
45 if (bandwidth <= 0.0) {
46 throw std::invalid_argument("GaussianKdeDensity: bandwidth must be positive");
47 }
48 constexpr double kInvSqrt2Pi = 0.3989422804014327;
49 double sum = 0.0;
50 for (double obs : observations) {
51 double z = (x - obs) / bandwidth;
52 sum += kInvSqrt2Pi * std::exp(-0.5 * z * z) / bandwidth;
53 }
54 return sum / static_cast<double>(observations.size());
55}
56
62inline double CategoricalDensity(const std::vector<std::string>& observations, size_t num_categories,
63 const std::string& category) {
64 if (num_categories == 0) {
65 throw std::invalid_argument("CategoricalDensity: num_categories must be positive");
66 }
67 size_t count = static_cast<size_t>(std::count(observations.begin(), observations.end(), category));
68 return (static_cast<double>(count) + 1.0) /
69 (static_cast<double>(observations.size()) + static_cast<double>(num_categories));
70}
71
87inline double LogDensityRatio(const SearchSpace& space, const Configuration& candidate,
88 const std::vector<Configuration>& good_configs,
89 const std::vector<Configuration>& bad_configs) {
90 if (good_configs.empty() || bad_configs.empty()) {
91 throw std::invalid_argument("LogDensityRatio: good_configs and bad_configs must both be non-empty");
92 }
93
94 double log_ratio = 0.0;
95 for (const auto& spec : space.parameters()) {
96 if (spec.kind == ParameterKind::Categorical) {
97 std::vector<std::string> good_values, bad_values;
98 for (const auto& c : good_configs) good_values.push_back(std::get<std::string>(c.at(spec.name)));
99 for (const auto& c : bad_configs) bad_values.push_back(std::get<std::string>(c.at(spec.name)));
100 std::string candidate_value = std::get<std::string>(candidate.at(spec.name));
101
102 double good_density = CategoricalDensity(good_values, spec.categories.size(), candidate_value);
103 double bad_density = CategoricalDensity(bad_values, spec.categories.size(), candidate_value);
104 log_ratio += std::log(good_density) - std::log(bad_density);
105 continue;
106 }
107
108 // Continuous, LogUniform, Integer -- all evaluated as a 1D KDE, LogUniform taken in
109 // log-space, Integer floored at bandwidth 1.0.
110 std::vector<double> good_values, bad_values;
111 double candidate_value;
112 double bandwidth;
113 if (spec.kind == ParameterKind::LogUniform) {
114 for (const auto& c : good_configs) good_values.push_back(std::log(std::get<double>(c.at(spec.name))));
115 for (const auto& c : bad_configs) bad_values.push_back(std::log(std::get<double>(c.at(spec.name))));
116 candidate_value = std::log(std::get<double>(candidate.at(spec.name)));
117 bandwidth = 0.2 * (std::log(spec.upper) - std::log(spec.lower));
118 } else if (spec.kind == ParameterKind::Integer) {
119 for (const auto& c : good_configs) good_values.push_back(static_cast<double>(std::get<int64_t>(c.at(spec.name))));
120 for (const auto& c : bad_configs) bad_values.push_back(static_cast<double>(std::get<int64_t>(c.at(spec.name))));
121 candidate_value = static_cast<double>(std::get<int64_t>(candidate.at(spec.name)));
122 bandwidth = std::max(1.0, 0.2 * (spec.upper - spec.lower));
123 } else {
124 for (const auto& c : good_configs) good_values.push_back(std::get<double>(c.at(spec.name)));
125 for (const auto& c : bad_configs) bad_values.push_back(std::get<double>(c.at(spec.name)));
126 candidate_value = std::get<double>(candidate.at(spec.name));
127 bandwidth = 0.2 * (spec.upper - spec.lower);
128 }
129
130 double good_density = GaussianKdeDensity(good_values, bandwidth, candidate_value);
131 double bad_density = GaussianKdeDensity(bad_values, bandwidth, candidate_value);
132 log_ratio += std::log(good_density) - std::log(bad_density);
133 }
134 return log_ratio;
135}
136
149template <typename ObjectiveFn, typename RNG>
150std::vector<Trial> RunTPELoop(const SearchSpace& space, ObjectiveFn objective_fn,
151 size_t num_initial_random, size_t num_iterations, double gamma,
152 size_t num_candidates, RNG& rng) {
153 if (num_initial_random < 2) {
154 throw std::invalid_argument("RunTPELoop: num_initial_random must be >= 2");
155 }
156 if (gamma <= 0.0 || gamma >= 1.0) {
157 throw std::invalid_argument("RunTPELoop: gamma must be in (0, 1)");
158 }
159
160 std::vector<Trial> trials;
161
162 auto evaluate = [&](const Configuration& config) {
163 Trial trial(config);
164 double value = objective_fn(config);
165 trial.RecordMetric("objective", value, 0);
166 trials.push_back(std::move(trial));
167 };
168
169 for (size_t i = 0; i < num_initial_random; ++i) {
170 evaluate(RandomSample(space, rng));
171 }
172
173 for (size_t iter = 0; iter < num_iterations; ++iter) {
174 std::vector<size_t> order(trials.size());
175 for (size_t i = 0; i < order.size(); ++i) order[i] = i;
176 std::sort(order.begin(), order.end(), [&](size_t a, size_t b) {
177 return *trials[a].LatestMetric("objective") > *trials[b].LatestMetric("objective");
178 });
179
180 size_t num_good = std::max<size_t>(1, static_cast<size_t>(gamma * static_cast<double>(order.size())));
181 num_good = std::min(num_good, order.size() - 1);
182
183 std::vector<Configuration> good_configs, bad_configs;
184 for (size_t i = 0; i < num_good; ++i) good_configs.push_back(trials[order[i]].configuration());
185 for (size_t i = num_good; i < order.size(); ++i) bad_configs.push_back(trials[order[i]].configuration());
186
187 Configuration best_candidate;
188 double best_score = -std::numeric_limits<double>::infinity();
189 for (size_t c = 0; c < num_candidates; ++c) {
190 Configuration candidate = RandomSample(space, rng);
191 double score = LogDensityRatio(space, candidate, good_configs, bad_configs);
192 if (score > best_score) {
193 best_score = score;
194 best_candidate = candidate;
195 }
196 }
197 evaluate(best_candidate);
198 }
199
200 return trials;
201}
202
203} // namespace pulsatrix
Describes a hyperparameter search space as an ordered list of named, typed parameters....
Definition search_space.hpp:54
const std::vector< ParameterSpec > & parameters() const
Every parameter, in the order added.
Definition search_space.hpp:116
A configuration paired with the metric history observed while evaluating it (instantiate a network un...
Definition trial.hpp:31
void RecordMetric(std::string tag, double value, int step)
Records one metric observation.
Definition trial.hpp:39
Grid search and random search over a SearchSpace: the two simplest, baseline hyperparameter-optimizat...
Definition acquisition_functions.hpp:16
std::vector< Trial > RunTPELoop(const SearchSpace &space, ObjectiveFn objective_fn, size_t num_initial_random, size_t num_iterations, double gamma, size_t num_candidates, RNG &rng)
Runs TPE: num_initial_random uniformly-random trials (RandomSample), then num_iterations trials each ...
Definition tpe.hpp:150
double LogDensityRatio(const SearchSpace &space, const Configuration &candidate, const std::vector< Configuration > &good_configs, const std::vector< Configuration > &bad_configs)
log(l(candidate)) - log(g(candidate)): the TPE scoring function, summed independently over every para...
Definition tpe.hpp:87
std::map< std::string, ConfigValue > Configuration
A concrete hyperparameter configuration: parameter name -> concrete value.
Definition search_space.hpp:46
Configuration RandomSample(const SearchSpace &space, RNG &rng)
Draws one configuration uniformly at random from space: Continuous parameters uniform over [lower,...
Definition hpo_sampling.hpp:27
double GaussianKdeDensity(const std::vector< double > &observations, double bandwidth, double x)
Fixed-bandwidth Gaussian KDE: density(x) = mean over every observation o of N(x; o,...
Definition tpe.hpp:41
double CategoricalDensity(const std::vector< std::string > &observations, size_t num_categories, const std::string &category)
Laplace(add-one)-smoothed empirical probability of category among observations.
Definition tpe.hpp:62
Typed hyperparameter search-space description: named parameters, each continuous, log-uniform,...
A single hyperparameter-optimization trial: the configuration tried, plus every metric value recorded...