Deap框架—结合demo进行学习

文章目录

    • Deap框架—结合demo进行学习
      • 1. One Max Problem
        • 1.1 General Version
          • Modules
          • Creator
          • Toolbox
          • Evaluation Function
          • Genetic Operators
          • Main
          • 简单进化算法
        • 1.2 One Max Problem: Short Version
        • 1.3 One Max Problem: Numpy Version
      • 2. Knapsack Problem - 背包问题
          • Modules
          • Init
          • Creator
          • Toolbox
          • Evaluation Function
          • Genetic Operators
          • Main
          • $(\mu+\lambda)$ 进化算法
          • $(\mu,\lambda)$ 进化算法

Deap框架—结合demo进行学习

1. One Max Problem

1.1 General Version

问题描述:列表中仅有0和1,求和最大的情况

Modules
import random
import numpy as np

from deap import base
from deap import creator
from deap import tools
from deap import algorithms
Creator
# 创建FitnessMax类, 继承base.Fitness, attribute为weights
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
# Individual, 继承list, attribute为fitness
creator.create("Individual", list, fitness=creator.FitnessMax) # 定义个体类型
Toolbox

我们将使用的所有对象: 个体、种群以及所有函数、运算符和参数都将存储在一个名为“Toolbox”的DEAP container中。它包含两种添加和删除内容的方法: register()和unregister()

toolbox = base.Toolbox()
# attr_bool是函数random.randint(0,1)的别名
# 定义基因
toolbox.register("attr_bool", random.randint, 0, 1)

# individual是函数tools.initRepeat(creator.Individual, toolbox.attr_bool, 100)的别名
# tools.initRepeat(container, func, n)由func中的数据填充的容器实例
# container – 用于放入func生成的数据的类型
# func – 将被调用n次以填充container的函数
# n – 重复func的次数

# 创建个体,编码长度为100
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, 100)
# 创建种群
toolbox.register("population", tools.initRepeat, list, toolbox.individual) 
Evaluation Function
# 返回值的长度必须等于目标数(权重个数)。
def evalOneMax(individual):
    # 返回的是个元祖,注意,
    return sum(individual),
Genetic Operators
toolbox.register("evaluate", evalOneMax)
# 两点交叉,编码串中随机设置两段交叉点进行基因交换
toolbox.register("mate", tools.cxTwoPoint)
# 变异,用于bool值(二进制类型)的变异,indpb为变异概率
toolbox.register("mutate", tools.mutFlipBit, indpb=0.05)
# 选择,先随机选择tournsize个个体,然后根据个体的fitness,选择最佳个体,重复k次
toolbox.register("select", tools.selTournament, tournsize=3)
Main
def main():
    # 创建300个大小的种群,300*100(个体编码长度)
    pop = toolbox.population(n=300)
    # CXPB: 两个个体之间交叉的概率
    # MUTPB: 个体的变异概率
    CXPB, MUTPB = 0.5, 0.2
    
    print("Start of evolution")
    # 评价整个种群
    fitnesses = list(map(toolbox.evaluate, pop))
    for ind, fit in zip(pop, fitnesses):
        ind.fitness.values = fit
    
    # 获取整个种群的适应度
    fits = [ind.fitness.values[0] for ind in pop]
    # 当前种群的代数
    g = 0
    # 开始进化
    while max(fits) < 100 and g < 1000:
        # 下一代
        g += 1
        print("-- Generation %i --" % g)
        # 下一代种群选择
        offspring = toolbox.select(pop, len(pop))
        # 克隆选择的种群,确保每个个体为独立的实例,而不是引用
        offspring = list(map(toolbox.clone, offspring))
        
        # 交叉,inplace修改
        for child1, child2 in zip(offspring[::2], offspring[1::2]):
            if random.random() < CXPB:
                toolbox.mate(child1, child2)
                del child1.fitness.values
                del child2.fitness.values
        
        # 变异,inplace修改
        for mutant in offspring:
            if random.random() < MUTPB:
                toolbox.mutate(mutant)
                del mutant.fitness.values
        
        # 对新的子代进行评估
        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        fitnesses = map(toolbox.evaluate, invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            ind.fitness.values = fit
        # 下一代替代父代
        pop[:] = offspring
        
        # 种群适应度
        fits = [ind.fitness.values[0] for ind in pop]
        
        length = len(pop)
        mean = sum(fits) / length
        sum2 = sum(x*x for x in fits)
        std = abs(sum2 / length - mean**2)**0.5
        
        # 输出当前种群状态
        print("  Min %s" % min(fits))
        print("  Max %s" % max(fits))
        print("  Avg %s" % mean)
        print("  Std %s" % std)

main()
简单进化算法

DEAP中预置的简单进化算法deap.algorithms.eaSimple流程描述如下:

  1. 根据工具箱中注册的toolbox.evaluate评价族群
  2. 根据工具箱中注册的toolbox.select选择与父代相同个数的育种个体
  3. 在族群中进行第一次循环,用工具箱中注册的toolbox.mate进行配种,并用生成的两个子代替换对应父代
  4. 在族群中进行第二次循环,用工具箱中注册的toolbox.mutate进行变异,用变异后的子代替换对应父代
  5. 从1开始重复循环,直到达到设定的迭代次数

需要注意的是在这个过程中,生成子代有四种情况:受到配种影响;受到变异影响;既受到配种也受到变异影响;既不受配种影响也不受变异影响。

对应的伪代码可以表述为:

evaluate(population)
for g in range(ngen):
    population = select(population, len(population))
    offspring = varAnd(population, toolbox, cxpb, mutpb)
    evaluate(offspring)
    population = offspring

1.2 One Max Problem: Short Version

def main():
    pop = toolbox.population(n=300)
    # 用于trace最佳样本
    hof = tools.HallOfFame(1)
    # 对每次迭代时进行种群效果计算
    stats = tools.Statistics(lambda ind: ind.fitness.values)
    stats.register("avg", numpy.mean)
    stats.register("std", numpy.std)
    stats.register("min", numpy.min)
    stats.register("max", numpy.max)
    
    # 简单进化算法, ngen代数
    pop, log = algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=40, 
                                   stats=stats, halloffame=hof, verbose=True)
    return pop, log

  pop, log, hof = main()

1.3 One Max Problem: Numpy Version

'''第一处修改'''
creator.create("Individual", np.ndarray, fitness=creator.FitnessMax) # 定义个体类型

# 自定义numpy情况下的基因交叉操作
def cxTwoPointCopy(ind1, ind2):
    """Execute a two points crossover with copy on the input individuals. The
    copy is required because the slicing in numpy returns a view of the data,
    which leads to a self overwritting in the swap operation. It prevents
    ::
    
        >>> import numpy
        >>> a = numpy.array((1,2,3,4))
        >>> b = numpy.array((5,6,7,8))
        >>> a[1:3], b[1:3] = b[1:3], a[1:3]
        >>> print(a)
        [1 6 7 4]
        >>> print(b)
        [5 6 7 8]
    """
    size = len(ind1)
    cxpoint1 = random.randint(1, size)
    cxpoint2 = random.randint(1, size - 1)
    if cxpoint2 >= cxpoint1:
        cxpoint2 += 1
    else: # Swap the two cx points
        cxpoint1, cxpoint2 = cxpoint2, cxpoint1
		# 注意copy操作
    ind1[cxpoint1:cxpoint2], ind2[cxpoint1:cxpoint2] \
        = ind2[cxpoint1:cxpoint2].copy(), ind1[cxpoint1:cxpoint2].copy()
        
    return ind1, ind2

'''第二处修改'''
toolbox.register("mate", cxTwoPointCopy)

def main():
    '''第三处修改,增加similar的定义'''
    hof = tools.HallOfFame(1, similar=numpy.array_equal)

2. Knapsack Problem - 背包问题

Modules
import random
import numpy as np

from deap import base
from deap import creator
from deap import tools
from deap import algorithms
Init
IND_INIT_SIZE = 5  # 初始化物品数
MAX_ITEM = 50  # 最大可选物品数
MAX_WEIGHT = 50  # 最大背包承重
NBR_ITEMS = 20  # 物品数

# 保证可复现性
random.seed(64)

# 创建随机项字典,用来表示NBR_ITEMS个物品的重量及价值
items = {}
for i in range(NBR_ITEMS):
    items[i] = (random.randint(1, 10), random.uniform(0, 100))
