ITPub博客

首页 > 应用开发 > Python > 遗传算法库DEAP的示例代码的学习和分析

遗传算法库DEAP的示例代码的学习和分析

原创 Python 作者:张国平 时间:2019-03-26 17:23:47 0 删除 编辑

最近看到用遗传算法优化量化参数的文章,觉得还是有很先进,就开始学习。这个文章主要是针对遗传算法库DEAP的示例代码的学习和分析。

代码链接:其实有不少针对这段代码文章,这里主要说说自己学习和体会。

原文链接如下:


在分析代码前,先说说这段代码干了什么。这段代码就是如果有一个数组有十个随机数组成,如何得到一组十个最小随机数,其和是最小的。理论上,最小的就是组合就是10个0,但是因这里面因为引入了变异,可以获得负数,所以可以组合变异生成负数值。


1. 定义类型,方法creator.create(name,base,**kargs)

这个有点类似设计模式的工厂模式,这个方法创建一个新的类,名字为参数name,继承的父类为base,这个类的属性如之后的参数名称和参数值。

creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
对应的就是类创建代码:
class FitnessMin(base.Fitness):
    weights=(-1.0,)
        def __init__(self, values=()):
          super(FitnessMin, self).__init__(values=())
   
creator.create("Individual", list, fitness=creator.FitnessMin)
对应的就是类创建代码:
class Individul(list):
    self.fitness=FitnessMin
        def __init__(self):
            ….

     

        1.1 然后取看看base.Fitness这个父类,关键是weights这个属性,主要是做比较用。分析代码,会发现把传入的values序列和weights序列相乘,转为元祖wvalues,返回的时候就相除返回values

defgetValues(self):
    returntuple(map(truediv,self.wvalues,self.weights))
 
defsetValues(self,values):
    try:
        self.wvalues=tuple(map(mul,values,self.weights))


 

2.     注册方法;方法 toolbox.register (self,alias,function,*args,**kargs)

这个是一个比较核心的方法,参数第一个是别名,第二个是需要绑定的真正function,之后其实是针对这个方法的参数。这样做就可以把方法和参数二次打包成一个新方法用于遗传算法,类似于方法覆盖。


2.1 toolbox.register("attribute", random.random)

比如这个,就是给toolbox注册了random.random,使用" attribute "别名,就是random.random()返回一个0-1直接随机数,调用时候就是直接用toolbox.attr_float()

2.2 toolbox.register("individual", tools.initRepeat, creator.Individual,

                 toolbox.attr_float, n=IND_SIZE)

这个更加难以理解 , 是注册一个方法别名“ individual ”,关联 tools 原生方法 iniRepeat ,并且调用了三个参数。其中creator.Individual的父类就是 list 而toolbox.attr_float实际就是 random.random toolbox . individual实际效果就是initRepeat(list, random.random, 10 )。initRepeat(container, func, n)定义就是用 func 生成 n 个返回值,放在 container 。显示出来如下一个数列里面放置 10 个随机数。

2.3 toolbox.register("population", tools.initRepeat, list, toolbox.individual)

有了上面基础这个就理解,用一个list作为容器,然后防止toolbox.individual生成的随机数list,组成双层list。但是没有给重复次数,所以要在调用时候给定。比如两次的效果。

 

3.     定义运算, 方法还是之前注册 register 方法,最重要就是 evaluate 这个方法必须自己实现。


3.1 def evaluate(individual):

            return sum(individual),

        toolbox.register("evaluate", evaluate)

实际调用就是把之前individual的list中是个随机数,算出来随机数求和。

3.2 toolbox.register("mate", tools.cxTwoPoint)

这个方法是交叉,输入两个长度相同数据,通常是数组;返回两个同样长度,但是组成元素交叉互换的;类似于遗传中的基因交叉。

3.2 toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=1, indpb=0.1)

这个方法是调用高斯变异,提供遗传变异,高斯变异我也不懂。就像下图显示的,输入一个数组,返回的会数组变动后如下。

其中参数mu按照介绍是均值;sigma是标准差,这个值决定变异强度,如果sigma为0时候,没有变异;indpb是独立概率(Independent probability),决定变异概率,如果为1时候,所有元素都会变异,如果是0.1,就是0.1概率;

