Hyperparameter Optimization with Genetic Algorithms - A Hands-On Tutorial
A step-by-step tutorial of using genetic algorithms for optimization tasks.
A step-by-step tutorial of using genetic algorithms for optimization tasks

This post introduces an optimization strategy inspired by the realm of genetics and the process of natural selection, as the name of genetic algorithms suggests - let's call them GAs going forward.
We will formally define how GAs work, but let's first qualitatively try to describe the process, which sounds just like natural selection. As we all recall from biology, natural selection is the nature's way of choosing which traits will be passed on to the next generation, which results in the gradual evolution. With that context in mind, the overall GA process can be broken down into 6 smaller steps:
Start Somewhere ("Initialization"): Let's say there is a problem we would like to solve and we do not really know what the solution is. We can just randomly start with some solutions, which collectively we will call the "Population" - and then we can later on evaluate each of the individual solutions within the population. We will represent each solution with a "Chromosome".
Evaluate Existing Solutions ("Evaluation"): Now that we have started with some randomly-selected solutions, we will just measure how good or bad these solutions (or Chromosomes) are. The function that we will use to evaluate each solution will be called the "Fitness Function" and the evaluation results for each solution will be called a "fitness score".
Selection: Since we have the fitness scores for each of the solutions, we will go ahead and choose the best ones. We are going to call the selected solutions the "Parents", since we will use these parents to create the next generation of solutions, which we will call "Children" or "Offsprings". The weaker solutions that were not selected will be removed, similar to how natural selection works in evolution.
Reproduction ("Crossover"): As you probably guessed by now, the surviving "Parents" will use their "Genes" (defined as part of the solution or Chromosome) to create the next set of solutions ("Children"). As the name "Crossover" suggests, we indeed combine genes from two parents to produce an "Offspring", which will be the next generation of solutions. The idea is that regenerating based on the winning solutions that were selected in the "Selection" step will get us slightly and gradually closer to better outcomes - we will review this improvement trend with data in our example!
Mutation: Similar to nature and in order to explore more possibilities, some of the children will have random new changes so that our population continues evolving. This in fact happens in our biological genes as well so there is some randomness built into natural systems, such as us humans!
Repeat ("Evolution"): We then evaluate the new population with our fitness function and repeat steps 2 to 6 multiple times to evolve and approach better solutions.
Now that we understand the overall process, let's look at how we can implement this in Python. We are going to use genetic algorithms as a hyperparameter optimization methodology. Hyperparameter optimization, as the name suggests, is the process of identifying the best combination of hyperparameters for a machine learning model to satisfy an optimization function (i.e. maximize the performance of the model, given the dataset in study).
Let's better understand this during the implementation!
1. Implementation
In order to implement genetic algorithms , we are going to walk through two examples - a simple one just to better understand the process and then a more complicated problem, which is a better representative of what this optimization methodology can be used for in practice.
1.1. Implementation - Simple Problem
Let's first define an optimization problem. We will then look for optimization it using GA:
Objective: Maximize the function below, when x is between 0 and 1, inclusive:

This is an appropriate function for optimization, because it has multiple local maximums or maxima (and minimums or minima) in the above range so we want to make sure our optimization strategy does not get stuck in a local maximum. Let's visualize the space to better demonstrate the point:
Results:

As we can see above, this function has multiple local maximums in the range above so let's see if GA can find a good optimization path. Note that we didn't really need the "random" library that I imported above but since we will be using it later, I just added it in.
Let's start with defining the GA in a chronological order: Fitness Function: Fitness Function is how we evaluate each solution in the population and therefore it would be similar to the objective function we defined for our problem. So let's go ahead and define the same function as the Fitness Function:
Generate Population: As discussed earlier, we will come up with a random set of solutions as the first "Population" so let's create that as follows:
Fitness Score: Now that we have the population and the fitness function, we can go ahead and evaluate each solution within the population, as measured by "Fitness Score". This function basically just runs the "fitness_function" on our population:
Selection: Now it's time for us to simulate the natural selection of keeping good solutions and discarding the bad ones, through our "selection" function. This function receives three variables of population, fitness score and the number of parents. Then it randomly selects two members of the population, compares their fitness scores and keeps the one with the higher score in the "parents" list, which is roughly how natural selection works.
Crossover: As you recall from the overview of GA, once we select the better solutions as parents, we want to create offsprings by combining two randomly-selected parents. The function below does just that:
Mutation: This function adds some randomness to our system! Recall that in nature, some levels of randomness get introduced at the genes level. This is our genes' natural way of trying new ideas, exploring and evolving. We will also replicate this process by adding some randomness to our population during a "mutation" process:
GA Optimization: Now that we have defined the functions required for GA, we can start implementing our GA optimization. We are going to make some assumptions to define the parameters of the optimization.

