Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Aprende Evolution and Adaptation Principles | Fundamentals of Bio-Inspired Algorithms
Bio-Inspired Algorithms

bookEvolution and Adaptation Principles

Mastering the principles of evolution and adaptation is essential when designing bio-inspired algorithms. In both natural and computational systems, these principles explain how groups of individuals change and improve over time. The foundational concepts include:

  • Population: The set of all candidate solutions or individuals considered at one time;
  • Fitness: The measure of how well each individual performs with respect to the problem objective;
  • Selection: The process of choosing individuals to produce the next generation, often favoring those with higher fitness;
  • Mutation: The introduction of random changes to individuals, maintaining diversity and enabling exploration of new solutions;
  • Crossover: The combination of parts from two or more individuals to create new offspring;
  • Adaptation: The ongoing improvement of the population in response to selection, mutation, and crossover.

Each concept supports the development of robust solutions by mimicking processes that drive adaptation and success in nature.

Population

import random

def create_population(size, length, value_range=(0, 100)):
    """
    Generates a population of individuals, each as a list of random integers.
    
    Args:
        size (int): Number of individuals in the population.
        length (int): Number of genes in each individual.
        value_range (tuple): Allowed range for gene values (inclusive).
    Returns:
        list: Population represented as a list of individuals.
    """
    return [[random.randint(value_range[0], value_range[1]) for _ in range(length)] for _ in range(size)]

# Example usage:
population = create_population(size=5, length=3, value_range=(0, 50))
print("Generated population:", population)

The create_population function generates a group of individuals, where each individual is represented by a list of random integers. Each integer can be thought of as a gene. You specify the population size, the length of each individual, and the range of gene values.

Maintaining diversity in the population is essential. A diverse population explores more of the problem space, reducing the risk of getting stuck in poor solutions and increasing the chance of finding high-quality answers.

Fitness: Measuring Solution Quality

def fitness_function(solution, target=100):
    """
    Calculates fitness based on how close the solution is to the target value.
    Higher fitness indicates a solution closer to the target.
    """
    return 1 / (1 + abs(target - solution))

# Example usage:
solution = 97
fitness = fitness_function(solution, target=100)
print(f"Fitness score: {fitness}")

The fitness_function measures how good a solution is by comparing it to a target value. The smaller the difference, the higher the fitness score. For example, if the solution is 97 and the target is 100, the fitness score will be higher than if the solution is 80.

Note
Note

Fitness scores are used to guide selection, helping you identify which candidates are more likely to produce better solutions in the next generation.

Selection: Choosing the Fittest Candidates

import random

def roulette_wheel_selection(population, fitnesses, num_selected):
    """
    Selects individuals from the population using roulette wheel selection.
    Probability of selection is proportional to fitness.
    Handles the case where all fitness scores are zero by selecting randomly.
    """
    total_fitness = sum(fitnesses)
    if total_fitness == 0:
        # If all fitnesses are zero, select randomly
        return random.choices(population, k=num_selected)
    # Otherwise, select based on fitness weights
    return random.choices(population, weights=fitnesses, k=num_selected)

# Example usage:
population = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [2, 4, 6]]
fitnesses = [10, 0, 30, 5]  # Higher fitness means higher chance of selection
selected = roulette_wheel_selection(population, fitnesses, num_selected=2)
print("Selected individuals:", selected)

Roulette wheel selection chooses individuals with probability proportional to their fitness scores. This means candidates with higher fitness are more likely to be selected, but individuals with lower fitness can still be chosen, helping maintain diversity in the population.

Mutation: Introducing Variation

import random

def mutate_individual(individual, mutation_rate=0.2, value_range=(0, 100)):
    """
    Randomly changes each gene in an individual with a given probability.
    Returns a new individual with possible mutations.
    """
    return [random.randint(*value_range) if random.random() < mutation_rate else gene for gene in individual]

# Example: Mutating a single individual
original = [10, 20, 30, 40, 50]
mutated = mutate_individual(original, mutation_rate=0.4, value_range=(0, 100))
print("Original:", original)
print("Mutated:", mutated)

# Example: Mutating an entire population
population = [[5, 15, 25], [35, 45, 55], [65, 75, 85]]
mutated_population = [mutate_individual(ind, mutation_rate=0.3) for ind in population]
print("Original Population:", population)
print("Mutated Population:", mutated_population)

