由于本人在研究生期间,许多论文中涉及到优化问题,即给定某个目标函数,以及一些约束条件来求解最优值以及最优解。对于求解这类优化问题,遗传算法是一种不错的选择。接下来,我将分享给大家我对于遗传算法的一些拙见,如有不足之处,请大家多多指正。
遗传算法,顾名思义就是根据生物界的遗传进化行为而得名的,其核心思想就是优胜劣汰。
首先让我们先简单回顾一下生物界的遗传进化过程,假设存在某一物种,其种群在一代一代繁衍过程当中,某些个体由于基因突变等因素,导致了变异现象的发生。然而变异也有好坏之分,并非所有的变异都是好的。如果某个个体产生了较好的变异,那么这个个体将会更加能够适应生存环境,因此其生存能力更强,个体更优。相反的如果某个个体产生了不好的变异,那么这个个体必然会被生存环境所淘汰。这便是种群的进化过程。
接下来让我们将生物界中的遗传映射到遗传算法当中。上文中所提到的个体对应于优化问题中的一个解(准确的说是一个解所对应的编码,在下文中会进行讲解),上文中个体的生存能力对应于优化问题中某个解所对应的目标函数值(在遗传算法中称为适应度)。在遗传算法求解过程中,我们也会对个体进行变异等操作,如何判断变异后的个体的好坏呢,自然是根据这个个体的目标函数值(适应度)来进行判断,如果目标函数为最大化问题,那么目标函数值大的个体自然就是好的个体,相反就是不好的个体。
接下来对遗传算法的每一步进行详细说明,但是在此之前需要对一些参数进行说明,这些参数将会运用到遗传算法的过程当中。
种群数量:即种群中个体的数量,此处用来表示
变异概率:用来控制种群中发生变异行为的个体的数量,用表示,取值范围在0~1之间
交叉概率:用来控制种群中发生交叉行为的个体的数量,用表示,取值范围在0~1之间
迭代次数:遗传算法终止的条件,用表示,即经过多少代的种群迭代之后停止
我们以目标函数最大化为例来讲解遗传算法的具体步骤:
遗传算法总共可以分为6步:1.编码,2.解码,3.求解适应度,4.复制,5.交叉,6.变异
1.编码:遗传算法中的编码可以采用实数编码,以及二进制编码,在这里我们主要介绍二进制编码。假设优化问题的解为,其中每个决策变量对应的的编码长度为,则一个个体的总的编码长度记做,即每个个体被编码为一个长度为的二进制串。由于我们上文中说明了种群数量为,因此,我们在初始化种群时,将对个个体进行随机编码,即生成个长度为的二进制串作为我们的初始种群,其中每一个二进制串为一个个体。
2.解码:解码的目的是为了得到每个个体对应的解的实际值。具体过程如下。首先我们需要取出一个个体中每个决策变量对应的二进制编码段,某个个体的决策变量对应的编码段为个体的位置到位置,即(假设个体的下标从0开始,并且设),其中包含开始位置对应的二进制位,不包含结束位置对应的二进制位。之后将编码段转换为对应的十进制值。设的取值范围为,则解码后的实际值为。最后我们根据一个个体可以得到一个对应的优化问题的解,最后对整个种群解码后,得到个解。
3.求解适应度:通过第二步解码后,我们便得到了个解,依次将这些解带入到目标函数,求取出目标函数值便得到了这些解所对应的适应度值,我们记为。
4.复制:首先,我们可以将适应度最优的个体找出来,作为复制得到的种群中的第一个个体,作为当前种群最优个体,这个操作也称之为精英保留策略。之后我们再从种群中通过某种选择方案选出个个体,和一开始选出的最优个体组成复制后形成的种群。接下来介绍选择方案,即如何选出个个体。首先通过公式计算出,之后再通过公式计算得出。之后我们通过产生一个0到1之间的随机数,判断位于的哪个位置,例如,如果,则将原先种群中第个个体选出,如此循环次便得到了选出的个个体。
5.交叉:将种群中的的第一个个体,也就是之前选出的最优个体排除在外不参加交叉操作,其他个个体进行交叉操作。首先有序生成个0到1之间的随机数,记录其中小于交叉概率的随机数的位置,将这些位置对应的个体选出来作为进行交叉操作的个体(在程序编写中,我一般限制选出个体的数量大于零且为偶数,如不符合条件则重新生成随机数进行选择)。之后,从选出的个体中每次按顺序遍历出两个个体进行交叉操作。接下来介绍两个个体之间如何进行交叉操作,设个体起始位下标为0,则个体的最后一位对应的下标为。首先产生一个1(包含)到(不包含)之间的随机数,作为这两个个进行体交叉位置,之后将这两个个体在这个位置之后的片段进行交换便完成了交叉操作。依次对之前选出的个体进行此操作,直到被选出的个体全部完成,则种群的交叉操作完成。由于进行了变异操作,种群中最优个体有可能已经发生了变化,因此当完成遍历操作时我们也可以再次进行解码,求适应度,将种群中最优的个体放置在种群的第一个作为新的。
6.变异:首先同交叉操作相同,种群中的第一个个体不进行变异操作,之后的个个体进行变异操作。变异操作的具体方法如下,首先将参加变异操作的个个体连成一条串,其长度为,之后一次产生个0到1之间的随机数,记录小于变异概率的随机数的位置,所有小于变异概率的随机数的位置便是这条串上发生变异的位置,通过这个位置可以找到发生变异的个体的,以及在这个个体上的哪个位置发生的变异。例如,某个小于变异概率的随机数在这条串上的位置为,则可以求得发生变异的个体索引为,“//”表示整除(向下取整),在这个个体上发生变异的位置为,“mod"表示取余。最终确定了变异位置之后,只需观察这个位置上的二进制值是0还是1,如果是0则变异为1,如果是1则变异为0。如此循环直到把所有的发生变异的位置均进行变异操作。最终便产生了新的种群。
最后依次循环执行2至6步来进行种群迭代。
至此,遗传算法的6个步骤都已介绍完毕,需要注意的是,第1步编码操作只在第一次种群迭代时进行,之后的次迭代都只执行2至6步。
细心的同学可能会发现,这种方法只适用于目标函数值为正的情况,如果目标函数值既有正值又有负值则在复制操作时会出问题,那么我的解决方法是如果目标函数既有正值又有负值,因为目标函数是求取最大化,所以负值所对应的个体应该被舍弃,所以所有适应度为负值的个体将其适应度设置为0,这个问题便得以解决。也许又会有同学问,如果目标函数值是小于0的呢?确实,目标函数值小于0在当前的复制操作也会出现问题,因此,可以对目标函数加负号,这样目标函数值便为正,并且原问题如果是最小化问题,则现在转化为了最大化问题,这个问题便得以解决。但是如果原问题是最大化问题,加负号之后虽然目标函数值为正,但是转为了最小化问题,此时我们可以采取取倒数转化为最大化问题,同样可以解决这个问题。
到这里,遗传算法的基本原理已经讲完,由于这也是本人的第一篇博客,所以可能存在许多不足,也请大家多多包涵,多多指正,希望对正在学习遗传算法的你有帮助!
以下附上本人使用python编写的遗传算法的相关程序以供大家参考
from numpy import random as rd
import copy
import numpy as np
import matplotlib.pyplot as plt
import time
np.set_printoptions(suppress=True, linewidth=1000)
def test_func(x1, x2):
# 这是一个用于测试的目标函数
return 1 / (x1 ** 2 + x2 ** 2 + 0.5)
def GetTime(func_name):
def inner(*args, **kwargs):
start_time = time.time()
ret = func_name(*args, **kwargs)
end_time = time.time()
run_time = end_time - start_time
print("遗传算法迭代用时%f秒" % run_time)
return ret
return inner
class GA(object):
def __init__(self, pop_num, iter_time, cp, mp, coding_way, var_code_lenth, objective_function, **kwargs):
"""
:param pop_num: 种群数量
:param iter_time: 迭代次数
:param cp: 交叉概率
:param mp: 变异概率
:param coding_way: 编码方式,传入"b"为二进制编码,传入"r"为实数编码
:param var_code_lenth: 每个决策变量的编码长度
:param objective_function: 传入自定义的计算目标函数值得函数对象, 函数对象在定义时需要设置决策变量为传入参数,
决策变量参数的传入顺序需与**kwargs决策变量一一对应, 例如def objective_func(x1, x2, x3),
在**kwargs传入时应该以x1=(x1_min, x1_max), x2=(x2_min, x2_max), x3=(x3_min, x3_max)的顺序传入
并且目标函数最终返回变量带入后求得的结果, 要求目标函数求最大值,一定要注意目标函在给定的定义域内不全小于等于0
:param kwargs: 传入方式为:决策变量1 = (决策变量1的最大值, 决策变量1的最小值), 决策变量2 = (决策变量2的最大值, 决策变量2的最小值), ...
"""
self.pop_num = pop_num
self.iter_time = iter_time
self.cp = cp
self.mp = mp
self.coding_way = coding_way.lower()
self.var_code_lenth = var_code_lenth
self.obj_func = objective_function
self.kwargs = kwargs
# self.chrom_lenth为每条染色体(每个个体)编码长度
self.chrom_lenth = len(self.kwargs) * self.var_code_lenth
for min_, max_ in kwargs.values():
assert min_ < max_, "参数填写有误,最小值不能大于最大值"
# self.best_fitness_value用于存放每一代种群中最优的适应度,迭代结束后通过self.best_fitness_value[-1]取出最终的最优适应度值
# self.best_value用于存放每一代种群中最优适应度对应的个体解码值,也就是决策变量的值,迭代结束后通过self.best_value[-1]取出最优适应度对应的决策变量
self.best_fitness_value = []
self.best_value = []
try:
slices = tuple([slice(value_range[0], value_range[1], 300j) for value_range in self.kwargs.values()])
var_values = np.mgrid[slices]
if np.all(objective_function(*var_values) <= 0):
exit("目标函数值域小于等于0,请将目标函数值域转换为大于等于0(对目标函数取负号,注意取负号后原目标求最大则转为了求最小,可以在采取取倒数转为求最大)")
except Exception as e:
pass
def code_(self):
# 编码
while True:
if self.coding_way == "b":
self.chromsoms = rd.randint(0, 2, (self.pop_num, self.chrom_lenth))
else:
self.chromsoms =[((("%"+"."+str(self.var_code_lenth)+"f,") * len(self.kwargs)) % tuple(i)).strip(",").split(",")\
for i in rd.random((self.pop_num, len(self.kwargs)))]
self.decode_()
if not np.all(np.array(self.get_fitness_value()) <= 0):
break
def decode_(self):
# 解码
# self.values用于存放解码后的变量值
self.values = []
if self.coding_way == "b":
for chrom in self.chromsoms:
# value用于每个个体解码后的变量值,解码规则:变量最小值 + (变量最大值 - 变量最小值)/ (2 ** 单个变量编码长度 - 1) * 变量实际编码转换为10进制的值
# len(value)应该与决策变量个数相同
value = []
for j in range(len(self.kwargs)):
var_min, var_max = list(self.kwargs.values())[j]
var_chrom = "".join([str(i) for i in chrom[j * self.var_code_lenth:(j + 1) * self.var_code_lenth]])
var_bin_value = int(var_chrom, 2)
bin_value = 2 ** self.var_code_lenth - 1
var_value = var_min + var_bin_value * (var_max - var_min) / bin_value
value.append(var_value)
self.values.append(value)
else:
for chrom in self.chromsoms:
value = []
for i in range(len(chrom)):
var_min, var_max = list(self.kwargs.values())[i]
value.append(var_min + float(chrom[i]) * (var_max - var_min))
self.values.append(value)
def get_fitness_value(self):
# 求适应度
# self.fit_values用于存放每个个体的适应度,即目标函数值
self.fit_values = []
for value in self.values:
if self.obj_func(*tuple(value)) < 0:
self.fit_values.append(0)
else:
self.fit_values.append(self.obj_func(*tuple(value)))
return self.fit_values
def copy_(self):
# 复制操作
best_chrom_index = self.fit_values.index(max(self.fit_values))
self.best_value.append(copy.deepcopy(self.values[best_chrom_index]))
self.best_fitness_value.append(self.fit_values[best_chrom_index])
chromsoms = [copy.deepcopy(self.chromsoms[best_chrom_index])]
p = np.array(self.fit_values) / np.sum(self.fit_values)
p_cum = np.cumsum(p)
for i in range(self.pop_num - 1):
rand_num = rd.random()
if rand_num < p_cum[0]:
chromsoms.append(copy.deepcopy(self.chromsoms[0]))
continue
for k in range(self.pop_num - 1):
if p_cum[k] <= rand_num < p_cum[k + 1]:
chromsoms.append(copy.deepcopy(self.chromsoms[k + 1]))
break
self.chromsoms = chromsoms
def crossover(self):
while True:
cros_index = []
for i in range(1, self.pop_num):
rand_num = rd.random()
if rand_num < self.cp:
cros_index.append(i)
if cros_index and len(cros_index) % 2 == 0:
break
if self.coding_way == "b":
for i in range(0, len(cros_index), 2):
cros_point = rd.randint(1, self.chrom_lenth)
a = copy.deepcopy(self.chromsoms[cros_index[i]][cros_point:])
self.chromsoms[cros_index[i]][cros_point:] = copy.deepcopy(self.chromsoms[cros_index[i + 1]][cros_point:])
self.chromsoms[cros_index[i + 1]][cros_point:] = a
else:
for i in range(0, len(cros_index), 2):
cros_point = rd.randint(3, self.var_code_lenth + 2, len(self.kwargs))
for k in range(len(self.kwargs)):
a = "".join(list(self.chromsoms[cros_index[i]][k])[cros_point[k]:])
self.chromsoms[cros_index[i]][k] = self.chromsoms[cros_index[i]][k][:cros_point[k]] + self.chromsoms[cros_index[i + 1]][k][cros_point[k]:]
self.chromsoms[cros_index[i + 1]][k] = self.chromsoms[cros_index[i + 1]][k][:cros_point[k]] + a
def mutate(self):
self.decode_()
self.get_fitness_value()
best_chrom_index = self.fit_values.index(max(self.fit_values))
# 精英保留
chromsoms = [copy.deepcopy(self.chromsoms[best_chrom_index])]
# 排除精英,剩下的个体进行变异操作
self.chromsoms.pop(best_chrom_index)
while True:
mutate_point = []
for i in range(self.chrom_lenth * len(self.chromsoms)):
if rd.random() < self.mp:
mutate_point.append(i)
if mutate_point:
break
if self.coding_way == "b":
for i in mutate_point:
mutate_index = i // self.chrom_lenth
mutate_point = i % self.chrom_lenth
if self.chromsoms[mutate_index][mutate_point] == 0:
self.chromsoms[mutate_index][mutate_point] = 1
else:
self.chromsoms[mutate_index][mutate_point] = 0
else:
for i in mutate_point:
mutate_index = i // self.chrom_lenth
mutate_point = i % self.chrom_lenth
mutate_var_index = mutate_point // self.var_code_lenth
mutate_var_point = mutate_point % self.var_code_lenth
a = copy.deepcopy(self.chromsoms[mutate_index])
while True:
self.chromsoms[mutate_index][mutate_var_index] = self.chromsoms[mutate_index][mutate_var_index][:2 + mutate_var_point] + str(rd.randint(0, 10)) + self.chromsoms[mutate_index][mutate_var_index][mutate_var_point + 3:]
if self.chromsoms[mutate_index] != a:
break
chromsoms.extend(self.chromsoms)
self.chromsoms = chromsoms
@GetTime
def run(self):
"""
如果目标函数极值求解没有约束条件,则可直接调用run来进行求解
否则需要自己调用code_, decode_, get_fitness_value, copy_,
crossover, mutate方法进行迭代
"""
self.code_()
for i in range(self.iter_time):
if i == 0:
print("遗传算法开始迭代...")
if i % int(0.15 * self.iter_time) == 0:
print("遗传算法迭代第%d代" % i)
self.decode_()
self.get_fitness_value()
self.copy_()
self.crossover()
self.mutate()
print("最优适应度:", self.best_fitness_value[-1])
print("最优个体:", self.best_value[-1])
self.draw()
return self.best_fitness_value[-1], self.best_value[-1]
def draw(self):
fig = plt.figure()
ax = plt.subplot(1, 1, 1)
ax.plot(np.arange(1, self.iter_time + 1), self.best_fitness_value, color="r", label="fitness value")
ax.set_xlabel("generations")
ax.set_ylabel("fitness value")
if self.coding_way == "r":
ax.set_title("real number coding GA")
else:
ax.set_title("binary coding GA")
ax.legend()
plt.show()
def main():
print("########二进制编码##############")
ga_b = GA(200, 200, 0.75, 0.02, "b", 20, test_func, x1=(-30, 30), x2=(-10, 22))
ga_b.run()
print("########实数编码################")
ga_r = GA(200, 200, 0.75, 0.02, "r", 8, test_func, x1=(-30, 30), x2=(-10, 22))
ga_r.run()
if __name__ == "__main__":
main()