Some time ago, I promised more details about the support for island models that was added to the Watchmaker Framework for Evolutionary Computation in version 0.7.0. I’ve finally completed a first draft of some documentation for this feature, attempting to cover both the motivation for this approach to evolutionary algorithms and the practicalities of implementing this idea in your own programs that use Watchmaker. If anything is unclear or wrong please leave suggestions for improvements in the comments.
In the natural world, populations of organisms might be separated by geography. Left to evolve in isolation over millions of years, vastly different species will occur in different locations. Consider Australia, an island continent protected by its seas. With little opportunity for outside organisms to interfere, and few opportunities for its land-based organisms to migrate to other land masses, Australian wildlife evolved to be distinctly different from that of other continents and countries. The majority of Australia’s plant and animal species, including 84% of its mammals, are endemic. They occur nowhere else in the world.
Australia is not the only island to exhibit such levels of endemism. It was a visit to the Galápagos Islands in 1835 that started Charles Darwin on the path to formulating his theory of evolution. Darwin noticed the pronounced differences between the species of mocking birds and tortoises present on the different islands of the archipelago and began to speculate on how such variations might have occurred.
In the world of evolutionary computation we can mimic this idea of having multiple isolated populations evolving in parallel. Having additional populations would increase the likelihood of finding a solution that is close to the global optimum. However, it is not just a question of having a larger global population. A system of 10 islands each with a population of 50 individuals is not equivalent to a single island with a population of 500. The reason for this is that the island system partitions the search. If one island prematurely converges on a sub-optimal solution it does not affect the evolution happening on the other islands; they are following their own paths. A single large population does not have this in-built resilience.
There is of course no real difference between evolving 10 completely separate islands in parallel and running the same single-population evolution 10 times in a row, other than how the computing resources are utilised. In practice the populations are not kept permanently isolated from each other and there are occasional opportunities for individuals to migrate between islands.
In nature external species have been introduced to foreign ecosystems in several ways. In an ice age the waters that previously separated two land masses might freeze providing a route for land animals to migrate to previously unreachable places. Microorganisms and insects have often strayed beyond their usual environment by hitching a ride with larger species.
The effect of introducing a foreign species to a new environment can vary. The new species might be ill-adapted to its new surroundings and quickly perish. Alternatively, a lack of natural predators may cause it to flourish, often to the detriment of indigenous species. One such example is the introduction of rabbits to Australia. Australia was a land without rabbits until the arrival of European settlers. An Englishman named Thomas Austin released 24 rabbits into the wild of Victoria in October 1859 with the intention of hunting them. If rabbits are famous for one thing it is for reproducing prodigiously. The mild winters allowed year-round breeding and the absence of any natural rabbit predators, such as foxes, allowed the Australian rabbit population to explode unchecked. Within 10 years an annual cull of two million rabbits was having no noticeable effect on rabbit numbers and the habitats of some native animals were being destroyed by the floppy-eared pests. Today there are hundreds of millions of rabbits in Australia, despite efforts to reduce the population, and the name of Thomas Austin is widely cursed for his catastrophic lack of foresight.
While such invasions of separate species provide a useful analogy for what can happen when we introduce migration into island model evolutionary algorithms, we are specifically interested in the effects of migration involving genetically different members of the same species. This is because, in our simplified model of evolution, all individuals are compatible and can reproduce. The island model of evolution provides the isolation necessary for diversity to thrive while still providing opportunities for diverse individuals to be combined to produce potentially fitter offspring.
In an island model, the isolation of the separate populations often leads to different traits originating on different islands. Migration brings these diverse individuals together occasionally to see what happens when they are combined. Remember that, even if the immigrants are weak, cross-over can result in offspring that are fitter than either of their parents. In this way, the introduction to the population of new genetic building blocks may result in evolutionary progress even if the immigrants themselves are not viable in the new population.
Islands in the Watchmaker Framework
The Watchmaker Framework for Evolutionary Computation supports islands models via the
IslandEvolution class. Each island is a self-contained
EvolutionEngine just like those we have been using previously for single-population evolutionary algorithms. The evolution is divided into epochs. Each epoch consists of a fixed number of generations that each island completes in isolation. At the end of an epoch migration occurs. Then, if the termination conditions are not yet satisfied, a new epoch begins.
IslandEvolution supports pluggable migration strategies via different implementations of the
Migration interface. An island version of the string evolution example from this previous article might look something like this:
IslandEvolution engine = new IslandEvolution(5, // Number of islands. new RingMigration(), candidateFactory, evolutionaryOperator, fitnessEvaluator, selectionStrategy, rng); engine.evolve(100, // Population size per island. 5, // Elitism for each island. 50, // Epoch length (no. generations). 3, // Migrations from each island at each epoch. new TargetFitness(0, false));
We can add listeners to an
IslandEvolution object, just as we can with individual
EvolutionEngines. We use a different interface for this though,
IslandEvolutionObserver, which provides two call-backs. The
populationUpdate method reports the global state of the combined population of all islands at the end of each epoch. The
islandPopulationUpdate method reports the state of individual island populations at the end of each generation.
In the example code above we specified how many islands we wanted to use and the
IslandEvolution class created one
GenerationalEvolutionEngine for each island. Using this approach all of the islands have the same configuration; they use the same candidate factory, evolutionary operator(s) and selection strategy. This is the easiest way to create an island system but it is also possible to construct each island individually for ultimate flexibility.
List> islands = new ArrayList>(); // Create individual islands here and add them to the list. // ... IslandEvolution engine = new IslandEvolution(islands, new RingMigration(), false, // Natural fitness? rng);
One reason you might choose to construct the islands explicitly is that it makes it possible to configure individual islands differently. You may choose to have different islands use different parameters for evolutionary operators, or even to use different evolutionary operators all together. Alternatively, you could use the same evolutionary operators and parameters but have different selection strategies so that some islands have stronger selection pressure than others. You should generally use the same fitness function for all islands though, otherwise you might get some strange results.
Another possible reason for creating the islands explicitly is so you don’t have to use the standard
GenerationalEvolutionEngine for the islands. You can choose to use any implementation of the
EvolutionEngine interface, such as the
SteadyStateEvolutionEngine class or the
EvolutionStrategyEngine class. You can even use a mixture of different island types with the same
This is the fourth entry in a short and increasingly infrequent series of articles on practical Evolutionary Computation, based on the work-in-progress documentation for the Watchmaker Framework for Evolutionary Computation. The first article provided an introduction to the field of evolutionary computation, the second article showed how to implement a simple evolutionary algorithm using the Watchmaker Framework, and the third article briefly discussed the concept of elitism in evolutionary algorithms.