Source code for pyec.distribution.cmaes

from numpy import *
from numpy.linalg import cholesky, eig, inv, det
from basic import PopulationDistribution, FixedCube, Gaussian
from pyec.config import Config, ConfigBuilder


[docs]class CmaesConfigurator(ConfigBuilder): """ A :class:`ConfigBuilder` object to construct the Correlated Matrix Adaption Evolution Strategy (CMA-ES). By default, sets the ratio of `mu` over `lambda` to .5 """ def __init__(self, *args): super(CmaesConfigurator, self).__init__(Cmaes) self.cfg.muProportion = .5 self.cfg.restart = True def postConfigure(self, cfg): if cfg.varInit is None: cfg.initialDistribution = FixedCube(cfg) else: cfg.usePrior = False cfg.initialDistribution = Gaussian(cfg)
[docs]class Cmaes(PopulationDistribution): """ The Correlated Matrix Adaptation Evolution Strategy algorithm as described by: Hansen, N. and Ostermeier, A. Completely derandomized self-adaptation in evolution strategies. In Evolutionary Computation 9(2), pp 159--195, 2001. See <http://en.wikipedia.org/wiki/CMA-ES> for details. A fast-converging population-based optimizer that finds reasonably good solutions with relatively few resources. In general, each population is sampled from a multi-variate Gaussian whose parameters are altered to optimize the probability of finding good solutions. Akimoto (2010) proved that CMA-ES is an instance of Natural Evolution Strategies (Wierstra, 2008), which implements a gradient-following method at the population level. Config parameters: * muProportion -- the ratio of `mu` to `lambda`, i.e. the proportion of solutions from each generation used as parents for the next. By default equal to 0.5. * initialDistribution -- the distribution used to sample the initial mean * populationSize -- the size of the population for each generation * scale -- the scale of the space, used to initialize the variance of the Gaussian * dim -- the dimension of the real vector space being searched. * restart -- whether to restart when the distribution collapses; defaults to ``True`` (see e.g. `Auger and Hansen, 2005<http://www.google.com/url?sa=t&rct=j&q=ipop%20cma%20es&source=web&cd=1&ved=0CE0QFjAA&url=http%3A%2F%2Fciteseerx.ist.psu.edu%2Fviewdoc%2Fdownload%3Fdoi%3D10.1.1.97.8108%26rep%3Drep1%26type%3Dpdf&ei=xBT_T5aHNueG2gX8o8XRBA&usg=AFQjCNFKgH1YeysEfsu08_-AZa0JXhRq8A>`_ :params cfg: The configuration object for CMA-ES. :type cfg: :class:`Config` """ def __init__(self, cfg): super(Cmaes, self).__init__(cfg) self.sigma = self.config.scale * .5 self.mean = self.config.initialDistribution() self.ps = zeros(self.config.dim) self.pc = zeros(self.config.dim) self.mu = int(self.config.populationSize * self.config.muProportion) weights = log(self.mu + .5) - log(array([i+1. for i in xrange(self.mu)])) weights /= sum(weights) muw = 1. / sum(weights ** 2) mueff = (sum(weights) ** 2) * muw cc = (4+mueff/self.config.dim) / (self.config.dim + 4 + 2 * mueff / self.config.dim); cs = (mueff + 2) / (self.config.dim + mueff + 5.) c1 = 2. / ((self.config.dim + 1.3)**2 + mueff) cmu = 2 * (mueff -2 + 1./mueff) / ((self.config.dim + 2.) ** 2 + mueff) damps = 1 + 2*max([0, sqrt((mueff-1)/(self.config.dim + 1)) - 1]) + cs self.weights = weights self.muw = muw self.mueff = mueff self.cc = cc self.cs = cs self.c1 = c1 self.cmu = cmu self.damps = damps self.chiN = sqrt(self.config.dim) * (1. - 1. / (4 * self.config.dim) + 1. / (21. * self.config.dim * self.config.dim)) self.eigeneval = 0 self.B = eye(self.config.dim, self.config.dim) self.D = ones(self.config.dim) self.covar = dot(self.B, dot(diag(self.D ** 2),self.B.transpose())) self.active = dot(self.B, dot(diag(1./self.D),self.B.transpose())) @classmethod def configurator(cls): return CmaesConfigurator(cls) def __call__(self): next = self.mean + self.sigma * dot(self.active, random.randn(self.mean.size)) return next def batch(self, popSize): if self.mean is None: if hasattr(self.config.initialDistribution, 'batch'): return self.config.initialDistribution.batch(popSize) else: return [self.config.initialDistribution() for i in xrange(popSize)] return [self.__call__() for i in xrange(popSize)] def density(self, x): diff = self.mean - x pow = -.5 * dot(diff, dot(inv(self.covar), diff)) d = ((((2*pi)**(len(x))) * det(self.covar)) ** -.5) * exp(pow) return d @property def var(self): return self.sigma * det(self.covar) def update(self, n, population): base = array([x for x, s in population[:self.mu]]) if self.mean is None: oldMean = average([x for x,s in population]) else: oldMean = self.mean self.mean = (outer(self.weights, ones(self.config.dim)) * base).sum(axis=0) cc = self.cc cs = self.cs muw = self.muw mueff = self.mueff chiN = self.chiN self.ps *= (1. - cs) self.ps += sqrt(cs * (2. - cs) * mueff) \ * dot(self.active, self.mean - oldMean) / self.sigma isonorm = sqrt((self.ps ** 2).sum()) hsig = isonorm / sqrt(1 - (1 - cs) ** (2. * n / len(population))) / self.chiN self.pc *= (1 - cc) if hsig < 1.4 + 2/(self.config.dim + 1): self.pc += sqrt(cc * (2 - cc) * mueff) * (self.mean - oldMean) / self.sigma matfactor = outer(self.pc, self.pc) else: matfactor = outer(self.pc, self.pc) + cc * (2 - cc) * self.covar artmp = (base - oldMean) / self.sigma oldCovar = self.covar self.covar *= (1 - self.c1 - self.cmu) self.covar += self.c1 * matfactor self.covar += self.cmu * dot(artmp.transpose(), dot(diag(self.weights),artmp)) self.sigma *= exp((cs / self.damps) * (isonorm / chiN - 1.)) if n - self.eigeneval > len(population) / (self.c1 + self.cmu) / self.config.dim / 10.: try: self.eigenval = n self.covar = triu(self.covar) + triu(self.covar,1).transpose() self.B, self.D = eig(self.covar) self.D = sqrt(diag(abs(self.D))) self.active = cholesky(self.covar) # dot(self.B, dot(diag(1. / self.D), self.B.transpose())) except: self.covar = oldCovar if self.sigma > self.config.scale: self.sigma = self.config.scale detcv = det(self.covar) if self.sigma * detcv > self.config.scale: self.sigma /= (detcv * self.sigma) self.sigma *= self.config.scale if self.config.restart and self.sigma * detcv < 1e-25: # print "restarting" self.__init__(self.config)