首先了解一下达尔文进化论:人类在繁衍过程中,通过交配产生基因重组和变异,从而产生更好的个体,也可能是更差的个体,

每一代人接受大自然的考验,优胜略汰,适应能力强的被保存下来,差的被淘汰,使得人类对环境的适应能力越来越强;

 

遗传算法就是借鉴了人类的进化过程,更好地适应环境就是我们的目标(y),每一代人就是我们的可行解(取值范围内的一组 x),遗传算法就是通过不断交叉变异 x 来获取最优的 y,也可以理解为一种启发式搜索算法

 

基本概念

粘过来的,主要看加粗的,废话比较多

基因型(genotype):性状染色体的内部表现;

表现型(phenotype):染色体决定的性状的外部表现,或者说,根据基因型形成的个体的外部表现;

进化(evolution):种群逐渐适应生存环境,品质不断得到改良。生物的进化是以种群的形式进行的。

 

个体(individual):指染色体带有特征的实体;

种群(population):个体的集合,该集合内个体数称为种群的大小。

适应度(fitness):度量某个物种对于生存环境的适应程度。

选择(selection):以一定的概率从种群中选择若干个个体。一般,选择过程是一种基于适应度的优胜劣汰的过程。

复制(reproduction):细胞分裂时,遗传物质DNA通过复制而转移到新产生的细胞中,新细胞就继承了旧细胞的基因。

交叉(crossover):两个染色体的某一相同位置处DNA被切断,前后两串分别交叉组合形成两个新的染色体。也称基因重组或杂交;

变异(mutation):交叉时可能(很小的概率)产生某些复制差错,变异产生新的染色体,表现出新的性状。

编码(coding):DNA中遗传信息在一个长链上按一定的模式排列。遗传编码可看作从表现型到基因型的映射。    【对 x 的另一种表达,方便交叉变异】

解码(decoding):基因型到表现型的映射。    【把编码重新换算成 x,方便计算适应度】

 

算法框架

本图是遗传算法的整体流程,学会了之后看图会比较清晰

以下为专业术语,吹牛逼专用

以决策变量的编码作为运算对象,使得优化过程借鉴生物学中的概念成为可能

直接以目标函数作为搜索信息,确定搜索方向和范围,属于无导数优化

同时使用多个搜索点的搜索信息,算是一种隐含的并行性

是一种基于概率的搜索技术

具有自组织,自适应和自学习等特性

 

具体步骤--干货

星表示重要度以及难度

编码 ****

核心是 解码后 能够 覆盖 x 的取值范围;

编码的方式对模型影响非常大,包括 算子的设计难度、模型的收敛效果等;

常用方法有 二进制编码(110110)、浮点数编码(12.3)、字符编码(AcDa)

种群初始化 **

1. 最好保证初始编码为可行解,这样可以加速收敛,当然你也可以不保证,具体根据业务难易而定

2. 注意每个个体之间不能一样,个体要有区别

适应度 *****

遗传算法的核心,相当于达尔文进化论中的环境,负责对可行解进行筛选;

适应度分为 单目标和多目标函数,需要根据业务进行人工设计;

适应度变换 **

适应度变换主要是对适应度的一个衡量,如越大越好,还是越小越好,如果想求最小值,那就给适应度加个负号

生成新种群

核心思想是保证好的基因尽可能保留,并存在交叉和突变;

操作思路是通过选择、交叉、变异,产生期望的可行解,淘汰不符合要求的可行解

注意事项

1. 演变过程中种群大小始终不变(如200个),每轮选择+交叉+变异共同生成 200个 个体

2. 其实还有一个环节叫 复制,也就是说 直接选择为 下一代,不做任何变化,其实和 选择 作用差不多

3. 选择比复制的功能更强大一点,比如我可以添加各种选择策略,选上的才 复制

4. 一般来说先交叉再变异,直接复制的不会变异

5. 先选择还是先交叉,视具体情况而定

6. 选择、交叉、变异的方式都可自行设计,当然有一些常用的方法

 

7. 在编程时,一定要注意 可变变量,避免处理过程中,改变原个体,这样会导致模型不稳定等各种小问题    【实战中很容易出错】

 

遗传算子-选择 ****

选择的目的是让 适应度高 的个体有更大概率保存下来,注意是更大概率保存,不是一定保存;  【也就是说选 200次,适应度最高的可能被选中 50次,但也可能选中了适应度最低的】

最常用的是 轮盘赌 选择法;

更多请参考 我的博客

 

遗传算子-交叉 **

常用的有:单点交叉、多点交叉、均匀交叉、算术交叉等,都很简单,也可自行设计

单点交叉

一般单点交叉加上合适的交叉概率就够用了

多点交叉

均匀交叉(也称一致交叉,Uniform Crossover):两个配对个体的每个基因座上的基因都以相同的交叉概率进行交换,从而形成两个新个体。

算术交叉(Arithmetic Crossover):由两个个体的线性组合而产生出两个新的个体。该操作对象一般是由浮点数编码表示的个体

更多请参考 参考资料 

 

遗传算子-变异 **

基本位变异(Simple Mutation):对个体编码串中以变异概率、随机指定的某一位或某几位基因座上的值做变异运算。

均匀变异(Uniform Mutation):分别用符合某一范围内均匀分布的随机数,以某一较小的概率来替换个体编码串中各个基因座上的原有基因值。(特别适用于在算法的初级运行阶段)

边界变异(Boundary Mutation):随机的取基因座上的两个对应边界基因值之一去替代原有基因值。特别适用于最优点位于或接近于可行解的边界时的一类问题。

