Python implementation of genetic algorithm (solving function extremum problem)

Posted by HairBomb on Fri, 11 Feb 2022 18:25:17 +0100

Problem description

f(x)=x*sin(x)+1, x belongs to [0,2 π]. Taking solving the maximum and minimum value of f(x) as an example, a genetic algorithm program for f(x) is designed, and then run to solve it
(1) Determine the basic functions: this experiment is to solve the maximum and minimum values of f (x).
(2) Encoding f(x): a chromosome is represented by a binary vector, and the chromosome represents the real value of variable x.
(3) Design fitness function: since the maximum value of F (x) is required, the fitness function is f (x).
(4) Design and implement genetic algorithm program for f(x): genetic operation mainly includes replication, crossover and mutation. Replication is to directly pass on the parent generation to the offspring, that is, whether it is eliminated or inherited in the next generation is determined according to the quality measured by the fitness function value of the individual. Crossover selects two individuals who can enter the next generation and exchanges part of their code values. Mutation is to select an individual according to the mutation probability and change its bit code randomly.
(5) Design initial population: the default setting is 50 randomly generated 23 bit chromosomes. The population size can be set through input.
(6) Debug crossover and mutation probability: within the commonly used crossover and mutation probability range, the results change with the change of crossover and mutation probability, and the difference between them is relatively less obvious

Population initialization

The process of population initialization is to randomly generate random binary coded individuals with population size and quantity

Reference code

def get_chromosome(size, length):
    """
    generate size Length of length Chromosome list
    :param size: Population size
    :param length: Chromosome length
    :return: 2D list
    """
    population_temp = []
    for i in range(size):
        population_temp.append([random.randint(0, 1) for _ in range(length)])   # Generate a random binary list with length and store it in population_temp list
    return population_temp

Calculate search accuracy

Here, according to the accuracy calculation formula:
δ = U x − L x 2 l − 1 \delta = \frac{U_{x} - L_{x}}{2 ^{l} - 1} δ=2l−1Ux​−Lx​​

Reference code

def get_accuracy(min_, max_, length):
    """
    Calculate search accuracy
    :param min_: Minimum value of gene
    :param max_: Maximum value of gene
    :param length: Chromosome length
    :return: accuracy
    """
    return (max_ - min_) / (2 ** length - 1)    # Accuracy calculation formula

Chromosome decoding

Here, according to the decoding formula:
x = L x + δ ∑ i = 1 l A i 2 i − 1 x = L_{x} + \delta \sum_{i=1}^l A_{i} 2^{i-1} x=Lx​+δi=1∑l​Ai​2i−1

Reference code

def chromosome_decode(chromosome_list, min_, accuracy_):
    """
    Chromosome decoding
    :param chromosome_list: Binary chromosome list
    :param min_: Minimum value of gene
    :param accuracy_: accuracy
    :return: Decoding results
    """
    decimal = int(''.join([str(i) for i in chromosome_list]), 2)    # Convert binary list to decimal integer
    return min_ + accuracy_ * decimal   # Decoding formula

Selection operator

There are many ways to realize the selection operator in genetic algorithm. The more effective ones are roulette algorithm and League selection algorithm. Roulette algorithm is used here

The essence of roulette algorithm is that it can make random selection according to individual fitness. The greater the fitness, the greater the probability of individual selection. When the population scale is large, this algorithm can truly simulate the situation in the natural state

Reference code

def select(chromosome_list, fitness_list):
    """
    choice(roulette algorithm )
    :param chromosome_list: Population of two-dimensional list
    :param fitness_list: Fitness list
    :return: Population list after selection
    """
    population_fitness = np.array(fitness_list).sum()  # Population fitness
    fit_ratio = [i / population_fitness for i in fitness_list]  # Proportion of each individual in population fitness
    fit_ratio_add = [0]  # Individual cumulative probability
    for i in fit_ratio:
        fit_ratio_add.append(fit_ratio_add[len(fit_ratio_add) - 1] + i)     # Calculate the cumulative probability of each individual and store it in fit_ ratio_ In add
    fit_ratio_add = fit_ratio_add[1:]   # Remove the first 0

    rand_list = [random.uniform(0, 1) for _ in chromosome_list]     # Generate a list of random values equal to the population size for roulette selection
    rand_list.sort()
    fit_index = 0
    new_index = 0
    new_population = chromosome_list.copy()
    '''Individual selection start'''
    while new_index < len(chromosome_list):
        if rand_list[new_index] < fit_ratio_add[fit_index]:
            new_population[new_index] = chromosome_list[fit_index]
            new_index = new_index + 1
        else:
            fit_index = fit_index + 1
    '''Individual selection end'''
    return new_population

Crossover operator

The crossover operator is used to simulate the mating process in the natural state. In order to simplify this process, some chromosomes of two individuals are exchanged directly

Reference code

