예제 #1
0
    def _get_instances_to_run(
        self,
        challenger: Configuration,
        incumbent: Configuration,
        N: int,
        run_history: RunHistory,
    ) -> typing.Tuple[typing.List[InstSeedBudgetKey], float]:
        """
        Returns the minimum list of <instance, seed> pairs to run the challenger on
        before comparing it with the incumbent

        Parameters
        ----------
        incumbent: Configuration
            incumbent configuration
        challenger: Configuration
            promising configuration that is presently being evaluated
        run_history: RunHistory
            Stores all runs we ran so far
        N: int
            number of <instance, seed> pairs to select

        Returns
        -------
        typing.List[InstSeedBudgetKey]
            list of <instance, seed, budget> tuples to run
        float
            total (runtime) cost of running the incumbent on the instances (used for adaptive capping while racing)
        """
        # get next instances left for the challenger
        # Line 8
        inc_inst_seeds = set(
            run_history.get_runs_for_config(incumbent,
                                            only_max_observed_budget=True))
        chall_inst_seeds = set(
            run_history.get_runs_for_config(challenger,
                                            only_max_observed_budget=True))
        # Line 10
        missing_runs = sorted(inc_inst_seeds - chall_inst_seeds)

        # Line 11
        self.rs.shuffle(missing_runs)
        if N < 0:
            raise ValueError(
                'Argument N must not be smaller than zero, but is %s' % str(N))
        to_run = missing_runs[:min(N, len(missing_runs))]
        missing_runs = missing_runs[min(N, len(missing_runs)):]

        # for adaptive capping
        # because of efficiency computed here
        inst_seed_pairs = list(inc_inst_seeds - set(missing_runs))
        # cost used by incumbent for going over all runs in inst_seed_pairs
        inc_sum_cost = run_history.sum_cost(
            config=incumbent,
            instance_seed_budget_keys=inst_seed_pairs,
        )

        return to_run, inc_sum_cost
예제 #2
0
    def _adapt_cutoff(self,
                      challenger: Configuration,
                      run_history: RunHistory,
                      inc_sum_cost: float) -> float:
        """Adaptive capping:
        Compute cutoff based on time so far used for incumbent
        and reduce cutoff for next run of challenger accordingly

        !Only applicable if self.run_obj_time

        !runs on incumbent should be superset of the runs performed for the
         challenger

        Parameters
        ----------
        challenger : Configuration
            Configuration which challenges incumbent
        run_history : smac.runhistory.runhistory.RunHistory
            Stores all runs we ran so far
        inc_sum_cost: float
            Sum of runtimes of all incumbent runs

        Returns
        -------
        cutoff: float
            Adapted cutoff
        """

        if not self.run_obj_time:
            raise ValueError('This method only works when the run objective is quality')

        curr_cutoff = self.cutoff if self.cutoff is not None else np.inf

        # cost used by challenger for going over all its runs
        # should be subset of runs of incumbent (not checked for efficiency
        # reasons)
        chall_inst_seeds = run_history.get_runs_for_config(challenger, only_max_observed_budget=True)
        chal_sum_cost = run_history.sum_cost(
            config=challenger,
            instance_seed_budget_keys=chall_inst_seeds,
        )
        cutoff = min(curr_cutoff,
                     inc_sum_cost * self.adaptive_capping_slackfactor - chal_sum_cost
                     )
        return cutoff
