由于需要,所以最近在看一些WSNS的资料,也接触到了以前自己一直想看看的遗传算法,我蹭这个机会稍微学习下。结合一些博客和文章,记录本次学习过程:
遗传算法(Genetic Algorithm)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自
然进化过程搜索最优解的方法。简单来说,就是利用编码,把你要的变量转化到染色体编码形式,然后在经历染色体交叉、变异,再经
所谓的物竞天择,得到满意的结果。它的目标一般来说是致力于全局最优解,但由于种种问题,最后也会可能得到局部最优。
我们先假设一个情景,现在你是一国之王,为了让你的国家免于灾祸,你实施了一套法案:
● 你选出所有的好人,要求其通过生育来扩大国民数量。
● 这个过程持续进行了几代。
● 你将发现,你已经有了一整群的好人。
总结一下我们做过的事情:
1. 首先,我们设定好了国民的初始人群大小。
2. 然后,我们定义了一个函数,用它来区分好人和坏人。
3. 再次,我们选择出好人,并让他们繁殖自己的后代。
4. 最后,这些后代们从原来的国民中替代了部分坏人,并不断重复这一过程。
因此,为了形式化定义一个遗传算法,我们可以将它看作一个优化方法,它可以尝试找出某些输入,凭借这些输入我们便可以得到
最佳的输出值或者是结果。
基本步骤:
1. 编码
GA在进行搜索之前先将解空间的解数据表示成遗传空间的基因型串结构数据,这些串结构数据的不同组合便构成了不同的点。
2. 初始群体的生成
随机产生N个初始串结构数据,每个串结构数据称为一个个体,N个个体构成了一个群体。GA以这N个串结构数据作为初始点开始进化。
3. 适应度评估
适应度表明个体或解的优劣性。不同的问题,适应度函数的定义方式也不同。
4. 选择
选择的目的是为了从当前群体中选出优良的个体,使它们有机会作为父代为下一代繁殖子孙。遗传算法通过选择过程体现这
一思想,进行选择的原则是适应度强的个体为下一代贡献一个或多个后代的概率大。选择体现了达尔文的适者生存原则。
5. 交叉
交叉操作是遗传算法中最主要的遗传操作。通过交叉操作可以得到新一代个体,新个体组合了其父辈个体的特性。交叉体现
了信息交换的思想。
6. 变异
变异首先在群体中随机选择一个个体,对于选中的个体以一定的概率随机地改变串结构数据中某个串的的值。同生物界一
样,GA中变异发生的概率很低,通常取值很小。
import math
import random
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)
#以下是测试
if __name__ == '__main__':
a = [[-3, 1], [4, 5]]
chromosome = Chromosome(a, 10)
print(chromosome.code_x1_length)
print(chromosome.code_x2_length)
chromosome.decoding('000000', '1111')
print(chromosome.x1, " ", chromosome.x2)
print(random.randint(0, 1))
print(chromosome.code_x1)
# print(chromosome.test)
"""
代码内容:遗传算法的简单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 copy
import random
import matplotlib.pyplot as plt
from Chromosome import Chromosome
"""
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