In developing the Heuristic Optimisation Platform (HOP, paper here), I hand-coded various metaheuristics (algorithms) including Particle Swarm Optimisation and Genetic Evolution. These can get quite involved, but of course a lot can be learned from such deep dive. HOP also has a hyper-heuristic feature, which can be thought of as a metaheuristic to select other algorithms from a low-level metaheuristic pool. I wanted a way of rapidly expanding the catalogue of metaheuristics available to the hyperfeature, and the Inspyred python package gives me just that capability.
Consider the following stand-alone, compact Inspyred example implementing an Evolutionary Strategy (ES) algorithm.
from random import Random
from time import time
import inspyred
def main(prng=None, display=False):
if prng is None:
prng = Random()
prng.seed(time())
problem = inspyred.benchmarks.Rosenbrock(2)
ea = inspyred.ec.ES(prng)
ea.terminator = [inspyred.ec.terminators.evaluation_termination,
inspyred.ec.terminators.diversity_termination]
final_pop = ea.evolve(generator=problem.generator,
evaluator=problem.evaluator,
pop_size=100,
bounder=problem.bounder,
maximize=problem.maximize,
max_evaluations=30000)
if display:
best = max(final_pop)
print('Best Solution: \n{0}'.format(str(best)))
return ea
if __name__ == '__main__':
main(display=True)
Now with the Inspyred ES implemented in HOP below. Note that for the parameters observor (monitoring the evolution), generator (creates random problem solution) and evaluator (the fitness function evaluating a problem solution), Inspyred will use my own custom methods written in a wrapper class.
from optimizers.optimizer import Optimizer
import inspyred
from optimizers.inspyred_wrapper import InspyredWrapper
class ES(Optimizer):
def __init__(self, **kwargs):
Optimizer.__init__(self, **kwargs)
def optimize(self):
self.evolve()
def evolve(self):
es = inspyred.ec.ES(self.random)
es.observer = InspyredWrapper.observer
es.terminator = [inspyred.ec.terminators.evaluation_termination,
inspyred.ec.terminators.diversity_termination]
final_pop = es.evolve(generator=InspyredWrapper.generator,
evaluator=InspyredWrapper.evaluator,
pop_size=self.hj.initial_pop_size,
maximize=False,
max_evaluations=self.hj.budget,
slf=self)
final_pop.sort(reverse=True)
self.hj.rbest.fitness = final_pop[0].fitness
# Inspyred ES extends candidate with strategy elements, slice for actual solution cand. associated with fitness
self.hj.rbest.candidate = final_pop[0].candidate[:self.hj.pid_cls.n]
self.hj.rft = list(set(self.hj.rft)) # Remove duplicates
self.hj.rft.sort(reverse=True)
The custom wrapper class is shown below, and is common for all Inspyred algorithms I want to include in HOP. This allows for very rapid integration and expansion of the low-level heuristic pool of available metaheuristics.
import copy
class InspyredWrapper:
def __init__(self):
pass
@staticmethod
def generator(random, args):
if args['slf'].hj.population:
candidate = copy.deepcopy(args['slf'].hj.population[0].candidate)
args['slf'].hj.population.pop(0)
else:
candidate = args['slf'].get_generator()(lb=args['slf'].hj.pid_lb, ub=args['slf'].hj.pid_ub)
return candidate
@staticmethod
def evaluator(candidates, args):
fitness = []
for c in candidates:
if isinstance(c[0], float) and args['slf'].hj.pid_type == 'combinatorial':
c = args['slf'].hj.pid_cls.candidate_spv_continuous_to_discrete(c)
f, _ = args['slf'].hj.pid_cls.evaluator(c)
fitness.append(f)
args['slf'].hj.budget -= 1
return fitness
@staticmethod
def observer(population, num_generations, num_evaluations, args):
if args['slf'].hj.oid_cls.__class__.__name__ == 'DEA':
ft = [o.fitness for o in population]
args['slf'].hj.rft.extend(ft)
else:
# Persist best fitness as population evolves. Note use of max is correct irrespective of max or min problem,
# as Inspyred knows which type of problem the heuristic is instantiated with
best = max(population)
args['slf'].hj.rft.append(round(best.fitness, 2))
args['slf'].hj.rft.sort()
if args['slf'].hj.rft[0] < args['slf'].hj.rbest.fitness:
args['slf'].hj.rbest.fitness = args['slf'].hj.rft[0]
if not args['slf'].fromhyper:
args['slf'].hj.iter_last_imp[args['slf'].hj.run] = args['slf'].hj.budget_total - args['slf'].hj.budget
args['slf'].hj.imp_count[args['slf'].hj.run] += 1