R/TerminatorGenerationPerfReached.R
mlr_terminators_genperfreached.Rd
Terminator
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.
archive
Archive
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