遗传算法求取函数最值问题

目录

1. 关于遗传算法

2. 遗传算法的步骤

3. 代码实现

3.1 工具函数

3.1.1 目标函数

3.1.2 解码

3.1.3 交叉

3.1.4 变异

3.2 主函数部分

3.3 代码

4. 其他


1. 关于遗传算法

遗传算法是根据生物进化论提出的计算最优解的一种算法,核心思想是物竞天择,适者生存

网上关于遗传算法的讲解很多,本章会利用python实现遗传算法实现计算函数最大值,将其中的细节进行讨论

本章利用的函数为:y = x^2 ,定义域为0-10

关于遗传算法利用的其实是基因的遗传重组,所以这里每一个基因都是二进制的序列,例如10101

那二进制映射回定义域就是解码过程, start + (end - start) * tmp / (pow(2,length)-1) ,start 是定义域的左值,end 是定义域的右值,tmp是二进制序列,length 是二进制序列的长度。通过这样的计算就可以将二进制映射回给定的定义域中

那么二进制序列的长度如何定义?

是根据计算精度而言的,例如这里要计算的精度为0.01。那么就需要0.01->0.02->...->10.00中间共有10*10^2 = 1000个数字,那么需要的二进制就是10,因为2^10 = 1024可以保存1000个数字。那么二进制的序列长度就是 10

2. 遗传算法的步骤

接下来看看遗传算法的步骤:

这里只是根据自己实现的代码进行步骤分析,有些细节或者和网上实现不同的地方会在最后补充

1. 初始化种群 : 因为产生的二进制序列一般来说不是一个,这样是为了下面更好的交叉操作。并且,多个二进制序列也有利于找到最优的解

2. 解码 : 解码的意思就是将产生的二进制序列映射到对应的定义域当中,因为二进制产生的值很大,例如上述例子,定义域在0-10,精度为0.01,那么二进制长度为10,如果不进行映射的话,随便的一个二进制都会超过定义域10

当解码完成后,初始化种群已经全部变成在定义域当中的随机n(产生二进制序列的个数)个点

3. 计算适应度 :将定义域中的随机解码的点进行计算,例子中的y = x^2 被称为适应度函数

计算适应度其实就是计算这些值(x)对应的y,比如要求取最大值,那么看一下随机产生的x,哪一个y大,然后对较大的进行操作。这样反复操作的话,就可以找到最大值

4. 轮盘赌选取父系存活概率 : 当随机产生的n个点,计算完适应度函数后,会生成对应的n个函数值。函数值越大,我们说他越好(我们要计算最大值),那么他应该存活的概率就大。对应的方法是轮盘赌,例如产生的y值: 2 和 9,那么2/(2+9) 和 9/(2+9) 就是2和9存活的概率,这种就是轮盘赌。

注意:这只是概率,不是绝对的 9 > 2 ,就一定是 9 存活 

5. 交叉 : 交叉就是根据父系的二进制,将随机的比特位进行交换的操作,这样遗传的思维就出现了。

6. 变异 : 变异是为了跳出极值,将子代的二进制编码,随机取反。0- >1,1->0

具体的实现方式在代码里面讲解

3. 代码实现

如图,本章遗传算法实现的是计算y = x^2 的最大值

关于遗传算法的定义在这:

 

其中,mutation_rate 是二进制序列变异的概率,这个不应该过大,要不然子代就完全和父代不一样,那么遗传算法也就失去了意义

parents_rate 是父代中保存的概率,例如这里总共有10个种群,0.3就会保存3个父代,这里保存的方式是通过轮盘赌实现

实现的效果为:

3.1 工具函数

为了代码的模块化,这里utils里面存放了四个函数

3.1.1 目标函数

也就是适应度函数

# 目标函数
def function(x):
    # y = np.sin(x) * np.exp(-x)
    y = x**2
    return y

3.1.2 解码

decode 是根据传递的二进制序列矩阵bit_matrix(n*m,n是种群个数,m是二进制的长度) 进行编码,产生n个在start-end定义域中的十进制自变量

# 将二进制编码为十进制,并映射到定义域中
def decode(bit_matrix,num_group,start,end,length):
    ret = np.zeros(num_group)
    temp = []       # 保存转换的十进制数
    for i in range(num_group):
        tmp = int(''.join(map(lambda x:str(x),bit_matrix[i])),2)    # 获得每一条染色体的十进制
        ret[i] = start + (end - start) * tmp / (pow(2,length)-1)        # 映射回原始的定义域
        temp.append(tmp)
    return temp,ret

3.1.3 交叉

这里实现的方法有所不同

本章实现的遗传算法,种群数目是固定的 ,也就是说初始化是10个,那么父代保留了3个后,交叉产生的子代就只有7个,也就是代码中的count变量。

parents_groups 是父代的所有种群,而非保留之后的3个

实现交叉的方式为,将两组随机的二进制序列进行交叉。最后返回

# 交叉繁殖
def cross(count,parents_groups,length,cross_num=2):
    childen = []                        # 子代

    while len(childen) != count:       # 保证子代的数量和父代一样
        index = np.random.choice(np.arange(length),cross_num,replace=False)   # 随机交换cross_num个基因
        male = parents_groups[np.random.randint(0,len(parents_groups+1))]       # 从父代中随机挑选两个交叉繁殖
        female = parents_groups[np.random.randint(0,len(parents_groups+1))]

        childen_one = male.copy()
        childen_two = female.copy()

        childen_one[index] = female[index]          # 交换父母双方的基因产生两个子代
        childen.append(childen_one)

        if len(childen) == count:
            break

        childen_two[index] = male[index]
        childen.append(childen_two)
    return np.array(childen)

