def rl_mutate(self, transitions): """ Mutate genes using DQN to suggest which knob to randomly mutate. Mutations happen inplace on the "transitions" that are input. """ for i, transition in enumerate(transitions): gene = transition.gene next_gene = gene.copy() if len(self.visited) < len(self.space): action = self.mutation_agent.get_action(gene) # An action value of 0 means no mutation occurs if action != 0: next_gene[action - 1] = np.random.randint( self.dims[action - 1]) # If next gene already visited, fallback to random mutation. while knob2point(next_gene, self.dims) in self.visited: action = np.random.randint(len(self.dims)) next_gene[action] = np.random.randint(self.dims[action]) transitions[i] = Transition(self.normalise_state(gene), self.normalise_state(next_gene), action, next_gene) self.visited.add(knob2point(gene, self.dims)) else: break
def rl_mutate(self, transitions): """ Mutate genes using DQN to suggest which knob to randomly mutate. Mutations happen inplace on the "transitions" that are input. """ for i, transition in enumerate(transitions): gene = transition.gene next_gene = gene.copy() if len(self.visited) < len(self.space): action = self.mutation_agent.get_action(gene) # An action value of 0 means no mutation occurs if action != 0: next_gene[action-1] = np.random.randint(self.dims[action-1]) # If next gene already visited, fallback to random mutation. while knob2point(next_gene, self.dims) in self.visited: action = np.random.randint(len(self.dims)) next_gene[action] = np.random.randint( self.dims[action]) transitions[i] = Transition(self.normalise_state(gene), self.normalise_state(next_gene), action, next_gene) self.visited.add(knob2point(gene, self.dims)) else: break # Debugging occurrences = Counter(t.action for t in transitions) for action, occurrence in occurrences.items(): marker_size = occurrence * 2 self.action_plot_mutate.update_plot(self.mutation_step_count, action-1, marker_size)
def tune(self, n_trial, measure_option, early_stopping=None, callbacks=(), si_prefix="G"): """ GADQNTuner requires custom tuning pipeline as it requires partial measurement of genes after crossover, before mutation. DISCLAIMER: In order to customise the tuning pipeline we had to reimplement the tune function. This method is mostly taken from Tuner with the exception of an implementation of a custom tuning pipeline. """ measure_batch = create_measure_batch(self.task, measure_option) n_parallel = getattr(measure_batch, "n_parallel", 1) early_stopping = early_stopping or 1e9 format_si_prefix(0, si_prefix) GLOBAL_SCOPE.in_tuning = True do_crossover = True self.mutation_agent, self.crossover_agent = self.create_rl_agents( self.discount, int(ceil(n_trial / 2)), self.hidden_sizes, self.learning_rate) while self.step_count < n_trial: if not self.has_next(): break # Initialise a random population. if self.step_count < self.pop_size: for _ in range(self.pop_size): gene = point2knob(np.random.randint(len(self.space)), self.dims) while knob2point(gene, self.dims) in self.visited: gene = point2knob(np.random.randint(len(self.space)), self.dims) transition = Transition(None, None, None, gene) self.population.append(transition) self.visited.add(knob2point(gene, self.dims)) self.measure_configs(self.population, n_parallel, measure_batch, callbacks) self.initial_score = np.mean([p.score for p in self.population]) self.reserve_elites() # Apply GA-DQN tuning once initial population has been created. else: if do_crossover: self.population.extend(self.elite_population) self.reserve_elites() self.crossover_update(n_parallel, measure_batch, callbacks) do_crossover = False else: self.mutate_update(n_parallel, measure_batch, callbacks) do_crossover = True self.ttl = min(early_stopping + self.best_iter, n_trial) - self.step_count if self.step_count >= self.best_iter + early_stopping: logger.debug("Early stopped. Best iter: %d.", self.best_iter) break GLOBAL_SCOPE.in_tuning = False del measure_batch
def __init__(self, task, pop_size=100, elite_num=3, mutation_prob=0.1, debug=False): super(GATuner, self).__init__(task) # algorithm configurations self.pop_size = pop_size self.elite_num = elite_num self.mutation_prob = mutation_prob assert elite_num <= pop_size, "The number of elites must be less than population size" # space info self.space = task.config_space self.dim_keys = [] self.dims = [] for k, v in self.space.space_map.items(): self.dim_keys.append(k) self.dims.append(len(v)) self.visited = set([]) # current generation self.genes = [] self.scores = [] self.elites = [] self.elite_scores = [] self.trial_pt = 0 self.steps = 0 # random initialization self.pop_size = min(self.pop_size, len(self.space)) self.elite_num = min(self.pop_size, self.elite_num) for _ in range(self.pop_size): tmp_gene = point2knob(np.random.randint(len(self.space)), self.dims) while knob2point(tmp_gene, self.dims) in self.visited: tmp_gene = point2knob(np.random.randint(len(self.space)), self.dims) self.genes.append(tmp_gene) self.visited.add(knob2point(tmp_gene, self.dims)) self.debug = debug if self.debug: plt.ion() self.best_score_plot = DynamicPlot("Best score", "steps", "best score") self.average_score_plot = DynamicPlot("Average score", "steps", "average score") self.action_plot = DynamicScatterPlot("Action selection", "steps", "action value")
def next_batch(self, batch_size): ret = [] for _ in range(batch_size): gene = self.genes[self.trial_pt % self.pop_size] self.trial_pt += 1 ret.append(self.space.get(knob2point(gene, self.dims))) return ret
def measure_configs(self, transitions, n_parallel, measure_batch, callbacks): """ Measure results for current population. """ for i in range(ceil(len(transitions) / n_parallel)): configs = [] batch_size = min(n_parallel, len(transitions) - (i * n_parallel)) transitions_offset = (i * n_parallel) - 1 # Get configs for j in range(transitions_offset, transitions_offset + batch_size): gene = transitions[j].gene configs.append(self.space.get(knob2point(gene, self.dims))) # Measure batch inputs = [ MeasureInput(self.task.target, self.task, config) for config in configs ] results, end_time = measure_batch(inputs) # Unpack result for j in range(len(results)): self.step_count += 1 transition = transitions[transitions_offset + j] input, result = inputs[j], results[j] transition.input = inputs[j] transition.result = results[j] transition.score = input.task.flop / np.mean( result.costs) if result.error_no == 0 else 0.0 self.scores.append(transition.score) # Update best if transition.score > self.best_flops: self.best_flops = transition.score self.best_config = transition.input.config self.best_measure_pair = (transition.input, transition.result) self.best_iter = self.step_count for callback in callbacks: inputs = [t.input for t in transitions] results = [t.result for t in transitions] callback(self, inputs, results)
def update(self, inputs, results): # Update results without fitting model self.train_ct = len(self.xs) super().update(inputs, results) if len(self.visited) >= self.next_update: # Fit model with adaptive sampler self.train_ct = -1 super().update([], []) assert (self.train_ct == 0) maximums = self.trials print(" >> Adaptive Sampling of %d samples.." % len(maximums)) samples = [point2knob(config, self.dims) for config in maximums] reduced_samples = self.sampler.sample(samples, self.dims) maximums = [ knob2point(sample, self.dims) for sample in reduced_samples ] print(" >> Adaptive Sampling: Reducing samples to %d" % len(maximums)) self.trails = maximums self.next_update += len(maximums)
def update(self, inputs, results): for i, (inp, res) in enumerate(zip(inputs, results)): if res.error_no == 0: y = inp.task.flop / np.mean(res.costs) self.scores.append(y) else: self.scores.append(0.0) if self.debug: self.best_score_plot.update_plot( self.steps + i, round(self.best_flops / 1000000000, 2)) self.average_score_plot.update_plot( self.steps + i, round(np.mean(self.scores[-100:]) / 1e9, 2)) self.steps += len(results) if len(self.scores) >= len(self.genes) and len(self.visited) < len( self.space): genes = self.genes + self.elites scores = np.array(self.scores[:len(self.genes)] + self.elite_scores) # reserve elite self.elites, self.elite_scores = [], [] elite_indexes = np.argpartition(scores, -self.elite_num)[-self.elite_num:] for ind in elite_indexes: self.elites.append(genes[ind]) self.elite_scores.append(scores[ind]) # cross over indices = np.arange(len(genes)) scores += 1e-8 scores /= np.max(scores) probs = scores / np.sum(scores) tmp_genes = [] for _ in range(self.pop_size): p1, p2 = np.random.choice(indices, size=2, replace=False, p=probs) p1, p2 = genes[p1], genes[p2] point = np.random.randint(len(self.dims)) tmp_gene = p1[:point] + p2[point:] tmp_genes.append(tmp_gene) # mutation next_genes = [] transitions = [] for tmp_gene in tmp_genes: for j, dim in enumerate(self.dims): if np.random.random() < self.mutation_prob: tmp_gene[j] = np.random.randint(dim) if len(self.visited) < len(self.space): j = -1 while knob2point(tmp_gene, self.dims) in self.visited: j = np.random.randint(len(self.dims)) tmp_gene[j] = np.random.randint(self.dims[j]) # pylint: disable=invalid-sequence-index next_genes.append(tmp_gene) transitions.append(j) self.visited.add(knob2point(tmp_gene, self.dims)) else: break if self.debug: occurrences = Counter(transitions) for action, occurrence in occurrences.items(): marker_size = occurrence * 2 self.action_plot.update_plot(self.steps, action, marker_size) self.genes = next_genes self.trial_pt = 0 self.scores = []