def _mut_shift(self, genotype, ling_var_genotype_range): logging.debug("Mut shift") (start_idx, num_alleles) = ling_var_genotype_range end_idx_exclusive = (start_idx + num_alleles) # shift flips a randomly chosen one to a zero then # sets either the allele before or after to a one depending on what # is possible one_allele_idxs = [ idx for idx in range(start_idx, end_idx_exclusive) if genotype[idx] == 1 ] assert len(one_allele_idxs) >= 1 idx_to_flip = get_rng().choice(one_allele_idxs) genotype[idx_to_flip] = 0 # get info about adjacent alleles of the ling var prev_idx = max(start_idx, (idx_to_flip - 1)) end_idx_inclusive = (end_idx_exclusive - 1) next_idx = min(end_idx_inclusive, (idx_to_flip + 1)) # pick an adjacent allele to set to 1 if prev_idx == start_idx: # edge case, allele flipped to zero originally is first one in seq. # for ling var, so set the allele to the right of it to one idx_for_one = next_idx elif next_idx == end_idx_inclusive: # edge case, allele flipped to zero originally is last one in seq. # for ling var, so set the allele to the left of it to one idx_for_one = prev_idx else: # nominal case, allele flipped to zero originally not at # boundaries, pick either prev or next allele at random to set to # one idx_for_one = get_rng().choice([prev_idx, next_idx]) genotype[idx_for_one] = 1
def mutate_condition(self, condition, situation=None): genotype = condition.genotype for allele_idx in range(len(genotype)): should_mutate = get_rng().rand() < get_hyperparam("mu") if should_mutate: mutation_magnitude = get_rng().uniform(0, get_hyperparam("m")) mutation_sign = get_rng().choice([1, -1]) mutation_amount = mutation_magnitude * mutation_sign genotype[allele_idx] += mutation_amount self._enforce_genotype_maps_to_valid_phenotype(genotype)
def mutate_condition(self, condition, situation=None): genotype = condition.genotype for allele_idx in range(len(genotype)): should_mutate = get_rng().rand() < get_hyperparam("mu") if should_mutate: # mutation draws from +-[0, m_nought) m_nought = get_hyperparam("m_nought") assert m_nought > 0 mut_choices = range(0, m_nought) mutation_magnitude = get_rng().choice(mut_choices) mutation_sign = get_rng().choice([1, -1]) mutation_amount = mutation_magnitude * mutation_sign genotype[allele_idx] += mutation_amount self._enforce_genotype_maps_to_valid_phenotype(genotype)
def make_fuzzy_linear_prediction_classifier(rule, time_step): return FuzzyLinearPredictionClassifier(rule, get_hyperparam("epsilon_I"), get_hyperparam("fitness_I"), time_step, get_hyperparam("x_nought"), get_hyperparam("delta_rls"), get_rng())
def _select_random_action_with_valid_prediction(prediction_array): if len(prediction_array) != 0: possible_actions = prediction_array.keys() return get_rng().choice(list(possible_actions)) else: return _fallback_to_random_selection_from_action_set( prediction_array.env_action_set)
def _epsilon_greedy(prediction_array, epsilon): logging.debug(f"Epsilon = {epsilon}") assert 0.0 <= epsilon <= 1.0 should_explore = get_rng().rand() <= epsilon if should_explore: action = \ _select_random_action_with_valid_prediction(prediction_array) else: action = select_greedy_action(prediction_array) return ActionSelectResponse(action=action, did_explore=should_explore)
def __call__(self, operating_set): """SELECT OFFSPRING function from 'An Algorithmic Description of XCS' (Butz and Wilson, 2002).""" fitness_sum = sum([classifier.fitness for classifier in operating_set]) choice_point = get_rng().rand() * fitness_sum fitness_sum = 0 for classifier in operating_set: fitness_sum += classifier.fitness if fitness_sum > choice_point: return classifier
def gen_covering_condition(self, situation): alleles = [] for (idx, situation_elem) in enumerate(situation): lower = situation_elem - get_rng().uniform( 0, get_hyperparam("s_nought")) upper = situation_elem + get_rng().uniform( 0, get_hyperparam("s_nought")) dimension = self._situation_space[idx] lower = truncate_val(lower, lower_bound=dimension.lower, upper_bound=dimension.upper) upper = truncate_val(upper, lower_bound=dimension.lower, upper_bound=dimension.upper) assert lower <= upper frac_to_upper = self._calc_frac_to_upper(lower, upper, dimension.upper) alleles.append(lower) alleles.append(frac_to_upper) genotype = Genotype(alleles) return Condition(genotype)
def _mut_contract(self, genotype, ling_var_genotype_range): logging.debug("Mut contract") (start_idx, num_alleles) = ling_var_genotype_range end_idx_exclusive = (start_idx + num_alleles) # contraction flips a randomly chosen one to a zero one_allele_idxs = [ idx for idx in range(start_idx, end_idx_exclusive) if genotype[idx] == 1 ] assert len(one_allele_idxs) >= 2 idx_to_flip = get_rng().choice(one_allele_idxs) genotype[idx_to_flip] = 0
def _mut_expand(self, genotype, ling_var_genotype_range): logging.debug("Mut expand") (start_idx, num_alleles) = ling_var_genotype_range end_idx_exclusive = (start_idx + num_alleles) # expansion flips a randomly chosen zero to a one zero_allele_idxs = [ idx for idx in range(start_idx, end_idx_exclusive) if genotype[idx] == 0 ] assert len(zero_allele_idxs) >= 1 idx_to_flip = get_rng().choice(zero_allele_idxs) genotype[idx_to_flip] = 1
def _perform_crossover(self, children, parents, situation): should_do_crossover = get_rng().rand() < get_hyperparam("chi") if should_do_crossover: (child_one, child_two) = children logging.debug(f"Before crossover {child_one.condition}, " f"{child_two.condition}") self._rule_repr.crossover_conditions(child_one.condition, child_two.condition, self._crossover_strat) logging.debug(f"After crossover {child_one.condition}, " f"{child_two.condition}") self._update_children_params(children, parents, situation)
def mutate_condition(self, condition, situation): """First part (condition mutation) of APPLY MUTATION function from 'An Algorithmic Description of XCS' (Butz and Wilson, 2002).""" genotype = condition.genotype for idx, (allele, situation_elem) in enumerate(zip(genotype, situation)): should_mutate_allele = get_rng().rand() < get_hyperparam("mu") if should_mutate_allele: if self._is_wildcard(allele): genotype[idx] = situation_elem else: genotype[idx] = self._WILDCARD_ALLELE
def gen_covering_condition(self, situation): alleles = [] for (idx, situation_elem) in enumerate(situation): # covering draws from (0, r_nought) r_nought = get_hyperparam("r_nought") assert r_nought > 1 cover_choices = range(1, r_nought) lower = situation_elem - get_rng().choice(cover_choices) upper = situation_elem + get_rng().choice(cover_choices) dimension = self._situation_space[idx] lower = truncate_val(lower, lower_bound=dimension.lower, upper_bound=dimension.upper) upper = truncate_val(upper, lower_bound=dimension.lower, upper_bound=dimension.upper) assert lower <= upper span_to_upper = self._calc_span_to_upper(lower, upper, dimension) alleles.append(lower) alleles.append(span_to_upper) genotype = Genotype(alleles) return Condition(genotype)
def _mutate_condition(self, condition): ling_var_genotype_ranges = self._get_ling_var_genotype_ranges() ling_var_idx_to_mut = get_rng().choice(range(len(self._ling_vars))) ling_var_genotype_range = ling_var_genotype_ranges[ling_var_idx_to_mut] mut_strat = self._choose_mut_strat_for_ling_var( condition.genotype, ling_var_genotype_range) if mut_strat == "expand": self._mut_expand(condition.genotype, ling_var_genotype_range) elif mut_strat == "contract": self._mut_contract(condition.genotype, ling_var_genotype_range) elif mut_strat == "shift": self._mut_shift(condition.genotype, ling_var_genotype_range) else: raise InternalError("Should not get here")
def gen_covering_condition(self, situation): """First part (condition generation) of GENERATE COVERING CLASSIFIER function from 'An Algorithmic Description of XCS' (Butz and Wilson, 2002). """ alleles = [] for situation_elem in situation: should_make_wildcard = get_rng().rand() < get_hyperparam( "p_wildcard") if should_make_wildcard: alleles.append(self._WILDCARD_ALLELE) else: # copy situation alleles.append(situation_elem) genotype = Genotype(alleles) return Condition(genotype)
def _correct_crossover_res_if_necessary(self, genotype_before, genotype_after): ling_var_genotype_ranges = self._get_ling_var_genotype_ranges() for (start_idx, num_alleles) in ling_var_genotype_ranges: end_idx_exclusive = (start_idx + num_alleles) after_ling_var_alleles = genotype_after[ start_idx:end_idx_exclusive] has_no_ones_after = after_ling_var_alleles.count(1) == 0 if has_no_ones_after: one_allele_idxs_before = [ idx for idx in range(start_idx, end_idx_exclusive) if genotype_before[idx] == 1 ] assert len(one_allele_idxs_before) >= 1 idx_for_one = get_rng().choice(one_allele_idxs_before) genotype_after[idx_for_one] = 1
def _select_for_deletion(self, population): """First loop (selecting classifier to delete) of DELETE FROM POPULATION function from 'An Algorithmic Description of XCS' (Butz and Wilson, 2002). This method should only ever be called if the population is past its microclassifier capacity.""" mean_fitness_in_pop = calc_summary_stat(population, "mean", "fitness") votes = [ self._calc_deletion_vote(classifier, mean_fitness_in_pop) for classifier in population ] vote_sum = sum(votes) choice_point = get_rng().rand() * vote_sum vote_sum = 0 for (classifier, vote) in zip(population, votes): vote_sum += vote if vote_sum > choice_point: return classifier
def _choose_mut_strat_for_ling_var(self, genotype, ling_var_genotype_range): ling_var_alleles = self._get_ling_var_alleles(genotype, ling_var_genotype_range) # can always shift because it ensures needs at least a single one # allele present to operate and guarantees that at least a single one # allele remains afterwards possible_strats = ["shift"] # need at least a single zero allele to expand could_expand = ling_var_alleles.count(0) >= 1 if could_expand: possible_strats.append("expand") # need at least two one alleles to contract so at least a single one # allele remains could_contract = ling_var_alleles.count(1) >= 2 if could_contract: possible_strats.append("contract") mut_strat = get_rng().choice(possible_strats) return mut_strat
def __call__(self, first_vec, second_vec): assert len(first_vec) == len(second_vec) for swap_idx in range(0, len(first_vec)): should_swap = get_rng().rand() < get_hyperparam("upsilon") if should_swap: _swap_vec_elems(first_vec, second_vec, swap_idx)
def _choose_random_crossover_idxs(self, vec_len): num_idxs = 2 return tuple([get_rng().randint(0, vec_len) for _ in range(num_idxs)])
def _fallback_to_random_selection_from_action_set(env_action_set): logging.warning("Falling back to random action selection due to empty " "prediction array.") return get_rng().choice(list(env_action_set))
def mutate_condition(self, condition, situation=None): should_do_mutation = get_rng().rand() < get_hyperparam("mu") if should_do_mutation: self._mutate_condition(condition) self._assert_genotype_is_valid(condition.genotype)
def _gen_covering_action(self, match_set): possible_covering_actions = \ tuple(self._env_action_set - get_unique_actions_set(match_set)) assert len(possible_covering_actions) > 0 return get_rng().choice(possible_covering_actions)
def _select_random_classifier_from_set(self, operating_set): return get_rng().choice(list(operating_set))