That looks pretty good! Looking at the diagram that we had earlier, we can see where the actual maximum happens and what the objective function's value at that location is. Looks like the approach is working and we are finding the best fit over generations.
Now that we know how GA works, let's move on to another example and compare the GA optimization outcome to Random Search, which is another method of optimization.
1.2. Implementation - Neural Networks
For this example, we are going to use the Fashion MNIST data set, which includes 70,000 28*28 labeled grayscale images, available under MIT License. This data set is used for benchmarking machine learning algorithms, which makes it a great one for evaluating our optimization approach. We will first run the optimization using GA and then run the same optimization with a Random Search function for comparison to the GA approach.
1.2.1. Neural Network - GA Approach
The overall approach is similar to what we did for the previous example so instead of providing step-by-step descriptions, I will just add descriptive comments in the code.
Results:

The GA optimization above took about 4.5 minutes on my laptop. What is more interesting than the final results, is the visualization above. As you can see, GA is learning at each step of the way through the natural selecting process of keeping the winning members of the population and discarding the rest and therefore with more iterations, it is getting closer by improving the average accuracy.
Now that we have the results above, let's implement the same optimization problem, using the Random Search approach and compare the results.
1.2.2. Neural Network - Random Search Approach
Random Search is a popular and yet simple optimization approach. I have discussed this approach in detail in a separate post linked below:
Hyperparameter Optimization - Intro and Implementation of Grid Search, Random Search and Bayesian...
I will not go into details in the current post but generally, Random Search looks at the "Search Space", which is the universe of values for all the hyperparameters involved in an optimization task and then randomly selects a set of hyperparameters and calculates the objective function for the randomly-selected set. Then it simply picks the best combination, based on the objective function. What is important is that Random Search, unlike GA, does not learn from each iteration and rather starts from scratch every time and randomly selects the parameters. Therefore, if it gets lucky during the random selection, it can find a very good combination but in more complex search spaces with a large search space, such as this example, finding a good combination becomes less likely. On the other hand, Random Search can be very computationally light since it only calculates the objective function for a fixed number of times, instead of learning through iteration that GA does. So in conclusion, each approach has its pros and cons.
Let's implement the Random Search approach and look at the results
Results:

As you can see, Random Search can come across good results and also poor ones, since it just randomly selects the hyperparameters. Using the GA we managed to get to 0.880 accuracy in 4.5 minutes, while Random Search reached 0.866 in less than a minute. The decision between the two approaches depends on the problem being solved and business considerations. For example, if the optimization will impact a large number of users, we may be willing to pay the higher computational cost of GA optimization to get to better results. On the other hand, if we are not very sensitive about finding the best outcomes, Random Search can be the way to go, which also runs significantly faster.
In this post, we introduced Genetic Algorithms as a hyperparameter optimization methodology. We described how these algorithms are inspired by the natural selection - an iterative approach of keeping the winners while discarding the rest. We then implemented this approach for two examples, a simple and a more advanced one and then compared the performance of the advanced example to Random Search. We finally reviewed the trade-off between finding the better optimization using Genetic Algorithms at a much higher computational cost, compared to Random Search.
Thanks For Reading!
If you found this post helpful, please follow me on Medium and subscribe to receive my latest posts!
(All images, unless otherwise noted, are by the author.)








