Ultra 0.9.4
Loading...
Searching...
No Matches
gp/src/evaluator.tcc
Go to the documentation of this file.
1
12
13#if !defined(ULTRA_SRC_EVALUATOR_H)
14# error "Don't include this file directly, include the specific .h instead"
15#endif
16
17#if !defined(ULTRA_SRC_EVALUATOR_TCC)
18#define ULTRA_SRC_EVALUATOR_TCC
19
25template<EvaluationDataset D>
26evaluator<D>::evaluator(D &d) noexcept : dat_(&d)
27{
28}
29
38template<EvaluationDataset D>
39const auto &evaluator<D>::data() const noexcept
40{
41 if constexpr (is_multi_dataset_v<D>)
42 return dat_->selected();
43 else
44 return *dat_;
45}
46
57template<EvaluationDataset D>
58bool evaluator<D>::set_parallel_evaluation(unsigned helpers,
59 std::size_t min_samples) noexcept
60{
61 helpers_ = helpers;
62 min_samples_ = !min_samples ? default_min_samples_per_thread : min_samples;
63 return true;
64}
65
71template<EvaluationDataset D>
72unsigned evaluator<D>::parallel_helpers() const noexcept
73{
74 return helpers_;
75}
76
83template<EvaluationDataset D>
84std::size_t evaluator<D>::min_samples_per_thread() const noexcept
85{
86 return min_samples_;
87}
88
94template<Individual P, class F, class D, aggregation_mode A, evaluation_mode M>
95requires ExampleEvaluator<F, D, P>
96aggregate_evaluator<P, F, D, A, M>::aggregate_evaluator(D &d) noexcept
97 : evaluator<D>(d)
98{
99}
100
111template<Individual P, class F, class D, aggregation_mode A, evaluation_mode M>
112requires ExampleEvaluator<F, D, P>
113bool aggregate_evaluator<P, F, D, A, M>::set_parallel_evaluation(
114 unsigned n, std::size_t min_samples) noexcept
115 requires parallel_safe_example_evaluator<F>
116{
117 return evaluator<D>::set_parallel_evaluation(n, min_samples);
118}
119
126template<Individual P, class F, class D, aggregation_mode A, evaluation_mode M>
127requires ExampleEvaluator<F, D, P>
128double aggregate_evaluator<P, F, D, A, M>::operator()(const P &prg) const
129{
130 return eval_impl(prg, 1);
131}
132
149template<Individual P, class F, class D, aggregation_mode A, evaluation_mode M>
150requires ExampleEvaluator<F, D, P>
151double aggregate_evaluator<P, F, D, A, M>::fast(const P &prg) const
152{
153 const auto n(std::ranges::distance(this->data()));
154 return eval_impl(prg, n >= fast_min_examples ? fast_step : 1);
155}
156
169template<Individual P, class F, class D, aggregation_mode A, evaluation_mode M>
170requires ExampleEvaluator<F, D, P>
171auto aggregate_evaluator<P, F, D, A, M>::predictor(const P &prg) const
173{
174 return reg_oracle<P>(prg);
175}
176
196template<Individual P, class F, class D, aggregation_mode A, evaluation_mode M>
197requires ExampleEvaluator<F, D, P>
198double aggregate_evaluator<P, F, D, A, M>::eval_impl(const P &prg,
199 std::ptrdiff_t step) const
200{
201 const auto &dat(this->data());
202 const auto examples(std::ranges::distance(dat));
203 Expects(0 < step && step <= examples);
204
205 const auto end(std::end(dat));
206 const auto sampled_count(static_cast<std::size_t>((examples - 1) / step + 1));
207
208 std::size_t workers(1);
209 if constexpr (std::ranges::forward_range<decltype(dat)>
210 && parallel_safe_example_evaluator<F>)
211 if (this->parallel_helpers())
212 workers = std::clamp(sampled_count / this->min_samples_per_thread(),
213 1uz, this->parallel_helpers() + 1uz);
214
215 const auto stride(step * static_cast<std::ptrdiff_t>(workers));
216
217 struct partial
218 {
219 double mean {0.0};
220 std::size_t sampled {0};
221 std::size_t valid {0};
222
223 void merge(const partial &part)
224 {
225 sampled += part.sampled;
226 if (!part.valid)
227 return;
228
229 if (valid)
230 mean += (part.mean - mean)
231 * (static_cast<double>(part.valid)
232 / static_cast<double>(valid + part.valid));
233 else
234 mean = part.mean;
235
236 valid += part.valid;
237 }
238 };
239
240 const auto evaluate_strip([&](std::size_t strip)
241 {
242 const F f(prg); // each strip owns its oracle
243 partial result;
244
245 for (auto it(std::ranges::next(std::begin(dat),
246 static_cast<std::ptrdiff_t>(strip) * step, end));
247 it != end;
248 std::ranges::advance(it, stride, end))
249 {
250 ++result.sampled;
251
252 if (const std::optional<double> value(f(*it)); value)
253 result.mean += (*value - result.mean)
254 / static_cast<double>(++result.valid);
255 }
256
257 return result;
258 });
259
260 std::vector<std::future<partial>> futures;
261 futures.reserve(workers - 1);
262
263 for (std::size_t strip(1); strip < workers; ++strip)
264 futures.push_back(std::async(std::launch::async, evaluate_strip, strip));
265
266 partial total(evaluate_strip(0)); // caller handles the first strip
267
268 for (auto &future : futures)
269 total.merge(future.get());
270
271 Ensures(0 < total.sampled
272 && total.sampled <= static_cast<std::size_t>(examples));
273
274 if (!total.valid)
275 return std::numeric_limits<double>::lowest();
276
277 double acc(total.mean);
278
279 if (total.valid < total.sampled)
280 {
281 const auto missing_per_valid(static_cast<double>(total.sampled - total.valid)
282 / static_cast<double>(total.valid));
283
284 if constexpr (M == evaluation_mode::error)
285 acc += missing_per_valid * (acc + 1.0);
286 else
287 acc -= missing_per_valid * (std::fabs(acc) + 1.0);
288 }
289
290 if constexpr (A == aggregation_mode::sum)
291 acc *= static_cast<double>(examples);
292
293 if constexpr (M == evaluation_mode::error)
294 acc = -acc;
295
296 return acc;
297}
298
304template<Individual P>
305mae_error_functor<P>::mae_error_functor(const P &prg) : oracle_(prg)
306{
307}
308
314template<Individual P>
315std::optional<double>
316mae_error_functor<P>::operator()(const example &example) const
317{
318 if (const auto foreseen(oracle_(example.input)); has_value(foreseen))
319 return std::fabs(lexical_cast<D_DOUBLE>(foreseen)
320 - label_as<D_DOUBLE>(example));
321
322 return std::nullopt;
323}
324
330template<Individual P>
331rmae_error_functor<P>::rmae_error_functor(const P &prg) : oracle_(prg)
332{
333}
334
341template<Individual P>
342std::optional<double>
343rmae_error_functor<P>::operator()(const example &example) const
344{
345 if (const auto foreseen(oracle_(example.input)); has_value(foreseen))
346 {
347 const auto approx(lexical_cast<D_DOUBLE>(foreseen));
348 const auto target(label_as<D_DOUBLE>(example));
349
350 const auto delta(std::fabs(target - approx));
351
352 // Avoid `0 / 0` when both values are zero (uses an inequality, which never
353 // triggers a warning on any compiler. Mathematically identical to
354 // `delta==0.0` because `delta` is non-negative).
355 if (delta <= 0.0)
356 return 0.0;
357
358 return delta / (std::fabs(approx) + std::fabs(target));
359 // Some alternatives for the error:
360 // * delta / std::max(approx, target)
361 // * delta / std::fabs(target)
362 //
363 // The chosen formula seems numerically more stable and gives a result
364 // in a limited range of values.
365 }
366
367 return std::nullopt;
368}
369
375template<Individual P>
376mse_error_functor<P>::mse_error_functor(const P &prg) : oracle_(prg)
377{
378}
379
386template<Individual P>
387std::optional<double>
388mse_error_functor<P>::operator()(const example &example) const
389{
390 if (const auto foreseen(oracle_(example.input)); has_value(foreseen))
391 {
392 const double err(lexical_cast<D_DOUBLE>(foreseen)
393 - label_as<D_DOUBLE>(example));
394 return err * err;
395 }
396
397 return std::nullopt;
398}
399
405template<Individual P>
406count_error_functor<P>::count_error_functor(const P &prg) : oracle_(prg)
407{
408}
409
416template<Individual P>
417double count_error_functor<P>::operator()(const example &example) const
418{
419 const auto foreseen(oracle_(example.input));
420
421 if (!has_value(foreseen))
422 return 1.0;
423
424 if (!issmall(lexical_cast<D_DOUBLE>(foreseen) - label_as<D_DOUBLE>(example)))
425 return 1.0;
426
427 return 0.0;
428}
429
435template<Individual P>
436gaussian_evaluator<P>::gaussian_evaluator(multi_dataset<dataframe> &d)
437 : evaluator(d)
438{
439}
440
447template<Individual P>
448double gaussian_evaluator<P>::operator()(const P &prg) const
449{
450 const auto &dat(this->data());
451 Expects(dat.classes() >= 2);
452 basic_gaussian_oracle<P, false> oracle(prg, dat);
453
454 double d(0.0);
455 for (auto &example : dat)
456 if (const auto res(oracle.tag(example.input)); res.label == label(example))
457 {
458 const auto scale(static_cast<double>(dat.classes() - 1));
459 // Note:
460 // * `(1.0 - res.sureness)` is the sum of the errors;
461 // * `(res.sureness - 1.0)` is the opposite (standardized fitness);
462 // * `(res.sureness - 1.0) / scale` is the opposite of the average error.
463 d += (res.sureness - 1.0) / scale;
464 }
465 else
466 {
467 // Note:
468 // * the maximum single class error is `1.0`;
469 // * the maximum average class error is `1.0 / dat.classes()`;
470 // So `-1.0` is like to say that we have a complete failure.
471 d -= 1.0;
472 }
473
474 return d;
475}
476
483template<Individual P>
484auto gaussian_evaluator<P>::predictor(const P &prg) const
485{
486 return gaussian_oracle<P>(prg, this->data());
487}
488
494template<Individual P>
495binary_evaluator<P>::binary_evaluator(multi_dataset<dataframe> &d)
496 : evaluator(d)
497{
498}
499
508template<Individual P>
509double binary_evaluator<P>::operator()(const P &prg) const
510{
511 const auto &dat(this->data());
512 Expects(dat.classes() == 2);
513 basic_binary_oracle<P, false> oracle(prg, dat);
514
515 double err(0.0);
516
517 for (auto &example : dat)
518 if (const auto res(oracle.tag(example.input)); res.label != label(example))
519 err += 1.0 + res.sureness;
520
521 return -err;
522}
523
530template<Individual P>
531auto binary_evaluator<P>::predictor(const P &prg) const
532{
533 return binary_oracle<P>(prg, this->data());
534}
535
536#endif // include guard
class_t label(const example &e)
Definition dataframe.cc:87
constexpr bool has_regression_predictor_v
Convenience variable template for has_regression_predictor.
Definition evaluator_internal.tcc:54
bool has_value(const value_t &v) noexcept
Definition value.cc:31