Creator
# 物品重量最小化, 物品价值最大化, 多目标优化问题 — 物品重量尽可能小,价值尽可能大
creator.create("Fitness", base.Fitness, weights=(-1.0, 1.0))
# 继承自set,保证每个物品只取一次
creator.create("Individual", set, fitness=creator.Fitness)
Toolbox
toolbox = base.Toolbox()
# 基因取值-[0,NBR_ITEMS)表示各个物品的index
toolbox.register("attr_item", random.randrange, NBR_ITEMS)

# 个体, 基因长度IND_INIT_SIZE, 代表选IND_INIT_SIZE个物品
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_item, IND_INIT_SIZE)
# 种群
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
Evaluation Function

返回weight, value代表两个目标的当前适应度值

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             # 行李超重
    return weight, value
Genetic Operators
def cxSet(ind1, ind2):
    """两个set的交叉方法"""
    temp = set(ind1)                # Used in order to keep type
    ind1 &= ind2                    # 求 交集
    ind2 ^= temp                    # 求 并集-交集
    return ind1, ind2

def mutSet(individual):
    """变异情况为增加或减少物品数"""
    if random.random() < 0.5:
        if len(individual) > 0:     # We cannot pop from an empty set
            # set不支持index操作,因此不能随机选择,要进行类型转换
            individual.remove(random.choice(sorted(tuple(individual))))
    else:
        individual.add(random.randrange(NBR_ITEMS))
    return individual,
    
toolbox.register("evaluate", evalKnapsack)
toolbox.register("mate", cxSet)
toolbox.register("mutate", mutSet)
# 这是一个多目标问题,采用NSGA-II选择方案,在多目标情况下选择合适较优的种群
toolbox.register("select", tools.selNSGA2)
Main
def main():
    random.seed(64)
    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", np.mean, axis=0)
    stats.register("std", np.std, axis=0)
    stats.register("min", np.min, axis=0)
    stats.register("max", np.max, axis=0)
    
    # 每次生成100子代,在父代+子代中选择MU个作为子代
    # mu+lambda进化算法,该算法中子代不会同时受到变异和配种影响
    algorithms.eaMuPlusLambda(pop, toolbox, MU, LAMBDA, CXPB, MUTPB, NGEN, stats,
                              halloffame=hof)
    # pop, log = algorithms.eaSimple(pop, toolbox, cxpb=CXPB, mutpb=MUTPB, ngen=NGEN, 
    #                                stats=stats, halloffame=hof, verbose=True)
    return pop, stats, hof

pop, stats, hof = main()
( μ + λ ) (\mu+\lambda) (μ+λ) 进化算法

Deap中的 ( μ + λ ) (\mu+\lambda) (μ+λ) 进化算法deap.algorithms.eaMuPlusLambda

该算法的流程如下:

  1. 根据工具箱中注册的toolbox.evaluate评价族群
  2. 在族群中进行循环,在每次循环中,随机选择crossover,mutation和reproduction三者之一:如果选择到crossover,那么随机选择2个个体,用工具箱中注册的toolbox.mate进行配种,将生成的第一个子代加入到后代列表中,第二个子代丢弃;如果选择到mutation,用工具箱中注册的toolbox.mutate进行变异,将变异后的子代加入到后代列表中;如果选择到reproduction,随机选择一个个体,将其复制加入到后代列表中
  3. 根据工具箱中注册的toolbox.select,在父代+子代中选择给定数量的个体作为子代
  4. 从1开始重复循环,直到达到设定的迭代次数

注意在这个子代生成的过程中,子代不会同时受到变异和配种影响

对应的伪代码可以表述为:

evaluate(population)
for g in range(ngen):
    offspring = varOr(population, toolbox, lambda_, cxpb, mutpb)
    evaluate(offspring)
    population = select(population + offspring, mu)
( μ , λ ) (\mu,\lambda) (μ,λ) 进化算法

Deap中的 ( μ , λ ) (\mu,\lambda) (μ,λ) 进化算法deap.algorithms.eaMuCommaLambda

该算法的流程和 ( μ + λ ) (\mu+\lambda) (μ+λ)基本相同,唯一的区别在于生成子代族群时,只在产生的子代中选择,而丢弃所有父代。

对应的伪代码可以表述为

evaluate(population)
for g in range(ngen):
    offspring = varOr(population, toolbox, lambda_, cxpb, mutpb)
    evaluate(offspring)
    population = select(offspring, mu)

你可能感兴趣的:(优化调度算法)