问题描述

f(x)=x*sin(x)+1, x属于[0,2π] ,以求解f(x)的最大值和最小值问题为例,设计针对f(x)的遗传算法程序,然后进行运行求解
(1)确定基本功能:本实验是实现f(x)的最大值和最小值的求解。
(2)对f(x)进行编码:用一个二进制矢量表示一个染色体,由染色体来代表变量x的实数值。
(3)设计适应度函数:由于要求f(x)的最值,所以适应度函数就是f(x)。
(4)针对f(x)的设计并且实现遗传算法程序:遗传操作主要包括复制、交叉和变异。复制是直接将父代遗传给子代,即根据个体的适应度函数值所度量的优劣程度决定它在下一代是被淘汰还是被遗传。交叉从能进入下一代的个体中选出两个,将两者的部分码值进行交换。变异是根据变异概率选出一个个体,随机对其某位编码进行改变。
(5)设计初始种群:默认设置为50个随机产生的23位字节的染色体,可以通过输入来设置种群规模。
(6)调试交叉和变异概率:在常用的交叉和变异概率范围内,结果随交叉和变异的概率的改变而改变,之间差异相对来说不太明显.

种群初始化

种群初始化的过程就是随机生成种群规模数量的随机二进制编码个体

参考代码

def get_chromosome(size, length):
    """
    生成size个长度为length的染色体列表
    :param size: 种群规模规模
    :param length: 染色体长度
    :return: 二维列表
    """
    population_temp = []
    for i in range(size):
        population_temp.append([random.randint(0, 1) for _ in range(length)])   # 生成长度为length的随机二进制列表,并存放到population_temp列表中
    return population_temp

计算搜索精度

此处根据精度计算公式:

$$ \delta = \frac{U_{x} - L_{x}}{2 ^{l} - 1} $$

参考代码

def get_accuracy(min_, max_, length):
    """
    计算搜索精度
    :param min_: 基因的最小值
    :param max_: 基因的最大值
    :param length: 染色体长度
    :return: 精度
    """
    return (max_ - min_) / (2 ** length - 1)    # 精度计算公式

染色体解码

此处根据解码公式:

$$ x = L_{x} + \delta \sum_{i=1}^l A_{i} 2^{i-1} $$

参考代码

def chromosome_decode(chromosome_list, min_, accuracy_):
    """
    染色体解码
    :param chromosome_list: 二进制染色体列表
    :param min_: 基因的最小值
    :param accuracy_: 精度
    :return: 解码的结果
    """
    decimal = int(''.join([str(i) for i in chromosome_list]), 2)    # 二进制列表转为十进制整型
    return min_ + accuracy_ * decimal   # 解码公式

选择算子

遗传算法中选择算子的实现方式有很多种,比较有效的是轮盘赌算法和联赛选择算法,此处使用轮盘赌算法

轮盘赌算法的精髓就是可以根据个体的适应度进行随机选择,适应度越大的个体被选择的概率越大,当种群规模较大时,这种算法可以比较真实的模拟出自然状态中的情况

参考代码

def select(chromosome_list, fitness_list):
    """
    选择(轮盘赌算法)
    :param chromosome_list: 二维列表的种群
    :param fitness_list: 适应度列表
    :return: 选择之后的种群列表
    """
    population_fitness = np.array(fitness_list).sum()  # 种群适应度
    fit_ratio = [i / population_fitness for i in fitness_list]  # 每个个体占种群适应度的比例
    fit_ratio_add = [0]  # 个体累计概率
    for i in fit_ratio:
        fit_ratio_add.append(fit_ratio_add[len(fit_ratio_add) - 1] + i)     # 计算每个个体的累计概率,并存放到fit_ratio_add中
    fit_ratio_add = fit_ratio_add[1:]   # 去掉首位的0

    rand_list = [random.uniform(0, 1) for _ in chromosome_list]     # 生成和种群规模相等的随机值列表,用于轮盘赌选择个体
    rand_list.sort()
    fit_index = 0
    new_index = 0
    new_population = chromosome_list.copy()
    '''个体选择 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
    '''个体选择 end'''
    return new_population

交叉算子

交叉算子用来模拟自然状态中的交配过程,算法中为了简化此过程,直接将两个个体的部分染色体交换

参考代码

def exchange(chromosome_list, pc):
    """
    交叉
    :param chromosome_list: 二维列表的种群
    :param pc: 交叉概率
    """
    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]))    # 随机生成交叉点
            '''对第i位和i+1位进行交叉 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])])
            '''对第i位和i+1位进行交叉 end'''

            '''将新交叉后的染色体替换原染色体 start'''
            chromosome_list[i] = exchanged_list1
            chromosome_list[i + 1] = exchanged_list2
            '''将新交叉后的染色体替换原染色体 end'''

