(二) 创建类型
本节将展示如何通过creator创建类型以及如何使用toolbox进行初始化。
1、Fitness
已经提供的Fitness类是一个抽象类,它需要weight来使得它成为一个函数。一个最小化的适应度是通过负权重构建的,而一个最大化适应度则需要正权重。例如,下面的代码使用creator创建了一个单目标最小化适应度函数——FitnessMin
creator.create("FitnessMin",base.Fitness,weights=(-1.0,))
creat()函数至少需要两个元素,一个名称是新创建的类和它基于的类(在上例中为“FitnessMin”和“base.Fitness”)。任何序列元素都会对类的属性产生影响。在具体的Fitness文档中,weights属性一定是一个元组,这样才可以使得多目标和单目标适应度可以保持一致。(我认为使用元组的原因在于元组中的元素是不可修改的,因此在不同类型的优化问题中,可以始终保持优化目标与实际一致——Min&NegativeWeights以及Max&PositiveWeights?)一个FitnessMulti可以使用相似的方法进行设置
creator.create("FitnessMin",base.Fitness,weights=(-1.0,1.0))
这串代码是对第一个目标求最小值,对第二个目标求最大值。它的权重也可以被用于衡量两个目标的重要程度。这意味着权重可以使任何实数并且只有一个标志决定最大化与最小化的完成(实现优化目标)。在NSGA-II算法(一种高效的多目标遗传算法)中密集的距离排列里可以体现权重的作用。
2、Individual
通过简单地思考不同的演化算法的风格(GA\GP\ES\PSO\DE....),我们发现个体间存在极大不同是有可能的,这就验证了所有的类型都不是可以被开发的假设。这里有一个教程说明如何使用creator创建一些个体并且使用Toolbox对它们进行初始化。
2.1、 List of Floats
我们要创建的第一个个体将是一个简单的包含float的list。为了去产生这种个体,我们需要创建一个Individual class,使用creator将会与标准list类型建立联系(creator.create里面会有list?),并且将会与fitness建立联系(每个individual对应其fitness)。

下面介绍一个新的register对象,它用于将deap包中所带方法与要编写的演化算法进行“固定”(包括初始化、操作方法以及适应度函数的计算)。register存在于base.Toolbox中。因此在使用时首先要调用base.Toolbox(),然后再对register进行调用。

register()至少需要两个参数,一个alias和一个分配到alias的函数。任何的序列参数会被叫做functools.partial()进行传递到函数当中。C2P2中的代码在toolbox中创造了两个alias,第一个重定向到random.random()函数,第二个是一个initRepeat()函数,将它容器中的元素填充到creator.Individual class中,它对toolbox.attr_float()函数的func参数根据IND_SIZE(这里的n)进行重复。
现在,叫做toolbox.individual()将会使用设置好的参数调用initRepeat(),并且会返回到一个完成的creator.Individual中,这个creator.Individual是由IND_SIZE的浮点数和最大化单目标优化适应度信息fitness组成。(即在本例中,individual是由十个随机地folat类型的数构成,并且它们的适应值是一个最大适应值?individual(number, fitness)?)
这种类型的变化同样也可以与array.array联系,numpy.ndarray联系,代码样式如下
creator.create('Individual', array.array, typecode = 'd', fitness = creator.FitnessMax)
creator.create('Individual', numpy.ndarray, fitness = creator.FitnessMax)
从arrays类型继承的初始化与之前的一致。
2.2、Permutation(排列)
排列表示的个体几乎与一般列表个体相似。事实上他们都是从list继承而来的。唯一的不同在于permutation使用的是一个通过一些了float对list进行填充。我们需要产生一串随机地排列并且向个体提供这个排列。

第一个注册的函数indices重指向random.sample()函数,数目为IND_SIZE。(类似于之前的attr_float,将随机生成的方式进行确定,同时对生成的list进行限定?)第二个注册的函数individual是一个initIterate()函数的调用,它的存储器参数设定为creator.Individual类以及它的generator参数会被设定到toolbox.indice()。
调用toolbox.individual()可以通过填充参数并返回完整的creator.Individual来调用intiIterate(),组成一个最小化的单目标优化fitness序列。