非均匀变异:对原有的基因值做一随机扰动,以扰动后的结果作为变异后的新基因值。对每个基因座都以相同的概率进行变异运算之后,相当于整个解向量在解空间中作了一次轻微的变动。

高斯近似变异:进行变异操作时用符号均值为P的平均值,方差为P**2的正态分布的一个随机数来替换原有的基因值。

 

实战总结

一)需要区分不可行解和非最优解,不可行解是不满足约束的解,非最优解是满足约束但y非最优的解,对待他们的方式是不同的

二)遗传算法的难点是 适应度函数 和 遗传算子的设计,无固定套路,但可遵循如下原则

三)要注意交叉和变异的概率,以及交叉、变异、选择的顺序,目的是避免变换太频繁,或者破坏最优解

四)要注意解空间的大小,解空间大很难收敛,或者模型不稳定,尽量把解空间大的问题分解成解空间小的问题,例如把 24小时规划 变成 24 个 1小时规划

 

时刻关注不可行解

1. 演变过程中要监控 解是否是可行解,过程产生的不可行解,可采用拒绝法直接淘汰,也可进行人工修复,对于可行但非最优解,可采取适当(小惩或者不惩)的淘汰策略 

2. 交叉和变异 最好不要 产生不可行解,因为这种操作很可能被直接淘汰,这样就没有起到样本多样化的作用,白白浪费了一次迭代

约束处理

1. 拒绝法:直接拒绝所有不可行解,这种方式简单粗暴,大部分时间效果不错,但丧失了一定的样本多样性,适用于要求极其严格的约束  

2. 人工修复法:把不可行解变成可行解,常用于 组合优化 问题

3. 奖惩函数法:类似于机器学习中的正则化,如

 

该方法直接把有约束问题变成了无约束问题,但该方法允许部分 不可行解 的存在 

 

应用场景

遗传算法的有趣应用很多,诸如寻路问题,8数码问题,囚犯困境,动作控制,找圆心问题(在一个不规则的多边形中,寻找一个包含在该多边形内的最大圆圈的圆心),TSP问题,生产调度问题,人工生命模拟等 

 

示例代码

求一个表达式的最大值

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
CROSSOVER_RATE = 0.8
MUTATION_RATE = 0.005
N_GENERATIONS = 50
X_BOUND = [-3, 3]
Y_BOUND = [-3, 3]

def F(x, y):
    # 目标函数
    z = 3 * (1 - x) ** 2 * np.exp(-(x ** 2) - (y + 1) ** 2) - 10 * (x / 5 - x ** 3 - y ** 5) * np.exp(
        -x ** 2 - y ** 2) - 1 / 3 ** np.exp(-(x + 1) ** 2 - y ** 2)
    return z

def plot_3d(ax):
    X = np.linspace(*X_BOUND, 100)
    Y = np.linspace(*Y_BOUND, 100)
    X, Y = np.meshgrid(X, Y)
    Z = F(X, Y)
    ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm)
    ax.set_zlim(-10, 10)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')
    plt.pause(3)
    plt.show()

def get_fitness(pop):
    # 适应度
    x, y = translateDNA(pop)    # 解码 DNA
    pred = F(x, y)
    ### 求最大值
    # 减去最小的适应度是为了防止适应度出现负数,通过这一步fitness的范围为[0, np.max(pred)-np.min(pred)],最后在加上一个很小的数防止出现为0的适应度
    # return (pred - np.min(pred)) + 1e-3          # 适应度函数变换
    ### 求最小值
    return (np.max(pred) - pred) + 1e-3

def translateDNA(pop):
    # pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
    print(pop)
    x_pop = pop[:, 1::2]  # 奇数列表示X
    y_pop = pop[:, ::2]  # 偶数列表示y

    # pop:(POP_SIZE,DNA_SIZE)*(DNA_SIZE,1) --> (POP_SIZE,1)
    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)  # 随机产生一个实数,代表要变异基因的位置
        child[mutate_point] = child[mutate_point] ^ 1  # 将变异点的二进制为反转

def select(pop, fitness):  # nature selection wrt pop's fitness
    # 轮盘赌选择:适应度高的被选择的概率更大
    idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True,
                           p=(fitness) / (fitness.sum()))       # p实际是个数组,大小(size)应该与指定的a相同,用来规定选取a中每个元素的概率,默认为概率相同
    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]))


if __name__ == "__main__":
    fig = plt.figure()
    ax = Axes3D(fig)
    plt.ion()  # 将画图模式改为交互模式,程序遇到plt.show不会暂停,而是继续执行
    plot_3d(ax)

    pop = np.random.randint(2, size=(POP_SIZE, DNA_SIZE * 2))  # matrix (POP_SIZE, DNA_SIZE)
    for _ in range(N_GENERATIONS):  # 迭代N代
        x, y = translateDNA(pop)
        print(locals())
        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)     # 计算最后种群的适应度,并取适应度最大的个体DNA,解码成 xy
    plt.ioff()
    plot_3d(ax)

 

 

 

参考资料:

https://blog.csdn.net/daydream13580130043/article/details/87916668  遗传算法 与 作业车间调度问题(C++实现)

https://blog.csdn.net/daydream13580130043/article/details/95927841  作业车间调度与遗传算法Python/Java实现及应用:BitMES,基于Electron的作业车间调度系统

 

https://blog.csdn.net/u010451580/article/details/51178225  遗传算法详解(GA)

https://www.jianshu.com/p/ae5157c26af9  【算法】超详细的遗传算法(Genetic Algorithm)解析

 

https://blog.csdn.net/u012750702/article/details/54563515  遗传算法中几种交叉算子小结

https://www.omegaxyz.com/2018/04/21/crossover_mutation/  遗传算法的交叉变异详解