(四) 计算统计
- Computing Statistics
通常情况下,我们想要在优化过程中编辑数据。Statistic模块可以在任何设计好的目标上改变一些本不可改变的数据。为了达到这个目的,需要使用与工具箱中完全相同的语法在静态数据中注册统计函数。
states = tools.Statistics(key = lambda ind : ind.fitness.values)
使用key的第一个参数作为统计对象。这个key必须支持一个可以在之后被应用到数据上的函数从而得到统计结果。之前的例子使用了fitness.values()中每一个元素的属性。
states.register('avg', numpy.mean)
states.register('std', numpy.std)
states.register('min', numpy.min)
states.register('max', numpy.max)
这些统计函数现在被注册了。register函数希望alias(‘名字’)作为第一个属性以及一个在向量上操作的函数(numpy.mean)作为第二个属性。在调用时,任何在后面的元素都会传到函数上。统计目标的创建已经完成。
- Predefined Algorithms
当使用一个预定义的算法时,例如esSimple()/eaMuPlusLambada()/eaMuCommaLambda()/eaGenerateUpdata(),之前创建的统计目标可以作为算法的属性。
pop, logbook = algorithms.esSimple(pop, toolbox, cxpb = 0.5, mutpb = 0.2, ngen = 0, stats = stats, verbose = True)
统计将会在每一次迭代中自动的进行计算。详细参数在优化过程中会打印在屏幕上。一旦算法返回,最终的种群和一个logbook将会返回。在下一节可以看到更详细的信息。
- Writing Your Own Algorithm
当编写自己的算法时,包含统计时十分简单的。只需要去在需要的目标上编写统计。例如,在一个给定种群上编写统计需要调用compile()方法完成。
record = stats.compile(pop)
这些用于编辑函数的属性必须在一个迭代元素中,这样这些key才会被调用。这里,我们的种群(pop)包含了许多个体。统计目标将会在每一个个体上调用key函数获取fitness.values属性的值。这个结果数组的值最终会给到每一个统计函数并且将结果输入到record字典中,每一个key都会与相应的函数相关联。
在你的main函数中把这个命令放在更新种群之后就可以得到一系列的统计值:标准差-最大值-平均值-最小值。
- Multi-objective Statistics
正如统计可以通过numpy函数直接进行计算,所有的目标将会通过默认numpy的属性联合在一起。接下来,一个需要明确的事情是每一个axis的操作。这会通过给予axis一个额外的属性作为注册函数达成。
stats = tools.Statistics(key=lambda ind: ind.fitness.values)
stats.register("avg", numpy.mean, axis=0)
stats.register("std", numpy.std, axis=0)
stats.register("min", numpy.min, axis=0)
stats.register("max", numpy.max, axis=0)
- Multiple Statistics
计算种群个体的不同属性也是可以的。例如,在遗传程序设计(GP)中除了对适应度统计之外,对表达式树的高度统计也是常见的。一个可以使用多元Statistics目标的函数是MultiStatistics。
stats_fit = tools.Statistics(ket= lambda ind: ind.fitness.values)
stats_size = tools.Statistics(key = len)
mstats = tools.MultiStatistics(fitness = stats_fit, size = stats_size)
两个统计目标使用与之前相同的方式被创建。第二个目标将会通过调用len()获取每一个个体的长度。一旦创建完成,统计目标将会被给到MultiStatistics函数中,这里地元素都是使用keywords来定义的。这些keywords将会提供不同统计量的定义。这些统计函数仅仅可以在multi-statistics被注册一次
mstats.register("avg", numpy.mean)
mstats.register("std", numpy.std)
mstats.register("min", numpy.min)
mstats.register("max", numpy.max)
多元统计目标可以给到一个算法当中,或者他们可以以相似的形式运用到单一统计中
record = mstats.compile(pop)
在这种情况下,record就是一个字典的字典类型(嵌套)。第一个等级包括一些统计信息的关键字,第二个等级包括一些预制件相似的简单统计目标
>>> print(record)
{'fitness': {'std': 1.64, 'max': 6.86, 'avg': 1.71, 'min': 0.166},
'size': {'std': 1.89, 'max': 7, 'avg': 4.54, 'min': 3}}
- Logging Data
一旦数据通过统计产生,我们可以使用Logbook对它进行存储。Logbook是用来
按时间顺序排列的条目(如字典)。它会直接兼容数据类型并且返回统计目标,但是不会被数据限制。实际上,任何东西都可以包含在日志的条目中。
logbook = tools.Logbook()
logbook.record(gen=0, evals=30, **record)
record()方法采用可变数字作为参数,每一个参数作为数据都会被记录。在最后一个例子中,我们保存了一代,结果的数量和任何包含在record()中的东西都会通过这个方法产生统计学目标。所有的记录都会被保存在Logicbook中,直到它被销毁。在一些列的记录之后,我们可能会想去调用logbook中的信息
gen, avg = logbook.select("gen", "avg")
select()方法提供了一种方法去调用所有的与record中keyword相关的信息。这个方法采取了可变数量的字符串元素,就是在record或者statistics中的keywords。这里,我们调用代数和平均适应度,使用一个单独的select进行调用。一个logbook是一个可选择的目标(只要插入数据是可选择的),它可以提供一种很好的方法去存储演化过程中的统计参数。
import pickle
pickle.dump(logbook, lb_file)
注意:每一个算法返回的logbook包含着每一代的信息和适应度在整个进化过程中的数目。
- Printing to Screen
一个logbook可以打印在屏幕或者文档中。它的str()方法在第一个key中返回每一个key的头,同时使用这些keys完成logbook。按照行插入的时间顺序排列,而列将处于未定义的顺序。指定顺序的最简单方法是将header属性设置为指定列顺序的字符串列表。
logbook.header = 'gen', 'avg', 'spam'
结果为
>>>print(logbook)
gen avg spam
0 [ 50, 2 ]
每一个列名包含了没有明确记录的列名,它将会被空着,就像spam一样。
一个logbook同样包含尚未打印的条目的流属性。
>>> print(logbook.stream)
gen avg spam
0 [ 50.2]
>>> logbook.record(gen=1, evals=15, **record)
>>> print(logbook.stream)
1 [ 50.2]
- Dealing with Multi-statistics
Logbook可以与字典类型合作,返回MultiStatistics目标。事实上,它将在chapters中为每一个包含在字典中的子字典记录数据,接下来,一个multi record可以被看做一个record
logbook = tools.Logbook()
logbook.record(gen=0, evals=30, **record)
与列排序不同的一点是,当我们明确chapters的顺序时,它们的内容如下
logbook.header = "gen", "evals", "fitness", "size"
logbook.chapters["fitness"].header = "min", "avg", "max"
logbook.chapters["size"].header = "min", "avg", "max"
(看起来就是fitness包含min、avg、max;size也包含这些,然后它们的输出就都分别包含这些)
>>> print(logbook)
fitness size
------------------------- ---------------
gen evals min avg max min avg max
0 30 0.165572 1.71136 6.85956 3 4.54 7
这些数据的调用同样也可以通过chapter进行调用
gen = logbook.select("gen")
fit_mins = logbook.chapters["fitness"].select("min")
size_avgs = logbook.chapters["size"].select("avg")
迭代次数、最小适应度和平均尺寸就可以按照时间顺序获得了。如果一些数据不可用,一个None会出现在向量中。
- Some Plotting Sugar
在优化过程中最常用的操作就是在图中显示进化过程。Logbook可以有效地执行这一操作。使用选择方法,我们可以调用需要的数据并且使用matplotlib去绘制图形。
gen = logbook.select("gen")
fit_mins = logbook.chapters["fitness"].select("min")
size_avgs = logbook.chapters["size"].select("avg")
import matplotlib.pyplot as plt
fig, ax1 = plt.subplots()
line1 = ax1.plot(gen, fit_mins, "b-", label="Minimum Fitness")
ax1.set_xlabel("Generation")
ax1.set_ylabel("Fitness", color="b")
for tl in ax1.get_yticklabels():
tl.set_color("b")
ax2 = ax1.twinx()
line2 = ax2.plot(gen, size_avgs, "r-", label="Average Size")
ax2.set_ylabel("Size", color="r")
for tl in ax2.get_yticklabels():
tl.set_color("r")
lns = line1 + line2
labs = [l.get_label() for l in lns]
ax1.legend(lns, labs, loc="center right")
plt.show()
当添加到符号回归实例时,它将会给出下面图像:
10 总结
这一节主要讲的是
1、在迭代过程中输出统计学信息的方法,包括适应度值、种群平均数、方差等等。针对不同的目标可以设置多组统计数据并行输出。
2、在迭代过程中输出种群信息,将他们保存在文件或者输出到屏幕上。以及绘制它们在演化过程中的适应度图像等。