变异算子

变异过程模拟了自然状态中的基因突变,通过随机改变一个个体的一位基因,模拟变异过程

参考代码

def mutation(chromosome_list, pm):
    """
    变异
    :param chromosome_list: 二维列表的种群
    :param pm: 变异概率
    """
    for i in range(len(chromosome_list)):
        if random.uniform(0, 1) < pm:
            m_point = random.randint(0, len(chromosome_list[0]) - 1)    # 随机生成变异点
            chromosome_list[i][m_point] = chromosome_list[i][m_point] ^ 1   # 将该位的值与1异或(即将0置为1,1置为0)

可进行的优化

其实只实现上述函数,一个完整的遗传算法过程就可以实现了,但是实际操作中,我们可以对算法过程进行一些优化,让结果更符合我们的预期

  1. 使用精英保留策略,将最优个体直接保留到下一代,而不经过选择、交叉、变异过程
  2. 在每一代中直接淘汰一部分不符合预期的个体,例如去掉适应度为负数的个体

完整代码

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

population_size = 50                        # 种群初始规模
generation_count = 50                       # 遗传代数
gene_length = 23                            # 染色体长度
exchange_ratio = 0.8                        # 交叉概率
variation_ratio = 0.01                      # 变异概率
solve_max = True                            # 为True则求解最大值,为False则求解最小值
function = lambda x: x * np.sin(x) + 1      # 需要求解的数学函数
x_min = 0                                   # 基因的最小值,即变量x能取到的最小值
x_max = 2 * np.pi                           # 基因的最大值,即变量x能取到的最大值

plt.rcParams['font.sans-serif'] = ['FangSong']  # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False  # 支持负号显示


def get_chromosome(size, length):
    """
    生成size个长度为length的染色体列表
    :param size: 种群规模规模
    :param length: 染色体长度
    :return: 二维列表
    """
    population_temp = []
    for i in range(size):
        population_temp.append([random.randint(0, 1) for _ in range(length)])   # 生成长度为length的随机二进制列表,并存放到population_temp列表中
    return population_temp


def get_accuracy(min_, max_, length):
    """
    计算搜索精度
    :param min_: 基因的最小值
    :param max_: 基因的最大值
    :param length: 染色体长度
    :return: 精度
    """
    return (max_ - min_) / (2 ** length - 1)    # 精度计算公式


def chromosome_decode(chromosome_list, min_, accuracy_):
    """
    染色体解码
    :param chromosome_list: 二进制染色体列表
    :param min_: 基因的最小值
    :param accuracy_: 精度
    :return: 解码的结果
    """
    decimal = int(''.join([str(i) for i in chromosome_list]), 2)    # 二进制列表转为十进制整型
    return min_ + accuracy_ * decimal   # 解码公式


def get_fitness(x, solve_flag):
    """
    计算适应度
    :param x: 染色体解码的结果
    :param solve_flag: 求最大值则为True,最小值则为False
    :return: 适应度结果
    """
    if solve_flag:
        return function(x)
    return -(function(x))