예제 #3
0
class TestAbstractIntensifier(unittest.TestCase):
    def setUp(self):
        unittest.TestCase.setUp(self)

        self.rh = RunHistory()
        self.cs = get_config_space()
        self.config1 = Configuration(self.cs, values={'a': 0, 'b': 100})
        self.config2 = Configuration(self.cs, values={'a': 100, 'b': 0})
        self.config3 = Configuration(self.cs, values={'a': 100, 'b': 100})

        self.scen = Scenario({
            "cutoff_time": 2,
            'cs': self.cs,
            "run_obj": 'runtime',
            "output_dir": ''
        })
        self.stats = Stats(scenario=self.scen)
        self.stats.start_timing()

        self.logger = logging.getLogger(self.__module__ + "." +
                                        self.__class__.__name__)

    def test_get_next_challenger(self):
        """
            test get_next_challenger - pick from list/chooser
        """
        intensifier = AbstractRacer(tae_runner=None,
                                    stats=self.stats,
                                    traj_logger=None,
                                    rng=np.random.RandomState(12345),
                                    deterministic=True,
                                    run_obj_time=False,
                                    cutoff=1,
                                    instances=[1])

        # Error when nothing to choose from
        with self.assertRaisesRegex(ValueError,
                                    "No configurations/chooser provided"):
            intensifier.get_next_challenger(challengers=None,
                                            chooser=None,
                                            run_history=self.rh)

        # next challenger from a list
        config, _ = intensifier.get_next_challenger(
            challengers=[self.config1, self.config2],
            chooser=None,
            run_history=self.rh)
        self.assertEqual(config, self.config1)

        config, _ = intensifier.get_next_challenger(
            challengers=[self.config2, self.config3],
            chooser=None,
            run_history=self.rh)
        self.assertEqual(config, self.config2)

        # next challenger from a chooser
        intensifier = AbstractRacer(tae_runner=None,
                                    stats=self.stats,
                                    traj_logger=None,
                                    rng=np.random.RandomState(12345),
                                    deterministic=True,
                                    run_obj_time=False,
                                    cutoff=1,
                                    instances=[1])
        chooser = SMAC4AC(self.scen, rng=1).solver.epm_chooser

        config, _ = intensifier.get_next_challenger(challengers=None,
                                                    chooser=chooser,
                                                    run_history=self.rh)
        self.assertEqual(len(list(config.get_dictionary().values())), 2)
        self.assertTrue(24 in config.get_dictionary().values())
        self.assertTrue(68 in config.get_dictionary().values())

        config, _ = intensifier.get_next_challenger(challengers=None,
                                                    chooser=chooser,
                                                    run_history=self.rh)
        self.assertEqual(len(list(config.get_dictionary().values())), 2)
        self.assertTrue(95 in config.get_dictionary().values())
        self.assertTrue(38 in config.get_dictionary().values())

    def test_get_next_challenger_repeat(self):
        """
            test get_next_challenger - repeat configurations
        """
        intensifier = AbstractRacer(tae_runner=None,
                                    stats=self.stats,
                                    traj_logger=None,
                                    rng=np.random.RandomState(12345),
                                    deterministic=True,
                                    run_obj_time=False,
                                    cutoff=1,
                                    instances=[1])

        # should not repeat configurations
        self.rh.add(self.config1, 1, 1, StatusType.SUCCESS)
        config, _ = intensifier.get_next_challenger(
            challengers=[self.config1, self.config2],
            chooser=None,
            run_history=self.rh,
            repeat_configs=False)

        self.assertEqual(config, self.config2)

        # should repeat configurations
        config, _ = intensifier.get_next_challenger(
            challengers=[self.config1, self.config2],
            chooser=None,
            run_history=self.rh,
            repeat_configs=True)

        self.assertEqual(config, self.config1)

    def test_compare_configs_no_joint_set(self):
        intensifier = AbstractRacer(tae_runner=None,
                                    stats=self.stats,
                                    traj_logger=TrajLogger(output_dir=None,
                                                           stats=self.stats),
                                    rng=None,
                                    instances=[1])

        for i in range(2):
            self.rh.add(config=self.config1,
                        cost=2,
                        time=2,
                        status=StatusType.SUCCESS,
                        instance_id=1,
                        seed=i,
                        additional_info=None)

        for i in range(2, 5):
            self.rh.add(config=self.config2,
                        cost=1,
                        time=1,
                        status=StatusType.SUCCESS,
                        instance_id=1,
                        seed=i,
                        additional_info=None)

        # The sets for the incumbent are completely disjoint.
        conf = intensifier._compare_configs(incumbent=self.config1,
                                            challenger=self.config2,
                                            run_history=self.rh)
        self.assertIsNone(conf)

        # The incumbent has still one instance-seed pair left on which the
        # challenger was not run yet.
        self.rh.add(config=self.config2,
                    cost=1,
                    time=1,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=1,
                    additional_info=None)
        conf = intensifier._compare_configs(incumbent=self.config1,
                                            challenger=self.config2,
                                            run_history=self.rh)
        self.assertIsNone(conf)

    def test_compare_configs_chall(self):
        """
            challenger is better
        """
        intensifier = AbstractRacer(tae_runner=None,
                                    stats=self.stats,
                                    traj_logger=TrajLogger(output_dir=None,
                                                           stats=self.stats),
                                    rng=None,
                                    instances=[1])

        self.rh.add(config=self.config1,
                    cost=1,
                    time=2,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=None,
                    additional_info=None)

        self.rh.add(config=self.config2,
                    cost=0,
                    time=1,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=None,
                    additional_info=None)

        conf = intensifier._compare_configs(incumbent=self.config1,
                                            challenger=self.config2,
                                            run_history=self.rh)

        # challenger has enough runs and is better
        self.assertEqual(conf, self.config2, "conf: %s" % (conf))

    def test_compare_configs_inc(self):
        """
            incumbent is better
        """
        intensifier = AbstractRacer(tae_runner=None,
                                    stats=self.stats,
                                    traj_logger=TrajLogger(output_dir=None,
                                                           stats=self.stats),
                                    rng=None,
                                    instances=[1])

        self.rh.add(config=self.config1,
                    cost=1,
                    time=1,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=None,
                    additional_info=None)

        self.rh.add(config=self.config2,
                    cost=2,
                    time=2,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=None,
                    additional_info=None)

        conf = intensifier._compare_configs(incumbent=self.config1,
                                            challenger=self.config2,
                                            run_history=self.rh)

        # challenger worse than inc
        self.assertEqual(conf, self.config1, "conf: %s" % (conf))

    def test_compare_configs_unknow(self):
        """
            challenger is better but has less runs;
            -> no decision (None)
        """
        intensifier = AbstractRacer(tae_runner=None,
                                    stats=self.stats,
                                    traj_logger=TrajLogger(output_dir=None,
                                                           stats=self.stats),
                                    rng=None,
                                    instances=[1])

        self.rh.add(config=self.config1,
                    cost=1,
                    time=1,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=None,
                    additional_info=None)

        self.rh.add(config=self.config1,
                    cost=1,
                    time=2,
                    status=StatusType.SUCCESS,
                    instance_id=2,
                    seed=None,
                    additional_info=None)

        self.rh.add(config=self.config1,
                    cost=1,
                    time=1,
                    status=StatusType.SUCCESS,
                    instance_id=2,
                    seed=None,
                    additional_info=None)

        conf = intensifier._compare_configs(incumbent=self.config1,
                                            challenger=self.config2,
                                            run_history=self.rh)

        # challenger worse than inc
        self.assertIsNone(conf, "conf: %s" % (conf))

    def test_adaptive_capping(self):
        """
            test _adapt_cutoff()
        """
        intensifier = AbstractRacer(tae_runner=None,
                                    stats=self.stats,
                                    traj_logger=TrajLogger(output_dir=None,
                                                           stats=self.stats),
                                    rng=np.random.RandomState(12345),
                                    instances=list(range(5)),
                                    deterministic=False)

        for i in range(5):
            self.rh.add(config=self.config1,
                        cost=i + 1,
                        time=i + 1,
                        status=StatusType.SUCCESS,
                        instance_id=i,
                        seed=i,
                        additional_info=None)
        for i in range(3):
            self.rh.add(config=self.config2,
                        cost=i + 1,
                        time=i + 1,
                        status=StatusType.SUCCESS,
                        instance_id=i,
                        seed=i,
                        additional_info=None)

        inst_seed_pairs = self.rh.get_runs_for_config(
            self.config1, only_max_observed_budget=True)
        # cost used by incumbent for going over all runs in inst_seed_pairs
        inc_sum_cost = self.rh.sum_cost(
            config=self.config1, instance_seed_budget_keys=inst_seed_pairs)

        cutoff = intensifier._adapt_cutoff(challenger=self.config2,
                                           run_history=self.rh,
                                           inc_sum_cost=inc_sum_cost)
        # 15*1.2 - 6
        self.assertEqual(cutoff, 12)

        intensifier.cutoff = 5

        cutoff = intensifier._adapt_cutoff(challenger=self.config2,
                                           run_history=self.rh,
                                           inc_sum_cost=inc_sum_cost)
        # scenario cutoff
        self.assertEqual(cutoff, 5)
