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 _update_prediction(self, classifier, payoff_diff, situation, credit_weight): # Weighted recursive least squares enriched_situation = self._prepend_threshold_to_situation(situation) should_reset_cov_mat = \ (classifier.experience - classifier.cov_mat_reset_stamp) \ >= get_hyperparam("tau_rls") if should_reset_cov_mat: logging.debug("Resetting clfr cov mat") classifier.reset_cov_mat(get_hyperparam("delta_rls")) # calc cov mat update rate beta_rls = 1 + credit_weight* \ np.asscalar((np.dot(enriched_situation, classifier.cov_mat)).dot(enriched_situation.transpose())) # update cov mat classifier.cov_mat -= (1/beta_rls)*credit_weight* \ (np.dot(classifier.cov_mat, enriched_situation.transpose())).dot( (np.dot(enriched_situation, classifier.cov_mat))) # calc gain vector for weights gain_vec = np.dot(classifier.cov_mat, enriched_situation.transpose()) gain_vec = gain_vec.flatten() # update weights with gain vec and payoff diff (error) assert len(gain_vec) == len(classifier.weight_vec) for idx, gain in enumerate(gain_vec): classifier.weight_vec[idx] += gain * credit_weight * payoff_diff
def _update_prediction(self, classifier, payoff_diff): if classifier.experience < (1 / get_hyperparam("beta")): updated_prediction = classifier.get_prediction() + \ payoff_diff/classifier.experience else: updated_prediction = classifier.get_prediction() + \ get_hyperparam("beta") * payoff_diff classifier.set_prediction(updated_prediction)
def _update_niche_min_error(self, classifier, niche_min_error): # Use MAM for mu param niche_min_error_diff = niche_min_error - classifier.niche_min_error if classifier.experience < (1 / get_hyperparam("beta_e")): classifier.niche_min_error += \ niche_min_error_diff / classifier.experience else: classifier.niche_min_error += \ get_hyperparam("beta_e") * niche_min_error_diff
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 update_action_set_size(classifier, action_set): action_set_size_diff = action_set.num_micros \ - classifier.action_set_size if classifier.experience < (1 / get_hyperparam("beta")): classifier.action_set_size += action_set_size_diff / \ classifier.experience else: classifier.action_set_size += \ get_hyperparam("beta") * action_set_size_diff
def _update_prediction_error(self, classifier, payoff_diff): # first_term = abs(payoff_diff) - classifier.niche_min_error # if first_term < 0: # first_term = get_hyperparam("epsilon_nought") # error_diff = first_term - classifier.error error_diff = abs(payoff_diff) - classifier.error # Use MAM for error if classifier.experience < (1 / get_hyperparam("beta")): classifier.error += error_diff / classifier.experience else: classifier.error += get_hyperparam("beta") * error_diff
def _calc_deletion_vote(self, classifier, mean_fitness_in_pop): """DELETION VOTE function from 'An Algorithmic Description of XCS' (Butz and Wilson, 2002).""" vote = classifier.action_set_size * classifier.numerosity fitness_numerosity_ratio = classifier.fitness / classifier.numerosity has_sufficient_experience = classifier.experience > \ get_hyperparam("theta_del") has_low_fitness = fitness_numerosity_ratio < \ (get_hyperparam("delta") * mean_fitness_in_pop) if has_sufficient_experience and has_low_fitness: vote *= mean_fitness_in_pop / fitness_numerosity_ratio return vote
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 _update_population(self, children, parents, population): should_do_subsumption = get_hyperparam("do_ga_subsumption") for child in children: if should_do_subsumption: was_subsumed = self._try_subsume_with_parents( child, parents, population) if not was_subsumed: population.insert(child, operation_label="discovery") else: population.insert(child, operation_label="discovery")
def _calc_weight_deltas(self, payoff_diff, situation): augmented_situation = self._prepend_threshold_to_situation(situation) # Normalise by squared L2 norm: see e.g. # https://danieltakeshi.github.io/2015-07-29-the-least-mean-squares-algorithm/ normalisation_term = sum([elem**2 for elem in augmented_situation]) weight_deltas = [] for elem in augmented_situation: delta = (get_hyperparam("eta") / normalisation_term) \ * payoff_diff * elem weight_deltas.append(delta) return weight_deltas
def __call__(self, operating_set): tournament_size = math.ceil( get_hyperparam("tau") * operating_set.num_macros) assert 1 <= tournament_size <= operating_set.num_macros best_classifier = \ self._select_random_classifier_from_set(operating_set) for _ in range(2, (tournament_size + 1)): classifier = self._select_random_classifier_from_set(operating_set) if classifier.fitness > best_classifier.fitness: best_classifier = classifier return best_classifier
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): 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 _calc_accuracy_vec_and_accuracy_sum(self, action_set): accuracy_sum = 0 accuracy_vec = [] for classifier in action_set: is_below_error_threshold = classifier.error < \ get_hyperparam("epsilon_nought") if is_below_error_threshold: accuracy = self._MAX_ACCURACY else: accuracy = self._calc_accuracy(classifier) accuracy_vec.append(accuracy) accuracy_sum += accuracy * classifier.numerosity return accuracy_vec, accuracy_sum
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 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 _decay_epsilon(self, time_step): decayed_val = self._epsilon_max - \ get_hyperparam("e_greedy_decay_factor")*time_step self._epsilon = max(decayed_val, get_hyperparam("e_greedy_min_epsilon"))
def __call__(self, prediction_array, time_step=None): """SELECT ACTION function from 'An Algorithmic Description of XCS' (Butz and Wilson, 2002).""" epsilon = get_hyperparam("p_explore") return _epsilon_greedy(prediction_array, epsilon)
def _should_cover(self, match_set): return num_unique_actions(match_set) < get_hyperparam("theta_mna")
def make_classifier(rule, time_step): return Classifier(rule, get_hyperparam("prediction_I"), get_hyperparam("epsilon_I"), get_hyperparam("fitness_I"), time_step)
def _prepend_threshold_to_situation(self, situation): # return 1x(d+1) row vector res = np.insert(situation, 0, get_hyperparam("x_nought")) res = np.reshape(res, (1, len(res))) return res
def _decay_epsilon(self, time_step): self._epsilon *= get_hyperparam("e_greedy_decay_factor") assert self._epsilon >= 0.0
def _prepend_threshold_to_situation(self, situation): return np.insert(situation, 0, get_hyperparam("x_nought"))
def _update_prediction_error(self, classifier, payoff_diff, credit_weight): error_diff = abs(payoff_diff) - classifier.error classifier.error += \ (credit_weight * get_hyperparam("beta") * error_diff)
def _update_prediction_error(self, classifier, payoff_diff): error_diff = abs(payoff_diff) - classifier.error if classifier.experience < (1 / get_hyperparam("beta")): classifier.error += error_diff / classifier.experience else: classifier.error += get_hyperparam("beta") * error_diff
def _update_action_set_size(self, classifier, action_set): action_set_size_diff = action_set.num_micros \ - classifier.action_set_size classifier.action_set_size += get_hyperparam( "beta") * action_set_size_diff
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 could_subsume(self, classifier): """COULD SUBSUME function from 'An Algorithmic Description of XCS' (Butz and Wilson, 2002).""" return classifier.experience > get_hyperparam("theta_sub") and \ classifier.error < get_hyperparam("epsilon_nought")