Spambase 问题:强类型 GP¶
这个问题是一个使用 STGP(强类型遗传编程)的分类示例。进化后的程序处理浮点值和布尔值。程序必须返回一个布尔值,如果电子邮件是垃圾邮件则为真,否则为假。它使用一个电子邮件库(保存在 spambase.csv 中,参见 参考),从中随机选择 400 封电子邮件来评估每个个体。
基本元素集¶
强类型GP是一种更通用的GP,其中每个原语除了具有元数和相应的函数外,还具有特定的返回类型和特定的参数类型。通过这种方式,每个原语在某种程度上被描述为一个纯C函数,其中每个参数必须是正确的类型,并且返回值类型在运行时之前被指定。
备注
实际上,当用户未指定返回值或参数类型时,DEAP 会选择一个默认类型。在标准 GP 中,因为所有原语都使用这个默认类型,所以这表现得就像没有类型要求一样。
我们定义一个类型化的原语集几乎与定义一个普通的原语集相同,但我们必须指定所使用的类型。
spam = list(list(float(elem) for elem in row) for row in spamReader)
# defined a new primitive set for strongly typed GP
pset = gp.PrimitiveSetTyped("MAIN", itertools.repeat(float, 57), bool, "IN")
# boolean operators
pset.addPrimitive(operator.and_, [bool, bool], bool)
pset.addPrimitive(operator.or_, [bool, bool], bool)
pset.addPrimitive(operator.not_, [bool], bool)
# floating point operators
# Define a protected division function
def protectedDiv(left, right):
try: return left / right
except ZeroDivisionError: return 1
pset.addPrimitive(operator.add, [float,float], float)
pset.addPrimitive(operator.sub, [float,float], float)
pset.addPrimitive(operator.mul, [float,float], float)
pset.addPrimitive(protectedDiv, [float,float], float)
# logic operators
# Define a new if-then-else function
def if_then_else(input, output1, output2):
if input: return output1
else: return output2
pset.addPrimitive(operator.lt, [float, float], bool)
pset.addPrimitive(operator.eq, [float, float], bool)
pset.addPrimitive(if_then_else, [bool, float, float], float)
# terminals
在第一行,我们看到了一个带有类型声明的原语集,使用的是 PrimitiveSetTyped
。第一个参数仍然是集合名称,但接下来的参数是条目的类型(在这个例子中,我们有57个浮点数条目和一个布尔输出;我们可以写57次`float`,但使用 itertools.repeat()
函数更快且更易理解)。最后一个参数仍然是条目的前缀。
之后,我们定义原始类型本身。类型化原始类型的定义有两个额外的参数:一个包含参数类型的列表,按顺序排列,以及返回类型。
然后填充终端集,每个类型至少有一个终端,这是为了原始集声明。
评估函数¶
评估函数非常简单:它在垃圾邮件数据库中随机选取400封邮件,然后检查个体的预测是否与预期的布尔输出匹配。正确预测的邮件数量作为个体的适应度返回(最多为400)。
def evalSpambase(individual):
# Transform the tree expression in a callable function
func = toolbox.compile(expr=individual)
# Randomly sample 400 mails in the spam database
spam_samp = random.sample(spam, 400)
# Evaluate the sum of correctly identified mail as spam
result = sum(bool(func(*mail[:57])) is bool(mail[57]) for mail in spam_samp)
return result,
工具箱¶
使用的工具箱与符号回归示例中展示的非常相似,但请注意,我们现在使用特定的STGP操作符进行交叉和变异:
result = sum(bool(func(*mail[:57])) is bool(mail[57]) for mail in spam_samp)
return result,
toolbox.register("evaluate", evalSpambase)
toolbox.register("select", tools.selTournament, tournsize=3)
结论¶
虽然它与其他问题并无本质区别,但值得注意的是Python如何减少编程时间。实际上,垃圾邮件数据库是以csv格式存储的:使用许多框架,您可能需要手动读取它,或者使用非标准库,但在Python中,您可以使用内置模块 csv
,并且在两行代码内就可以完成!数据现在存储在矩阵 spam 中,并且可以轻松地在整个程序中使用: