遗传算法中几种不一样选择算子及Python实现

前言

本文对遗传算法中的几种选择策略进行了总结, 其中包括:python

  1. Proportionate Roulette Wheel Selection
  2. Linear Ranking Selection
  3. Exponential Ranking Selection
  4. Tournament Selection

对于每种选择策略我都使用Python进行了相应的实现并之内置插件的形式整合进了本人所写的遗传算法框架GAFT中。对须要使用遗传算法优化问题以及学习遗传算法的童鞋能够做为参考.git

项目连接:github

遗传算法中的选择算子

遗传算法(genetic algorithms, GAs)是一种自适应的启发式搜索算法, 它模仿达尔文进化论中的“适者生存”的原则, 最终获取优化目标的最优解。下图描述了一个简单的遗传算法流程:算法

对于种群中须要进行杂交的物种选择方法有不少,并且选择一种合适的选择策略对于遗传算法总体性能的影响将是很大的。若是一个选择算法选择多样性下降,便会致使种群过早的收敛到局部最优势而不是咱们想要的全局最优势,也就是所谓的”早熟”。而选择策略过于发散则会致使算法难以收敛到最优势。所以在这两点中咱们须要进行平衡才能使遗传算法以一种高效的方式收敛到全局最优势。bash

GAFT框架中的算子插件

GAFT是我根据本身需求开发的一个遗传算法框架,相关介绍的博客能够参见《GAFT-一个使用Python实现的遗传算法框架》,《使用MPI并行化遗传算法框架GAFT》。该框架提供了插件接口,用户能够经过自定义算子以及on-the-fly分析插件来放到gaft框架中运行遗传算法流程对目标问题进行优化。app

本部分我稍微介绍下gaft关于遗传算子相关接口规范,以及编写能用于gaft的算子的编写方法。框架

在gaft中遗传算子的编写都是须要继承框架内置的基类,而后根据基类提供的接口,实现本身的算子。其中基类的定义都在/gaft/plugin_interfaces/operators/目录下,下面我以选择算子为例,介绍下接口。dom

gaft中选择算子的基类为GASelection,其中在遗传算法流程中会调用该类实例的select方法,进而根据算子中的相关选择策略完成从种群中选取一对物种做为父亲和母亲产生子代。基类的定义为:函数

class GASelection(metaclass=SelectionMeta):    ''' Class for providing an interface to easily extend the behavior of selection operation. '''    def select(self, population, fitness):        ''' Called when we need to select parents from a population to later breeding. :param population: The current population. :type population: GAPopulation :return parents: Two selected individuals for crossover. :type parents: Tuple of tow GAIndividual objects. '''        raise NotImplementedError复制代码

select的方法的参数为当前种群population以及相应的适应度函数fitness,其中population须要是GAPopulation对象,fitness也必须是callable的对象。性能

固然,这些在Python这种动态类型语言中貌似看起来有些鸡肋,可是为了可以更加规范使用者,我利用Python的元类在实例化类对象的时候对接口的实现以及接口的参数类型加以限制。具体的实现都在/gaft/plugin_interfaces/metaclasses.py中,有兴趣的童鞋能够看看实现方法。

具体自定义算子的编写方法我将在下一部分同选择策略一块儿贴出来。

不一样的选择策略

本部分我主要对四种不一样的选择策略进行总结并加以gaft插件形式的Python实现。

选择算子决定了哪些个体将会从种群中被选择出来用于繁衍下一代种群中的新个体。其主要的原则就是:

the better is an individual; the higher is its chance of being a parent

选择算子在遗传算法迭代中将适应度函数引入进来,由于适应度函数式标定一个个体是否足够“好”的重要标准。可是选择过程又不能仅仅彻底依赖于适应度函数,由于一个种群中的最优物种并不必定是在全局最优势附近。所以咱们也应该给相对来讲并那么“好”的个体一点机会让他们繁衍后代, 避免“早熟”。

Proportionate Roulette Wheel Selection

此轮盘赌选择策略,是最基本的选择策略之一,种群中的个体被选中的几率与个体相应的适应度函数的值成正比。咱们须要将种群中全部个体的适应度值进行累加而后归一化,最终经过随机数对随机数落在的区域对应的个体进行选取,相似赌场里面的旋转的轮盘。

每一个个体ai被选中的几率为:

WX20170919-182624

好了,下面能够将此算法写成一个能够gaft中执行的算子。

from random import randomfrom bisect import bisect_rightfrom itertools import accumulate from ...plugin_interfaces.operators.selection import GASelection class RouletteWheelSelection(GASelection):    def __init__(self):        ''' Selection operator with fitness proportionate selection(FPS) or so-called roulette-wheel selection implementation. '''        pass     def select(self, population, fitness):        ''' Select a pair of parent using FPS algorithm. '''        # Normalize fitness values for all individuals. fit = [fitness(indv) for indv in population.individuals] min_fit = min(fit) fit = [(i - min_fit) for i in fit] # Create roulette wheel. sum_fit = sum(fit) wheel = list(accumulate([i/sum_fit for i in fit])) # Select a father and a mother. father_idx = bisect_right(wheel, random()) father = population[father_idx] mother_idx = (father_idx + 1) % len(wheel) mother = population[mother_idx] return father, mother复制代码

过程主要分为下面几个:

  1. 继承GASelection
  2. 实现select方法
  3. select的参数为GAPopulation实例和适应度函数
  4. 根据算法选择出两个须要繁衍的物种并返回便可

Tournament Selection

