Implementing Evolutionary Machine Learning using the Infinite Monkey Theorem
Let's be honest. We've all heard the thought experiment: an infinite number of monkeys, given infinite typewriters and infinite time, will eventually bash out the complete works of Shakespeare. It's a fun thought experiment, a staple of introductory probability classes, and a thoroughly depressing way to produce literature. It's the computational equivalent of sorting a library by throwing all the books in the air and hoping they land in alphabetical order - bogo-sort for the soul.
But what if the monkeys weren't just mindlessly pounding keys? What if they could learn? What if, instead of an eternity of random chance, we introduced a little Darwinism - a cruel, unforgiving world where only the fittest typists get to procreate (just like with the humans in all of history)?
Turns out, it's the foundation of a powerful optimization technique known as a Genetic Algorithm (GA), and it’s about to make those infinite monkeys look seriously inefficient.
From Typewriters to Chromosomes: Setting the Stage
The classic theorem is a lesson in probability. Our experiment is a lesson in guided evolution. We start by firing most of the monkeys. We don't need infinity; a few hundred or thousand will do just fine. Each monkey is born with a random string of text - its genome or chromosome. This string is the same length as our target text, say, the opening line of Hamlet: "To be, or not to be."
Our goal isn't to wait for a miracle. Our goal is to breed the miracle.
The first step is to establish a metric of success, a fitness function. In nature, fitness is about survival and reproduction. In our digital world, it's a simple function: for every character in the monkey's string that matches Shakespeare's, its fitness score goes up.
A perfect match yields a fitness of zero. The higher the score, the worse the typist. It’s a harsh literary critic.
The Circle of (Simulated) Life
Now the fun begins. We take our population of abysmal typists and rank them. The top 10% - the monkeys who managed to get a few letters right - are declared literary elites. They get a free pass to the next generation. No questions asked. Their genes are too valuable to lose.
The remaining 90% of the next generation must be forged through mating. We select parents from the best of the current generation (a process called selection) and combine their genetic material. How do we combine two strings of text? Through crossover.
Imagine two parent strings:
Parent A: Tx bo, or not xo be.
Parent B: To be, ir not to be!
We might choose a random point and splice them. A crossover after the 5th character could produce an offspring:
Child: Tx bo, or not to be!
This child has inherited traits from both parents. It's not perfect, but it's already closer than most.
But evolution needs randomness. It needs spice. That's where mutation comes in. During the mating process, we introduce a small chance (say, 2-5%) that any given character in the child's genome will be completely random. This x might randomly mutate into an e. This i might become a t. Mutation prevents the population from becoming stagnant and homogenous; it's the engine of innovation, allowing the monkeys to explore possibilities that neither parent possessed.
Recommended by LinkedIn
We repeat this process: evaluate fitness, select the elites, mate the best performers, and introduce mutations. Generation after generation, the population's average fitness improves. The text begins to coagulate from the noise. What was once %gP1d@kzQynot|f@be slowly becomes To be, or not to be.
A Code-Level Look at Natural Selection
Let's translate this evolutionary drama into code. We define our universe of possible characters (genes) and our target string.
# The building blocks of our simian Shakespeare
GENES = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ .,!?;:'-"
TARGET = "To be, or not to be."
We then create a Monkey class, whose sole purpose is to carry a chromosome and know its own fitness.
class Monkey:
def __init__(self, chromosome):
self.chromosome = chromosome
self.fitness = self.calculate_fitness()
def calculate_fitness(self):
# Count the number of incorrect characters
return sum(1 for a, b in zip(self.chromosome, TARGET) if a != b)
def mate(self, partner):
# Crossover and mutation
child_chromosome = []
for a, b in zip(self.chromosome, partner.chromosome):
prob = random.random()
if prob < 0.44: # Inherit from self
child_chromosome.append(a)
elif prob < 0.88: # Inherit from partner
child_chromosome.append(b)
else: # Mutate!
child_chromosome.append(random.choice(GENES))
return Monkey(child_chromosome)
The main simulation loop is a stark, algorithmic reflection of natural selection. It's a cycle of life, death, and reproduction measured in CPU cycles.
# Initialize a population of random monkeys
population = [Monkey(create_random_genome()) for _ in range(POPULATION_SIZE)]
generation = 1
while True:
# The Great Reckoning: evaluate fitness
population.sort(key=lambda x: x.fitness)
# Check for our prodigy
if population[0].fitness == 0:
break
# Create the next generation
new_generation = []
# Elite survival (top 10%)
new_generation.extend(population[:ELITE_SIZE])
# Mate the top 50% to fill out the rest
for _ in range(POPULATION_SIZE - ELITE_SIZE):
p1 = random.choice(population[:TOP_SURVIVORS])
p2 = random.choice(population[:TOP_SURVIVORS])
new_generation.append(p1.mate(p2))
population = new_generation
generation += 1
Watching the console output as this runs is a thing of beauty. You witness a random soup of characters slowly, inevitably, resolve into coherence. It’s a digital fossil record, compressing eons of hypothetical evolution into seconds.
Why this is more than just a simple trick
This isn't just about amusing ourselves with simulated monkeys. Genetic Algorithms are a powerful tool in the Machine Learning application, particularly for problems where the search space is vast, complex, and poorly understood. They excel at:
The infinite monkey theorem is a statement about the raw, unfathomable power of brute force. A Genetic Algorithm is a statement about the elegant, directed power of evolution. It replaces blind luck with guided selection, proving that with a little bit of pressure and a lot of iteration, you can achieve the seemingly impossible without waiting for eternity.
So the next time someone brings up the infinite monkeys, you can tell them there's a better way. All you need is a few hundred monkeys, a ruthless fitness function, and a solid breeding program.
It's Evolution with a purpose.