Welcome to this fourth part of building Neural Networks evolved by Genetic Algorithms. If you haven’t done so yet, I would advise to read the first, second and third parts before reading this article.
Basics about Genetic Algorithms
So, what exactly is a Genetic Algorithm? The name applied to Computer Science sounds both scary and mysterious.
However, it is actually quite simple and refers to Darwin’s theory of evolution which can be summed up in one simple sentence: “The fittest individuals in a given environment have a better chance than other individuals of surviving and having an offspring that will survive”. Additionally, the offspring carries a mutated crossover version of the genes of the parents.
Given this general definition, the transposition to Computer Science is the following:
- design “individuals” to solve a particular problem, whose parameters are in the form of a “chromosome”, most of the time a simple array of bits,
- create a “population” of these individuals,
- evaluate all individuals in the population and sort them by their fitness (e.g. how close they get to solving the problem well),
- create new individuals by making crossovers and mutations on the best individuals of the current generation,
- replace the worst individuals in the population by those new individuals,
- rinse and repeat: go back to evaluating all individuals in the population.
With this in mind, the choice in part 3 to store our neural networks in simple arrays comes into a new light: those arrays are the chromosomes of our individuals.
Our Genetic Algorithm
The genetic algorithm I built went through several phases already.
Here is the first phase:
- generate n individuals, each representing a player, note that players may also be non Neural-Network-driven players or players using different types of NNs, we can actually mix players of different types in the population,
- make them play against each other a certain number of games,
- select the ones with more wins and discard the others,
- replace the discarded players either by new random players, either by mutations and crossovers of the best players.
- go back to step 2.
Still, with this simple algorithm, there are many possible parameters:
- the population size,
- how many individuals to keep? Keep the top x %? Or all the ones managing to have a certain score?
- how much mutation and crossover percentage should be applied?
- how many games to play to get a good confidence score on every player?
The supporting UI
To evaluate the impacts of these parameters, I then built an UI on top of this to observe the evolution of the population and see how the best individuals evolve. The UI is made of a simple table, sorted by “performance” (that’s to say the percentage of wins). Every row shows one individual, its ranking, its age (since a single individual can survive for multiple generations), its original parent, and the number of total mutated generations it comes from.
Later, I also added at the bottom of the screen the progression of the score of the best individuals.
Here is a simple screenshot:
The green part represents the individuals that will be selected for the next generation. All individuals in red will be discarded. The gray ones are non NN implementations that can be used as “benchmarks” against the NN implementations. When the first population is created randomly, they are generally beaten very easily by those standard implementations, but we can see that after some iterations, the NNs selected one generation after the other end up beating those standard implementations. We’ll dig into that in the next post, along with the choice of the different parameters.
Next comes part 5.