遗传算法python实现 解决求解二元函数最大值问题 (数据科学实验报告)

参考几个博客实现遗传算法的python代码(参考文章放在后面包括原理解释和代码)
1 问题描述
现有优化问题如下:
max (1, 2) = 201 + 102 + 1012 − 0.21 − 0.32 10 ≤ 1 ≤ 80 0 ≤ 2 ≤ 40 1, 2为整数
使用遗传算法对该问题进行求解。

2 技术路线
通过实现遗传算法,求解函数最大值

遗传算法是从代表问题可能潜在的解集的一个种群开始的,而一个种群则由经过基因编码的一定数目的个体组成。每个个体实际上是染色体带有特征的实体。

染色体作为遗传物质的主要载体,即多个基因的集合,其内部表现(即基因型)是某种基因组合,它决定了个体的形状的外部表现,如黑头发的特征是由染色体中控制这一特征的某种基因组合决定的。因此,在一开始需要实现从表现型到基因型的映射即编码工作。由于仿照基因编码的工作很复杂,我们往往进行简化,如二进制编码。

初代种群产生之后,按照适者生存和优胜劣汰的原理,逐代演化产生出越来越好的近似解,在每一代,根据问题域中个体的适应度大小选择个体,并借助于自然遗传学的遗传算子进行组合交叉和变异,产生出代表新的解集的种群。

这个过程将导致种群像自然进化一样的后生代种群比前代更加适应于环境,末代种群中的最优个体经过解码(decoding),可以作为问题近似最优解。

下图是遗传算法的流程:
遗传算法python实现 解决求解二元函数最大值问题 (数据科学实验报告)_第1张图片

遗传算法大致实现过程解释为:

  1. 随机产生种群。
  2. 根据策略判断个体的适应度,是否符合优化准则,若符合,输出最佳个体及其最优解,结束。否则,进行下一步。
  3. 依据适应度选择父母,适应度高的个体被选中的概率高,适应度低的个体被淘汰。
  4. 用父母的染色体按照一定的方法进行交叉,生成子代。
  5. 对子代染色体进行变异。

预先对代码做一些说明:
7. 编码方法选择二进制编码方式
8. 选择函数中的选择算子采用轮盘赌选择,每个个体进入下一代的概率等于它的适应度值与整个种群中个体适应度值和的比例。
9. 遗传中染色体的交叉采用的是单点交叉
10. 基因突变中选择的是基本位突变

3 .算法实现

import math
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D

# 定义编码长度
DNA_SIZE = 24
# 定义种群容量 即个体数量
POP_SIZE = 200
# 定义交叉概率,范围一般是0.6~1
CROSSOVER_RATE = 0.8
# 突变常数(又称为变异概率),通常是0.1或者更小。
MUTATION_RATE = 0.005
# 迭代次数
N_GENERATIONS = 50
# x1,x2取值范围
x_bound = [10,80]
y_bound = [0,40]

# 定义适应度函数
def F(x,y):
    return 20 * x + 10 * y + 10 * x * y - np.exp(0.2 * x) - np.exp(0.3 * y)

# 3d绘图
def plot_3d(ax):
    X = np.arange(*x_bound, 5)
    Y = np.arange(*y_bound, 5)
    X, Y = np.meshgrid(X, Y)   # 网格的创建
    Z = F(X,Y)
    # R = np.sqrt(X ** 2 + Y**2)
    # Z = np.sin(R)\

    # rstride:行之间的跨度 cstride:列之间的跨度 camp是颜色映射表
    ax.plot_surface(X, Y, Z, rstride=20, cstride=20,alpha=0.5,cmap=plt.cm.coolwarm)
    # 设置z轴的维度
    ax.set_zlim(-40, 40)

    # 坐标标签
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')
    plt.pause(3)
    plt.show()

# 评价个体对环境的适应度 在MAX问题中适应度越大越有可能留下
def get_fitness(pop):
    x, y = translateDNA(pop)
    # pred是将可能解带入函数F中得到的预测值
    pred = F(x, y)
    return (pred - np.min(pred)) + 1e-3
    # 减去最小的适应度是为了防止适应度出现负数,通过这一步fitness的范围为[0, np.max(pred)-np.min(pred)],最后在加上一个很小的数防止出现为0的适应度

