def __init__(self, automaton, corpus, checkpoint, pref_prob, distfp, turns_for_each, factors, temperatures): """ Learner class implements a simulated annealing method and is capable of applying downhill simplex as a preference instead of random changing if needed. @param corpus: needs to be normalized with corpus.normalize_corpus() @param automaton: initialized Automaton instance @param pref_prob: probability of using downhill preference @param distfp: distance function pointer (kullback, l1err, sqerr) @param turns_for_each: iterations at one temperature @param factors: list of factors, length of list is how many bursts/ epochs we want to run, and every element is a float which is the factor of the actual epoch @param temperatures: same length list as factors and the elements are the tolerance parameters for the simmulated annealing """ self.turns_for_each = turns_for_each self.preference_probability = pref_prob self.automaton = automaton self.corpus = corpus self.dist_cache = DistanceCache(automaton, corpus) self.distfp = getattr(Automaton, distfp) if len(temperatures) != len(factors): print temperatures print factors raise Exception("temperatures has to have the same length as " + "factors when creating a Learner") self.factors = factors self.temps = temperatures self.checkpoint = checkpoint self.previous_change_options = None
class Learner(object): def __init__(self, automaton, corpus, checkpoint, pref_prob, distfp, turns_for_each, factors, temperatures): """ Learner class implements a simulated annealing method and is capable of applying downhill simplex as a preference instead of random changing if needed. @param corpus: needs to be normalized with corpus.normalize_corpus() @param automaton: initialized Automaton instance @param pref_prob: probability of using downhill preference @param distfp: distance function pointer (kullback, l1err, sqerr) @param turns_for_each: iterations at one temperature @param factors: list of factors, length of list is how many bursts/ epochs we want to run, and every element is a float which is the factor of the actual epoch @param temperatures: same length list as factors and the elements are the tolerance parameters for the simmulated annealing """ self.turns_for_each = turns_for_each self.preference_probability = pref_prob self.automaton = automaton self.corpus = corpus self.dist_cache = DistanceCache(automaton, corpus) self.distfp = getattr(Automaton, distfp) if len(temperatures) != len(factors): print temperatures print factors raise Exception("temperatures has to have the same length as " + "factors when creating a Learner") self.factors = factors self.temps = temperatures self.checkpoint = checkpoint self.previous_change_options = None #self.preferred_node_pair = None #self.preferred_direction = None #self.disallowed_node_pair = None @staticmethod def create_from_options(automaton, corpus, options): return Learner(automaton, corpus, checkpoint=None, pref_prob=options.downhill_factor, distfp=options.distfp, turns_for_each=options.iter, factors=options.factors, temperatures=options.temps) def change_automaton(self, options=None, revert=False): if not revert: edge = options["edge"] factor = options["factor"] if self.automaton.quantizer is not None: s1 = edge[0] s2 = edge[1] factor = self.automaton.quantizer.shift( self.automaton.m[s1][s2], factor) - self.automaton.m[s1][s2] self.automaton.boost_edge(edge, factor) else: src, transitions = self.previous_change_options["transitions"] self.automaton.m[src] = copy(transitions) def randomize_automaton_change(self, factor): change_options = {} s1 = None s2 = None states = self.automaton.m.keys() while True: s1 = random.choice(states) if len(self.automaton.m[s1]) < 2: continue s2 = random.choice(self.automaton.m[s1].keys()) if self.previous_change_options is None: break else: if not (self.previous_change_options["result"] == False and (s1, s2) == self.previous_change_options["edge"]): break change_options["edge"] = (s1, s2) factor = (factor if random.random() > 0.5 else -factor) change_options["factor"] = factor change_options["transitions"] = (s1, copy(self.automaton.m[s1])) return change_options def choose_change_options(self, change_options_random, *args): if (random.random() < self.preference_probability and self.previous_change_options is not None): # downhill change_options = self.previous_change_options else: change_options = change_options_random(*args) return change_options def simulated_annealing(self, compute_energy, change_something, change_back, option_randomizer): energy = compute_energy() for factor, temperature in zip(self.factors, self.temps): for turn_count in xrange(self.turns_for_each): #if turn_count == 0: #logging.info("SA e={0}".format(energy) + #" f={0} t={1}".format(factor, temperature)) if turn_count * 10 % self.turns_for_each == 0: logging.info("SA tc ={0} e={1}".format(turn_count, energy) + " f={0} t={1}".format(factor, temperature)) change_options = self.choose_change_options(option_randomizer, factor) change_something(change_options) new_energy = compute_energy() energy_change = new_energy - energy self.previous_change_options = change_options if energy_change < 0: accept = True else: still_accepting_probability = random.random() accept = (still_accepting_probability < math.exp(-energy_change/temperature)) if accept: energy = new_energy self.previous_change_options["result"] = True logging.debug("Accepting {0} energy change".format( energy_change)) else: self.previous_change_options["result"] = False change_back() if self.checkpoint: self.checkpoint(factor, temperature) def main(self): compute_energy = lambda: self.dist_cache.distance(self.distfp) change_something = lambda x: self.change_automaton(x, False) change_back = lambda: self.change_automaton(None, True) option_randomizer = lambda x: self.randomize_automaton_change(x) self.simulated_annealing(compute_energy, change_something, change_back, option_randomizer)