Reverse Arrow of Time with Genetic Algorithm and GPU
By: Eric Bouteillon
Tags: Pytorch, Machine Learning, Artificial Intelligence, Programming, Kaggle

How does a Genetic Algorithm work?

Genetic algorithms are among the most fascinating techniques for optimizing problems. They draw inspiration from Charles Darwin’s theory of natural evolution.

How the genetic algorithm works:

First, the algorithm creates a random initial population of individuals. Scores each individual of the current population by computing how well it solves the problem. Selects individuals, called parents, based on their scores. Some of the individuals in the current population that have a lower score are chosen as elite. These elite individuals are passed to the next population. Produces children from the parents. Children are produced either by making random changes — mutation — or by combining the vector entries of a pair of parents — crossover. Replaces the current population with the children to form the next generation. Return to step 2 unless we meet a stopping criterion.

For this competition, individuals considered are potential boards at their initial state. To take full advantage of pytorch, the population is stored in a single boolean tensor with dimensions (number_of_individuals, N, N) where N is the dimension of a board; N=25 for this competition.

Random initial population

Create a random initial population made of n_parents boards of size NxN . I also included a hyper-parameter to set how many cells are live (20% in sample code below).

Score individuals

The competition metric is defined in the evaluation section of the competition:

You are evaluated on the mean absolute error of your predictions, stepped forward by the specified, and compared to the provided ending solution.

In other words, to score an individual, we need to apply the Game Of Life rules to this board and count how many cells are different than the target solution.

Note: each cell is expressed with a boolean, therefore computing the absolute error for each cell between the input (board after applying the game of life rules) and the target can be done with a single xor . Then we can compute the score for all individuals within the population with a sum on only the two last two tensor’s dimensions.

Apply Game Of Life rules

The first step is to determine for each cell how many of its direct neighbors are lives. For counting neighbors, we are using a simple 2D-convolution on the whole board.

Notes:

I am using float16 (mixed precision) as it offers a x2 speedup on RTX NVIDIA GPU. int8 would offer a x4 speedup but at time of writing, pytorch did not support convolution with this type.

Crossover

We are combining the genetic information of two parents to create a new individual. Choosing randomly cells between the two parents was less efficient than replacing a complete region of one parent with cells from another parent. That is the reason why I am using masks of random 17x17 squares. Cells outside the square are taken from the first parent and cells inside the square are cells from the second parent. To improve speed, 625 masks are generated in advance and kept in GPU memory and are randomly chosen for each new individual.

Mutation

We randomly flip cells of parents from live to death and conversely, with a probability of MUTATION .

Results

The results of this implementation are pretty good as it reaches 9th position in the competition leader-board by running less than 4 hours on my computer’s GPU (9 hours on Kaggle’s GPU). The score continues to improve if we run the algorithm longer (for instance by increasing the population size).

The implementation is also memory efficient as we can grow the population up to 1 million individuals on a 11GB GPU.

Conclusion

We saw how to implement from scratch a genetic algorithm on GPU using pytorch. It is a pretty performant solution both in term of speed and results.

I learned a lot along the way about Genetic Algorithm, pytorch and how to optimize computation on the GPU.

I would like to thank my amazing teammates: Konstantin, Robert and Maximiliano for their great approaches that push the team to the second position.

Complete Source Code

Implementation is available as a Kaggle notebook:

As well as on my github repository:

References