因为算法执行的效率以及易实现的的特色,锦标赛选择算法是遗传算法中最流行的选择策略。在本人的实际应用中的确此策略比基本的轮盘赌效果要好些。他的策略也很直观,就是咱们再整个种群中抽取n个个体,让他们进行竞争(锦标赛),抽取其中的最优的个体。参加锦标赛的个体个数成为tournament size。一般当n=2即是最常使用的大小,也称做Binary Tournament Selection.

Tournament Selection的优点:

  1. 更小的复杂度O(n)
  2. 易并行化处理
  3. 不易陷入局部最优势
  4. 不须要对全部的适应度值进行排序处理

下图显示了n=3的Tournament Selection的过程:

能够开始写成自定义算子在gaft运行了:

from random import sample from ...plugin_interfaces.operators.selection import GASelection class TournamentSelection(GASelection):    def __init__(self, tournament_size=2):        ''' Selection operator using Tournament Strategy with tournament size equals to two by default. '''        self.tournament_size = tournament_size     def select(self, population, fitness):        ''' Select a pair of parent using Tournament strategy. '''        # Competition function. complete = lambda competitors: max(competitors, key=fitness) # Check validity of tournament size. if self.tournament_size >= len(population): msg = 'Tournament size({}) is larger than population size({})' raise ValueError(msg.format(self.tournament_size, len(population))) # Pick winners of two groups as parent. competitors_1 = sample(population.individuals, self.tournament_size) competitors_2 = sample(population.individuals, self.tournament_size) father, mother = complete(competitors_1), complete(competitors_2) return father, mother复制代码

Linear Ranking Selection

下面两个介绍的选择策略都是基于排序的选择策略,上面提到的第一种基本轮盘赌选择算法,有一个缺点,就是若是一个个体的适应度值为0的话,则被选中的几率将会是0, 这个个体将不能产生后代。因而咱们须要一种基于排序的算法,来给每一个个体安排相应的选中几率。

在Linear Ranking Selection中,种群中的个体首先根据适应度的值进行排序,而后给全部个体赋予一个序号,最好的个体为N, 被选中的几率为Pmax, 最差的个体序号为1, 被选中的几率为Pmin,因而其余的在他们中间的个体的几率即可以根据以下公式获得:

WX20170919-222303@2x

实现代码:

from random import randomfrom itertools import accumulatefrom bisect import bisect_right from ...plugin_interfaces.operators.selection import GASelection class LinearRankingSelection(GASelection):    def __init__(self, pmin=0.1, pmax=0.9):        ''' Selection operator using Linear Ranking selection method. Reference: Baker J E. Adaptive selection methods for genetic algorithms[C]//Proceedings of an International Conference on Genetic Algorithms and their applications. 1985: 101-111. '''        # Selection probabilities for the worst and best individuals. self.pmin, self.pmax = pmin, pmax def select(self, population, fitness): ''' Select a pair of parent individuals using linear ranking method. ''' # Individual number. NP = len(population) # Add rank to all individuals in population. sorted_indvs = sorted(population.individuals, key=fitness, reverse=True) # Assign selection probabilities linearly. # NOTE: Here the rank i belongs to {1, ..., N} p = lambda i: (self.pmin + (self.pmax - self.pmin)*(i-1)/(NP-1)) probabilities = [self.pmin] + [p(i) for i in range(2, NP)] + [self.pmax] # Normalize probabilities. psum = sum(probabilities) wheel = list(accumulate([p/psum for p in probabilities])) # Select parents. father_idx = bisect_right(wheel, random()) father = population[father_idx] mother_idx = (father_idx + 1) % len(wheel) mother = population[mother_idx] return father, mother复制代码

Exponential Ranking Selection

相似上面的Linear Ranking选择策略,这种指数排序即是在肯定每一个个体的选择几率的时候使用了指数形式的表达式, 其中c为底数,知足0<c<1:

WX20170919-222353@2x

实现代码:

from random import randomfrom itertools import accumulatefrom bisect import bisect_right from ...plugin_interfaces.operators.selection import GASelection class ExponentialRankingSelection(GASelection):     def __init__(self, base=0.5):        ''' Selection operator using Exponential Ranking selection method. :param base: The base of exponent :type base: float in range (0.0, 1.0) '''        if not (0.0 < base < 1.0):            raise ValueError('The base of exponent c must in range (0.0, 1.0)')        self.base = base     def select(self, population, fitness):        ''' Select a pair of parent individuals using exponential ranking method. '''        # Individual number. NP = len(population) # NOTE: Here the rank i belongs to {1, ..., N} p = lambda i: self.base**(NP - i) probabilities = [p(i) for i in range(1, NP + 1)] # Normalize probabilities. psum = sum(probabilities) wheel = list(accumulate([p/psum for p in probabilities])) # Select parents. father_idx = bisect_right(wheel, random()) father = population[father_idx] mother_idx = (father_idx + 1) % len(wheel) mother = population[mother_idx] return father, mother复制代码

总结

本文对于遗传算法中四种不一样的选择策略进行了介绍和总结,同时对于本文所写的遗传算法框架的自定义算子接口进行了简要的介绍,针对本文中的选择策略分别根据接口的要求实现了相应的算子,这些算子也做为GAFT框架的内置算子放入到GAFT中,对于使用GAFT的童鞋能够直接拿来使用。

参考

  • Shukla, Anupriya, Hari Mohan Pandey, and Deepti Mehrotra. “Comparative review of selection techniques in genetic algorithm.” Futuristic Trends on Computational Analysis and Knowledge Management (ABLAZE), 2015 International Conference on. IEEE, 2015.
相关文章
相关标签/搜索