def exchange(chromosome_list, pc):
    """
    overlapping
    :param chromosome_list: Population of two-dimensional list
    :param pc: Crossover probability
    """
    for i in range(0, len(chromosome_list) - 1, 2):
        if random.uniform(0, 1) < pc:
            c_point = random.randint(0, len(chromosome_list[0]))    # Randomly generated intersection
            '''On the first i Bit sum i+1 Bit crossover start'''
            exchanged_list1 = []
            exchanged_list2 = []
            exchanged_list1.extend(chromosome_list[i][0:c_point])
            exchanged_list1.extend(chromosome_list[i + 1][c_point:len(chromosome_list[i])])
            exchanged_list2.extend(chromosome_list[i + 1][0:c_point])
            exchanged_list2.extend(chromosome_list[i][c_point:len(chromosome_list[i])])
            '''On the first i Bit sum i+1 Bit crossover end'''

            '''Replace the original chromosome with the newly crossed chromosome start'''
            chromosome_list[i] = exchanged_list1
            chromosome_list[i + 1] = exchanged_list2
            '''Replace the original chromosome with the newly crossed chromosome end'''

Mutation operator

The mutation process simulates the gene mutation in the natural state

Reference code

def mutation(chromosome_list, pm):
    """
    variation
    :param chromosome_list: Population of two-dimensional list
    :param pm: Variation probability
    """
    for i in range(len(chromosome_list)):
        if random.uniform(0, 1) < pm:
            m_point = random.randint(0, len(chromosome_list[0]) - 1)    # Randomly generated variation points
            chromosome_list[i][m_point] = chromosome_list[i][m_point] ^ 1   # XOR the value of this bit with 1 (that is, set 0 to 1 and 1 to 0)

optimization

In fact, only by realizing the above functions, a complete genetic algorithm process can be realized, but in practice, we can optimize the algorithm process to make the results more in line with our expectations

  1. Using the elite retention strategy, the optimal individual is directly retained to the next generation without the selection process
  2. Directly eliminate some individuals who do not meet expectations in each generation, such as individuals with negative fitness

Complete code

# coding=utf-8
# @Author: GongDeFeng
import random
import numpy as np
import matplotlib.pyplot as plt

population_size = 50                        # Initial population size
generation_count = 50                       # Genetic algebra
gene_length = 23                            # Chromosome length
exchange_ratio = 0.8                        # Crossover probability
variation_ratio = 0.01                      # Variation probability
solve_max = True                            # True to solve the maximum value and False to solve the minimum value
function = lambda x: x * np.sin(x) + 1      # Mathematical function to be solved
x_min = 0                                   # The minimum value of gene, that is, the minimum value that variable x can take
x_max = 2 * np.pi                           # The maximum value of gene, that is, the maximum value that variable x can take

plt.rcParams['font.sans-serif'] = ['FangSong']  # Set Chinese font
plt.rcParams['axes.unicode_minus'] = False  # Show negative sign support


def get_chromosome(size, length):
    """
    generate size Length of length Chromosome list
    :param size: Population size
    :param length: Chromosome length
    :return: 2D list
    """
    population_temp = []
    for i in range(size):
        population_temp.append([random.randint(0, 1) for _ in range(length)])   # Generate a random binary list with length and store it in population_temp list
    return population_temp


def get_accuracy(min_, max_, length):
    """
    Calculate search accuracy
    :param min_: Minimum value of gene
    :param max_: Maximum value of gene
    :param length: Chromosome length
    :return: accuracy
    """
    return (max_ - min_) / (2 ** length - 1)    # Accuracy calculation formula


def chromosome_decode(chromosome_list, min_, accuracy_):
    """
    Chromosome decoding
    :param chromosome_list: Binary chromosome list
    :param min_: Minimum value of gene
    :param accuracy_: accuracy
    :return: Decoding results
    """
    decimal = int(''.join([str(i) for i in chromosome_list]), 2)    # Convert binary list to decimal integer
    return min_ + accuracy_ * decimal   # Decoding formula


def get_fitness(x, solve_flag):
    """
    Calculate fitness
    :param x: Chromosome decoding results
    :param solve_flag: The maximum value is True,The minimum value is False
    :return: Fitness results
    """
    if solve_flag:
        return function(x)
    return -(function(x))


def select(chromosome_list, fitness_list):
    """
    choice(roulette algorithm )
    :param chromosome_list: Population of two-dimensional list
    :param fitness_list: Fitness list
    :return: Population list after selection
    """
    population_fitness = np.array(fitness_list).sum()  # Population fitness
    fit_ratio = [i / population_fitness for i in fitness_list]  # Proportion of each individual in population fitness
    fit_ratio_add = [0]  # Individual cumulative probability
    for i in fit_ratio:
        fit_ratio_add.append(fit_ratio_add[len(fit_ratio_add) - 1] + i)     # Calculate the cumulative probability of each individual and store it in fit_ ratio_ In add
    fit_ratio_add = fit_ratio_add[1:]   # Remove the first 0

    rand_list = [random.uniform(0, 1) for _ in chromosome_list]     # Generate a list of random values equal to the population size for roulette selection
    rand_list.sort()
    fit_index = 0
    new_index = 0
    new_population = chromosome_list.copy()
    '''Individual selection start'''
    while new_index < len(chromosome_list):
        if rand_list[new_index] < fit_ratio_add[fit_index]:
            new_population[new_index] = chromosome_list[fit_index]
            new_index = new_index + 1
        else:
            fit_index = fit_index + 1
    '''Individual selection end'''
    return new_population