예제 #4
0
class TestAbstractRacer(unittest.TestCase):
    def setUp(self):
        unittest.TestCase.setUp(self)

        self.rh = RunHistory()
        self.cs = get_config_space()
        self.config1 = Configuration(self.cs, values={'a': 0, 'b': 100})
        self.config2 = Configuration(self.cs, values={'a': 100, 'b': 0})
        self.config3 = Configuration(self.cs, values={'a': 100, 'b': 100})

        self.scen = Scenario({
            "cutoff_time": 2,
            'cs': self.cs,
            "run_obj": 'runtime',
            "output_dir": ''
        })
        self.stats = Stats(scenario=self.scen)
        self.stats.start_timing()

        self.logger = logging.getLogger(self.__module__ + "." +
                                        self.__class__.__name__)

    def test_compare_configs_no_joint_set(self):
        intensifier = AbstractRacer(stats=self.stats,
                                    traj_logger=TrajLogger(output_dir=None,
                                                           stats=self.stats),
                                    rng=None,
                                    instances=[1])

        for i in range(2):
            self.rh.add(config=self.config1,
                        cost=2,
                        time=2,
                        status=StatusType.SUCCESS,
                        instance_id=1,
                        seed=i,
                        additional_info=None)

        for i in range(2, 5):
            self.rh.add(config=self.config2,
                        cost=1,
                        time=1,
                        status=StatusType.SUCCESS,
                        instance_id=1,
                        seed=i,
                        additional_info=None)

        # The sets for the incumbent are completely disjoint.
        conf = intensifier._compare_configs(incumbent=self.config1,
                                            challenger=self.config2,
                                            run_history=self.rh)
        self.assertIsNone(conf)

        # The incumbent has still one instance-seed pair left on which the
        # challenger was not run yet.
        self.rh.add(config=self.config2,
                    cost=1,
                    time=1,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=1,
                    additional_info=None)
        conf = intensifier._compare_configs(incumbent=self.config1,
                                            challenger=self.config2,
                                            run_history=self.rh)
        self.assertIsNone(conf)

    def test_compare_configs_chall(self):
        """
            challenger is better
        """
        intensifier = AbstractRacer(stats=self.stats,
                                    traj_logger=TrajLogger(output_dir=None,
                                                           stats=self.stats),
                                    rng=None,
                                    instances=[1])

        self.rh.add(config=self.config1,
                    cost=1,
                    time=2,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=None,
                    additional_info=None)

        self.rh.add(config=self.config2,
                    cost=0,
                    time=1,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=None,
                    additional_info=None)

        conf = intensifier._compare_configs(incumbent=self.config1,
                                            challenger=self.config2,
                                            run_history=self.rh)

        # challenger has enough runs and is better
        self.assertEqual(conf, self.config2, "conf: %s" % (conf))

    def test_compare_configs_inc(self):
        """
            incumbent is better
        """
        intensifier = AbstractRacer(stats=self.stats,
                                    traj_logger=TrajLogger(output_dir=None,
                                                           stats=self.stats),
                                    rng=None,
                                    instances=[1])

        self.rh.add(config=self.config1,
                    cost=1,
                    time=1,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=None,
                    additional_info=None)

        self.rh.add(config=self.config2,
                    cost=2,
                    time=2,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=None,
                    additional_info=None)

        conf = intensifier._compare_configs(incumbent=self.config1,
                                            challenger=self.config2,
                                            run_history=self.rh)

        # challenger worse than inc
        self.assertEqual(conf, self.config1, "conf: %s" % (conf))

    def test_compare_configs_unknow(self):
        """
            challenger is better but has less runs;
            -> no decision (None)
        """
        intensifier = AbstractRacer(stats=self.stats,
                                    traj_logger=TrajLogger(output_dir=None,
                                                           stats=self.stats),
                                    rng=None,
                                    instances=[1])

        self.rh.add(config=self.config1,
                    cost=1,
                    time=1,
                    status=StatusType.SUCCESS,
                    instance_id=1,
                    seed=None,
                    additional_info=None)

        self.rh.add(config=self.config1,
                    cost=1,
                    time=2,
                    status=StatusType.SUCCESS,
                    instance_id=2,
                    seed=None,
                    additional_info=None)

        self.rh.add(config=self.config1,
                    cost=1,
                    time=1,
                    status=StatusType.SUCCESS,
                    instance_id=2,
                    seed=None,
                    additional_info=None)

        conf = intensifier._compare_configs(incumbent=self.config1,
                                            challenger=self.config2,
                                            run_history=self.rh)

        # challenger worse than inc
        self.assertIsNone(conf, "conf: %s" % (conf))

    def test_adaptive_capping(self):
        """
            test _adapt_cutoff()
        """
        intensifier = AbstractRacer(stats=self.stats,
                                    traj_logger=TrajLogger(output_dir=None,
                                                           stats=self.stats),
                                    rng=np.random.RandomState(12345),
                                    instances=list(range(5)),
                                    deterministic=False)

        for i in range(5):
            self.rh.add(config=self.config1,
                        cost=i + 1,
                        time=i + 1,
                        status=StatusType.SUCCESS,
                        instance_id=i,
                        seed=i,
                        additional_info=None)
        for i in range(3):
            self.rh.add(config=self.config2,
                        cost=i + 1,
                        time=i + 1,
                        status=StatusType.SUCCESS,
                        instance_id=i,
                        seed=i,
                        additional_info=None)

        inst_seed_pairs = self.rh.get_runs_for_config(
            self.config1, only_max_observed_budget=True)
        # cost used by incumbent for going over all runs in inst_seed_pairs
        inc_sum_cost = self.rh.sum_cost(
            config=self.config1, instance_seed_budget_keys=inst_seed_pairs)

        cutoff = intensifier._adapt_cutoff(challenger=self.config2,
                                           run_history=self.rh,
                                           inc_sum_cost=inc_sum_cost)
        # 15*1.2 - 6
        self.assertEqual(cutoff, 12)

        intensifier.cutoff = 5

        cutoff = intensifier._adapt_cutoff(challenger=self.config2,
                                           run_history=self.rh,
                                           inc_sum_cost=inc_sum_cost)
        # scenario cutoff
        self.assertEqual(cutoff, 5)