# 解码环节  即将二进制按权展开为十进制,将转换后的实数压缩到[0,1]之间的小数,再将其映射到给定区间中即可
# pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
# 注:此处的DNA_SIZE即染色体长度 为24,两个实数共48位二进制,奇数24列为x的编码,偶数为y的
def translateDNA(pop):
    x_pop = pop[:, 1::2]  # 奇数列表示x1
    y_pop = pop[:, ::2]  # 偶数列表示x2

    # pop:(POP_SIZE,DNA_SIZE)*(DNA_SIZE,1) --> (POP_SIZE,1)  dot用于向量点积和矩阵乘法。
    x = x_pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * (x_bound[1] - x_bound[0]) + x_bound[0]
    y = y_pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * (y_bound[1] - y_bound[0]) + y_bound[0]
    return x, y


# 实现交叉和变异 同时将后代加入新种群中,输出新种群
def crossover_and_mutation(pop, CROSSOVER_RATE=0.8):
    new_pop = []
    for father in pop:  # 遍历种群中的每一个个体,将该个体作为父亲
        child = father  # 孩子先得到父亲的全部基因(指二进制中的0、1字符)
        if np.random.rand() < CROSSOVER_RATE:  # 产生子代时不是必然发生交叉,而是以一定的概率发生交叉
            mother = pop[np.random.randint(POP_SIZE)]  # 在种群中选择另一个个体,并将该个体作为母亲
            cross_points = np.random.randint(low=0, high=DNA_SIZE * 2)  # 随机产生交叉的点
            child[cross_points:] = mother[cross_points:]  # 孩子得到位于交叉点后的母亲的基因
        mutation(child)  # 每个后代有一定的机率发生变异
        new_pop.append(child)  #将后代加入新种群中
    return new_pop

# 后代变异函数
def mutation(child, MUTATION_RATE=0.003):
    if np.random.rand() < MUTATION_RATE:  # 以MUTATION_RATE的概率进行变异
        mutate_point = np.random.randint(0, DNA_SIZE * 2)  # 随机产生一个实数,代表要变异基因的位置
        child[mutate_point] = child[mutate_point] ^ 1  # 将变异点的二进制为反转

# 选择留下的 不能单纯的选择适应度高的,否则会陷入局部最优而非全局最优
# 此处进行折中处理,即适应度越高的,被选择的机会越高,适应度低的,被选择的机会越低。
def select(pop, fitness):  # nature selection wrt pop's fitness
    # choice(a,size,replace,p)从一维array a 或 int 数字a 中,以概率p随机选取大小为size的数据,
    # replace表示是否重用元素,即抽取出来的数据是否放回原数组中,默认为true(抽取出来的数据有重复)
    idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True,
                           p=(fitness) / (fitness.sum()))
    return pop[idx]

# 输出
def print_info(pop):
    fitness = get_fitness(pop)
    max_fitness_index = np.argmax(fitness)
    print("max_fitness:", fitness[max_fitness_index])
    x, y = translateDNA(pop)
    print("最优的基因型:", pop[max_fitness_index])
    print("(x, y):", (x[max_fitness_index], y[max_fitness_index]))
    x1 = x[max_fitness_index]
    y1 = y[max_fitness_index]
    print(F(x1,y1))

if __name__ == "__main__":
    # 创建一个画布 并转换为三维建图
    fig = plt.figure()
    ax = Axes3D(fig,auto_add_to_figure=False)
    fig.add_axes(ax)
    plt.ion()  # 将画图模式改为交互模式,程序遇到plt.show不会暂停,而是继续执行
    plot_3d(ax)

    # 随机生成二进制码 即染色体
    pop = np.random.randint(2, size=(POP_SIZE, DNA_SIZE * 2))
    for _ in range(N_GENERATIONS):  # 迭代N代
        # 使用自定义translateDNA函数进行解码
        x, y = translateDNA(pop)
        if 'sca' in locals():
            sca.remove()
        # 颜色是黑色,标记为●
        sca = ax.scatter(x, y, F(x, y), c='black', marker='o');
        plt.show();
        plt.pause(0.1)
        pop = np.array(crossover_and_mutation(pop, CROSSOVER_RATE))
        # F_values = F(translateDNA(pop)[0], translateDNA(pop)[1])#x, y --> Z matrix
        fitness = get_fitness(pop)
        pop = select(pop, fitness)  # 选择生成新的种群

    print_info(pop)
    plt.ioff()
plot_3d(ax)

3D绘图结果如下:
遗传算法python实现 解决求解二元函数最大值问题 (数据科学实验报告)_第2张图片
输出结果:
遗传算法python实现 解决求解二元函数最大值问题 (数据科学实验报告)_第3张图片
参考帖子:
以下几个帖子供参考,原理和代码都解释的非常清楚。
遗传算法原理详解
原理详解2

python代码详解博客参考
Python实现代码2

C语言代码详解
matplotlib 画图用

你可能感兴趣的:(数据分析,数据分析,python)