예제 #1
0
    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
예제 #2
0
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)