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 中,并且可以轻松地在整个程序中使用:

完整的 examples/gp/spambase

参考

数据来自机器学习仓库,http://www.ics.uci.edu/~mlearn/MLRepository.html