def exchange(chromosome_list, pc):
    """
    overlapping
    :param chromosome_list: Population of two-dimensional list
    :param pc: Crossover probability
    """
    for i in range(0, len(chromosome_list) - 1, 2):
        if random.uniform(0, 1) < pc:
            c_point = random.randint(0, len(chromosome_list[0]))    # Randomly generated intersection
            '''On the first i Bit sum i+1 Bit crossover start'''
            exchanged_list1 = []
            exchanged_list2 = []
            exchanged_list1.extend(chromosome_list[i][0:c_point])
            exchanged_list1.extend(chromosome_list[i + 1][c_point:len(chromosome_list[i])])
            exchanged_list2.extend(chromosome_list[i + 1][0:c_point])
            exchanged_list2.extend(chromosome_list[i][c_point:len(chromosome_list[i])])
            '''On the first i Bit sum i+1 Bit crossover end'''

            '''Replace the original chromosome with the newly crossed chromosome start'''
            chromosome_list[i] = exchanged_list1
            chromosome_list[i + 1] = exchanged_list2
            '''Replace the original chromosome with the newly crossed chromosome end'''


def mutation(chromosome_list, pm):
    """
    variation
    :param chromosome_list: Population of two-dimensional list
    :param pm: Variation probability
    """
    for i in range(len(chromosome_list)):
        if random.uniform(0, 1) < pm:
            m_point = random.randint(0, len(chromosome_list[0]) - 1)    # Randomly generated variation points
            chromosome_list[i][m_point] = chromosome_list[i][m_point] ^ 1   # XOR the value of this bit with 1 (that is, set 0 to 1 and 1 to 0)


def get_best(fitness_list):
    """
    Calculate the best individual in this generation
    :param fitness_list: Fitness list
    :return: Subscript of optimal individual
    """
    return fitness_list.index(max(fitness_list))


def eliminate(fitness_list):
    """
    eliminate(Remove negative values)
    :param fitness_list: Fitness list
    :return: Obsolete list
    """
    fit_value = []
    for i in range(len(fitness_list)):
        fit_value.append(fitness_list[i] if fitness_list[i] >= 0 else 0.0)   # Set fitness less than 0 to 0
    return fit_value


if __name__ == '__main__':
    results = []  # Store the optimal solution of each generation, two-dimensional list
    all_fitness = []    # Store the highest fitness and population fitness in each generation
    population = get_chromosome(population_size, gene_length)   # Population initialization
    for _ in range(generation_count):
        accuracy = get_accuracy(x_min, x_max, gene_length)  # Calculate search accuracy
        decode_list = [chromosome_decode(individual, x_min, accuracy) for individual in population]  # Decoded list
        fit_list = [get_fitness(decode_i, solve_max) for decode_i in decode_list]  # Calculate the fitness of each individual
        fit_list = eliminate(fit_list)  # Eliminate a part and remove the negative value
        results.append([decode_list[get_best(fit_list)],
                        fit_list[get_best(fit_list)] if solve_max else -fit_list[get_best(fit_list)]])  # Save the optimal solution of each generation, that is, the individual with the highest fitness
        all_fitness.append(np.array(fit_list).sum() if solve_max else -np.array(fit_list).sum())    # Preserve the highest fitness and population fitness in each generation
        population = select(population.copy(), fit_list)
        exchange(population, exchange_ratio)
        mutation(population, variation_ratio)

    if solve_max:
        results.sort(key=lambda x: x[1])
    else:
        results.sort(key=lambda x: x[1], reverse=True)
    print('most{}Value point x={},y={}'.format('large' if solve_max else 'Small', results[-1][0], results[-1][1]))

    '''Draw extreme value trend chart and population fitness trend chart start'''
    X = [generation_i for generation_i in range(generation_count)]
    Y1 = [results[generation_i][1] for generation_i in range(generation_count)]
    Y2 = [all_fitness[generation_i] for generation_i in range(generation_count)]

    fig1 = plt.figure('figure', figsize=(13, 5)).add_subplot(121)
    fig1.plot(X, Y1)
    fig2 = plt.figure('figure', figsize=(13, 5)).add_subplot(122)
    fig2.plot(X, Y2)

    fig1.set_title('Extreme point trend chart')
    fig1.set_xlabel("Genetic algebra")
    fig1.set_ylabel("extremum")
    fig2.set_title('Overall fitness trend of population')
    fig2.set_xlabel("Genetic algebra")
    fig2.set_ylabel("Population fitness")

    plt.show()
    '''Draw extreme value trend chart and population fitness trend chart end'''

Topics: Python Algorithm AI