这样看来,上述操作的主要目的就在于使用不同的随机方法生成个体数组,random.random为float类型(实验结果显示均为0-1之间的数),float.sample为int类型。
2.3、Arithmetic Expression
下一个个体是通常使用一个前缀树{ Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。}来进行数学表达的。在现在,一个PrimitiveSet必须被定义,它包括我们的个体将会可能用到的所有数学运算符。这里,这个集合被叫做MAIN并,它包括所有的单目运算符。二目运算符add(),sub()和mul()被添加到primitive set。接下来,Individual类被创建,在这之前添加到静态属性(?)pest的东西将会记忆全局的primitive set(原始集合)。现在,个体的内容将会通过genHalfAndHalf()函数生成,这个函数将会使用一个基于斜坡程序(???ramped list)的list产生树。再一次的,个体将会通过initIterate()函数进行初始化,并且把完整生成的迭代信息给到个体类当中。(意思是通过下面的操作,可以将数字与运算符结合,构成表达式树?话不多说看代码)

使用toolbox.individual()将会返回一个完整的个体,这个个体是通过一个计算表达式表达的,它的目标是一个最小单目标。
简单来说,上述步骤使用内置的GP模块完成了对表达式树的创建,参数范围应该是(1,2)。总结一下,创建个体(字符串个体和表达式树个体)的主要步骤,首先来说字符串个体:
1、设置等长字符串个体的长度(IND_SIZE);
2、设置优化目标(最大or最小,单目标or多目标);
3、创建个体的list,并与设定好的优化目标相对应;
4、使用toolbox工具设定(register)随机数生成方式(int or float)及长度(IND_SIZE);
5、使用toolbox工具设定个体的生成方式(tools.initIterate or tools.initRepeat等),不同的生成方式所需参数不一。
接下来是表达式树个体:
1、设定perimitiveSet(调用GP包,设定‘MAIN’为单目运算符),同时再向pset中加入一些多目运算符,包括add()\sub()\mul()等。命令为pset.addPrimitive(operator.name, 'arity number');
2、创建目标函数(同字符串个体一致)与个体(GP的Primitive树而不是随机数,同时要声明pset,其余与字符串类似);
3、使用toolbox工具将表达式与个体进行设定。
2.4、Evolution Strategy
个体的演化策略根据它们生成的list有轻微的不同,一个是对个体本身进行,另一个是对它们进行变异操作。现在,我们将会使用array.array继承个体与策略,而不是使用一个基于类的list实现演化策略。因为没有任何的帮助函数在单目标优化问题中可以产生两个不同的向量,我们必须自己去定义这些函数。initES()函数接收两种类(个体+策略?),同时实例化它们将会为自己产生随机数,这些随机数都是根据设定好的个体大小以及个体数目确定的。

调用toolbox.individual()将会返回一个完整的演化策略,有一个策略向量以及一个最小值目标函数。
不是特别明白它这个“EVOLUTION STRATEGY”是干嘛的,输出结果是一个双精度浮点型array,范围(|x| > 1 && |x| < 5)。
2.5、Particle
一个Particle是另一个特殊类型的个体,这是因为通常情况下它有一个速度,并且有一个最优的位置需要去记忆。这种类型个体的创建与通过list创建类似。现在,speed, best和速度限制将会加入到一个目标中。再一次的,一个初始化函数initParticle()函数也同时可以被注册并产生接受一些参数,包括Particle的个体、大小、领域和速度限制等。

调用toolbox.individual()将会容易地返回带有speed向量和最大双目标优化适应度的particle。这个运行报错(原因是139行的register为‘particle’,调用的却是individual,更改后即可使用),这个应该是PSO初始化要用的东西,不过PSO很简单,不一定非要用它的包,MATLAB也就五六十行代码自己写写就行了。
2.6、A Funky One
假设你的问题有着极其特别的需求,你同样可以轻松建立一个定制化的个体。下一个个体的创建是一个可以改变的整数和浮点数组成的list,使用initCycle()函数