예제 #5
0
    def eval_challenger(self,
                        challenger: Configuration,
                        incumbent: typing.Optional[Configuration],
                        run_history: RunHistory,
                        time_bound: float = float(MAXINT),
                        log_traj: bool = True) -> typing.Tuple[Configuration, float]:
        """
        Running intensification via successive halving to determine the incumbent configuration.
        *Side effect:* adds runs to run_history

        Parameters
        ----------
        challenger : Configuration
            promising configuration
        incumbent : typing.Optional[Configuration]
            best configuration so far, None in 1st run
        run_history : smac.runhistory.runhistory.RunHistory
            stores all runs we ran so far
        time_bound : float, optional (default=2 ** 31 - 1)
            time in [sec] available to perform intensify
        log_traj : bool
            whether to log changes of incumbents in trajectory

        Returns
        -------
        typing.Tuple[Configuration, float]
            incumbent and incumbent cost
        """
        # calculating the incumbent's performance for adaptive capping
        # this check is required because:
        #   - there is no incumbent performance for the first ever 'intensify' run (from initial design)
        #   - during the 1st intensify run, the incumbent shouldn't be capped after being compared against itself
        if incumbent and incumbent != challenger:
            inc_runs = run_history.get_runs_for_config(incumbent, only_max_observed_budget=True)
            inc_sum_cost = run_history.sum_cost(config=incumbent, instance_seed_budget_keys=inc_runs)
        else:
            inc_sum_cost = np.inf
            if self.first_run:
                self.logger.info("First run, no incumbent provided; challenger is assumed to be the incumbent")
                incumbent = challenger
                self.first_run = False

        # select which instance to run current config on
        curr_budget = self.all_budgets[self.stage]

        # selecting instance-seed subset for this budget, depending on the kind of budget
        if self.instance_as_budget:
            prev_budget = int(self.all_budgets[self.stage - 1]) if self.stage > 0 else 0
            curr_insts = self.inst_seed_pairs[int(prev_budget):int(curr_budget)]
        else:
            curr_insts = self.inst_seed_pairs
        n_insts_remaining = len(curr_insts) - self.curr_inst_idx - 1

        self.logger.debug(" Running challenger  -  %s" % str(challenger))

        # run the next instance-seed pair for the given configuration
        instance, seed = curr_insts[self.curr_inst_idx]

        # selecting cutoff if running adaptive capping
        cutoff = self.cutoff
        if self.run_obj_time:
            cutoff = self._adapt_cutoff(challenger=challenger,
                                        run_history=run_history,
                                        inc_sum_cost=inc_sum_cost)
            if cutoff is not None and cutoff <= 0:
                # ran out of time to validate challenger
                self.logger.debug("Stop challenger intensification due to adaptive capping.")
                self.curr_inst_idx = np.inf

        self.logger.debug('Cutoff for challenger: %s' % str(cutoff))

        try:
            # run target algorithm for each instance-seed pair
            self.logger.debug("Execute target algorithm")

            try:
                status, cost, dur, res = self.tae_runner.start(
                    config=challenger,
                    instance=instance,
                    seed=seed,
                    cutoff=cutoff,
                    budget=0.0 if self.instance_as_budget else curr_budget,
                    instance_specific=self.instance_specifics.get(instance, "0"),
                    # Cutoff might be None if self.cutoff is None, but then the first if statement prevents
                    # evaluation of the second if statement
                    capped=(self.cutoff is not None) and (cutoff < self.cutoff)  # type: ignore[operator] # noqa F821
                )
                self._ta_time += dur
                self.num_run += 1
                self.curr_inst_idx += 1

            except CappedRunException:
                # We move on to the next configuration if a configuration is capped
                self.logger.debug("Budget exhausted by adaptive capping; "
                                  "Interrupting current challenger and moving on to the next one")
                # ignore all pending instances
                self.curr_inst_idx = np.inf
                n_insts_remaining = 0
                status = StatusType.CAPPED

            # adding challengers to the list of evaluated challengers
            #  - Stop: CAPPED/CRASHED/TIMEOUT/MEMOUT (!= SUCCESS & DONOTADVANCE)
            #  - Advance to next stage: SUCCESS
            # curr_challengers is a set, so "at least 1" success can be counted by set addition (no duplicates)
            # If a configuration is successful, it is added to curr_challengers.
            # if it fails it is added to fail_challengers.
            if np.isfinite(self.curr_inst_idx) and status in [StatusType.SUCCESS, StatusType.DONOTADVANCE]:
                self.success_challengers.add(challenger)  # successful configs
            else:
                self.fail_challengers.add(challenger)  # capped/crashed/do not advance configs

            # get incumbent if all instances have been evaluated
            if n_insts_remaining <= 0:
                incumbent = self._compare_configs(challenger=challenger,
                                                  incumbent=incumbent,
                                                  run_history=run_history,
                                                  log_traj=log_traj)
        except BudgetExhaustedException:
            # Returning the final incumbent selected so far because we ran out of optimization budget
            self.logger.debug("Budget exhausted; "
                              "Interrupting optimization run and returning current incumbent")

        # if all configurations for the current stage have been evaluated, reset stage
        num_chal_evaluated = len(self.success_challengers.union(self.fail_challengers)) + self.fail_chal_offset
        if num_chal_evaluated == self.n_configs_in_stage[self.stage] and n_insts_remaining <= 0:

            self.logger.info('Successive Halving iteration-step: %d-%d with '
                             'budget [%.2f / %d] - evaluated %d challenger(s)' %
                             (self.sh_iters + 1, self.stage + 1, self.all_budgets[self.stage], self.max_budget,
                              self.n_configs_in_stage[self.stage]))

            self._update_stage(run_history=run_history)

        # get incumbent cost
        inc_perf = run_history.get_cost(incumbent)

        return incumbent, inc_perf