遗传算法求三元函数极值(python)-采用二进制编码

想看实数编码编码的
博客地址遗传算法求三元函数极值(python)-采用实数编码
*

遗传算法求三元函数极值(python)-采用二进制编码

本文的遗传算法采用二进制编码求三元函数极值
所求函数为
在这里插入图片描述
要想使用遗传算法,首要任务是进行编码

传统的 GA 中, DNA 我们能用一串二进制来表示, 比如:
DNA1 = [1, 1, 0, 1, 0, 0, 1]
DNA2 = [1, 0, 1, 1, 0, 1, 1]
这里,我们仍然使用二进制编码,但是如何与我们的问题对应起来呢?
为什么会要用二进制编码, 我们之后在下面的内容中详细说明这样编码的好处. 但是长成这样的 DNA 并不好使用. 如果要将它解码, 我们可以将二进制转换成十进制, 比如二进制的 11 就是十进制的 3. 这种转换的步骤在程序中很好执行. 但是有时候我们会需要精确到小数, 其实也很简单, 只要再将十进制的数浓缩一下就好. 比如我有 1111 这么长的 DNA, 我们产生的十进制数范围是 [0, 15], 而我需要的范围是 [-1, 1], 我们就将 [0, 15] 缩放到 [-1, 1] 这个范围就好.
我们知道二进制很容易转十进制,再区间压缩以下,这样一个DNA和一个解一一映射。

def translateDNA(pop):
    return pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2**DNA_SIZE-1) * X_BOUND[1]

例如,1 0 1 0 1 0 0 1 0 0 => (4+32+128+256)/(1024-1)*(5-0)=3.3
上面针对的单变量的编码,求解单变量的函数极值
其完整代码如下,这里照搬了别人的代码,后面我会给出引用地址,
用它主要是参考,并修改为求解三元函数极值的代码
单变量求函数极值:

"""
Visualize Genetic Algorithm to find a maximum point in a function.
可视化遗传算法去找到一个函数的最高点
"""
import numpy as np
import matplotlib.pyplot as plt
 
DNA_SIZE = 10            # DNA length
POP_SIZE = 100           # population size,种群中个体数目
CROSS_RATE = 0.8         # mating probability (DNA crossover)0.8的概率进行交叉配对
MUTATION_RATE = 0.003    # mutation probability,变异强度
N_GENERATIONS = 200      #迭代次数
X_BOUND = [0, 5]         # x upper and lower bounds,指定x的取值范围
 
 
def F(x):
    return np.sin(10*x)*x + np.cos(2*x)*x     # to find the maximum of this function
 
 
# find non-zero fitness for selection
#我们都需要一个评估好坏的方程, 这个方程通常被称为 fitness适应度.
#为了找到下面这个曲线当中的最高点. 那么这个 fitness 方程可以定义为高度, 越高的点, fitness 越高.
def get_fitness(pred):
    return pred + 1e-3 - np.min(pred)#因为如果直接返回pred可能是负值,而我们在计算概率的时候不能为负值。
    #要进行处理,np.min表示取最小,为最大的负数,可以使全部只变成正的;1e-3为了让float进行相除防止小数点后的数被省略
 
 
# convert binary DNA to decimal and normalize it to a range(0, 5)
#对基因的翻译,如这里函数,x轴是实数,这里解释了如何将遗传01序列翻译成实数。用十进制二进制转换
#pop (population)是一个储存二进制 DNA 的矩阵, 他的 shape 是这样 (pop_size, DNA_size)
#这里DNA_SIZE,X_BOUND是超参数
def translateDNA(pop):
    return pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2**DNA_SIZE-1) * X_BOUND[1]
    #dot()函数是矩阵乘,*则表示逐个元素相乘
	#np.arange()函数返回一个有终点和起点的固定步长的排列
	#pop.dot(2 ** np.arange(DNA_SIZE)[::-1])已经转换成十进制
	#但是需要归一化到0~5,如有1111这么长的DNA,要产生的十进制数范围是[0, 15], 而所需范围是 [-1, 1],就将[0,15]缩放到[-1,1]这个范围
	#a[::-1]相当于 a[-1:-len(a)-1:-1],也就是从最后一个元素到第一个元素复制一遍。所以你看到一个倒序
	#np.arange(DNA_SIZE)[::-1]得到10,9,8,...,0
 
#这里进行优胜劣汰的选择步骤
#适者生存的 select() 很简单, 我们只要按照适应程度 fitness 来选 pop 中的 parent 就好. fitness 越大, 越有可能被选到.
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())
	#这里概率不能为负,所以pred要进行非负处理
	#replace表示抽样后是否放回,这里为True表示有放回,则可能会出现相同的索引值
    # p 就是选它的比例,按比例来选择适应度高的,也会保留一些适应度低的,因为也可能后面产生更好的变异
    #np.random.choice表示从序列中取值  np.arange()函数返回一个有终点和起点的固定步长的排列
    return pop[idx]
 
#繁衍,交叉父母的基因
def crossover(parent, pop):     # mating process (genes crossover)
    if np.random.rand() < CROSS_RATE: #这里是0.8的概率父亲会选择一个母亲进行交叉配对
        i_ = np.random.randint(0, POP_SIZE, size=1)                           #select another individual from pop选择母亲索引一个
        cross_points = np.random.randint(0, 2, size=DNA_SIZE).astype(np.bool) #得到一行[01001100]也是01为了选择哪些点进行交叉;然后进行布尔化
        parent[cross_points] = pop[i_, cross_points]
		#布尔型数组可以用于数组索引,布尔型数组长度必须跟被索引的轴长度一致
		#生成布尔数组可以组合应用多个布尔条件,使用&(),|()之类的布尔算数运算符,python的关键字and和or在布尔型数组中无效
		#parent[cross_points]即parent列表中取出cross_points为True地方的值&&&&&!!!!
		#【母亲是pop的i_索引行DNA,选出母亲对应在cross_points为TRUE的地方的值】赋给【父亲DNA对应在cross_points选出为TRUE的地方的值】。
    return parent
 
#繁衍,有变异的基因会出现
#将某些 DNA 中的 0 变成 1, 1 变成 0
def mutate(child):
    for point in range(DNA_SIZE):
        if np.random.rand() < MUTATION_RATE:
            child[point] = 1 if child[point] == 0 else 0
    return child
 
#产生离散均匀分布的整数,若high不为None时,取[low,high)之间随机整数,否则取值[0,low)之间随机整数。
pop = np.random.randint(2, size=(POP_SIZE, DNA_SIZE))   # initialize the pop DNA
#pop = np.=random.randint(0,2,(1,DNA_SIZE).repeat(POP_SIZE,axis=0))这里是生成了一样的DNA,后面也可以随着变异变成不一样的
 
#[[01001100],
# [10111100],
# ...]
plt.ion()       # something about plotting开启图像交互模式
 
x = np.linspace(*X_BOUND, 200)
#linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
#X_BOUND = [0, 5],要产生200个样本点
#返回固定间隔的数据。他将返回num个等间距的样本,在区间[start,stop]中。其中,区间的结束端点可以被排除在外(用endpoint标识)
plt.plot(x, F(x))
 
for _ in range(N_GENERATIONS):
    F_values = F(translateDNA(pop))    # compute function value by extracting DNA传入到F函数
 
    # something about plotting
    if 'sca' in globals(): sca.remove()
    sca = plt.scatter(translateDNA(pop), F_values, s=200, lw=0, c='red', alpha=0.5); plt.pause(0.05)#plt.pause表示显示秒数
 
    # GA part (evolution)
    fitness = get_fitness(F_values) #计算适应度
    print("Most fitted DNA: ", pop[np.argmax(fitness), :])
    pop = select(pop, fitness)#这里选出了另外一种population
    pop_copy = pop.copy()# 备个份
    for parent in pop: #这里parent为遍历pop,一次为其中一行,而这里的pop是从原pop中按适应度概率有放回的选出了POP_SIZE行
        child = crossover(parent, pop_copy)#繁衍
        child = mutate(child) #进行变异
        parent[:] = child       # parent is replaced by its child# 宝宝变大人
 
plt.ioff(); plt.show()
 
#在使用matplotlib的过程中,不能像matlab一样同时开几个窗口进行比较,可以采用交互模式,但是放在脚本里运行一闪而过,图像并不停留
#python可视化库matplotlib有两种显示模式:阻塞(block)模式&交互(interactive)模式
#在交互模式下:plt.plot(x)或plt.imshow(x)是直接出图像,不需要plt.show()
#如果在脚本中使用ion()命令开启了交互模式,没有使用ioff()关闭的话,则图像不会常留。防止这种情况,需要在plt.show()之前加上ioff()命令。
#在阻塞模式下:打开一个窗口以后必须关掉才能打开下一个新的窗口。这种情况下,默认是不能像Matlab一样同时开很多窗口进行对比的
#plt.plot(x)或plt.imshow(x)是直接出图像,需要plt.show()后才能显示图像

关于三元函数代码的修改:
我的想法是这样的,不知道对不对,不对的话,请大佬留言指出,首先要改的是种群pop产生的矩阵,我的代码是这样的:

 pop = np.random.randint(0,2, size=(POP_SIZE, DNA_SIZE*3)) #matrix (POP_SIZE, DNA_SIZE*3)

第一个参数表示产生0-2范围的随机整数,不包括2

为了说明方便,这里先假设POP_SIZE=10,DNA_SIZE=4,则DNA_SIZE*3=12,因为有三个变量x,y,z,每个变量占一个DNA_SIZE,则DNA总长度是12位,
即[010011011010],
则上面产生的种群矩阵为
[[1 0 0 0 1 0 0 1 0 0 0 0]
[0 1 1 1 1 1 1 1 1 0 1 1]
[0 0 1 0 1 1 1 1 0 1 1 0]
[1 1 0 0 1 1 1 0 1 1 0 0]
[0 0 1 1 0 1 1 1 1 0 0 1]
[1 0 0 1 0 1 1 0 1 0 0 0]
[0 0 1 0 1 1 0 1 0 0 1 0]
[1 1 1 1 0 1 1 0 0 1 1 0]
[0 1 0 0 0 0 1 0 1 1 1 1]
[1 1 1 1 1 1 1 0 1 1 0 0]]

共10行,一行表示一个二进制编码表示的DNA,
矩阵的行数为种群数目
在上面的种群矩阵中,一行表示一个个体,而每个个体是有x,y,z三个基因片段所组成的,在这里
取前DNA_SIZE=4列表示x
所以x_pop为
[[1 0 0 0]
[0 1 1 1]
[0 0 1 0]
[1 1 0 0]
[0 0 1 1]
[1 0 0 1]
[0 0 1 0]
[1 1 1 1]
[0 1 0 0]
[1 1 1 1]],
然后依次类推,中间4列表示y,最后4列表示z
初始种群的个体即使都一样也无所谓,因为后面会通过交叉,配对等会变得不一样。
在translateDNA这块修改如下,
代码如下:

def translateDNA(pop): #pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
    x_pop = pop[:,0:DNA_SIZE]#取前DNA_SIZE个列表示x
    y_pop = pop[:,DNA_SIZE:DNA_SIZE*2]#取中间DNA_SIZE个列表示y
    z_pop = pop[:,DNA_SIZE*2:]#取后DNA_SIZE个列表示z
    #print(x_pop)
    #print(y_pop)
    #print(z_pop)
    #print("\n")

    '''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]
    z = z_pop.dot(2**np.arange(DNA_SIZE)[::-1])/float(2**DNA_SIZE-1)*(Z_BOUND[1]-Z_BOUND[0])+Z_BOUND[0]
    #print(x,z)
    return x,y,z

有两种求解三元函数极值的代码,
第一种是按照我自己的想法,在select操作中,关于概率的修改。
下面是我的求解三元函数极值的完整代码:


# x1*x2-x1*x2+x3
import numpy as np
import random


DNA_SIZE = 24
POP_SIZE = 100
CROSSOVER_RATE = 0.8
MUTATION_RATE = 0.015
N_GENERATIONS = 100
X_BOUND = [3.0,5.0]#x1
Y_BOUND = [2.1,6.7]#x2
Z_BOUND = [1.2,9.6]#x3


def F(x, y,z):
   val=x*x-x*y+z
   #print(val)
   '''
   for index in range(len(val)):
      if val[index] <0.2:
         val[index]=0.2
         '''
   return val



def get_fitness(pop): 
    x,y ,z= translateDNA(pop)
    pred = F(x, y,z)
    return pred

def translateDNA(pop): #pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
    x_pop = pop[:,0:DNA_SIZE]#取前DNA_SIZE个列表示x
    y_pop = pop[:,DNA_SIZE:DNA_SIZE*2] ##取中间DNA_SIZE个列表示y
    z_pop = pop[:,DNA_SIZE*2:]取后DNA_SIZE个列表示z
    #print(x_pop)
    #print(y_pop)
    #print(z_pop)
    #print("\n")

    '''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]
    z = z_pop.dot(2**np.arange(DNA_SIZE)[::-1])/float(2**DNA_SIZE-1)*(Z_BOUND[1]-Z_BOUND[0])+Z_BOUND[0]
    #print(x,z)
    return x,y,z


def mutation(child, MUTATION_RATE=0.003):
	if np.random.rand() < MUTATION_RATE: 				#以MUTATION_RATE的概率进行变异
		mutate_point = np.random.randint(0, DNA_SIZE*3)	#随机产生一个实数,代表要变异基因的位置
		child[mutate_point] = child[mutate_point]^1 	#将变异点的二进制为反转
		
def crossover_and_mutation(pop, CROSSOVER_RATE = 0.8):
	new_pop = []
	for father in pop:		#遍历种群中的每一个个体,将该个体作为父亲
		child = father		#孩子先得到父亲的全部基因(这里我把一串二进制串的那些01称为基因)
		if np.random.rand() < CROSSOVER_RATE:			#产生子代时不是必然发生交叉,而是以一定的概率发生交叉
			mother = pop[np.random.randint(POP_SIZE)]	#再种群中选择另一个个体,并将该个体作为母亲
			cross_points = np.random.randint(low=0, high=DNA_SIZE*3)	#随机产生交叉的点
			child[cross_points:] = mother[cross_points:]		#孩子得到位于交叉点后的母亲的基因
		mutation(child)	#每个后代有一定的机率发生变异
		new_pop.append(child)

	return new_pop



def select(pop, fitness):    # nature selection wrt pop's fitness
    #idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True,
    fitnew = fitness
    for index in range(len(fitnew)):
        if fitnew[index] <0:
           fitnew[index]=0
      
    p=(fitnew)/(fitnew.sum())

    idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True,
                           p=p)
    #print(idx)
    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,z = translateDNA(pop)
	print("最优的基因型:", pop[max_fitness_index])
	print("(x, y, z):", (x[max_fitness_index], y[max_fitness_index],z[max_fitness_index]))


if __name__ == "__main__":
   pop = np.random.randint(0,2, size=(POP_SIZE, DNA_SIZE*3)) #matrix (POP_SIZE, DNA_SIZE)
   #print(pop)
   for _ in range(N_GENERATIONS):#迭代N代
      x,y,z = translateDNA(pop)
      pop = np.array(crossover_and_mutation(pop, CROSSOVER_RATE))
      fitness = get_fitness(pop)
      pop = select(pop, fitness) #选择生成新的种群

	
   print_info(pop)