调用toolbox.individual()将会返回由四个整型和四个浮点型组成的list,它们的顺序是[int float int float int float... int float]。目标有两个,均为最大目标。
这个初始化方法看起来在混合规划问题中很好用,但美中不足在于看起来像一个整数对应一个浮点数这样子。
3 Population
种群与个体非常类似。它们不是用属性初始化,而是充满了个体、策略以及粒子(针对不同方法采取不同的种群)。
3.1 Bag
一个种群的包是最常用的类型。它没有特殊的排序,尽管这通常会使用list来实现。由于包没有特殊的属性,它不需要任何特殊的类。种群的初始化是通过使用toolbox中的initRepeat()函数直接实现的。
toolbox.register('population', tools.initRepeat, list, toolbox.individual)
调用toolbox.population(populationsize = n)就会返回一个种群数目为n的种群,例如2.6中的size为8的个体,使用上述命令即可生成100个这样不同的个体。
3.2 Grid
一个网格类型的种群时一种特殊结构的种群,在这个种群中,相邻个体相互之间是有直接地影响的。个体在散布到网格的时候,每一个网格将会具有一个单独的个体(类似于矩阵这样)。然而,它的实施与Bag的list实现不同之处在于,它是由多个list的个体构成的。(多个list合成一个矩阵)
N_COL = 4
N_ROW = 4
toolbox.register('row', tools.initRepeat, list, toolbox.individual, n = N_COL)
toolbox.register('population', tools.initRepeat, list, toolbox.individual, n = N_ROW)
调用toolbox.population()就可以实现生成一个网格类型的种群。例如上面实现了一个虽然想要生成4x4的“矩阵”,但是由于每一个个体是一个1x8的向量,因此实际的矩阵是一个4x8的。简单来说,就是如果产生的是单独个体,那么矩阵的大小是可以自己设置的。但如果是类似于2.6中这样的向量,那么如果维度超出你所设定的范围,计算机会自动扩充矩阵。
3.3 Swarm
一个Swarm是用在PSO中的。它的不同之处在于它在某种意义上存在着一种交流网络。最简单的网络是全连接网络,就是每一个particle(2.5的 speed+position)都知道其它个体所到达的最好位置。这通常由复制全局最优解gbest和全局最优适应度gbestfit实现。
creator.create('Swarm', list, gbest = None, gbestfit = creator.FitnessMax)
toolbox.register('swarm', tools.initRepeat, creator.Swarm, toolbox.particle)
调用toolbox.swarm(population_num = n)即可生成n个确定范围(2.5Particle)不同位置和速度的粒子。在计算gbest和gbestfit之后,就可以找到这代个体中的最佳粒子。
3.4 Demes(这是啥玩意儿?翻译都没有)
一个Deme是一个子种群(包含于种群中)。Demes是仅有的子种群,实际上与种群并没有什么不同,除了名字(意思就是一个拷贝?)。这里,我们创建了一个包含三个Demes的种群,每一个都有不同树木的个体,你可以通过使用在initRepeat()函数中的n参数对它进行调节。
toolbox.register('demes', tools.initRepeat, list, toolbox.individual)
DEME_SIZE = 10, 50, 100
populatiopn = [toolbox.demes(n = i) for i in DEME_SIZE]
这可以生成不同规模的种群,调用时候可以采取索引population[0],得到一个拥有十个个体的种群。
3.5 Seedling a Population
一些时候,一个首先猜想种群(???first guess population)可以用于初始化一个演化算法。这种初始化种群的关键点在于没有任何的随机个体是要去拥有一个个体初始化函数。(也就是说没有必要去先初始化个体然后不断initRepeat?)

种群将会从文件my_guess.json初始化,将会包含一个拥有第一猜想的列表。这种初始化方法可以与常规初始化方法相结合,这样就可以拥有部分随机个体和部分非随机个体。记住initIndividual()和individual_guess()是可以选择的,这是因为它们的默认结构是相似的。你可以移除212行使用217行的代码代替。
总的来说,3.5的方法提供了一种手动初始化的情况,可以通过猜想来对数据初始化,一定程度上可以加快收敛,并且也提供了不完全随机的生成方法。
4 总结
第二部分首先讲了优化目标的选择——其实很简单,最大与最小,单目标与多目标,在creator中的weights里面可以非常简单地设定。
接下来对个体的生成方法进行了详细的介绍,包括浮点型list,整型序列(Permutation),数值表达式(AE),演化策略(ES),粒子群(Particle)和混合个体(包含整型和浮点型,AFO)的生成。AE的生成方式是表达式树的生成,与其他的部分是不同的。但总体上看,都是首先确定优化目标,接下来创建个体,最后使用toolbox对之前标定的生成方法(随机方法以及数据结构:树or列表?)以及个体进行register,最后使用toolbox.register_name生成个体。
种群的生成本质上是多个个体的重复生成。这里介绍了包(Bag)、网格(Grid),粒子群种群(Swarm),子(多)种群(Demes)以及非随机种群(SaP)的生成。不同类型的种群可以应用到不同的算法当中。
2018.09.03
8.28pm
网友评论