3.1.4 变异

变异是为了种群能够产生突变,这样随机产生的新的子代也许能够跳出极值

实现方式也很简单,num_mutation可以控制二进制变异的个数

# 变异
def mutation(children,mutation_rate,length,num_mutation=1):
    children_mutation = []
    for i in range(len(children)):
        tmp = children[i]
        if np.random.random() < mutation_rate:
            index = np.random.choice(np.arange(length),num_mutation,replace=False)

            for j in range(num_mutation):       # 变异
                if tmp[index[j]] == 1:
                    tmp[index[j]] = 0
                else:
                    tmp[index[j]]= 1
        children_mutation.append(tmp)

    return np.array(children_mutation)

3.2 主函数部分

有几点需要注意,计算适应度的时候,将它进行下面的操作,要不然轮盘赌选择的时候会报错。因为概率不能为负值

3.3 代码

主函数部分:

import numpy as np
import matplotlib.pyplot as plt
from utils import decode,function,cross,mutation


# 设定超参数
start,end = 0,10
length = 10                     # 染色体长度 bit,精度
num_group = 10                  # 种群数量
iteration_time = 2000             # 迭代次数
mutation_rate = 0.1             # 变异率
parents_rate = 0.3              # 父代中的保存个数(概率)


# 初始化二进制种群
init_group = np.random.randint(0,2,size=(num_group,length))

parents_group = init_group      # 父代

# 迭代
decode_parents_group = 0
for i in range(iteration_time):

    # 将二进制种群转为十进制,并映射到定义域中
    _, decode_parents_group = decode(bit_matrix=parents_group, num_group=num_group, start=start, end=end, length=length)

    # 计算种群适应度
    f = function(decode_parents_group)
    f = (f - np.min(f))+1e-8     # 防止 f 为负值或 0

    select = np.random.choice(np.arange(num_group),int(num_group*parents_rate),replace=True,p=f/sum(f))
    best_parents_group = parents_group[select]       # 父代中的保留

    count = len(parents_group) - len(best_parents_group)     # 计算差值

    # 交叉繁殖
    children = cross(count=count, parents_groups=parents_group, length=length)
    children = np.concatenate((best_parents_group, children))
    # 变异
    children = mutation(children=children,mutation_rate=mutation_rate,length=length)

    parents_group = children

fun = function(decode_parents_group)
x = np.linspace(start,end,100)
plt.plot(x,function(x),color='r')
plt.scatter(decode_parents_group,function(decode_parents_group))
plt.title('max is :%.4f' % np.max(fun))
plt.show()

utils 部分:

import numpy as np


# 目标函数
def function(x):
    # y = np.sin(x) * np.exp(-x)
    y = x**2
    return y


# 将二进制编码为十进制,并映射到定义域中
def decode(bit_matrix,num_group,start,end,length):
    ret = np.zeros(num_group)
    temp = []       # 保存转换的十进制数
    for i in range(num_group):
        tmp = int(''.join(map(lambda x:str(x),bit_matrix[i])),2)    # 获得每一条染色体的十进制
        ret[i] = start + (end - start) * tmp / (pow(2,length)-1)        # 映射回原始的定义域
        temp.append(tmp)
    return temp,ret


# 交叉繁殖
def cross(count,parents_groups,length,cross_num=2):
    childen = []                        # 子代

    while len(childen) != count:       # 保证子代的数量和父代一样
        index = np.random.choice(np.arange(length),cross_num,replace=False)   # 随机交换cross_num个基因
        male = parents_groups[np.random.randint(0,len(parents_groups+1))]       # 从父代中随机挑选两个交叉繁殖
        female = parents_groups[np.random.randint(0,len(parents_groups+1))]

        childen_one = male.copy()
        childen_two = female.copy()

        childen_one[index] = female[index]          # 交换父母双方的基因产生两个子代
        childen.append(childen_one)

        if len(childen) == count:
            break

        childen_two[index] = male[index]
        childen.append(childen_two)
    return np.array(childen)


# 变异
def mutation(children,mutation_rate,length,num_mutation=1):
    children_mutation = []
    for i in range(len(children)):
        tmp = children[i]
        if np.random.random() < mutation_rate:
            index = np.random.choice(np.arange(length),num_mutation,replace=False)

            for j in range(num_mutation):       # 变异
                if tmp[index[j]] == 1:
                    tmp[index[j]] = 0
                else:
                    tmp[index[j]]= 1
        children_mutation.append(tmp)

    return np.array(children_mutation)

4. 其他

这里用很多地方和网上实现的不一致,还有一些地方自己也不是特别明白

例如,保留父代的时候,可以重复保留吗?

本章的方法是可以(改为False就可以不重复),这里个人认为,如果选择保留父代不重复的话,那么基本上保留的父代就是按照概率值从大到小进行保留,那么初始化不太好的时候,很容易掉入极值的坑里

    select = np.random.choice(np.arange(num_group),int(num_group*parents_rate),replace=True,p=f/sum(f))

其他的例如,如果变异率或者变异的个数过多的话,那么父代留给子代的信息就完全被破坏了,那么遗传的意义也就没有了

这里计算 y = np.sin(x) * np.exp(-x) 的结果为:

猜你喜欢

转载自blog.csdn.net/qq_44886601/article/details/130342562
今日推荐