How to Build Genetic Algorithms for Optimization Problems Using Microsoft's AI-For-Beginners Curriculum
You can build genetic algorithms for optimization problems by implementing a six-step evolutionary pipeline—encoding, fitness evaluation, variation operators, selection, replacement, and termination—using the self-contained Jupyter notebook at lessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb in the Microsoft AI-For-Beginners repository.
The AI-For-Beginners repository provides a comprehensive, open-source curriculum for learning artificial intelligence concepts through interactive Jupyter notebooks. When you need to build genetic algorithms for optimization problems, the dedicated lesson demonstrates a complete, runnable implementation that handles both binary and permutation encodings. This educational resource breaks down the evolutionary process into discrete, understandable components that you can adapt to combinatorial challenges ranging from fair treasure splitting to the N-Queens problem.
Understanding the Genetic Algorithm Pipeline
The genetic algorithm implementation in the AI-For-Beginners curriculum follows a classic evolutionary optimization structure defined in lines 33-60 of Genetic.ipynb. The pipeline consists of six essential stages that transform a random population into an optimized solution through iterative refinement.
- Encoding: Solutions are represented as vectors—binary arrays for the fair treasure split problem or permutation arrays for the N-Queens problem.
- Fitness Evaluation: A scalar cost function measures solution quality, calculating absolute differences between partitions or counting attacking queen pairs.
- Variation Operators: Mutation flips random bits or swaps elements, while Crossover blends parent solutions by mixing genetic material.
- Selection: Parents are chosen probabilistically based on fitness weights, giving superior solutions higher reproduction chances.
- Replacement: The population updates by pruning weak candidates, introducing random individuals, and inserting offspring.
- Termination: The algorithm stops when fitness reaches zero or generation limits expire.
Implementing Solution Encoding and Fitness Evaluation
According to lines 78-84 of the notebook, the treasure-split problem uses binary encoding where each bit indicates subset membership. The fitness function calculates the absolute difference between partition sums to minimize disparity.
import random
import numpy as np
def generate(S):
"""Binary mask indicating which side of the split each element belongs to."""
return np.array([random.randint(0, 1) for _ in S])
def fit(B, S):
"""Return the absolute difference between the sums of the two subsets."""
c1 = (B * S).sum()
c2 = ((1 - B) * S).sum()
return abs(c1 - c2)
Creating Variation Operators
Lines 98-108 of Genetic.ipynb implement the mutation and crossover functions that introduce genetic diversity. The mutation operator performs random bit-flipping, while crossover creates offspring by randomly mixing parental genes.
def mutate(b):
"""Flip a single random bit."""
x = b.copy()
i = random.randint(0, len(b) - 1)
x[i] = 1 - x[i]
return x
def xover(b1, b2):
"""Randomly mix bits from two parents."""
mask = generate(b1) # 0/1 mask decides which parent contributes each gene
return b1 * mask + b2 * (1 - mask)
Building the Evolution Loop
The evolve() function (lines 166-190) orchestrates the generational process, applying selection pressure through probabilistic operator choice. With a 30% mutation rate and 70% crossover rate, the function maintains population diversity while converging toward optimal solutions.
def evolve(pop, S, steps=2000):
history = []
for _ in range(steps):
# Record best fitness in the current population
best = min(fit(ind, S) for ind in pop)
history.append(best)
# Stop if we have an optimal solution
if best == 0:
break
# 30% chance to mutate the worst individual
if random.randint(1, 10) < 3:
i = random.randint(0, len(pop) - 1)
mutated = mutate(pop[i])
worst = np.argmax([fit(ind, S) for ind in pop])
pop[worst] = mutated
else:
# 70% chance to perform crossover
i, j = random.sample(range(len(pop)), 2)
child = xover(pop[i], pop[j])
# Replace a parent if the child is better
if fit(child, S) < fit(pop[i], S):
pop[i] = child
elif fit(child, S) < fit(pop[j], S):
pop[j] = child
# Return the best individual and the fitness history
best_idx = np.argmin([fit(ind, S) for ind in pop])
return pop[best_idx], history
Running the Complete Genetic Algorithm
The genetic() driver function (lines 260-300) initializes random problem instances and populations, then executes the evolutionary process. This entry point demonstrates how to build genetic algorithms for optimization problems with configurable population sizes and problem dimensions.
def genetic(N, pop_size=100):
# Create a random problem instance
S = np.array([random.randint(1, 10_000) for _ in range(N)])
# Initialise population
pop = [generate(S) for _ in range(pop_size)]
# Run the evolutionary process
solution, history = evolve(pop, S)
print("Best partition difference:", fit(solution, S))
return solution, history
# Example usage
solution, history = genetic(8)
Adapting to Different Problem Domains
While the treasure-split example uses binary encoding, the same repository demonstrates permutation encoding for the N-Queens problem. You can adapt this framework by modifying the generate() function to produce valid permutations and adjusting the fit() function to count constraint violations. The variation operators similarly require domain-specific adjustments—swap mutations for permutations rather than bit-flips—to maintain solution validity while exploring the search space.
Summary
- The AI-For-Beginners repository at
microsoft/AI-For-Beginnersprovides a complete genetic algorithm implementation inlessons/6-Other/21-GeneticAlgorithms/Genetic.ipynb. - The evolutionary pipeline requires six components: encoding, fitness evaluation, mutation, crossover, selection/replacement, and termination criteria.
- Binary encoding works for subset selection problems like fair treasure splitting, while permutation encoding suits ordering problems like N-Queens.
- The
evolve()function implements steady-state replacement with probabilistic operator selection, balancing exploration and exploitation. - You can import these functions into any Python environment to solve custom combinatorial optimization problems.
Frequently Asked Questions
What types of optimization problems work best with genetic algorithms?
Genetic algorithms excel at combinatorial optimization problems where the solution space is large, discrete, and non-convex, such as scheduling, routing, and partitioning tasks. The AI-For-Beginners implementation specifically targets NP-hard problems like the fair treasure split and N-Queens, where traditional exhaustive search becomes computationally infeasible as problem size grows.
How does the fitness function influence genetic algorithm performance?
The fitness function serves as the sole guide for evolutionary pressure, directing the population toward optimal regions of the search space. In the repository's treasure-split example, minimizing the absolute difference between subset sums provides a clear gradient toward fair partitions; poorly designed fitness landscapes with deceptive local optima can cause premature convergence or stagnation.
Can I adapt this code for continuous optimization problems?
While the Microsoft notebook focuses on discrete binary and permutation encodings, you can adapt the framework for continuous optimization by modifying the generate() function to produce floating-point vectors and implementing Gaussian mutation or blend crossover operators. The core evolve() loop and selection mechanism remain applicable across both discrete and continuous domains.
Where can I find additional theory about genetic algorithm operators?
The lessons/6-Other/21-GeneticAlgorithms/README.md file provides high-level learning objectives and conceptual context, while lines 33-60 of Genetic.ipynb contain detailed markdown explanations of selection pressure, genetic drift, and operator probabilities. The repository also includes timing experiments comparing genetic algorithms to exhaustive search, illustrating computational complexity trade-offs.
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 →