How to Implement Genetic Algorithms for Optimization Problems Using the Genetic.ipynb Notebook
The Genetic.ipynb notebook in Microsoft's AI-For-Beginners repository provides a lightweight, self-contained genetic algorithm framework that solves optimization problems through binary vector representation, single-bit mutation, mask-based crossover, and elitist replacement.
The microsoft/AI-For-Beginners repository offers a practical introduction to evolutionary computation in lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb. This notebook demonstrates how to implement genetic algorithms for optimization problems using pure Python and NumPy primitives rather than complex external libraries. The implementation includes both the core GA pipeline and working examples such as the partition problem and 8-Queens puzzle.
Understanding the Genetic Algorithm Architecture
The Genetic.ipynb notebook follows the standard evolutionary optimization pipeline with four key components: representation, fitness evaluation, variation operators, and selection. Each component is implemented as a standalone function that you can modify for specific problem domains.
Binary Representation and Initialization
Solutions are encoded as binary vectors matching the length of your problem data. The generate(S) function creates random initial populations by producing NumPy arrays of 0s and 1s:
def generate(S):
return np.array([random.randint(0,1) for _ in S])
In the source code at translations/en/lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb#L49-L51, this function initializes the population P with pop_size random individuals. Each binary vector acts as a selection mask over your problem data S.
Fitness Evaluation
The fit function computes a scalar cost measuring how far a candidate solution is from the objective. The notebook implements a partition problem fitness function that minimizes the absolute difference between two subset sums:
def fit(candidate, S):
c1 = (candidate * S).sum()
c2 = ((1 - candidate) * S).sum()
return abs(c1 - c2)
According to the implementation at Genetic.ipynb#L82-L86, you replace this function with any problem-specific metric while maintaining the same signature.
Variation Operators
The notebook implements two essential genetic operators:
Mutation – The mutate(b) function flips a single randomly chosen bit to introduce genetic diversity:
def mutate(b):
x = b.copy()
i = random.randint(0, len(b) - 1)
x[i] = 1 - x[i]
return x
Crossover – The xover(b1, b2) function creates offspring by randomly selecting bits from each parent using a generated mask:
def xover(b1, b2):
mask = generate(b1)
return b1 * mask + b2 * (1 - mask)
As implemented in Genetic.ipynb#L7-L15, these operators provide the exploration and exploitation mechanisms necessary for effective search.
Step-by-Step Implementation Guide
Setting Up the Population
Initialize your optimization run by creating a diverse population of random binary vectors. The notebook demonstrates this pattern at Genetic.ipynb#L33-L35:
import numpy as np
import random
S = np.random.randint(1, 100, size=30) # Your problem data
pop_size = 30
P = [generate(S) for _ in range(pop_size)]
Running the Evolution Loop
The evolve(P, S, n) function orchestrates the evolutionary process across n generations. It implements elitist selection by consistently replacing the worst-performing individual while preserving the best:
def evolve(P, S, n=2000):
history = []
for _ in range(n):
# Evaluate current generation
fitness_scores = [fit(ind, S) for ind in P]
best = min(fitness_scores)
history.append(best)
# Termination condition
if best == 0:
break
# Selection and breeding (30% mutation, 70% crossover)
worst_idx = fitness_scores.index(max(fitness_scores))
if random.randint(1, 10) < 3:
# Mutation: Replace worst with mutated random individual
parent_idx = random.randint(0, len(P)-1)
P[worst_idx] = mutate(P[parent_idx])
else:
# Crossover: Replace worst with child of two parents
a, b = random.sample(P, 2)
P[worst_idx] = xover(a, b)
return P, history
The source code at Genetic.ipynb#L70-L80 shows that the algorithm favors crossover (70% probability) over mutation (30% probability) to balance exploitation and exploration.
Practical Examples from the Source Code
Solving the Partition Problem
The notebook includes a concrete implementation of the partition problem where the goal is to divide a set of numbers into two subsets with equal sums:
# Initialize problem data
S = np.array([10, 15, 20, 25, 30, 35, 40]) # Example multiset
P = [generate(S) for _ in range(50)]
# Run evolution
final_pop, fitness_history = evolve(P, S, n=2000)
# Extract best solution
best = min(final_pop, key=lambda ind: fit(ind, S))
print(f"Final partition difference: {fit(best, S)}")
This example demonstrates how the binary encoding naturally represents partition membership (1 = subset A, 0 = subset B).
Adapting for the 8-Queens Problem
The Genetic.ipynb notebook extends the framework to solve the N-Queens combinatorial challenge. At Genetic.ipynb#L379-L391, the nqueens function evaluates board validity by checking for conflicting queen placements:
def nqueens(l, N=8):
"""Evaluate fitness for N-Queens placement.
Returns negative of conflict count (higher is better).
"""
# Implementation evaluates diagonal conflicts
# and uses evolve() to search for conflict-free boards
pass
# Usage
S = np.arange(64) # 8x8 board representation
P = [generate(S) for _ in range(30)]
solution = evolve(P, S, n=5000)
This adaptation shows how the same evolve function handles different fitness landscapes by plugging in problem-specific evaluation logic.
Customizing the Algorithm for Your Optimization Problem
To implement genetic algorithms for your specific optimization problem using this framework:
- Define your data representation – Create a vector
Sthat encodes your problem dimensionality - Implement a fitness function – Replace
fit(candidate, S)with your objective function, ensuring it returns a scalar where lower values indicate better solutions - Adjust population parameters – Modify
pop_sizeand generation countnbased on search space complexity - Tune operator probabilities – Change the 30/70 mutation/crossover split in the evolve loop if your problem requires more exploration or exploitation
The companion notebook lessons/6-Other/21-GeneticAlgorithms/Diophantine.ipynb demonstrates this customization process for solving Diophantine equations, providing a template for integer programming applications.
Summary
- The Genetic.ipynb notebook in
microsoft/AI-For-Beginnersprovides a complete genetic algorithm implementation using binary vector representation and NumPy operations. - Core functions include
generate()for initialization,fit()for evaluation,mutate()andxover()for variation, andevolve()for the main selection loop. - The algorithm uses elitist replacement with a 70/30 crossover-to-mutation ratio to evolve solutions over specified generations.
- You can adapt the framework for any optimization problem by customizing the fitness function while maintaining the binary vector structure.
- Working examples include the number partition problem and the 8-Queens puzzle, demonstrating versatility across combinatorial optimization tasks.
Frequently Asked Questions
What types of optimization problems work best with the Genetic.ipynb implementation?
The Genetic.ipynb implementation excels at combinatorial optimization problems where solutions can be encoded as binary vectors, including subset selection, partitioning, and constraint satisfaction problems like N-Queens. The framework works best when your problem has a clear binary distinction (include/exclude, select/reject) and a quantifiable fitness metric that returns a scalar cost value.
How does the selection mechanism determine which individuals survive?
The evolve function implements elitist selection by always replacing the worst-performing individual (highest fitness value) with either a mutated copy or a crossover child. This ensures the population quality never degrades significantly, as the best solutions persist across generations while inferior candidates are gradually eliminated.
Can I adjust the mutation rate and crossover probability?
Yes. The probabilities are hardcoded in the evolve function at lines 70-80 as random.randint(1, 10) < 3 for mutation (30%) and the else branch for crossover (70%). You can modify these thresholds to increase mutation for more exploration (higher diversity) or increase crossover for more exploitation (faster convergence) depending on your problem's fitness landscape.
Where can I find additional examples of this genetic algorithm framework?
The repository includes Diophantine.ipynb in the same directory (lessons/6-Other/21-GeneticAlgorithms/), which applies the same GA scaffold to solving Diophantine equations. This companion notebook demonstrates how to adapt the fit function for mathematical constraints while using the identical evolve, mutate, and xover primitives, providing a template for integer programming and equation solving applications.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →