最近在做关于农业重金属的项目,中间有需要用到经典遗传算法的部分方法,重新借用Python把遗传算法理论梳理一遍,毕竟用代码逻辑一方面对算法理论更深刻同时对Coding能力提高不少:
【经典方法】
1、随机生成指定维度的1和0矩阵方法
#先生成全0矩阵,后面在逐行随机产生1并更新矩阵
chromosomes = np.zeros((10, 31), dtype=np.uint8)
for i in range(10):
chromosomes[i, :] = np.random.randint(0, 2, 31)
2、enumerate() 函数
用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列。
seq = ['one', 'two', 'three']
for i, element in enumerate(seq):
print i, element
#输出:0 one, 1 two, 2 three
3、Lambda 表达式
#Lambda 表达式简化代码逻辑
# 定义适应度函数
def fitnessFunction():
return lambda x: 21.5 + x[0] * np.sin(4 * np.pi * x[0]) + x[1] * np.sin(20 * np.pi * x[1])
pass
#使用
def getFitnessValue(func, chromosomesdecoded):
func(chromosomesdecoded[i, :](传入参数))
4、 np.cumsum()和np.where()
a = [1, 2, 3, 4]
b = np.cumsum(a)
#得到[1, 3, 6, 10]
logical = [False, True, True, True, True]
index = np.where(logical == 1)
#得到index[1,2,3,4]
【Python实现主要逻辑】
第一步:得到初始种群编码(二进制编码)
第二步:初始种群解码
第三步:计算个体适应度值和个体的累积概率
#对得到的值,带入需要解决的函数值,计算结果。
第四步:选择新的种群
#传入:初始化种群chromosomes,累计概率cum_probability
#计算逻辑:代码实现就是一个排列组合,相当于从chromosomes进行放回取样,这样得到一个和初始化种群一样维度的新种群newpopulation
第五步:进行交叉操作
#传入:新种群newpopulation,交叉概率Pc
#计算逻辑:对新种群中newpopulation不需要进行交叉的染色体,先直接copy到updatepopulation中,接下来是交叉操作:首先随机选取两条需要交叉的染色体a和b,随机生成一个交叉点,对a和b进行交叉操作(就是对制定位置进行相互替换操作),最终得到交叉操作后的种群crossoverpopulation。
第六步:变异操作
#传入:交叉后种群crossoverpopulation,变异概率Pm
#计算逻辑:将crossoverpopulation,所有基因视为一体,根据变异概率确定需要变异多少个基因,之后随即生成需要变异的基因位置进行变异操作,最终得到变异后种群mutationpopulation
第七步:变异后种群进行解码
第八步:进行适应度评价
#得到个体适应度值和个体的累积概率
第九步:选择最好的一个个体
#迭代了一次,我们从中选择一个满足目标的最好的个体保存。
结束:最终经过很多轮的迭代,我们将每次迭代的结果进行一个比较选择最好的个体,得到最终结果。
一下整个逻辑的完整代码:
# !/usr/bin/env python
# -*- coding:utf-8 -*-
# 注:以下代码来源于网络搜集,更多交流欢迎关注“武汉AI算法研习”
#
import numpy as np
from scipy.optimize import fsolve, basinhopping
import random
import timeit
# 根据解的精度确定染色体(chromosome)的长度
# 需要根据决策变量的上下边界来确定
def getEncodedLength(delta=0.0001, boundarylist=[]):
# 每个变量的编码长度
lengths = []
for i in boundarylist:
lower = i[0]
upper = i[1]
# lamnda 代表匿名函数f(x)=0,50代表搜索的初始解
res = fsolve(lambda x: ((upper - lower) * 1 / delta) - 2 ** x - 1, 50)
length = int(np.floor(res[0]))
lengths.append(length)
return lengths
pass
# 随机生成初始编码种群
def getIntialPopulation(encodelength, populationSize):
# 随机化初始种群为0
chromosomes = np.zeros((populationSize, sum(encodelength)), dtype=np.uint8)
for i in range(populationSize):
chromosomes[i, :] = np.random.randint(0, 2, sum(encodelength))
# print('chromosomes shape:', chromosomes.shape)
return chromosomes
# 染色体解码得到表现型的解
def decodedChromosome(encodelength, chromosomes, boundarylist, delta=0.0001):
populations = chromosomes.shape[0]
variables = len(encodelength)
decodedvalues = np.zeros((populations, variables))
for k, chromosome in enumerate(chromosomes):
chromosome = chromosome.tolist()
start = 0
for index, length in enumerate(encodelength):
# 将一个染色体进行拆分,得到染色体片段
power = length - 1
# 解码得到的10进制数字
demical = 0
for i in range(start, length + start):
demical += chromosome[i] * (2 ** power)
power -= 1
lower = boundarylist[index][0]
upper = boundarylist[index][1]
decodedvalue = lower + demical * (upper - lower) / (2 ** length - 1)
decodedvalues[k, index] = decodedvalue
# 开始去下一段染色体的编码
start = length
return decodedvalues
# 得到个体的适应度值及每个个体被选择的累积概率
def getFitnessValue(func, chromosomesdecoded):
# 得到种群规模和决策变量的个数
population, nums = chromosomesdecoded.shape
# 初始化种群的适应度值为0
fitnessvalues = np.zeros((population, 1))
# 计算适应度值
for i in range(population):
fitnessvalues[i, 0] = func(chromosomesdecoded[i, :])
# 计算每个染色体被选择的概率
probability = fitnessvalues / np.sum(fitnessvalues)
# 得到每个染色体被选中的累积概率
cum_probability = np.cumsum(probability)
return fitnessvalues, cum_probability
# 新种群选择
def selectNewPopulation(chromosomes, cum_probability):
m, n = chromosomes.shape
newpopulation = np.zeros((m, n), dtype=np.uint8)
# 随机产生M个概率值
randoms = np.random.rand(m)
for i, randoma in enumerate(randoms):
logical = cum_probability >= randoma
index = np.where(logical == 1)
# index是tuple,tuple中元素是ndarray
newpopulation[i, :] = chromosomes[index[0][0], :]
return newpopulation
pass
# 新种群交叉
def crossover(population, Pc=0.8):
"""
:param population: 新种群
:param Pc: 交叉概率默认是0.8
:return: 交叉后得到的新种群
"""
# 根据交叉概率计算需要进行交叉的个体个数
m, n = population.shape
numbers = np.uint8(m * Pc)
# 确保进行交叉的染色体个数是偶数个
if numbers % 2 != 0:
numbers += 1
# 交叉后得到的新种群
updatepopulation = np.zeros((m, n), dtype=np.uint8)
# 产生随机索引
index = random.sample(range(m), numbers)
# 不进行交叉的染色体进行复制
for i in range(m):
if not index.__contains__(i):
updatepopulation[i, :] = population[i, :]
# crossover
while len(index) > 0:
a = index.pop()
b = index.pop()
# 随机产生一个交叉点
crossoverPoint = random.sample(range(1, n), 1)
crossoverPoint = crossoverPoint[0]
# one-single-point crossover
updatepopulation[a, 0:crossoverPoint] = population[a, 0:crossoverPoint]
updatepopulation[a, crossoverPoint:] = population[b, crossoverPoint:]
updatepopulation[b, 0:crossoverPoint] = population[b, 0:crossoverPoint]
updatepopulation[b, crossoverPoint:] = population[a, crossoverPoint:]
return updatepopulation
pass
# 染色体变异
def mutation(population, Pm=0.01):
"""
:param population: 经交叉后得到的种群
:param Pm: 变异概率默认是0.01
:return: 经变异操作后的新种群
"""
updatepopulation = np.copy(population)
m, n = population.shape
# 计算需要变异的基因个数
gene_num = np.uint8(m * n * Pm)
# 将所有的基因按照序号进行10进制编码,则共有m*n个基因
# 随机抽取gene_num个基因进行基本位变异
mutationGeneIndex = random.sample(range(0, m * n), gene_num)
# 确定每个将要变异的基因在整个染色体中的基因座(即基因的具体位置)
for gene in mutationGeneIndex:
# 确定变异基因位于第几个染色体
chromosomeIndex = gene // n
# 确定变异基因位于当前染色体的第几个基因位
geneIndex = gene % n
# mutation
if updatepopulation[chromosomeIndex, geneIndex] == 0:
updatepopulation[chromosomeIndex, geneIndex] = 1
else:
updatepopulation[chromosomeIndex, geneIndex] = 0
return updatepopulation
pass
# 定义适应度函数
def fitnessFunction():
return lambda x: 11.5 + x[0] * np.sin(3 * np.pi * x[0]) + x[1] * np.sin(18 * np.pi * x[1])
pass
def main(max_iter=500):
# 每次迭代得到的最优解
optimalSolutions = []
optimalValues = []
# 决策变量的取值范围
decisionVariables = [[-3.0, 12.1], [4.1, 5.8]]
# 得到染色体编码长度
lengthEncode = getEncodedLength(boundarylist=decisionVariables)
for iteration in range(max_iter):
# 得到初始种群编码
chromosomesEncoded = getIntialPopulation(lengthEncode, 10)
# 种群解码
decoded = decodedChromosome(lengthEncode, chromosomesEncoded, decisionVariables)
# 得到个体适应度值和个体的累积概率
evalvalues, cum_proba = getFitnessValue(fitnessFunction(), decoded)
# 选择新的种群
newpopulations = selectNewPopulation(chromosomesEncoded, cum_proba)
# 进行交叉操作
crossoverpopulation = crossover(newpopulations)
# mutation
mutationpopulation = mutation(crossoverpopulation)
# 将变异后的种群解码,得到每轮迭代最终的种群
final_decoded = decodedChromosome(lengthEncode, mutationpopulation, decisionVariables)
# 适应度评价
fitnessvalues, cum_individual_proba = getFitnessValue(fitnessFunction(), final_decoded)
# 搜索每次迭代的最优解,以及最优解对应的目标函数的取值
optimalValues.append(np.max(list(fitnessvalues)))
index = np.where(fitnessvalues == max(list(fitnessvalues)))
optimalSolutions.append(final_decoded[index[0][0], :])
# 搜索最优解
optimalValue = np.max(optimalValues)
optimalIndex = np.where(optimalValues == optimalValue)
optimalSolution = optimalSolutions[optimalIndex[0][0]]
return optimalSolution, optimalValue
solution, value = main()
print('最优解: x1, x2')
print(solution[0], solution[1])
print('最优目标函数值:', value)
# 测量运行时间
elapsedtime = timeit.timeit(stmt=main, number=1)
print('Searching Time Elapsed:(S)', elapsedtime)
最优解: x1, x2
11.505082741414958 5.252218763352255
最优目标函数值: 28.202816638353244
Searching Time Elapsed:(S) 0.6843510135749541
【总结】
根据遗传算法逻辑,其是很容易在实际优化过程中达到一个局部最优解的,当然目前也有很多的变种算法。