R/TerminatorGenerationPerfReached.R
mlr_terminators_genperfreached.RdTerminator that terminates when a value, aggregated over generations, reaches a target value.
The user-supplied fitness_aggregator function is called whenever the archive of evaluated configurations contains a new generation.
The function is supplied with the fitness values, and optionally other data, of all individuals that are alive at that point
(include_previous_generations = FALSE) or at any point (include_previous_generations = TRUE).
Its result is saved inside the $data_extra field of the Archive object.
Termination is then signaled when the aggregated value meets or exceeds level.
The mies_aggregate_single_generation() function is used, see the documentation there for the functioning of fitness_aggregator.
The fitness_aggregator functions used for termination must return a scalar value or NULL, if a generation should be ignored.
The value returned by fitness_aggregator should be increasing for better performance, even if the underlying objective is being minimized.
Multi-fidelity optimization can introduce a few edge-cases because the individuals inside the generation(s) being aggregated may have been evaluated with different fidelity values, which can give biased results.
When OptimizerMies is constructed with multi_fidelity set to TRUE, it typically evaluates some configurations multiple times,
at first with a lower fidelity, followed by an evaluation at "full" fidelity.
fitness_aggregator will only be called for generations containing entirely full-fidelity-evaluations will be aggregated.
This is achieved by caching aggregated fitness values in the $data_extra field of the Archive and only ever calling
fitness_aggregator for a generation that does not have a cached value. Since mies_step_fidelity() will
count low-fidelity evaluations as part of the "previous" generation, fitness_aggregator will not see them.
Note, however that if fitness_aggregator returns NULL, it will be called again should a second evaluation occur in the same generation,
since NULL is not cached and instead treated as absent.
It is possible for fitness_aggregator to see fitness values that were evaluated with different fidelities when using OptimizerMies,
and
fidelity_monotonic is set to TRUE and fidelity decreases (unlikely setup), or
if fidelity_current_gen_only is set to FALSE (advanced usage), or
The value returned by the fidelity configuration parameter (not fidelity_offspring) changes over the course of optimization and
include_previous_generations of TerminatorGenerationStagnation is set to TRUE.
(1) and (2) only need consideration in advanced scenarios, but (3) may be a common, e.g. when doing multi-fidelity optimization
and stopping on reaching an overall dominated hypervolume target. In this case, it may be necessary to inspect the budget value given to fitness_aggregator
and to remove all individuals evaluated with a different than the current fidelity.
When using a custom-written optimization loop, case (1) relates to fidelity_monotonic argument of mies_step_fidelity() and mies_init_population(),
and case (2) relates to the current_gen_only argument of mies_step_fidelity() and the fidelity_new_individuals_only argument of mies_init_population().
Case (3) relates to changing the fidelity given to mies_step_fidelity() if that function is used, or to changing the fidelity given to mies_evaluate_offspring() if
mies_step_fidelity() is not used.
This Terminator can be created with the short access form trm() (trms() to get a list),
or through the dictionary mlr_terminators in the following way:
fitness_aggregator :: function
Aggregation function, called with information about alive individuals of each generation.
This argument is passed to mies_aggregate_single_generation(), see there for more details.
The aggregated values returned by fitness_aggregator should be maximized, so a larger value must be returned to indicate improvement in a generation,
even if an underlying objective is being minimized. The return value must be a scalar numeric(1).
include_previous_generations :: logical(1)
Whether to aggregate over all individuals that were evaluated (TRUE), or only the individuals alive in the current generation (FALSE).
If multi-fidelity optimization is being performed and individuals were re-evaluated with a different fidelity, their x_id will be the same and only
the last fidelity-reevaluation will be given to fitness_aggregator. However, individuals from different generations may still have been evaluated
with different fidelity and it may be necessary to inspect the budget value given to fitness_aggregator if include_previous_generations is TRUE in a
multi-fidelity-setting. See the "Multi-Fidelity Optimization" section for more.
level :: numeric(1)
Minimum aggregated value for which to terminate.
bbotk::Terminator -> TerminatorGenerationPerfReached
is_terminated()Is TRUE if when the termination criterion is matched, FALSE otherwise.
archiveArchive
Archive to check.
set.seed(1)
library("bbotk")
lgr::threshold("warn")
# Terminate when hypervolume with nadir `c(0, 0, ...)`
# does not improve for 3 generations by at least 0.1:
tg <- trm("genperfreached",
fitness_aggregator = function(fitnesses) domhv(fitnesses),
include_previous_generations = TRUE,
level = 1
)
set.seed(1)
objective <- ObjectiveRFun$new(
fun = function(xs) {
list(y1 = xs$x1, y2 = xs$x2)
},
domain = ps(x1 = p_dbl(0, 1), x2 = p_dbl(-1, 0)),
codomain = ps(y1 = p_dbl(0, 1, tags = "maximize"),
y2 = p_dbl(-1, 0, tags = "minimize"))
)
oi <- OptimInstanceMultiCrit$new(objective, terminator = tg)
op <- opt("mies",
lambda = 4, mu = 4,
mutator = mut("gauss", sdev = 0.1),
recombinator = rec("xounif"),
parent_selector = sel("random"),
survival_selector = sel("best", scl("hypervolume"))
)
op$optimize(oi)
#> x1 x2 x_domain y1 y2
#> <num> <num> <list> <num> <num>
#> 1: 1 -1 <list[2]> 1 -1
# the observed aggregated values:
oi$archive$data_extra$TerminatorGenerationPerfReached
#> 1 2 3 4 5 6 7 8
#> 0.4299653 0.4900229 0.6562904 0.8724430 0.8986106 0.9286387 0.9722402 0.9727591
#> 9
#> 1.0000000
# ... or as calculated by mies_generation_apply
mies_generation_apply(oi$archive, function(fitnesses) {
domhv(fitnesses)
}, include_previous_generations = TRUE)
#> dob V1
#> <num> <num>
#> 1: 1 0.4299653
#> 2: 2 0.4900229
#> 3: 3 0.6562904
#> 4: 4 0.8724430
#> 5: 5 0.8986106
#> 6: 6 0.9286387
#> 7: 7 0.9722402
#> 8: 8 0.9727591
#> 9: 9 1.0000000
#' @export