优化问题
优化问题最终可以归结为:
minimize
subject to
subject to
对于常规的优化问题,使用常规的求导或优化算法就可以解决。但是,对于一些“多峰”的函数,这些常规的算法就很难求解,这时候就需要求助于一些启发式的优化算法。
遗传算法简介
遗传算法是一类借鉴生物界的进化规律(适者生存,优胜劣汰的遗传机制)演化而来的自适应概率性随机化迭代搜索算法。
类似于生物的进化过程,遗传算法处理的是变量集合的编码而非变量本身。它直接对结构对象进行操作,不存在求导和函数连续性的界定;具有内在的隐并行性和更好的全局寻优能力;采用概率化的寻优方法,能自动获取和指导优化的搜索空间,自适应的调整搜索方向,不需要确定规则。遗传算法的这些特点已被人们广泛的应用于组合优化、机器学习、信号处理、自适应控制和人工生命领域。它是现代有关智能计算中的关键技术之一。
遗传算法的基本流程:
遗传算法例子
准备要去野游 1 个月,但是你只能背一个限重 30 公斤的背包。现在你有不同的必需物品,它们每一个都有自己的「生存点数」(具体在下表中已给出)。因此,你的目标是在有限的背包重量下,最大化你的「生存点数」
1. 初始群体
第一步是初始群体。群体由很多个体所组成,每个个体都有一套自己的染色体。
2. 适应度函数
由之前的表格中的数值可以得到适应度
A1 染色体 [100110]:
3. 选择
轮盘赌选择法:
想象有一个轮盘,现在我们将它分割成 m 个部分,这里的 m 代表我们总体中染色体的个数。每条染色体在轮盘上占有的区域面积将根据适应度分数成比例表达出来:
4. 交叉
用生物学的话说,所谓「交叉」,其实就是指的繁殖。
单点交叉
多点交叉
5. 变异
后代的性状与和其父母的性状是否完全一致呢?答案是当然是否。其原因在于「变异」这个过程,它可以被定义为染色体上发生的随机变化。正是因为变异,种群中才会存在多样性:
6. 整体流程
在进行完一轮「遗传变异」之后,我们用适应度函数对这些新的后代进行验证,如果函数判定它们适应度足够,那么就会用它们从总体中替代掉那些适应度不够的染色体。
遗传算法简单实现
目标:在
;
范围内
最大化
代码:
#coding= utf-8
"""
代码内容:遗传算法的简单python实现
目标:在-3.0 <= x1 <= 12.1 4.1 <= x2 <= 5.8范围内,max f (x1, x2) = 21.5 + x1·sin(4p x1) + x2·sin(20p x2)
"""
import math
import copy
import random
import matplotlib.pyplot as plt
class Chromosome:
def __init__(self, bounds, precision):
self.x1 = 1 #只是定义了x1这里的数值没有意义,仅仅是为了方便
self.x2 = 1
self.y = 0
self.code_x1 = '' #x1转换为二进制编码的值(即用二进制表示x1)
self.code_x2 = ''
self.bounds = bounds #用来存放x1和x2的取值范围
temp1 = (bounds[0][1] - bounds[0][0]) * precision #即把小数转换为整数
self.code_x1_length = math.ceil(math.log(temp1, 2)) #公式不太清楚,但这里的意思是算出如果用二进制来编码染色体,
# 那么对于x1这一数值范围编码需要几位2进制
temp2 = (bounds[1][1] - bounds[1][0]) * precision
self.code_x2_length = math.ceil(math.log(temp2, 2))
self.rand_init() #随机生成染色体二进制序列
self.func()
#随机形成在范围内的染色体二进制序列
#:self
#r:无,在过程中生成了该二进制序列
def rand_init(self):
for i in range(self.code_x1_length):
self.code_x1 += str(random.randint(0, 1)) #字符串加法是直接在字符串后面继续补上字符,继而形成二进制序列
for i in range(self.code_x2_length):
self.code_x2 += str(random.randint(0, 1))
#这里是一个转换公式,将染色体所代表的二进制串转换到该染色体所对应的变量所在范围内的十进制值
#:self;将x1用二进制表示的值;将x2用二进制表示的值
#r:无,在过程中生成了转化后的十进制值
def decoding(self, code_x1, code_x2):
self.x1 = self.bounds[0][0] + int(code_x1, 2) * (self.bounds[0][1] - self.bounds[0][0]) / (
2 ** self.code_x1_length - 1)
self.x2 = self.bounds[1][0] + int(code_x2, 2) * (self.bounds[1][1] - self.bounds[1][0]) / (
2 ** self.code_x2_length - 1)
#算出y
#:self
#r:y
def func(self):
self.decoding(self.code_x1, self.code_x2)
self.y = 21.5 + self.x1 * math.sin(4 * math.pi * self.x1) + self.x2 * math.sin(20 * math.pi * self.x2)
"""
GeneticAlgorithm类
#p:self;变量范围;精度;变异概率;交叉概率;种群大小;最大迭代次数
"""
class GeneticAlgorithm:
def __init__(self, bounds, precision, pm, pc, pop_size, max_gen):
self.bounds = bounds
self.precision = precision
self.pm = pm
self.pc = pc
self.pop_size = pop_size
self.max_gen = max_gen
self.pop = []
self.bests = [0] * max_gen
self.g_best = 0
"""
算法主函数;目标:在-3.0 <= x1 <= 12.1 4.1 <= x2 <= 5.8范围内,max f (x1, x2) = 21.5 + x1·sin(4p x1) + x2·sin(20p x2)
#p:self
#r:无;在过程中输出y的值和作图
"""
def ga(self):
"""
:return:
"""
self.init_pop()
best = self.find_best()
self.g_best = copy.deepcopy(best)
y = [0] * self.pop_size #生成足够存放种群个数的数组
#循环进行遗传的交叉,变异,选择
for i in range(self.max_gen):
self.cross()
self.mutation()
self.select()
#选择经过三个阶段后的最佳的对象
best = self.find_best()
self.bests[i] = best
#如果当前的最佳对象比之前的都好,那么更新g_best的值
if self.g_best.y < best.y:
self.g_best = copy.deepcopy(best)
y[i] = self.g_best.y
print(self.g_best.y)
#画图
plt.figure(1)
x = range(self.pop_size)
plt.plot(x, y)
plt.ylabel('generations')
plt.xlabel('function value')
plt.show()
"""
找到当前种群中最好的个体;通过比较适应度,这里因为是最大化函数值,所以y的值就被选为适应度
#p:self
#r:最好的个体
"""
def find_best(self):
best = copy.deepcopy(self.pop[0])
for i in range(self.pop_size):
if best.y < self.pop[i].y:
best = copy.deepcopy(self.pop[i])
return best
"""
初始化初始化种群;通过Chromosome生成种群对象,一个对象带有两条染色体
#p:self
#r:无;主要是在过程中向pop数组添加对象
"""
def init_pop(self):
for i in range(self.pop_size):
chromosome = Chromosome(self.bounds, self.precision)
self.pop.append(chromosome)
"""
染色体交叉;在选择的对象的交叉概率对于随机值时,开始交叉遗传;
#p:self
#r:无;主要是在过程中向pop数组添加对象
"""
def cross(self):
for i in range(int(self.pop_size / 2)):
if self.pc > random.random(): #如果交叉概率大于随机值
#在种群中随机选择两个染色体
i = 0
j = 0
while i == j:
i = random.randint(0, self.pop_size-1)
j = random.randint(0, self.pop_size-1)
pop_i = self.pop[i]
pop_j = self.pop[j]
#随机选择染色体的交叉点
pop_1 = random.randint(0, pop_i.code_x1_length - 1)
pop_2 = random.randint(0, pop_i.code_x2_length - 1)
#进行交叉
new_pop_i_code1 = pop_i.code_x1[0: pop_1] + pop_j.code_x1[pop_1: pop_i.code_x1_length]
new_pop_i_code2 = pop_i.code_x2[0: pop_2] + pop_j.code_x2[pop_2: pop_i.code_x2_length]
new_pop_j_code1 = pop_j.code_x1[0: pop_1] + pop_i.code_x1[pop_1: pop_i.code_x1_length]
new_pop_j_code2 = pop_j.code_x2[0: pop_2] + pop_i.code_x2[pop_2: pop_i.code_x2_length]
#生成新的染色体
pop_i.code_x1 = new_pop_i_code1
pop_i.code_x2 = new_pop_i_code2
pop_j.code_x1 = new_pop_j_code1
pop_j.code_x2 = new_pop_j_code2
"""
染色体基因变异;在选择的对象的变异概率对于随机值时,开始交叉变异;
#p:self
#r:无;主要是在过程中改变对象染色体上的基因
"""
def mutation(self):
for i in range(self.pop_size):
if self.pm > random.random(): #如果对象的变异概率大于随机值的话,就进行变异
pop = self.pop[i]
#选择变异的基因(这里对于变异每次只变一位)
index1 = random.randint(0, pop.code_x1_length-1)
index2 = random.randint(0, pop.code_x2_length-1)
#变异的主体过程:将选择的基因通过变异函数变异,然后再将字符串重新加合即可
i = pop.code_x1[index1]
i = self.__inverse(i)
pop.code_x1 = pop.code_x1[:index1] + i + pop.code_x1[index1+1:]
i = pop.code_x2[index2]
i = self.__inverse(i)
pop.code_x2 = pop.code_x2[:index2] + i + pop.code_x2[index2+1:]
"""
变异时候用的,将 1 变为 0 ,0 变为 1
#p:变异位置
#r:变异后的值
"""
def __inverse(self, i):
r = '1'
if i == '1':
r = '0'
return r
"""
轮盘赌选择;在选择的对象的变异概率对于随机值时,开始交叉变异;
#p:self
#r:经过'物竞天择'选择后的种族数组
"""
def select(self):
sum_f = 0 #所有对象的适应度之和(这里把y作为适应度,所以也就是y的和)
#循环计算y
for i in range(self.pop_size):
self.pop[i].func()
# guarantee fitness > 0
#遍历选取最小的适应度
min = self.pop[0].y
for i in range(self.pop_size):
if self.pop[i].y < min:
min = self.pop[i].y
#如果最小适应度小于0,那么全体加上一个负的最小适应度(即负负之后为正的适应度)
if min < 0:
for i in range(self.pop_size):
self.pop[i].y = self.pop[i].y + (-1) * min
# roulette
for i in range(self.pop_size):
sum_f += self.pop[i].y #得到所有对象的适应度之和
p = [0] * self.pop_size #创建一个可以存放所有被选择概率的数组
#遍历种群,计算被选择概率公式为yi/F
for i in range(self.pop_size):
p[i] = self.pop[i].y / sum_f
#存放逐个累加被选择概率的值,如果不累加的话,再接下来的轮盘选择中就难以区分
#比如0.22和0.25,随机数的取值很难刚好讲他们分开;当然也可以选择其他的方式,只是这里采用了累加形式
q = [0] * self.pop_size
q[0] = 0
for i in range(self.pop_size):
s = 0
#逐个累加被选择概率
for j in range(0, i+1):
s += p[j]
q[i] = s
#轮盘选择,随机生成r,选择累加概率大于r的对象
v = []
for i in range(self.pop_size):
r = random.random()
if r < q[0]: v.append(self.pop[0])
for j in range(1, self.pop_size):
if q[j - 1] < r <= q[j]: v.append(self.pop[j])
self.pop = v
if __name__ == '__main__':
bounds = [[-3, 12.1], [4.1, 5.8]]
precision = 100000
algorithm = GeneticAlgorithm(bounds, precision, 0.01, 0.8, 100, 100)
algorithm.ga()
pass
参考
参考文章:一文读懂遗传算法工作原理(附Python实现)
参考博客:https://tianle.me/2017/04/19/GA/