3.3 toolbox.register("select", tools.selTournament, tournsize=3)

这个方法调用的是

def selTournament(individuals, k, tournsize, fit_attr= "fitness" ):

输入一个数据组集合,比如有 10 individual 数据组; k 是举行多少次选拔,每举行一次选拔得到一个最优解,如果 k len(pop1) ,最后返回就是同样长度数组; tournsize 是每次在十个里面选择多少个个参加选拔,这里选择是随机的,所以结果也是也看得到,虽然选择最小,但是有 5.188 这样明显很大的。还有就是就算 tournsize 改成 10 ,返回应该就是最小单一值把,试了发现还不是,每次选参赛 individual 是可以重复的,就是可以有一个 individual 重复参加,算是一个逻辑 bug

还有就是就算 tournsize 改成 10情况

 

4.     算法,前面所有要求的都定义好,后面就是一步步繁殖变异筛选了

pop = toolbox.population(n=50)

首先使用polulation方法生成一个50个individual实例,就像前面说的individual是一个有10个0-1随机数组成数组。

fitnesses = map(toolbox.evaluate, pop)

利用map方法,对于pop这个list中每个individual操作,计算出每个individual是个随机数的和,返回一个元祖。50个individual返回就是50个元祖。

 

for ind, fit in zip(pop, fitnesses):

ind.fitness.values = fit

这个使用了zip方法,把两个数组组合成一个新的元祖数组,这个元祖包括一个individual数组和fitness元祖,然后用ind,和fit遍历,再把fit值赋值到individual上。

上面完成了第一代50个inidividual维护。

 

后面开始繁殖,首先定义出一些参数,后面用到时候说明。

CXPB, MUTPB, NGEN = 0.5, 0.2, 40

后面就是一个很长的逻辑,首先第一个定义了NGEN,进行多少代繁殖筛选,这里NGEN是40代。

然后使用select方法,进行50次选拔,每次三个,保留其中individual fitnes最新的那个。

第一遍的select只是引用,所以还要克隆。

然后利用zip,把offspring中两个不同individual组合成一个元祖数组,这个元祖有两个不同individual,然后有50%机会进行交叉繁殖。

同时有20%机会进行突变。

突变和繁殖后,这些individual都没有fitness,后面就是就是给这些繁殖和突变的individual计算fitness并绑定。

然后用所以的下一代代替上一代。最后经过四十代繁殖返回pop

    for g in range(NGEN):
        # Select the next generation individuals
        offspring = toolbox.select(pop, len(pop))
        # Clone the selected individuals
        offspring = map(toolbox.clone, offspring)
 
        # Apply crossover and mutation on the offspring
        for child1, child2 in zip(offspring[::2], offspring[1::2]):
            if random.random() < CXPB:
                toolbox.mate(child1, child2)
                del child1.fitness.values
                del child2.fitness.values
 
        for mutant in offspring:
            if random.random() < MUTPB:
                toolbox.mutate(mutant)
                del mutant.fitness.values
 
        # Evaluate the individuals with an invalid fitness
        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        fitnesses = map(toolbox.evaluate, invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            ind.fitness.values = fit
 
        # The population is entirely replaced by the offspring
        pop[:] = offspring

 

5.    执行 main() 就可以等到 40 代以后的 pop ,然后可以用遍历发现各个 sum 都是负数,由于原来随机并不会产生负数,应该都是高斯变异突变效果,而这些突变也很好保留下来。

最后, best_ind = tools.selBest(pop, 1 )[ ] 可以等到最好的一个 individual ,发现 individual 里面都是负数,都是突变的值。

    pop = main()
    for individual in pop:
        print(individual.fitness.values)
    print("-- End of (successful) evolution --")
    best_ind = tools.selBest(pop, 1)[0]
    print best_ind, best_ind.fitness.values


最后源代码可以在我的GitHub里面找到,

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/22259926/viewspace-2639405/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
SAP 金融风险管理产品专家

注册时间:2009-08-05

  • 博文量
    201
  • 访问量
    490171