本文對遺傳算法中的幾種選擇策略進行了總結, 其中包括:python
對於每種選擇策略我都使用Python進行了相應的實現並之內置插件的形式整合進了本人所寫的遺傳算法框架GAFT中。對須要使用遺傳算法優化問題以及學習遺傳算法的童鞋能夠做爲參考.git
項目連接:github
遺傳算法(genetic algorithms, GAs)是一種自適應的啓發式搜索算法, 它模仿達爾文進化論中的「適者生存」的原則, 最終獲取優化目標的最優解。下圖描述了一個簡單的遺傳算法流程:算法
對於種羣中須要進行雜交的物種選擇方法有不少,並且選擇一種合適的選擇策略對於遺傳算法總體性能的影響將是很大的。若是一個選擇算法選擇多樣性下降,便會致使種羣過早的收斂到局部最優勢而不是咱們想要的全局最優勢,也就是所謂的」早熟」。而選擇策略過於發散則會致使算法難以收斂到最優勢。所以在這兩點中咱們須要進行平衡才能使遺傳算法以一種高效的方式收斂到全局最優勢。bash
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
選擇算子在遺傳算法迭代中將適應度函數引入進來,由於適應度函數式標定一個個體是否足夠「好」的重要標準。可是選擇過程又不能僅僅徹底依賴於適應度函數,由於一個種羣中的最優物種並不必定是在全局最優勢附近。所以咱們也應該給相對來講並那麼「好」的個體一點機會讓他們繁衍後代, 避免「早熟」。
此輪盤賭選擇策略,是最基本的選擇策略之一,種羣中的個體被選中的機率與個體相應的適應度函數的值成正比。咱們須要將種羣中全部個體的適應度值進行累加而後歸一化,最終經過隨機數對隨機數落在的區域對應的個體進行選取,相似賭場裏面的旋轉的輪盤。
每一個個體ai被選中的機率爲:
好了,下面能夠將此算法寫成一個能夠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複製代碼
過程主要分爲下面幾個:
GASelection
類select
方法select
的參數爲GAPopulation
實例和適應度函數因爲算法執行的效率以及易實現的的特色,錦標賽選擇算法是遺傳算法中最流行的選擇策略。在本人的實際應用中的確此策略比基本的輪盤賭效果要好些。他的策略也很直觀,就是咱們再整個種羣中抽取n個個體,讓他們進行競爭(錦標賽),抽取其中的最優的個體。參加錦標賽的個體個數成爲tournament size。一般當n=2即是最常使用的大小,也稱做Binary Tournament Selection.
Tournament Selection的優點:
下圖顯示了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複製代碼
下面兩個介紹的選擇策略都是基於排序的選擇策略,上面提到的第一種基本輪盤賭選擇算法,有一個缺點,就是若是一個個體的適應度值爲0的話,則被選中的機率將會是0, 這個個體將不能產生後代。因而咱們須要一種基於排序的算法,來給每一個個體安排相應的選中機率。
在Linear Ranking Selection中,種羣中的個體首先根據適應度的值進行排序,而後給全部個體賦予一個序號,最好的個體爲N, 被選中的機率爲Pmax, 最差的個體序號爲1, 被選中的機率爲Pmin,因而其餘的在他們中間的個體的機率即可以根據以下公式獲得:
實現代碼:
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複製代碼
相似上面的Linear Ranking選擇策略,這種指數排序即是在肯定每一個個體的選擇機率的時候使用了指數形式的表達式, 其中c爲底數,知足0<c<1:
實現代碼:
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的童鞋能夠直接拿來使用。