第二种方法是按照网上的意思:关于函数值:因为如果直接返回pred可能是负值,而我们在计算概率的时候不能为负值。
# 要进行处理,np.min表示取最小,为最大的负数,可以使全部只变成正的;1e-3为了让float进行相除防止小数点后的数被省略
其完整代码如下:

# x1*x2-x1*x2+x3
import numpy as np
import random

DNA_SIZE = 24
POP_SIZE = 100
CROSSOVER_RATE = 0.8
MUTATION_RATE = 0.015
N_GENERATIONS = 100
X_BOUND = [3.0, 5.0]  # x1
Y_BOUND = [2.1, 6.7]  # x2
Z_BOUND = [1.2, 9.6]  # x3


def F(x, y, z):
    val = x * x - x * y + z
    return val


def get_fitness(pop):
    x, y, z = translateDNA(pop)
    pred = F(x, y, z)
    return pred + 1e-3 - np.min(pred)  # 因为如果直接返回pred可能是负值,而我们在计算概率的时候不能为负值。
    # 要进行处理,np.min表示取最小,为最大的负数,可以使全部只变成正的;1e-3为了让float进行相除防止小数点后的数被省略


def translateDNA(pop):  # pop表示种群矩阵,一行表示一个二进制编码表示的DNA,矩阵的行数为种群数目
    x_pop = pop[:, 0:DNA_SIZE]  # 取前DNA_SIZE个列表示x
    y_pop = pop[:, DNA_SIZE:DNA_SIZE * 2]  # 取中间DNA_SIZE个列表示y
    z_pop = pop[:, DNA_SIZE * 2:]  # 取后DNA_SIZE个列表示z


    '''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]
    z = z_pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * (Z_BOUND[1] - Z_BOUND[0]) + Z_BOUND[0]
    # print(x,z)
    return x, y, z


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


def crossover_and_mutation(pop, CROSSOVER_RATE=0.8):
    new_pop = []
    for father in pop:  # 遍历种群中的每一个个体,将该个体作为父亲
        child = father  # 孩子先得到父亲的全部基因(这里我把一串二进制串的那些01称为基因)
        if np.random.rand() < CROSSOVER_RATE:  # 产生子代时不是必然发生交叉,而是以一定的概率发生交叉
            mother = pop[np.random.randint(POP_SIZE)]  # 再种群中选择另一个个体,并将该个体作为母亲
            cross_points = np.random.randint(low=0, high=DNA_SIZE * 3)  # 随机产生交叉的点
            child[cross_points:] = mother[cross_points:]  # 孩子得到位于交叉点后的母亲的基因
        mutation(child)  # 每个后代有一定的机率发生变异
        new_pop.append(child)

    return new_pop


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())
    # print(idx)
    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, z = translateDNA(pop)
    print("(x, y, z):", (x[max_fitness_index], y[max_fitness_index], z[max_fitness_index]))
    x=x[max_fitness_index]
    y=y[max_fitness_index]
    z=z[max_fitness_index]
    print("函数极值:", F(x,y,z))


if __name__ == "__main__":
    pop = np.random.randint(0, 2, size=(POP_SIZE, DNA_SIZE * 3))  # matrix (POP_SIZE, DNA_SIZE)
    # print(pop)
    for _ in range(N_GENERATIONS):  # 迭代N代
        x, y, z = translateDNA(pop)
        pop = np.array(crossover_and_mutation(pop, CROSSOVER_RATE))
        fitness = get_fitness(pop)
        pop = select(pop, fitness)  # 选择生成新的种群

    print_info(pop)


参考文献:
https://blog.csdn.net/yyyxxxsss/article/details/82464736
https://morvanzhou.github.io/tutorials/machine-learning/evolutionary-algorithm/2-01-genetic-algorithm/
https://www.cnblogs.com/lfri/p/12240355.html
https://blog.csdn.net/zxxxxxxy/article/details/105287743?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值