遗传算法:理论基础、Python实现及其在scikit-opt中的应用

251 阅读7分钟

遗传算法(Genetic Algorithm,GA)是一种受自然选择和遗传学启发的优化算法。在计算机科学和人工智能领域,遗传算法被广泛用于解决复杂的优化和搜索问题。本文将介绍遗传算法的基本原理、工作流程及其应用。

遗传算法的基本原理

遗传算法的灵感来源于生物进化过程中的自然选择和遗传变异。基本思想是模拟生物进化过程,通过选择、交叉和变异操作,使得种群中的个体逐步优化,最终找到问题的最优解。

主要概念

  1. 染色体编码 :染色体的编码结果是一个二进制符号序列,便于后面交叉和变异
  2. 种群(Population) :一组可能的解(个体),每个个体表示问题的一个解。
  3. 个体(Individual) :种群中的一个成员,通常用一个字符串或数组表示。
  4. 基因(Gene) :个体的组成部分,表示解的一个特定特征。
  5. 适应度函数(Fitness Function) :评估个体优劣的函数,适应度值越高表示个体越优越。
  6. 选择(Selection) :根据适应度值选择个体进行繁殖,适应度高的个体更有可能被选择。
  7. 交叉(Crossover) :两个个体交换部分基因,生成新的个体。
  8. 变异(Mutation) :随机改变个体的一部分基因,增加种群的多样性。

遗传算法的工作流程

遗传算法的工作流程可以概括为以下几个步骤:

  1. 初始化种群:随机生成初始种群,包含若干个体。
  2. 评估适应度:计算每个个体的适应度值。
  3. 选择父代:根据适应度值选择个体作为父代。
  4. 交叉操作:选择的父代进行交叉,生成子代。
  5. 变异操作:对子代进行变异操作。
  6. 形成新种群:用子代替换部分或全部父代,形成新种群。
  7. 终止条件:判断是否满足终止条件,如达到最大迭代次数或找到满意的解。

以上过程不断重复,直到满足终止条件为止。

基于sko的使用

这里使用sko.GA包求np.sin(x1) * np.cos(x2) + np.exp(-(x12 + x22))最小值

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sko.GA import GA


def objective_function(solution):
    x1, x2 = solution
    return np.sin(x1) * np.cos(x2) + np.exp(-(x1**2 + x2**2))


##需要定义目标函数、变量个数、种群大小等参数,迭代次数,变量的取值范围
ga = GA(func=objective_function, n_dim=2, size_pop=300, max_iter=300, lb=[-1, -1], ub=[1, 1], precision=1e-6)

# 运行遗传算法并获取最优解
best_x, best_y = ga.run()
print('Best x:', best_x)
print('Best y:', best_y)

# 绘制收敛曲线
history = pd.DataFrame(ga.all_history_Y)
fig, ax = plt.subplots(1, 2, figsize=(20, 8))

# 绘制每次迭代的最优值
ax[0].plot(history.index, history.values, color='red')
ax[0].set_title('Convergence Curve of Genetic Algorithm')
ax[0].set_xlabel('Generation')
ax[0].set_ylabel('Best Value')

# 绘制最优值的累积最小值
ax[1].plot(history.index, history.min(axis=1).cummin(), color='blue', label='Cumulative Minimum')
ax[1].set_title('Cumulative Minimum of Best Values')
ax[1].set_xlabel('Generation')
ax[1].set_ylabel('Cumulative Minimum Value')
ax[1].legend()

plt.show()

image.png

遗传算法原理具体流程如下:

1.定义适应度函数

对于种群中的每个个体,使用适应度函数计算其适应度值。适应度函数根据问题的目标定义,评估每个个体的优劣。适应度值越高,表示个体越优越。

# 定义适应度函数
def fitness(x):
    return np.sin(x) * np.cos(x)  # 适应度函数用于寻找最大值

2.定义解码函数

由于遗传算法中数值都是用二进制来表示,所以就需要解码函数将二进制转换为10进制,这里涉及到映射的概念,其实就是先将原本的二进制数转化为10进制,再查看该10进制的值对应变量区间的值。并且原本的二进制越长,转化后的实数就越精确。

原本:[11111110]转换为10进制就是254

转换为10进制:

254281=0.996078431372549\begin{align} \frac{254}{2^{8}-1} = 0.996078431372549 \end{align}

这样就是压缩到[0,1]区间,然后再进行归一化的逆运算

xxminxmaxxmin=num\begin{equation} \frac{x - xmin}{xmax - xmin} = num \end{equation}

所以:x=num(xmaxxmin)+xmin x = num*(xmax - xmin) + xmin

image.png

# 二进制解码函数
def decode(individual, x_min, x_max, bit_length):
    decimal_value = int("".join(map(str, individual)), 2)
    return x_min + (x_max - x_min) * decimal_value / (2**bit_length - 1)

3. 初始化种群(Initialization)

在遗传算法的开始阶段,首先需要生成初始种群。种群由一组个体组成,每个个体表示一个可能的解。初始化种群的方法通常是随机生成,即根据问题的解空间随机生成一定数量的个体。

def initialize_population(pop_size, bit_length):
    return np.random.randint(0, 2, (pop_size, bit_length))

4. 选择父代(Selection)

根据适应度值,从种群中选择个体作为父代,以生成下一代个体。常用的选择方法有轮盘赌选择(Roulette Wheel Selection)、锦标赛选择(Tournament Selection)和排序选择(Rank Selection)等。

轮盘赌选择算法计算所有染色体的适应值,占据总适应值的百分比Pi来计算各个个体被选中的概率。 image.png

