最近看到用遗传算法优化量化参数的文章,觉得还是有很先进,就开始学习。这个文章主要是针对遗传算法库DEAP的示例代码的学习和分析。
代码链接:其实有不少针对这段代码文章,这里主要说说自己学习和体会。
原文链接如下: https://deap.readthedocs.io/en/master/overview.html
在分析代码前,先说说这段代码干了什么。这段代码就是如果有一个数组有十个随机数组成,如何得到一组十个最小随机数,其和是最小的。理论上,最小的就是组合就是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里面找到,
https://github.com/BillyZhangGuoping/MarketDataAnaylzerbyDataFrame/blob/master/GeneticOptimizeforVNPYStrategy/GeneticTrain.py
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/22259926/viewspace-2639405/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/22259926/viewspace-2639405/