# 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