背包问题:继承自集合

再次以这个例子为例,我们将使用一个非常简单的问题,即0-1背包问题。这个例子的目的是展示DEAP的简单性以及从简单列表或数组以外的任何东西继承的便利性。

许多进化算法教科书提到,拥有一个高效的算法最好的方式是拥有一个接近问题的表示。在这里,有什么比集合更接近一个袋子呢?让我们让我们的个体继承自 set 类。

creator.create("Fitness", base.Fitness, weights=(-1.0, 1.0))
creator.create("Individual", set, fitness=creator.Fitness)

就是这样。你现在有了实际上是集合的个体,它们具有通常的属性 适应度 。适应度是第一个目标(背包的重量)的最小化和第二个目标(背包的价值)的最大化。我们现在将创建一个包含100个随机物品的字典,以映射其价值和重量。

# Create the item dictionary: item name is an integer, and value is 
# a (weight, value) 2-tuple.
items = {}
# Create random items and store them in the items' dictionary.
for i in range(NBR_ITEMS):
    items[i] = (random.randint(1, 10), random.uniform(0, 100))

现在我们需要初始化一个种群及其中的个体。为此,我们需要一个 Toolbox 来注册我们的生成器,因为集合也可以通过输入的可迭代对象来创建。

toolbox = base.Toolbox()
toolbox.register("attr_item", random.randrange, NBR_ITEMS)
toolbox.register("individual", tools.initRepeat, creator.Individual, 
    toolbox.attr_item, IND_INIT_SIZE)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

瞧!最后要做的是定义我们的评估函数。

def evalKnapsack(individual):
    weight = 0.0
    value = 0.0
    for item in individual:
        weight += items[item][0]
        value += items[item][1]
    if len(individual) > MAX_ITEM or weight > MAX_WEIGHT:
        return 10000, 0             # Ensure overweighted bags are dominated
    return weight, value

一切准备就绪,可以进行进化了。哦,等等,由于DEAP的开发者很懒,没有可以直接应用于集合的交叉和变异操作符。我们来定义一些。例如,一个交叉操作,从两个父代生成两个子代,可以是第一个子代是两个集合的交集,第二个子代是它们的绝对差。

def cxSet(ind1, ind2):
    """Apply a crossover operation on input sets. The first child is the
    intersection of the two sets, the second child is the difference of the
    two sets.
    """
    temp = set(ind1)                # Used in order to keep type
    ind1 &= ind2                    # Intersection (inplace)
    ind2 ^= temp                    # Symmetric Difference (inplace)
    return ind1, ind2

变异操作符可以随机地从集合输入个体中添加或移除一个元素。

def mutSet(individual):
    """Mutation that pops or add an element."""
    if random.random() < 0.5:
        if len(individual) > 0:     # We cannot pop from an empty set
            individual.remove(random.choice(sorted(tuple(individual))))
    else:
        individual.add(random.randrange(NBR_ITEMS))
    return individual,

然后我们在工具箱中注册这些操作符。由于这是一个多目标问题,我们选择了NSGA-II选择方案 : selNSGA2()

toolbox.register("evaluate", evalKnapsack)
toolbox.register("mate", cxSet)
toolbox.register("mutate", mutSet)
toolbox.register("select", tools.selNSGA2)

从这里开始,剩下的工作就是编写算法或使用 algorithms 中提供的算法。这里我们将使用 eaMuPlusLambda() 算法。

def main():
    NGEN = 50
    MU = 50
    LAMBDA = 100
    CXPB = 0.7
    MUTPB = 0.2

    pop = toolbox.population(n=MU)
    hof = tools.ParetoFront()
    stats = tools.Statistics(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)

    algorithms.eaMuPlusLambda(pop, toolbox, MU, LAMBDA, CXPB, MUTPB, NGEN, stats,
                              halloffame=hof)

    return pop, stats, hof

最后,可以使用 ParetoFront 来检索进化过程中最佳的非支配个体,并创建一个 Statistics 对象来编译四代不同的统计数据。Numpy 函数在统计对象中注册时使用了 axis=0 参数,以便独立计算每个目标的统计数据。否则,Numpy 会为两个目标计算一个单一的平均值。

完整的 examples/ga/knapsack