Hello,大家好,这里是六个核桃Lu!
Python | 用遗传算法 求解最优值
(代码在文末)
1 需求分析与设计原理
- 1需求分析
- 2算法分析及设计
求解最优值问题采用遗传算法,遗传算法是一种基于自然选择和群体遗传机理的搜索算法,它模拟了自然选择和自然遗传过程中的繁殖、杂交和突变现象。再利用遗传算法求解问题时,问题的每一个可能解都被编码成一个“染色体”,即个体,若干个个体构成了群体(所有可能解)。在遗传算法开始时,总是随机的产生一些个体(即初始解),根据预定的目标函数对每一个个体进行评估,给出一个适应度值,基于此适应度值,选择一些个体用来产生下一代,选择操作体现了“适者生存”的原理,“好”的个体被用来产生下一代,“坏”的个体则被淘汰,然后选择出来的个体,经过交叉和变异算子进行再组合生成新的一代,这一代的个体由于继承了上一代的一些优良性状,因而在性能上要优于上一代,这样逐步朝着最优解的方向进化。因此,遗传算法可以看成是一个由可行解组成的群体初步进化的过程。
遗传操作是模拟生物基因的操作,他的任务就是根据个体适应度对其施加一定的操作,从而实现优胜劣汰的进化过程.从优化搜索的角度来看,遗传操作可以使问题的解逐代优化,逼近最优解,遗传操作包括以下三个基本遗传算子:选择、交叉、变异.选择和交叉基本上完成了遗传算法的大部分搜索功能,变异增加了遗传算法找到最优解的能力。遗传算法的具体步骤如下:
1、选择
选择是指从群体中选择优良个体并淘汰劣质个体的操作.它建立在适应度评估的基础上.适应度越大的个体,被选中上的可能性就越大,他的“子孙”在下一代中的个数就越多,选择出来的个体就被放入配对库中.目前常用的选择方法有轮赌盘方法、最佳个体保留法、期望值法、排序选择法、竞争法、线性标准化法。
2、交叉
交叉就是指把两个父代个体的部分结构加以替换重组而生成新的个体的操作,交叉的目的是为了在下一代产生新的个体,通过交叉操作,遗传算法的搜索能力得到了飞跃性的提高.交叉是遗传算法获取优良个体的重要手段.交叉操作是按照一定的交叉概率在匹配库中随机的选取两个个体进行的,交叉位置也是随机的,交叉概率一般取得很大,为0.6~0.9。
3、变异
变异就是以很小的变异概率Pm随机地改变种群中个体的某些基因的值,变异操作的基本过程是:产生一个[0,1]之间的随机数rand,如果rand<Pm,则进行变异操作。变异操作本身是一种局部随机搜索,与选择、交叉算子结合在一起,能够避免由于选择和交叉算子而引起的某些信息永久性丢失,保证了遗传算法的有效性,使遗传算法具有了局部随机搜索能力,同时使得遗传算法能够保持群体的多样性,以防出现未成熟收敛。在变异操作中,变异概率不宜取得过大,如果Pm>0.5,遗传算法就退化为了随机搜索。
2 程序设计
2.1 解决最优值问题的流程
1、构造初始种群
定义initialPopulation函数,personNum为种群数量,length为种群每个个体编码的位数(personNum=50,length=16),在定义totalPopulation=[]为种群列表,用while循环,当种群列表长度不等于种群数量时就一直循环,循环随机产生16为二进制数,在将16位二进制数用字符串表示,将产生的二进制数加入种群列表,最后返回种群列表。
# 生成初始化种群
# personNum为种群数量,length为种群每个个体编码的位数
def initialPopulation(personNum=50,length=16):
totalPopulation=[] #种群列表
while len(totalPopulation)!=personNum:
person=[]
#循环随机产生16为二进制数
for i in range(16):
person.append(random.randint(0,1))
theStr = ''
# 将16位二进制数用字符串表示
for item in person:
theStr += str(item)
#print(theStr)
if domain_of_definition(theStr):
# 将产生的二进制数加入种群列表
if theStr not in totalPopulation:
totalPopulation.append(theStr)
print('初始种群=',totalPopulation)
return totalPopulation
注:这道题的定义域不是正无穷到负无穷,所以需要限定一下,我定义了一个domain_of_definition()函数用来判断生成的数值是否在定义域内,为了方便编程用x表示x1,y表示x2,二进制数16位,前8位代表x,后8位代表y。
# 判断生成的数是否满足函数定义域
def domain_of_definition (onePerson):
x = onePerson[0:8]
y = onePerson[8:16]
# 将x,y转换为十进制
x = int(x, 2)
y = int(y, 2)
x = x * 1.5 / 258
y = y * 1.5 / 256
if (x * x - 4 * y * y) >= 0 and (-1)*math.sin(x)+math.sqrt(x*x-4*y*y)!=0 and y!=0:
return True
else: return False
2、计算函数个体值
定义evaluate()函数,计算函数值。
# 计算x,y对应的函数值
# 一个个体的编码
def evaluate(onePerson):
x,y=decode(onePerson)
result=((-1)*math.sin(x)+math.sqrt(x*x-4*y*y))/y
return result
其中的调用decode()函数,是用来将传过来的二进制数转换为十进制数的。
# 将一个16位的编码转换为x,y的十进制解
def decode(onePerson):
# onePerson是一个16位的二进制,
# x=x1 y=x2
# 前8位代表x,后8位代表y
x = onePerson[0:8]
y = onePerson[8:16]
# 将x,y转换为十进制
x = int(x, 2)
y = int(y, 2)
x = x * 1.5 / 256
y = y * 1.5 / 256
return x,y
3、获取两个父母,然后进行交叉,生成子代个体。
# 获取一个父母进行交叉
# 输出:返回的是一个双亲在population的index
def getParents(evalList):
temp = random.uniform(0, 1)
portionList=[]
theSum=0
totalEval=sum(evalList)
for eval in evalList:
theSum+=eval/totalEval
portionList.append(theSum)
location=0
while(temp>portionList[location]):
location+=1
return location
构造getCross()函数,随机在0到15之间选择整数,随机出来的整数为切割的索引位置,在组合得到child染色体。
# 两个person生成子代
# 输出:生成的子代person编码
def getCross(father,mother):
theVisit=[]
crossLocation=random.randint(0,15)
theVisit.append(crossLocation)
#print(crossLocation)
print('交叉 ',end='')
child=''
child += father[0:crossLocation]
child += mother[crossLocation:16]
return child
4、变异
定义变异概率mutationProbability=0.9,temp等于随机取0到1之间的数,若temp小于0.9,则发生变异。变异后染色体所求出的函数值若优于(小于)原染色体,则返回变异染色体;若不优于(小于),则返回原染色体。
# 进行变异
def getVari(person):
#print(person)
temp = random.uniform(0, 1)
if temp<mutationProbability:
print()
print('变异')
location=random.randint(0,15)
#print(location)
tempStr=person[0:location]
tempStr+=str(1-int(person[location]))
tempStr+=person[location+1:]
if domain_of_definition(tempStr):
#变异后染色体所求出的函数值若优于(小于)原染色体,则返回变异染色体;若不优于(小于),则返回原染色体。
if evaluate(tempStr)<evaluate(person):
return tempStr
return person
5、终止
如果未到终止条件,则回到步骤1创造初始种群,当最优个体的适应度达到给定的阈值,或者最优个体的适应度和群体适应度不再上升时,或者迭代次数达到预设的代数时,算法终止,选出最佳个体的编码。我定义了iteration=30,即假设迭代30次即终止。
注:每次迭代时先将上一代最小的个体放到下一代种群中,这样能保证最优的值保留下来。
3 流程图![](https://i-blog.csdnimg.cn/blog_migrate/eceeb5ca57d03bcc8be4ef9abee75143.png)
最后附上完整版代码
import random
import math
personNum=300 #种群数量
mutationProbability=0.9 #变异概率
iteration=30 #假设迭代30次即终止
length=16 #染色体长度
# 生成初始化种群
# 参数:personNum为种群数量,length为种群每个个体编码的位数
def initialPopulation(personNum=50,length=16):
totalPopulation=[] #种群列表
while len(totalPopulation)!=personNum:
person=[]
#循环随机产生16为二进制数
for i in range(16):
person.append(random.randint(0,1))
theStr = ''
# 将16位二进制数用字符串表示
for item in person:
theStr += str(item)
#print(theStr)
if domain_of_definition(theStr):
# 将产生的二进制数加入种群列表
if theStr not in totalPopulation:
totalPopulation.append(theStr)
print('初始种群=',totalPopulation)
return totalPopulation
# 判断生成的数是否满足函数定义域
def domain_of_definition (onePerson):
x = onePerson[0:8]
y = onePerson[8:16]
# 将x,y转换为十进制
x = int(x, 2)
y = int(y, 2)
x = x * 1.5 / 258
y = y * 1.5 / 256
if (x * x - 4 * y * y) >= 0 and (-1)*math.sin(x)+math.sqrt(x*x-4*y*y)!=0 and y!=0:
return True
else: return False
# 将一个16位的编码转换为x,y的十进制解
def decode(onePerson):
# onePerson是一个16位的二进制,
# x=x1 y=x2
# 前8位代表x,后8位代表y
x = onePerson[0:8]
y = onePerson[8:16]
# 将x,y转换为十进制
x = int(x, 2)
y = int(y, 2)
x = x * 1.5 / 256
y = y * 1.5 / 256
return x,y
# 计算x,y对应的函数值
# 参数:一个个体的编码
def evaluate(onePerson):
x,y=decode(onePerson)
result=((-1)*math.sin(x)+math.sqrt(x*x-4*y*y))/y
return result
# 获取一个父母进行交叉
# 输出:返回的是一个双亲在population的index
def getParents(evalList):
temp = random.uniform(0, 1)
portionList=[]
theSum=0
totalEval=sum(evalList)
for eval in evalList:
theSum+=eval/totalEval
portionList.append(theSum)
location=0
while(temp>portionList[location]):
location+=1
return location
# 两个person生成子代
# 输出:生成的子代person编码
def getCross(father,mother):
theVisit=[]
crossLocation=random.randint(0,15)
theVisit.append(crossLocation)
#print(crossLocation)
print('交叉 ',end='')
child=''
child += father[0:crossLocation]
child += mother[crossLocation:16]
return child
# 进行变异
def getVari(person):
#print(person)
temp = random.uniform(0, 1)
if temp<mutationProbability:
print()
print('变异')
location=random.randint(0,15)
#print(location)
tempStr=person[0:location]
tempStr+=str(1-int(person[location]))
tempStr+=person[location+1:]
if domain_of_definition(tempStr):
#变异后染色体所求出的函数值若优于(小于)原染色体,则返回变异染色体;若不优于(小于),则返回原染色体。
if evaluate(tempStr)<evaluate(person):
return tempStr
return person
if __name__=='__main__':
theScore=[]
bestPerson=[]
theBestEval=0
for i in range(20): #设置跑多少轮,用来查看收敛性的
population = initialPopulation(personNum, length)
flag = 0
while flag!=iteration:
print("第",flag+1,"代")
evalList=[]
tempPopulation=[]
for person in population:
evalList.append(evaluate(person))
minEval=min(evalList)
print('minEval=',minEval)
theIndex=evalList.index(minEval)
tempPopulation.append(population[theIndex]) #每次迭代时先将上一代最小的个体放到下一代种群中
print("开始交叉")
for i in range(personNum):
#获得用于交叉的父母
parentsFaIndex=getParents(evalList)
parentsFa=population[parentsFaIndex]
parentsMaIndex=getParents(evalList)
parentsMa=population[parentsMaIndex]
child=getCross(parentsFa,parentsMa)
if domain_of_definition(child):
child=getVari(child)
tempPopulation.append(child)
population=tempPopulation
flag+=1
evalList = []
for person in population:
evalList.append(evaluate(person))
minEval=min(evalList)
if theBestEval>minEval:
theBestEval=minEval
theIndex = evalList.index(minEval)
person = population[theIndex]
if person not in bestPerson:
bestPerson.append(person)
theScore.append(1)
else:
theScore[bestPerson.index(person)] += 1
print('theBestEval=',theBestEval)