def select(chromosome_list, fitness_list):
    """
    选择(轮盘赌算法)
    :param chromosome_list: 二维列表的种群
    :param fitness_list: 适应度列表
    :return: 选择之后的种群列表
    """
    population_fitness = np.array(fitness_list).sum()  # 种群适应度
    fit_ratio = [i / population_fitness for i in fitness_list]  # 每个个体占种群适应度的比例
    fit_ratio_add = [0]  # 个体累计概率
    for i in fit_ratio:
        fit_ratio_add.append(fit_ratio_add[len(fit_ratio_add) - 1] + i)     # 计算每个个体的累计概率,并存放到fit_ratio_add中
    fit_ratio_add = fit_ratio_add[1:]   # 去掉首位的0

    rand_list = [random.uniform(0, 1) for _ in chromosome_list]     # 生成和种群规模相等的随机值列表,用于轮盘赌选择个体
    rand_list.sort()
    fit_index = 0
    new_index = 0
    new_population = chromosome_list.copy()
    '''个体选择 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
    '''个体选择 end'''
    return new_population


def exchange(chromosome_list, pc):
    """
    交叉
    :param chromosome_list: 二维列表的种群
    :param pc: 交叉概率
    """
    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]))    # 随机生成交叉点
            '''对第i位和i+1位进行交叉 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])])
            '''对第i位和i+1位进行交叉 end'''

            '''将新交叉后的染色体替换原染色体 start'''
            chromosome_list[i] = exchanged_list1
            chromosome_list[i + 1] = exchanged_list2
            '''将新交叉后的染色体替换原染色体 end'''


def mutation(chromosome_list, pm):
    """
    变异
    :param chromosome_list: 二维列表的种群
    :param pm: 变异概率
    """
    for i in range(len(chromosome_list)):
        if random.uniform(0, 1) < pm:
            m_point = random.randint(0, len(chromosome_list[0]) - 1)    # 随机生成变异点
            chromosome_list[i][m_point] = chromosome_list[i][m_point] ^ 1   # 将该位的值与1异或(即将0置为1,1置为0)


def get_best(fitness_list):
    """
    计算这一代中的最优个体
    :param fitness_list: 适应度列表
    :return: 最优个体的下标
    """
    return fitness_list.index(max(fitness_list))


def eliminate(fitness_list):
    """
    淘汰(去掉负值)
    :param fitness_list: 适应度列表
    :return: 淘汰后的列表
    """
    fit_value = []
    for i in range(len(fitness_list)):
        fit_value.append(fitness_list[i] if fitness_list[i] >= 0 else 0.0)   # 将小于0的适应度置为0
    return fit_value


if __name__ == '__main__':
    results = []  # 存储每一代的最优解,二维列表
    all_fitness = []    # 存放每一代中的最高适应度和种群适应度
    population = get_chromosome(population_size, gene_length)   # 种群初始化
    for _ in range(generation_count):
        accuracy = get_accuracy(x_min, x_max, gene_length)  # 计算搜索精度
        decode_list = [chromosome_decode(individual, x_min, accuracy) for individual in population]  # 解码之后的列表
        fit_list = [get_fitness(decode_i, solve_max) for decode_i in decode_list]  # 计算每个个体的适应度
        fit_list = eliminate(fit_list)  # 淘汰一部分,去掉负值
        results.append([decode_list[get_best(fit_list)],
                        fit_list[get_best(fit_list)] if solve_max else -fit_list[get_best(fit_list)]])  # 保存每一代最优解,即适应度最高的个体
        all_fitness.append(np.array(fit_list).sum() if solve_max else -np.array(fit_list).sum())    # 保存每一代中的最高适应度和种群适应度
        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('最{}值点 x={},y={}'.format('大' if solve_max else '小', results[-1][0], results[-1][1]))

    '''绘制极值趋势图和种群适应度趋势图 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('极值点趋势图')
    fig1.set_xlabel("遗传代数")
    fig1.set_ylabel("极值")
    fig2.set_title('种群整体适应度趋势图')
    fig2.set_xlabel("遗传代数")
    fig2.set_ylabel("种群适应度")

    plt.show()
    '''绘制极值趋势图和种群适应度趋势图 end'''
最后修改:2021 年 05 月 13 日
愿君多打钱,此物最相思