def selection(population, fitness_values):
    min_fitness = np.min(fitness_values)
    adjusted_fitness_values = fitness_values - min_fitness + 1e-10  # 确保非负
    total_fitness = np.sum(adjusted_fitness_values)
    probabilities = adjusted_fitness_values / total_fitness
    selected_indices = np.random.choice(len(population), size=len(population), p=probabilities)
    return population[selected_indices]

4. 交叉操作(Crossover)

选择的父代个体进行交叉操作,生成子代。交叉操作通过交换两个个体的部分基因,产生新的个体。常用的交叉方法有单点交叉(Single-point Crossover)、双点交叉(Two-point Crossover)和均匀交叉(Uniform Crossover)等。

这里使用单点交叉,并且染色体交叉一般是有概率的具体取这里(0.4~0.99之间)

# 交叉操作(单点交叉)
def crossover(parent1, parent2, crossover_prob=0.8):
    if np.random.random() < crossover_prob:
        crossover_point = np.random.randint(1, len(parent1))
        child1 = np.concatenate([parent1[:crossover_point], parent2[crossover_point:]])
        child2 = np.concatenate([parent2[:crossover_point], parent1[crossover_point:]])
    else:
        child1, child2 = parent1.copy(), parent2.copy()

    return child1, child2

5. 变异操作(Mutation)

对子代进行变异操作,通过随机改变个体的一部分基因,增加种群的多样性。变异操作有助于防止算法陷入局部最优解。常用的变异方法有位翻转变异(Bit-flip Mutation)等。

def mutation(individual, mutation_rate=0.01):
    for i in range(len(individual)):
        if np.random.rand() < mutation_rate:
            individual[i] = 1 - individual[i]
    return individual

6.主程序

# 遗传算法主程序
def genetic_algorithm(pop_size, x_min, x_max, generations, bit_length, mutation_rate=0.01):
    population = initialize_population(pop_size, bit_length)
    for generation in range(generations):
        decoded_population = np.array([decode(ind, x_min, x_max, bit_length) for ind in population])
        fitness_values = np.array([fitness(ind) for ind in decoded_population])
        population = selection(population, fitness_values)
        new_population = []
        for i in range(0, pop_size, 2):
            parent1, parent2 = population[i], population[i+1]
            child1, child2 = crossover(parent1, parent2)
            new_population.extend([child1, child2])
        population = np.array([mutation(ind, mutation_rate) for ind in new_population])
    best_individual = population[np.argmax([fitness(decode(ind, x_min, x_max, bit_length)) for ind in population])]
    best_solution = decode(best_individual, x_min, x_max, bit_length)
    return best_solution, fitness(best_solution)  # 返回最优个体和对应的最大值

7.运行代码

import numpy as np

# 定义适应度函数
def fitness(x):
    return np.sin(x) * np.cos(x)  # 适应度函数用于寻找最大值

# 二进制解码函数
def decode(individual, x_min, x_max, bit_length):
    decimal_value = int("".join(map(str, individual)), 2)
    return x_min + (x_max - x_min) * decimal_value / (2**bit_length - 1)

# 初始化种群
def initialize_population(pop_size, bit_length):
    return np.random.randint(0, 2, (pop_size, bit_length))

# 选择操作(轮盘赌选择)
def selection(population, fitness_values):
    min_fitness = np.min(fitness_values)
    adjusted_fitness_values = fitness_values - min_fitness + 1e-10  # 确保非负
    total_fitness = np.sum(adjusted_fitness_values)
    probabilities = adjusted_fitness_values / total_fitness
    selected_indices = np.random.choice(len(population), size=len(population), p=probabilities)
    return population[selected_indices]


# 交叉操作(单点交叉)
def crossover(parent1, parent2, crossover_prob=0.8):
    if np.random.random() < crossover_prob:
        crossover_point = np.random.randint(1, len(parent1))
        child1 = np.concatenate([parent1[:crossover_point], parent2[crossover_point:]])
        child2 = np.concatenate([parent2[:crossover_point], parent1[crossover_point:]])
    else:
        child1, child2 = parent1.copy(), parent2.copy()

    return child1, child2

# 变异操作
def mutation(individual, mutation_rate=0.01):
    for i in range(len(individual)):
        if np.random.rand() < mutation_rate:
            individual[i] = 1 - individual[i]
    return individual

# 遗传算法主程序
def genetic_algorithm(pop_size, x_min, x_max, generations, bit_length, mutation_rate=0.01):
    population = initialize_population(pop_size, bit_length)
    for generation in range(generations):
        decoded_population = np.array([decode(ind, x_min, x_max, bit_length) for ind in population])
        fitness_values = np.array([fitness(ind) for ind in decoded_population])
        population = selection(population, fitness_values)
        new_population = []
        for i in range(0, pop_size, 2):
            parent1, parent2 = population[i], population[i+1]
            child1, child2 = crossover(parent1, parent2)
            new_population.extend([child1, child2])
        population = np.array([mutation(ind, mutation_rate) for ind in new_population])
    best_individual = population[np.argmax([fitness(decode(ind, x_min, x_max, bit_length)) for ind in population])]
    best_solution = decode(best_individual, x_min, x_max, bit_length)
    return best_solution, fitness(best_solution)  # 返回最优个体和对应的最大值

# 参数设置
pop_size = 100  # 种群规模
x_min = -1** np.pi  # 搜索空间最小值
x_max = 1 * np.pi   # 搜索空间最大值
generations = 100  # 最大迭代次数
bit_length = 16    # 二进制字符串长度
mutation_rate = 0.01  # 变异率

# 运行遗传算法
best_solution, max_value = genetic_algorithm(pop_size, x_min, x_max, generations, bit_length, mutation_rate)
print(f"最优解: x = {best_solution}, 最大值: f(x) = {max_value}")