Mutation introduces random changes to individuals in the population. This randomness helps maintain diversity, allowing the algorithm to explore new solutions and reducing the risk of getting stuck at poor solutions.

Crossover: Mixing Traits for New Solutions

def single_point_crossover(parent1, parent2):
    """
    Performs single-point crossover between two parent individuals.
    Each parent is a list of integers of the same length.
    Returns two offspring as lists.
    """
    import random
    if len(parent1) != len(parent2):
        raise ValueError("Parents must be of the same length.")
    if len(parent1) < 2:
        raise ValueError("Parent length must be at least 2 for crossover.")
    crossover_point = random.randint(1, len(parent1) - 1)
    offspring1 = parent1[:crossover_point] + parent2[crossover_point:]
    offspring2 = parent2[:crossover_point] + parent1[crossover_point:]
    return offspring1, offspring2

This function takes two parent individuals (lists of integers) and produces two offspring by exchanging genetic material at a randomly chosen point. The crossover point is selected so that each offspring contains genes from both parents.

Example usage:

import random
random.seed(42)  # For reproducible results
parent1 = [10, 20, 30, 40, 50]
parent2 = [1, 2, 3, 4, 5]
offspring1, offspring2 = single_point_crossover(parent1, parent2)
print("Offspring 1:", offspring1)
print("Offspring 2:", offspring2)
Note
Note

Crossover Benefits:

  • Mixes traits from different solutions, increasing the chance of discovering high-quality candidates;
  • Promotes exploration of the solution space by recombining successful features;
  • Helps maintain diversity within the population, reducing the risk of premature convergence.

Adaptation: How Populations Improve Over Time

Adaptation is the cumulative effect of repeated evolutionary operations — selection, crossover, and mutation. As these processes repeat, the population gradually becomes better at solving the problem, leading to higher-quality solutions over time.

question mark

Which statement accurately describes the primary purpose of mutation in bio-inspired algorithms?

Select the correct answer

¿Todo estuvo claro?

¿Cómo podemos mejorarlo?

¡Gracias por tus comentarios!

Sección 1. Capítulo 2

Pregunte a AI

expand

Pregunte a AI

ChatGPT

Pregunte lo que quiera o pruebe una de las preguntas sugeridas para comenzar nuestra charla

Suggested prompts:

Can you explain how these evolutionary principles are applied in real-world algorithms?

What are some common challenges when implementing bio-inspired algorithms?

Can you give examples of problems that benefit from using these principles?

Awesome!

Completion rate improved to 6.25

bookEvolution and Adaptation Principles

Desliza para mostrar el menú

Mastering the principles of evolution and adaptation is essential when designing bio-inspired algorithms. In both natural and computational systems, these principles explain how groups of individuals change and improve over time. The foundational concepts include:

  • Population: The set of all candidate solutions or individuals considered at one time;
  • Fitness: The measure of how well each individual performs with respect to the problem objective;
  • Selection: The process of choosing individuals to produce the next generation, often favoring those with higher fitness;
  • Mutation: The introduction of random changes to individuals, maintaining diversity and enabling exploration of new solutions;
  • Crossover: The combination of parts from two or more individuals to create new offspring;
  • Adaptation: The ongoing improvement of the population in response to selection, mutation, and crossover.

Each concept supports the development of robust solutions by mimicking processes that drive adaptation and success in nature.

Population

import random

def create_population(size, length, value_range=(0, 100)):
    """
    Generates a population of individuals, each as a list of random integers.
    
    Args:
        size (int): Number of individuals in the population.
        length (int): Number of genes in each individual.
        value_range (tuple): Allowed range for gene values (inclusive).
    Returns:
        list: Population represented as a list of individuals.
    """
    return [[random.randint(value_range[0], value_range[1]) for _ in range(length)] for _ in range(size)]

# Example usage:
population = create_population(size=5, length=3, value_range=(0, 50))
print("Generated population:", population)

The create_population function generates a group of individuals, where each individual is represented by a list of random integers. Each integer can be thought of as a gene. You specify the population size, the length of each individual, and the range of gene values.

Maintaining diversity in the population is essential. A diverse population explores more of the problem space, reducing the risk of getting stuck in poor solutions and increasing the chance of finding high-quality answers.

Fitness: Measuring Solution Quality

def fitness_function(solution, target=100):
    """
    Calculates fitness based on how close the solution is to the target value.
    Higher fitness indicates a solution closer to the target.
    """
    return 1 / (1 + abs(target - solution))

