class ParticleSwarmOptimization(PopulationDistribution): """Particle Swarm Optimization. Config parameters * omega -- The decay factor for velocities * phig -- The global best component in velocity update * phip -- The local best component in velocity update """ config = Config(history=PSOHistory, omega=-.5, phig=2.0, phip=2.0) def __init__(self, **kwargs): super(ParticleSwarmOptimization, self).__init__(**kwargs) if self.config.space.type != np.ndarray: raise ValueError("Space must have type numpy.ndarray") def compatible(self, history): return isinstance(history, PSOHistory) def batch(self, popSize): positions = self.history.positions() + self.history.velocities() return positions
class Annealing(Selection): """ Base class for annealed selection. See e.g. Lockett, A. and Miikkulainen, R. Real-space Evolutionary Annealing, 2011. For a more up-to-date version, see Chapters 11-13 of <http://nn.cs.utexas.edu/downloads/papers/lockett.thesis.pdf>. Config parameters (many like simulated annealing) * schedule -- The cooling schedule to use. May be any callable function, or a string, one of ("log", "linear", "discount"). If it is a callable, then it will be passed the ``updates`` value in :class:`History` and should return a floating point value that will divide the exponent in the Boltzmann distribution. That is, ``schedule(n)`` should converge to zero as n goes to infinity. * learningRate -- A divisor for the cooling schedule, used for built-in schedules "log" and "inear". As a divisor, it divides the temperature but multiplies the exponent. * temp0 -- An initial temperature for the temperature decay in "discount" * divisor -- A divisor that divides the ``updates`` property of :class:`History`, scaling the rate of decline in the temperature * discount -- The decay factor for the built-in discount schedule; ``temp0`` is multiplied by ``discount`` once each time the ``update`` method is called * separator -- A :class:`SeparationAlgorithm` for partitioning regions """ config = Config(schedule="log_area", learningRate=1.0, divisor=1.0, temp0=1.0, discount=.99, separator=SeparationAlgorithm, history=PartitionHistory) def __init__(self, **kwargs): super(Annealing, self).__init__(**kwargs) if self.config.jogo2012: self.config.schedule = "log" self.config.separator = VectorSeparationAlgorithm def compatible(self, history): return isinstance(history, PartitionHistory) def sample(self): """ Child classes should override this method in order to select a point from the active segment in :class:`pyec.util.partitions.Point`. The actual return should be a partition node. """ pass def batch(self, m): return [self.sample() for i in xrange(m)]
def test_partition_binary(self): stats = RunStats() segment = Segment( Config(space=Binary(dim=100), separator=BinarySeparationAlgorithm, stats=stats, taylorDepth=10, learningRate=1.0, taylorCenter=1.0, minimize=False, pressure=0.025)) points = [ Point(segment=segment, point=TernaryString(0L, -1L, 100), score=2.0), Point(segment=segment, point=TernaryString(1L, -1L, 100), score=1.0), Point(segment=segment, point=TernaryString(2L, -1L, 100), score=3.0), Point(segment=segment, point=TernaryString(3L, -1L, 100), score=1.2), ]
class Gaussian(Mutation): """ Gaussian mutation. Randomly mutates a random selection of components of a real vector. Config parameters * sd -- The standard deviation to use. * p -- The probability of mutating each component; defaults to 1.0 """ config = Config(sd=0.01, p=1.0) def __init__(self, **kwargs): super(Gaussian, self).__init__(**kwargs) if self.config.space.type != np.ndarray: raise ValueError("Space for Gaussian must have type numpy.ndarray") def sd(self): return self.config.sd def mutate(self, x): p = np.random.binomial(1, self.config.p, len(x)) ret = x + p * np.random.randn(len(x)) * self.sd() if not self.config.space.in_bounds(ret): scale = self.config.space.scale center = self.config.space.center ret = np.maximum(np.minimum(ret, scale + center), center - scale) return ret def density(self, center, point): sd = self.sd() return np.exp(-(1. / (2. * sd * sd)) * ((point - center)**2).sum()) / sd / np.sqrt(2 * np.pi)
class NelderMead(PopulationDistribution): """The Nelder-Mead method of optimization for real spaces. """ config = Config( alpha=1.0, beta=.5, gamma=2., delta=.5, tol=1e-10, # tolerance for vertex spread before restart history=NelderMeadHistory) def __init__(self, **kwargs): super(NelderMead, self).__init__(**kwargs) if not isinstance(self.config.space, Euclidean): raise ValueError("Cannot use Nelder-Mead in non-Euclidean spaces.") self.config.populationSize = 1 def compatible(self, history): return isinstance(history, NelderMeadHistory) def batch(self, popSize): # regardless of requested size, we only return 1 item, the current # proposal for nelder mead current = self.history.current if not self.config.space.in_bounds(current): center, scale = self.config.space.extent() self.history.current = np.minimum( center + scale, np.maximum(center - scale, current)) return [self.history.current]
class AreaSensitiveGaussianWeightMutation(GaussianWeightMutation): """Weight mutation, sensitive to area partition. Config parameters: * decay -- A function ``decay(n,config)`` to compute the multiplier that controls the rate of decrease in standard deviation. Faster decay causes faster convergence, but may miss the optimum. Default is ``((1/generations))`` """ config = Config(decay=lambda n, cfg: (1. / (2 * np.log(n)))) def gaussInt(self, z): # x is std normal from zero to abs(z) x = .5 * erf(np.abs(z) / np.sqrt(2)) return .5 + np.sign(z) * x def sd(self, net): try: area, path = self.config.segment.partitionTree.traverse(net) lower, upper = area.bounds.extent() sd = .5 * (upper - lower) scale = self.config.space.scale sd = self.gaussInt(upper / scale) - self.gaussInt(lower / scale) sd *= self.config.decay(self.history.updates, self.config) except Exception: n = self.history.updates return self.config.sd * self.config.decay(n, self.config)
class RemoveLinkMutation(Mutation): """Remove a random link from a network. Config parameters: * link_deletion_prob - The probability of removing a random link """ config = Config(link_deletion_prob=0.025) def mutate(self, net): if net.changed: return net p = self.config.link_deletion_prob if np.random.random_sample() > p: return net # randomly pick two layers satisfactory = lambda lay: (not isinstance(lay, RnnLayerInput) and not isinstance(lay, RnnLayerBias)) targets = [layer for layer in net.layers if satisfactory(layer)] target = targets[np.random.randint(0, len(targets))] source = net.layers[np.random.randint(0, len(targets))] if (source, target) in net.links: net = self.config.space.copy(net) source = [layer for layer in net.layers if layer.id == source.id][0] target = [layer for layer in net.layers if layer.id == target.id][0] net.disconnect(source, target) net.changed = True #net.checkLinks() return net
class EvolutionStrategySelection(Selection): """ Implements standard selection for evolution strategies. The property ``config.selection`` determines the type of selection, either "plus" or "comma". The config should provide $\mu$, and the population size together with mu and the selection type determines lambda. Config parameters * mu -- The number of parents to be taken from the prior population * selection -- Either "plus" selection or "comma"; "comma" is default """ config = Config(mu=None, selection="comma") def __init__(self, **kwargs): super(EvolutionStrategySelection, self).__init__(**kwargs) self.total = 0 self.mu = self.config.mu self.plus = self.config.selection == 'plus' # plus or comma selection, for ES def sample(self): idx = np.random.randint(0, self.mu) return self.history.lastPopulation()[idx][0] def batch(self, popSize): if self.plus: return ([x for x, s in self.history.lastPopulation()[:self.mu]] + [self.sample() for i in xrange(popSize - self.mu)]) else: return [self.sample() for i in xrange(popSize)]
def __init__(self, **kwargs): config = BayesNet.config.merge(Config(**kwargs)) super(BayesNet, self).__init__(**config.__properties__) self.numVariables = self.config.numVariables self.variableGenerator = self.config.variableGenerator self.structureGenerator = self.config.structureGenerator self.randomizer = self.config.randomizer self.sampler = self.config.sampler self.variables = [] for i in xrange(self.numVariables): self.variables.append(self.variableGenerator(i, self.config)) self.decay = 1 self.dirty = False self.acyclic = True self.edges = [] self.edgeRatio = 0.0 self.edgeTuples = None self.cacheKeys = dict([(v.index, v.cacheKey) for v in self.variables]) self.edgeMap = {} self.binary = zeros(len(self.variables)**2) self.deferred = False self.deferredWeights = False self.edgeRep = None self.densityStored = None self.cacheHits = 0 self.cacheTries = 0 self.changed = {} self.last = {} self.__class__.counter += 1 self.__class__.created += 1
class ProportionalAnnealing(Annealing): """ Proportional Annealing, as described in Lockett, A. And Miikkulainen, R. Real-space Evolutionary Annealing (2011). See :class:`Annealing` for more details. Config parameters: * taylorCenter -- Center for Taylor approximation to annealing densities. * taylorDepth -- The number of coefficients to use for Taylor approximation of the annealing density. """ config = Config(taylorCenter=1.0, taylorDepth=10, history=TaylorPartitionHistory) def compatible(self, history): return isinstance(history, TaylorPartitionHistory) def sample(self): return Point.sampleProportional(self.history.segment, self.history.temperature(), self.config)
class Distribution(object): """ A Distribution that can be sampled. :param config: A set of configuration parameters for the distribution. :type config: :class:`Config` """ config = Config() def __init__(self, **kwargs): super(Distribution, self).__init__() self.config = Distribution.config.merge(Config(**kwargs)) def __call__(self): return self.sample() def sample(self, **kwargs): """Get a single sample from the distribution.""" return self.batch(1)[0] def batch(self, sampleSize): """ Get a sample from the distribution. :param sampleSize: The size of the sample to generate. :type sampleSize: int """ pass
def test_segment(self): space = Euclidean(dim=2) segment = Segment(Config(space=space)) self.assertTrue(segment.config.space is space) self.assertEqual(segment.points, []) self.assertTrue(isinstance(segment.partitionTree, Partition)) self.assertTrue(isinstance(segment.scoreTree, ScoreTree)) segment.clearSegment()
class Selection(PopulationDistribution): """A selection method (Abstract class)""" config = Config(history=SortedMarkovHistory) def needsScores(self): return True def compatible(self, history): return hasattr(history, 'lastPopulation')
class AreaSensitiveStructureMutator(Mutation): config = Config(sd=1.0) def mutate(self, x): net, area = x net = self.config.space.copy(net) area = area.area net.decay = self.config.sd * log(1 + -log2(area)) return net.structureSearch(net.structureGenerator.config.data)
def test_cls_getitem(): DE7 = DE[Config(populationSize=7)] assert DE7.config.populationSize == 7 de7 = DE7(space=space) assert de7.config.populationSize == 1 de5 = DE7(populationSize=5, space=space) assert de5.config.populationSize == 1 run(de7) run(de5) assert isinstance(de7.history, GeneratingSetSearchHistory) assert isinstance(de5.history, GeneratingSetSearchHistory)
class GaussianWeightMutation(WeightMutation): config = Config(link_mutation_prob=0.5, sd=.1) def sd(self, net): return self.config.sd def mutateWeightMatrix(self, net, weights): sd = self.sd(net) mask = np.random.binomial(1, self.config.link_mutation_prob, np.shape(weights)) return weights + sd * mask * np.random.randn(*np.shape(weights))
def test_cls_getitem(): DE7 = DE[Config(populationSize=7)] assert DE7.config.populationSize == 7 de7 = DE7(space=space) assert de7.config.populationSize == 7 de5 = DE7(populationSize=5, space=space) assert de5.config.populationSize == 5 run(de7) run(de5) assert isinstance(de7.history, PSOHistory) assert isinstance(de5.history, PSOHistory)
class Boa(PopulationDistribution): """The Bayesian Optimization Algorithm of Martin Pelikan. """ config = Config( variableGenerator=BinaryVariable, branchFactor=3, structureGenerator=GreedyStructureSearch( 3, BayesianInformationCriterion()), randomizer=lambda net: TernaryString(0L, -1L, net.numVariables), sampler=DAGSampler(), data=None, truncate=0.60, # percentage of the population to keep space=BinaryReal(realDim=5), history=SortedMarkovHistory) def __init__(self, **kwargs): super(Boa, self).__init__(**kwargs) self.network = BayesNet(numVariables=self.config.space.dim, **self.config.__properties__) self.network = StructureProposal(**self.config.__properties__).search( self.network) self.trained = False def compatible(self, history): return hasattr(history, "lastPopulation") and history.sorted def sample(self): x = self.network.__call__() cnt = 0 while not self.config.space.in_bounds(x): if cnt > 10000: raise ValueError( "Rejection sampling failed after 10,000 attempts in BOA") x = self.network.__call__() cnt += 1 return x def batch(self, num): return [self.sample() for i in xrange(num)] def update(self, history, fitness): super(Boa, self).update(history, fitness) if history.lastPopulation() is None: return population = [x for x, s in history.lastPopulation()] selected = int(self.config.truncate * len(population)) self.network = BayesNet(numVariables=self.config.space.dim, **self.config.__properties__) self.network.config.data = None self.network = StructureProposal( **self.network.config.__properties__).search(self.network) self.network.config.data = population self.network.structureSearch(population) self.network.update(self.history.updates, population)
def test_cls_getitem(): DE7 = DE[Config(populationSize=7)] assert DE7.config.populationSize == 7 de7 = DE7(space=space) assert de7.config.populationSize == 1 # pop size 1 is enforced by the alg # at instantiation time de5 = DE7(populationSize=5, space=space) assert de5.config.populationSize == 1 run(de7) run(de5) assert isinstance(de7.history, NelderMeadHistory) assert isinstance(de5.history, NelderMeadHistory)
def parse(self, fname): f = open(fname) totalLine = "" done = False for line in f: totalLine += line lefts = len(totalLine.split("(")) rights = len(totalLine.split(")")) if lefts == rights: self.processLine(totalLine) totalLine = "" categories = [[]] * self.index for name, idx in self.indexMap.iteritems(): categories[idx] = self.variables[name]['vals'] cfg = Config() cfg.numVariables = len(self.variables) cfg.variableGenerator = MultinomialVariableGenerator(categories) cfg.randomizer = MultinomialRandomizer() cfg.sampler = DAGSampler() cfg.structureGenerator = StructureProposal(cfg) net = BayesNet(cfg) for variable in net.variables: variable.tables = self.variables[self.revIndexMap[ variable.index]]['cpt'] #print names[variable.index], self.variables[self.revIndexMap[variable.index]]['parents'] variable.known = [ self.indexMap[parent] for parent in self.variables[ self.revIndexMap[variable.index]]['parents'] ] variable.known = sorted(variable.known) variable.parents = dict([(i, net.variables[i]) for i in variable.known]) net.dirty = True net.computeEdgeStatistics() """ for variable in net.variables: print "(var ", self.revIndexMap[variable.index], " (", " ".join(variable.categories[variable.index]), "))" for variable in net.variables: print "(parents ", self.revIndexMap[variable.index], " (", " ".join([self.revIndexMap[i] for i in variable.known]), ") " for key, val in variable.tables.iteritems(): if key == "": expanded = "" else: cfg = array([int(num) for num in key.split(",")]) expanded = " ".join(self.variables[self.revIndexMap[variable.known[k]]]['vals'][c-1] for k,c in enumerate(cfg)) total = val.sum() vals = " ".join([str(i) for i in val]) print "((", expanded, ") ", vals, (1. - total), ")" print ")" """ return net
def test_obj_convolve(): DE2 = DE[Config(populationSize=11, space=space)] dede = DE2() << DE2() assert isinstance(dede, Convolution) assert dede.config.populationSize == 1 assert len(dede.subs) == 2 de = dede.subs[0] assert de.config.populationSize == 1 assert isinstance(de, DE2) run(dede) assert isinstance(dede.history, CheckpointedMultipleHistory) assert isinstance(de.history, GeneratingSetSearchHistory) assert dede.history.evals == de.history.evals
def test_base(self): cfg = Config() history = History(cfg) self.assertEqual(history.evals, 0) self.assertEqual(history.printEvery, 1000000000000L) self.assertEqual(history.updates, 0) self.assertEqual(history.minScore, np.inf) self.assertTrue(history.minSolution is None) self.assertEqual(history.maxScore, -np.inf) self.assertTrue(history.maxSolution is None) self.assertTrue(history.empty()) self.assertTrue(history.config is cfg) self.assertTrue(history.cache is not None)
class Proportional(GeneralizedProportional): """ Fitness proportional selection (roulette wheel selection). See <http://en.wikipedia.org/wiki/Fitness_proportionate_selection>. Fitness values must be nonnegative. This :class:`Selection` method is just :class:`GeneralizedProportional` with a modulating function """ config = Config( modulator=lambda s, i, cfg: cfg.minimize and -abs(s) or abs(s))
class Crossover(PopulationDistribution): """ Performs recombination using a crossover policy given by a :class:`Crosser` instance. Config parameters * crossoverProb -- A probability that is sampled; if a bernouli test on this value fails, then no crossover is performed, and the first selected organism is chosen * order -- The number of organisms to crossover at each step * crosser -- A :class:`Crosser` to perform the crossover """ config = Config( crossoverProb=1.0, # probability of performing crossover order=2, # number of solutions to crossover crosser=UniformCrosser, # descendant of Crosser history=MultiStepMarkovHistory) # num steps must match order def __init__(self, **kwargs): super(Crossover, self).__init__(**kwargs) self.dual = (hasattr(self.config.crosser, 'dual') and self.config.crosser.dual) def compatible(self, history): return (hasattr(history, 'populations') and hasattr(history, 'order') and history.order() == self.config.order) def batch(self, popSize): if self.dual: psize = popSize / 4 else: psize = popSize crossoverProb = self.config.crossoverProb if crossoverProb < 1e-16: return [x for x, s in self.history.populations[0]] self.crosser = self.config.crosser(self.config) pops = self.history.populations newpop = [ self.crosser([org for org, s in orgs], crossoverProb) for orgs in zip(*pops) ] if self.dual: pop = [] for x, y in newpop: pop.append(x) pop.append(y) newpop = pop return newpop
class AddNodeMutation(Mutation): """Add a node to a neural network. Config parameters: * node_creation_prob - The probability of adding a node * sd - The standard deviation for the Gaussian used to make weights for any new nodes """ config = Config(node_creation_prob=.01, sd=1.0) def mutate(self, net): if net.changed: return net p = self.config.node_creation_prob if np.random.random_sample() > p: return net sd = self.config.sd hiddenLayers = net.hiddenLayers() if len(hiddenLayers) == 0: return net net = self.config.space.copy(net) layer = net.hiddenLayers()[np.random.randint(0, len(hiddenLayers))] layer.size += 1 for inLayer in layer.inLinks: w0 = net.links[(inLayer, layer)] w = np.zeros((layer.size, inLayer.size)) if inLayer == layer: w[:(layer.size - 1), :(layer.size - 1)] = w0 w[:, layer.size - 1] = sd * np.random.randn(layer.size) else: w[:(layer.size - 1), :] = w0 w[layer.size - 1, :] = sd * np.random.randn(inLayer.size) net.connect(inLayer, layer, w) for outLayer in layer.outLinks: if outLayer == layer: continue # we already did this! w0 = net.links[(layer, outLayer)] w = np.zeros((outLayer.size, layer.size)) w[:, :(layer.size - 1)] = w0 w[:, layer.size - 1] = sd * np.random.randn(outLayer.size) net.connect(layer, outLayer, w) net.changed = True #net.checkLinks() return net
class TournamentAnnealing(Annealing): """ Tournament Annealing, as described in Chapter 11 of <http://nn.cs.utexas.edu/downloads/papers/lockett.thesis.pdf> See :class:`Annealing` for more details. """ config = Config(pressure=0.025) def sample(self): return Point.sampleTournament(self.history.segment, self.history.temperature(), self.config)
def test_obj_convex(): DE2 = DE[Config(populationSize=13, space=space)] de = .1 * DE2() + .6 * DE2() assert isinstance(de, Convex) assert len(de.subs) == 2 assert de.subs[0].weight == .1 assert de.subs[1].weight == .6 assert isinstance(de.subs[0], DE2) assert isinstance(de.subs[1], DE2) run(de) assert isinstance(de.history, MultipleHistory) assert isinstance(de.subs[0].history, GeneratingSetSearchHistory) assert isinstance(de.subs[1].history, GeneratingSetSearchHistory) assert de.subs[0].history.evals == de.history.evals
class ExponentiatedProportional(GeneralizedProportional): """Fitness propotional selection, but with an exponentional modulationg function so that any fitness values may be used. $p(x) = \exp(\frac{-f(x)}{T})$ Config parameters: * T -- A "temperature" to divide the explicand. """ config = Config( T=1.0, # a temperature value modulator=lambda s, i, cfg: cfg.minimize and np.exp(-s / cfg.T) or np. exp(s / cfg.T))
def test_obj_truncate_convolve(): DE2 = DE[Config(populationSize=19, space=space)] det = DE2() >> DE2() assert isinstance(det, Convolution) assert isinstance(det.subs[0], DE) assert isinstance(det.subs[1], TrajectoryTruncation) assert det.subs[1].delay == 1 assert isinstance(det.subs[1].opt, DE) run(det) assert isinstance(det.history, CheckpointedMultipleHistory) assert isinstance(det.subs[0].history, GeneratingSetSearchHistory) assert isinstance(det.subs[1].history, DelayedHistory) assert isinstance(det.subs[1].opt.history, GeneratingSetSearchHistory) assert det.history.evals == det.subs[0].history.evals assert det.subs[1].history.evals == det.history.evals - space.dim - 1
class StructureMutator(Mutation): config = Config(varDecay=1.0, varExp=0.25) def __init__(self, **kwargs): super(StructureMutator, self).__init__(**kwargs) self.decay = 1.0 def mutate(self, net): net = self.config.space.copy(net) net.decay = self.decay return net.structureSearch(net.structureGenerator.config.data) def update(self, history, fitness): super(StructureMutator, self).update(history, fitness) self.decay = exp(-self.history.updates * self.config.varDecay)**self.config.varExp
def parse(self, fname): f = open(fname) totalLine = "" done = False for line in f: totalLine += line lefts = len(totalLine.split("(")) rights = len(totalLine.split(")")) if lefts == rights: self.processLine(totalLine) totalLine = "" categories = [[]] * self.index for name, idx in self.indexMap.iteritems(): categories[idx] = self.variables[name]['vals'] cfg = Config() cfg.numVariables = len(self.variables) cfg.variableGenerator = MultinomialVariableGenerator(categories) cfg.randomizer = MultinomialRandomizer() cfg.sampler = DAGSampler() cfg.structureGenerator = StructureProposal(cfg) net = BayesNet(cfg) for variable in net.variables: variable.tables = self.variables[self.revIndexMap[variable.index]]['cpt'] #print names[variable.index], self.variables[self.revIndexMap[variable.index]]['parents'] variable.known = [self.indexMap[parent] for parent in self.variables[self.revIndexMap[variable.index]]['parents']] variable.known = sorted(variable.known) variable.parents = dict([(i, net.variables[i]) for i in variable.known]) net.dirty = True net.computeEdgeStatistics() """ for variable in net.variables: print "(var ", self.revIndexMap[variable.index], " (", " ".join(variable.categories[variable.index]), "))" for variable in net.variables: print "(parents ", self.revIndexMap[variable.index], " (", " ".join([self.revIndexMap[i] for i in variable.known]), ") " for key, val in variable.tables.iteritems(): if key == "": expanded = "" else: cfg = array([int(num) for num in key.split(",")]) expanded = " ".join(self.variables[self.revIndexMap[variable.known[k]]]['vals'][c-1] for k,c in enumerate(cfg)) total = val.sum() vals = " ".join([str(i) for i in val]) print "((", expanded, ") ", vals, (1. - total), ")" print ")" """ return net