def _one_iter(self, start_point: Configuration, *args) -> Tuple[float, Configuration]: incumbent = start_point # Compute the acquisition value of the incumbent acq_val_incumbent = self.acquisition_function([incumbent], *args)[0] local_search_steps = 0 neighbors_looked_at = 0 time_n = [] while True: local_search_steps += 1 if local_search_steps % 1000 == 0: self.logger.warning( "Local search took already %d iterations. Is it maybe " "stuck in a infinite loop?", local_search_steps) # Get neighborhood of the current incumbent # by randomly drawing configurations changed_inc = False # Get one exchange neighborhood returns an iterator (in contrast of # the previously returned list). all_neighbors = get_one_exchange_neighbourhood( incumbent, seed=self.rng.randint(MAXINT)) for neighbor in all_neighbors: s_time = time.time() acq_val = self.acquisition_function([neighbor], *args) neighbors_looked_at += 1 time_n.append(time.time() - s_time) if acq_val > acq_val_incumbent + self.epsilon: self.logger.debug("Switch to one of the neighbors") incumbent = neighbor acq_val_incumbent = acq_val changed_inc = True break if (not changed_inc) or \ (self.max_iterations is not None and local_search_steps == self.max_iterations): self.logger.debug( "Local search took %d steps and looked at %d " "configurations. Computing the acquisition " "value for one configuration took %f seconds" " on average.", local_search_steps, neighbors_looked_at, np.mean(time_n)) break return acq_val_incumbent, incumbent
def _one_iter(self, start_generation: [Configuration], *args) -> [Tuple[float, Configuration]]: # if we have too little to start with if self.generation_size > len(start_generation): start_generation = start_generation + \ self.config_space.sample_configuration(size=self.generation_size - len(start_generation)) ## Breed phase: population = [] # Add parents: population += [ start_generation[i] for i in range(len(start_generation)) ] # Generate new individuals with crossover: for i in range(0, len(start_generation) - 1): for j in range(i + 1, len(start_generation)): if (self.rng.standard_normal() > self.crossover_probability): population += self.crossover(start_generation[i], start_generation[j]) ## Mutation phase (produces new individuals too): for incumbent in start_generation: neighbours = get_one_exchange_neighbourhood( incumbent, seed=self.rng.randint(MAXINT)) population += neighbours # Make population contain only unique individuals: arrays = list(map(lambda x: x.get_array(), population)) indices = np.unique(arrays, return_index=True, axis=0) population = [population[ind] for ind in indices[1][::-1]] # #Selection phase: survivals = [] sorted = self._sort_configs_by_acq_value(population) elite_num = int(self.generation_size * self.elite_rate) for i in range(0, elite_num): individual = sorted[i] survivals.append(individual) return survivals
def maximize(self, start_point, *args): """ Starts a local search from the given startpoint and quits if either the max number of steps is reached or no neighbor with an higher improvement was found. Parameters: ---------- start_point: np.array(1, D): The point from where the local search starts *args : Additional parameters that will be passed to the acquisition function Returns: ------- incumbent np.array(1, D): The best found configuration acq_val_incumbent np.array(1,1) : The acquisition value of the incumbent """ incumbent = start_point # Compute the acquisition value of the incumbent incumbent_array = convert_configurations_to_array([incumbent]) acq_val_incumbent = self.acquisition_function(incumbent_array, *args) local_search_steps = 0 neighbors_looked_at = 0 time_n = [] while True: local_search_steps += 1 if local_search_steps % 1000 == 0: self.logger.warn( "Local search took already %d iterations." "Is it maybe stuck in a infinite loop?", local_search_steps) # Get neighborhood of the current incumbent # by randomly drawing configurations changed_inc = False # Get one exchange neighborhood returns an iterator (in contrast of # the previously returned list). all_neighbors = get_one_exchange_neighbourhood( incumbent, seed=self.rng.seed()) for neighbor in all_neighbors: s_time = time.time() neighbor_array_ = convert_configurations_to_array([neighbor]) acq_val = self.acquisition_function(neighbor_array_, *args) neighbors_looked_at += 1 time_n.append(time.time() - s_time) if acq_val > acq_val_incumbent + self.epsilon: self.logger.debug("Switch to one of the neighbors") incumbent = neighbor acq_val_incumbent = acq_val changed_inc = True break if (not changed_inc) or (self.max_iterations != None and local_search_steps == self.max_iterations): self.logger.debug( "Local search took %d steps and looked at %d configurations. " "Computing the acquisition value for one " "configuration took %f seconds on average.", local_search_steps, neighbors_looked_at, np.mean(time_n)) break return incumbent, acq_val_incumbent
def local_search(self, start_point: Configuration): """Starts a local search from the given startpoint and quits if either the max number of steps is reached or no neighbor with an higher improvement was found. Parameters: ---------- start_point: Configuration The point from where the local search starts Returns: ------- incumbent: Configuration The best found configuration """ self.intensifier.minR = self.fast_race_minR # be aggressive here! self.intensifier.Adaptive_Capping_Slackfactor = self.fast_race_adaptive_capping_factor incumbent = start_point local_search_steps = 0 neighbors_looked_at = 0 time_n = [] while True: local_search_steps += 1 # Get neighborhood of the current incumbent # by randomly drawing configurations changed_inc = False # Get one exchange neighborhood returns an iterator (in contrast of # the previously returned list). all_neighbors = list( get_one_exchange_neighbourhood(incumbent, seed=self.rng.seed())) acq_val = self.acquisition_func(all_neighbors) sorted_neighbors = sorted(zip(all_neighbors, acq_val), key=lambda x: x[1], reverse=True) prev_incumbent = incumbent for neighbor in all_neighbors[:self.max_neighbors]: neighbors_looked_at += 1 neighbor.origin = "SLS" self.logger.debug("Intensify") incumbent, inc_perf = self.intensifier.intensify( challengers=[neighbor], incumbent=incumbent, run_history=self.runhistory, aggregate_func=self.aggregate_func, time_bound=0.01, log_traj=False) # first improvement SLS if incumbent != prev_incumbent: changed_inc = True break if not changed_inc: self.logger.info( "Local search took %d steps and looked at %d configurations." % (local_search_steps, neighbors_looked_at)) break return incumbent
def run(self): """Runs the Bayesian optimization loop Returns ---------- incumbent: np.array(1, H) The best found configuration """ self.stats.start_timing() try: self.incumbent = self.initial_design.run() except FirstRunCrashedException as err: if self.scenario.abort_on_first_run_crash: raise # Main loop iteration = 1 while True: if self.scenario.shared_model: pSMAC.read(run_history=self.runhistory, output_dirs=self.scenario.input_psmac_dirs, configuration_space=self.config_space, logger=self.logger) # model training self.logger.info("Model Training") X, Y = self.rh2EPM.transform(self.runhistory) self.model.train(X, Y) self.acquisition_func.update(model=self.model, eta=self.runhistory.get_cost( self.incumbent)) if iteration == 1: start_point = self.incumbent else: # Restart? if self.rng.rand() < self.restart_prob: self.logger.info("Restart Search") start_point = self.scenario.cs.sample_configuration() else: # pertubate inc self.logger.info("Pertubate Incumbent") start_point = self.incumbent for _ in range(self.pertubation_steps): start_point = random.choice( list( get_one_exchange_neighbourhood( start_point, seed=self.rng.seed()))) # SLS self.logger.info("SLS") local_inc = self.local_search(start_point=start_point) # decide global inc self.logger.info("Race local incumbent against global incumbent") # don't be too aggressive here self.intensifier.minR = self.slow_race_minR self.intensifier.Adaptive_Capping_Slackfactor = self.slow_race_adaptive_capping_factor # log traj self.incumbent, inc_perf = self.intensifier.intensify( challengers=[local_inc], incumbent=self.incumbent, run_history=self.runhistory, aggregate_func=self.aggregate_func, time_bound=0.01, log_traj=True) if self.incumbent == local_inc: self.logger.info("Changed global incumbent!") if self.scenario.shared_model: pSMAC.write(run_history=self.runhistory, output_directory=self.stats.output_dir, num_run=self.num_run) iteration += 1 self.logger.debug("Remaining budget: %f (wallclock), " "%f (ta costs), %f (target runs)" % (self.stats.get_remaing_time_budget(), self.stats.get_remaining_ta_budget(), self.stats.get_remaining_ta_runs())) if self.stats.is_budget_exhausted(): break self.stats.print_stats(debug_out=True) return self.incumbent
def _do_search( self, start_points: List[Configuration], ) -> List[Tuple[float, Configuration]]: # Gather data strucuture for starting points if isinstance(start_points, Configuration): start_points = [start_points] candidates = start_points # Compute the acquisition value of the candidates num_candidates = len(candidates) acq_val_candidates = self.acquisition_function(candidates) if num_candidates == 1: acq_val_candidates = [acq_val_candidates[0][0]] else: acq_val_candidates = [a[0] for a in acq_val_candidates] # Set up additional variables required to do vectorized local search: # whether the i-th local search is still running active = [True] * num_candidates # number of plateau walks of the i-th local search. Reaching the maximum number is the stopping criterion of # the local search. n_no_plateau_walk = [0] * num_candidates # tracking the number of steps for logging purposes local_search_steps = [0] * num_candidates # tracking the number of neighbors looked at for logging purposes neighbors_looked_at = [0] * num_candidates # tracking the number of neighbors generated for logging purposse neighbors_generated = [0] * num_candidates # how many neighbors were obtained for the i-th local search. Important to map the individual acquisition # function values to the correct local search run obtain_n = [self.vectorization_min_obtain] * num_candidates # Tracking the time it takes to compute the acquisition function times = [] # Set up the neighborhood generators neighborhood_iterators = [] for i, inc in enumerate(candidates): neighborhood_iterators.append( get_one_exchange_neighbourhood(inc, seed=self.rng.randint( low=0, high=100000))) local_search_steps[i] += 1 # Keeping track of configurations with equal acquisition value for plateau walking neighbors_w_equal_acq = [[] for _ in range(num_candidates) ] # type: List[List[Configuration]] num_iters = 0 while np.any(active): num_iters += 1 # Whether the i-th local search improved. When a new neighborhood is generated, this is used to determine # whether a step was made (improvement) or not (iterator exhausted) improved = [False] * num_candidates # Used to request a new neighborhood for the candidates of the i-th local search new_neighborhood = [False] * num_candidates # gather all neighbors neighbors = [] for i, neighborhood_iterator in enumerate(neighborhood_iterators): if active[i]: neighbors_for_i = [] for j in range(obtain_n[i]): try: n = next(neighborhood_iterator) neighbors_generated[i] += 1 neighbors_for_i.append(n) except StopIteration: obtain_n[i] = len(neighbors_for_i) new_neighborhood[i] = True break neighbors.extend(neighbors_for_i) if len(neighbors) != 0: start_time = time.time() acq_val = self.acquisition_function(neighbors) end_time = time.time() times.append(end_time - start_time) if np.ndim(acq_val.shape) == 0: acq_val = [acq_val] # Comparing the acquisition function of the neighbors with the acquisition value of the candidate acq_index = 0 # Iterating the all i local searches for i in range(num_candidates): if not active[i]: continue # And for each local search we know how many neighbors we obtained for j in range(obtain_n[i]): # The next line is only true if there was an improvement and we basically need to iterate to # the i+1-th local search if improved[i]: acq_index += 1 else: neighbors_looked_at[i] += 1 # Found a better configuration if acq_val[acq_index] > acq_val_candidates[i]: self.logger.debug( "Local search %d: Switch to one of the neighbors (after %d configurations).", i, neighbors_looked_at[i], ) candidates[i] = neighbors[acq_index] acq_val_candidates[i] = acq_val[acq_index] new_neighborhood[i] = True improved[i] = True local_search_steps[i] += 1 neighbors_w_equal_acq[i] = [] obtain_n[i] = 1 # Found an equally well performing configuration, keeping it for plateau walking elif acq_val[acq_index] == acq_val_candidates[i]: neighbors_w_equal_acq[i].append( neighbors[acq_index]) acq_index += 1 # Now we check whether we need to create new neighborhoods and whether we need to increase the number of # plateau walks for one of the local searches. Also disables local searches if the number of plateau walks # is reached (and all being switched off is the termination criterion). for i in range(num_candidates): if not active[i]: continue if obtain_n[i] == 0 or improved[i]: obtain_n[i] = 2 else: obtain_n[i] = obtain_n[i] * 2 obtain_n[i] = min(obtain_n[i], self.vectorization_max_obtain) if new_neighborhood[i]: if not improved[i] and n_no_plateau_walk[ i] < self.n_steps_plateau_walk: if len(neighbors_w_equal_acq[i]) != 0: candidates[i] = neighbors_w_equal_acq[i][0] neighbors_w_equal_acq[i] = [] n_no_plateau_walk[i] += 1 if n_no_plateau_walk[i] >= self.n_steps_plateau_walk: active[i] = False continue neighborhood_iterators[i] = get_one_exchange_neighbourhood( candidates[i], seed=self.rng.randint(low=0, high=100000), ) self.logger.debug( "Local searches took %s steps and looked at %s configurations. Computing the acquisition function in " "vectorized for took %f seconds on average.", local_search_steps, neighbors_looked_at, np.mean(times), ) return [(a, i) for a, i in zip(acq_val_candidates, candidates)]