# Example usage:
solution = 97
fitness = fitness_function(solution, target=100)
print(f"Fitness score: {fitness}")

The fitness_function measures how good a solution is by comparing it to a target value. The smaller the difference, the higher the fitness score. For example, if the solution is 97 and the target is 100, the fitness score will be higher than if the solution is 80.

Note
Note

Fitness scores are used to guide selection, helping you identify which candidates are more likely to produce better solutions in the next generation.

Selection: Choosing the Fittest Candidates

import random

def roulette_wheel_selection(population, fitnesses, num_selected):
    """
    Selects individuals from the population using roulette wheel selection.
    Probability of selection is proportional to fitness.
    Handles the case where all fitness scores are zero by selecting randomly.
    """
    total_fitness = sum(fitnesses)
    if total_fitness == 0:
        # If all fitnesses are zero, select randomly
        return random.choices(population, k=num_selected)
    # Otherwise, select based on fitness weights
    return random.choices(population, weights=fitnesses, k=num_selected)

# Example usage:
population = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [2, 4, 6]]
fitnesses = [10, 0, 30, 5]  # Higher fitness means higher chance of selection
selected = roulette_wheel_selection(population, fitnesses, num_selected=2)
print("Selected individuals:", selected)

Roulette wheel selection chooses individuals with probability proportional to their fitness scores. This means candidates with higher fitness are more likely to be selected, but individuals with lower fitness can still be chosen, helping maintain diversity in the population.

Mutation: Introducing Variation

import random

def mutate_individual(individual, mutation_rate=0.2, value_range=(0, 100)):
    """
    Randomly changes each gene in an individual with a given probability.
    Returns a new individual with possible mutations.
    """
    return [random.randint(*value_range) if random.random() < mutation_rate else gene for gene in individual]

# Example: Mutating a single individual
original = [10, 20, 30, 40, 50]
mutated = mutate_individual(original, mutation_rate=0.4, value_range=(0, 100))
print("Original:", original)
print("Mutated:", mutated)

# Example: Mutating an entire population
population = [[5, 15, 25], [35, 45, 55], [65, 75, 85]]
mutated_population = [mutate_individual(ind, mutation_rate=0.3) for ind in population]
print("Original Population:", population)
print("Mutated Population:", mutated_population)

Mutation introduces random changes to individuals in the population. This randomness helps maintain diversity, allowing the algorithm to explore new solutions and reducing the risk of getting stuck at poor solutions.

Crossover: Mixing Traits for New Solutions

def single_point_crossover(parent1, parent2):
    """
    Performs single-point crossover between two parent individuals.
    Each parent is a list of integers of the same length.
    Returns two offspring as lists.
    """
    import random
    if len(parent1) != len(parent2):
        raise ValueError("Parents must be of the same length.")
    if len(parent1) < 2:
        raise ValueError("Parent length must be at least 2 for crossover.")
    crossover_point = random.randint(1, len(parent1) - 1)
    offspring1 = parent1[:crossover_point] + parent2[crossover_point:]
    offspring2 = parent2[:crossover_point] + parent1[crossover_point:]
    return offspring1, offspring2

This function takes two parent individuals (lists of integers) and produces two offspring by exchanging genetic material at a randomly chosen point. The crossover point is selected so that each offspring contains genes from both parents.

Example usage:

import random
random.seed(42)  # For reproducible results
parent1 = [10, 20, 30, 40, 50]
parent2 = [1, 2, 3, 4, 5]
offspring1, offspring2 = single_point_crossover(parent1, parent2)
print("Offspring 1:", offspring1)
print("Offspring 2:", offspring2)
Note
Note

Crossover Benefits:

  • Mixes traits from different solutions, increasing the chance of discovering high-quality candidates;
  • Promotes exploration of the solution space by recombining successful features;
  • Helps maintain diversity within the population, reducing the risk of premature convergence.

Adaptation: How Populations Improve Over Time

Adaptation is the cumulative effect of repeated evolutionary operations — selection, crossover, and mutation. As these processes repeat, the population gradually becomes better at solving the problem, leading to higher-quality solutions over time.

question mark

Which statement accurately describes the primary purpose of mutation in bio-inspired algorithms?

Select the correct answer

¿Todo estuvo claro?

¿Cómo podemos mejorarlo?

¡Gracias por tus comentarios!

Sección 1. Capítulo 2
some-alt