Python進化算法之多目標優化與代碼實戰

 

目錄

前言

正文

1 多目標優化概念

2 多目標進化優化算法

3 代碼實戰

3.1 用NSGA2 優化 ZDT1

3.2 用NSGA3 優化 DTLZ1

3.3 用MOEA/D 優化 ZDT1

3.3 更多的比較

總結


前言

自從上三篇博客詳細講解了Python遺傳和進化算法工具箱及其在帶約束的單目標函數值優化中的應用、利用遺傳算法求解有向圖的最短路徑、利用進化算法優化SVM參數之後,這篇不再侷限於單一的進化算法工具箱的講解,相反,這次來個橫向對比,比較目前最流行的幾個python進化算法工具箱/框架在求解多目標問題上的表現。

正文

1 多目標優化概念

首先大致講一下多目標優化:

在生活中的優化問題,往往不只有一個優化目標,並且往往無法同時滿足所有的目標都最優。例如工人的工資與企業的利潤。那麼多目標優化裏面什麼解纔算是優秀的?我們一般採用“帕累托最優”來衡量解是否優秀,其定義我這裏摘錄百度百科的一段話:

帕累托最優(Pareto Optimality),是指資源分配的一種理想狀態。假定固有的一羣人和可分配的資源,從一種分配狀態到另一種狀態的變化中,在沒有使任何人境況變壞的前提下,使得至少一個人變得更好。帕累托最優狀態就是不可能再有更多的帕累託改進的餘地;換句話說,帕累託改進是達到帕累托最優的路徑和方法。 帕累托最優是公平與效率的“理想王國”。是由帕累託提出的。

這段話好像讓人看着依舊有點懵逼,下面直接摘錄一段學術性的定義:

【摘自:http://www.geatpy.com

因此,只要找到一組解,其對應的待優化目標函數值的點均落在上面的黑色加粗線上,那麼就是我們想要的“帕累托最優解”了。此外,假如帕累托最優解個數不可數,那麼我們只需找到上面黑色加粗線上的若干個點即可,並且這些點越分散、分佈得越均勻,說明算法的效果越好。

2 多目標進化優化算法

多目標進化優化算法即利用進化算法結合多目標優化策略來求解多目標優化問題。經典而久經不衰的多目標優化算法有:NSGA2、NSGA3、MOEA/D等。其中NSGA2和NSGA3是基於支配的MOEA(Multi-objective evolutionary algorithm),而MOEA/D是基於分解的MOEA。

前兩者(NSGA2、NSGA3)通過非支配排序(後面馬上講到)來篩選出一堆解中的“非支配解”,並且通過種羣的不斷進化,使得種羣個體對應的解對應的目標函數值的點不斷逼近上圖的黑色加粗線。具體算法就不作詳細闡述了,可詳見以下參考文獻,或看下方的代碼實戰部分:

Deb K , Pratap A , Agarwal S , et al. A fast and elitist multiobjective genetic algorithm: NSGA-II[J]. IEEE Transactions on Evolutionary Computation, 2002, 6(2):0-197.

Deb K , Jain H . An Evolutionary Many-Objective Optimization Algorithm Using Reference-Point-Based Nondominated Sorting Approach, Part I:  Solving Problems With Box Constraints[J]. IEEE Transactions on  Evolutionary Computation, 2014, 18(4):577-601.

後者(MOEA/D)通過線性或非線性的方式將原多目標問題中各個目標進行聚合,即將多個目標聚合成一個目標,然後利用種羣進化不斷逼近全局帕累托最優解。這裏可能有人會有疑問:“爲什麼MOEA/D是基於分解的MOEA,但過程中需要對各個目標進行聚合?那不就不叫分解了嗎?”答案很簡單:分解是指將多目標優化問題分解爲一組單目標子問題或多個多目標子問題,利用子問題之間的鄰域關係,通過協作來同時優化所有的子問題,從而不斷逼近全局帕累托最優解;而聚合是指將多個目標聚合成一個目標,因此MOEA/D裏面有“分解”和“聚合”兩個步驟,分解是確定鄰域關係,聚合是用來方便比較解的優劣,兩者並不是矛盾的。具體算法就不作詳細闡述了,可詳見以下參考文獻,或看下方的代碼實戰部分:

Qingfu Zhang, Hui Li. MOEA/D: A Multiobjective Evolutionary Algorithm Based on Decomposition[M]. IEEE Press, 2007.

那麼存在只利用聚合而沒有分解這一步來進化優化的算法嗎?答案是存在的,比如多目標優化自適應權重法(awGA),代碼詳見鏈接:

https://github.com/geatpy-dev/geatpy/blob/master/geatpy/templates/moeas/awGA/moea_awGA_templet.py

這個算法就是通過自適應地生成一個權重向量,來將所有的優化目標聚合成單一的優化目標,然後進行進化優化,當然這樣效果自然比不上MOEA/D。

有些單目標優化學得比較溜的讀者可能會疑問:”我找一組固定的權重,把各個優化目標加權聚合成一個目標,再用單目標優化的方法進行優化不就完事了嗎?“答案非常簡單:如果有理論能證明所找的這組權重是最合理、最適合當前的優化模型的,那麼用單目標優化的方法來解決多目標優化問題當然是好事;相反,假如沒有依據地隨便設置一組權重,那麼肯定不能用這種方法。

3 代碼實戰

下面以一個雙目標優化測試函數ZDT1和一個三目標優化測試函數DTLZ1爲例,橫向對比deap、pymoo和geatpy三款進化算法代碼包的NSGA2、NSGA3和MOEA/D算法的表現,版本分別爲1.3、0.4.0、2.5.0,測試代碼均爲三款代碼包官網給出的案例(在代碼組織結構上稍作修改以方便本文顯示)。

【注】:由於在計算ZDT1和DTLZ1時deap默認採用的是循環來計算種羣中每個個體的目標函數值,而pymoo和geatpy均爲利用numpy來矩陣化計算的,爲了統一個體評價時間,避免前者帶來的性能下降,這裏將deap也改用與後兩者相同的方法。

實驗設備:cpu i5 9600k,16g ddr4內存,windows10,Python 3.7 x64。

相關庫的安裝:

pip install deap
pip install pymoo
pip install geatpy

ZDT1的模型爲:

DTLZ1的模型爲:

其中M爲待優化的目標個數,y爲決策變量。

3.1 用NSGA2 優化 ZDT1

下面用NSGA2算法來優化上面的ZDT1,實驗參數爲:【種羣個體數40,進化代數200,其他相關參數均設爲一樣】,代碼1、代碼2、代碼3分別爲用deap、pymoo和geatpy的nsga2來優化ZDT1:

代碼 1. deap_nsga2.py

import array
import random
from deap import base
from deap import creator
from deap import tools
import numpy as np
import matplotlib.pyplot as plt
import time

def ZDT1(Vars, NDIM): # 目標函數
    ObjV1 = Vars[:, 0]
    gx = 1 + 9 * np.sum(Vars[:, 1:], 1) / (NDIM - 1)
    hx = 1 - np.sqrt(np.abs(ObjV1) / gx) # 取絕對值是爲了避免浮點數精度異常帶來的影響
    ObjV2 = gx * hx
    return np.array([ObjV1, ObjV2]).T

creator.create("FitnessMin", base.Fitness, weights=(-1.0, -1.0))
creator.create("Individual", array.array, typecode='d', fitness=creator.FitnessMin)
toolbox = base.Toolbox()
# Problem definition
BOUND_LOW, BOUND_UP = 0.0, 1.0
NDIM = 30
def uniform(low, up, size=None):
    try:
        return [random.uniform(a, b) for a, b in zip(low, up)]
    except TypeError:
        return [random.uniform(a, b) for a, b in zip([low] * size, [up] * size)]

toolbox.register("attr_float", uniform, BOUND_LOW, BOUND_UP, NDIM)
toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.attr_float)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("mate", tools.cxSimulatedBinaryBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0)
toolbox.register("mutate", tools.mutPolynomialBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0, indpb=1.0/NDIM)
toolbox.register("select", tools.selNSGA2)

def main(seed=None):
    random.seed(seed)
    NGEN = 300
    MU = 40
    CXPB = 1.0
    pop = toolbox.population(n=MU)
    # Evaluate the individuals with an invalid fitness
    invalid_ind = [ind for ind in pop if not ind.fitness.valid]
    ObjV = ZDT1(np.array(invalid_ind), NDIM)
    for ind, i in zip(invalid_ind, range(MU)):
        ind.fitness.values = ObjV[i, :]
    # This is just to assign the crowding distance to the individuals
    # no actual selection is done
    pop = toolbox.select(pop, len(pop))
    # Begin the generational process
    for gen in range(1, NGEN):
        # Vary the population
        offspring = tools.selTournamentDCD(pop, len(pop))
        offspring = [toolbox.clone(ind) for ind in offspring]      
        for ind1, ind2 in zip(offspring[::2], offspring[1::2]):
            if random.random() <= CXPB:
                toolbox.mate(ind1, ind2)
            toolbox.mutate(ind1)
            toolbox.mutate(ind2)
            del ind1.fitness.values, ind2.fitness.values
        # Evaluate the individuals with an invalid fitness
        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        ObjV = ZDT1(np.array([ind for ind in offspring if not ind.fitness.valid]), NDIM)
        for ind, i in zip(invalid_ind, range(MU)):
            ind.fitness.values = ObjV[i, :]
        # Select the next generation population
        pop = toolbox.select(pop + offspring, MU)
    return pop
if __name__ == "__main__":
    start = time.time()
    pop = main()
    end = time.time()
    p = np.array([ind.fitness.values for ind in pop])
    plt.scatter(p[:, 0], p[:, 1], marker="o", s=10)
    plt.grid(True)
    plt.show()
    print('耗時:', end - start, '秒')

上述代碼1利用deap提供的進化算法框架來實現NSGA2算法。deap的主要特點是輕量化和支持擴展。整個deap內部的代碼量很少,可以通過”函數註冊“來擴展模塊,但由此帶來的結果便是需要自己寫大量的代碼。比如要在deap上寫一個基因表達式編程(GEP),以geppy這個GEP框架爲例,它除了使用到了deap中的”函數註冊“功能(方便模塊調用)外,幾乎等於重寫了一遍deap。

代碼2:pymoo_nsga2.py

from pymoo.algorithms.nsga2 import NSGA2
from pymoo.factory import get_problem
from pymoo.optimize import minimize
import matplotlib.pyplot as plt
import time
problem = get_problem("ZDT1")
algorithm = NSGA2(pop_size=40, elimate_duplicates=False)
start = time.time()
res = minimize(problem,
               algorithm,
               ('n_gen', 300),
               verbose=False)
end = time.time()
plt.scatter(res.F[:, 0], res.F[:, 1], marker="o", s=10)
plt.grid(True)
plt.show()
print('耗時:', end - start, '秒')

上述代碼2調用了pymoo內置的NSGA2算法模塊,不過該模塊與pymoo框架深度耦合,無法直觀地看到NSGA2的執行過程,非pymoo開發者想要在上面擴展自己設計的算法會遇到較大的困難。

代碼3:geatpy_nsga2.py

# -*- coding: utf-8 -*-
import numpy as np
import geatpy as ea

class ZDT1(ea.Problem): # 繼承Problem父類
    def __init__(self):
        name = 'ZDT1' # 初始化name(函數名稱,可以隨意設置)
        M = 2 # 初始化M(目標維數)
        maxormins = [1] * M # 初始化maxormins(目標最小最大化標記列表,1:最小化該目標;-1:最大化該目標)
        Dim = 30 # 初始化Dim(決策變量維數)
        varTypes = [0] * Dim # 初始化varTypes(決策變量的類型,0:實數;1:整數)
        lb = [0] * Dim # 決策變量下界
        ub = [1] * Dim # 決策變量上界
        lbin = [1] * Dim # 決策變量下邊界(0表示不包含該變量的下邊界,1表示包含)
        ubin = [1] * Dim # 決策變量上邊界(0表示不包含該變量的上邊界,1表示包含)
        # 調用父類構造方法完成實例化
        ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)
    def aimFunc(self, pop): # 目標函數
        Vars = pop.Phen # 得到決策變量矩陣
        ObjV1 = Vars[:, 0]
        gx = 1 + 9 * np.sum(Vars[:, 1:], 1) / (self.Dim - 1)
        hx = 1 - np.sqrt(np.abs(ObjV1) / gx) # 取絕對值是爲了避免浮點數精度異常帶來的影響
        ObjV2 = gx * hx
        pop.ObjV = np.array([ObjV1, ObjV2]).T # 把結果賦值給ObjV

if __name__ == "__main__":
    """================================實例化問題對象============================="""
    problem = ZDT1()          # 生成問題對象
    """==================================種羣設置================================"""
    Encoding = 'RI'           # 編碼方式
    NIND = 40                 # 種羣規模
    Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges, problem.borders) # 創建區域描述器
    population = ea.Population(Encoding, Field, NIND) # 實例化種羣對象(此時種羣還沒被初始化,僅僅是完成種羣對象的實例化)
    """================================算法參數設置==============================="""
    myAlgorithm = ea.moea_NSGA2_templet(problem, population) # 實例化一個算法模板對象
    myAlgorithm.MAXGEN = 300  # 最大進化代數
    myAlgorithm.drawing = 1   # 設置繪圖方式(0:不繪圖;1:繪製結果圖;2:繪製過程動畫)
    """===========================調用算法模板進行種羣進化===========================
    調用run執行算法模板,得到帕累托最優解集NDSet。NDSet是一個種羣類Population的對象。
    NDSet.ObjV爲最優解個體的目標函數值;NDSet.Phen爲對應的決策變量值。
    詳見Population.py中關於種羣類的定義。
    """
    NDSet = myAlgorithm.run() # 執行算法模板,得到非支配種羣
    print('用時:%f 秒'%(myAlgorithm.passTime))

上面代碼3調用了Geatpy內置的NSGA2算法模板,見鏈接:

https://github.com/geatpy-dev/geatpy/blob/master/geatpy/templates/moeas/nsga2/moea_NSGA2_templet.py

該算法模板代碼與Geatpy提供的進化算法框架的耦合度很小,可以清晰地看到NSGA2算法的執行過程。只需遵循Geatpy中設計的種羣數據結構(如染色體用什麼表示、目標函數值用什麼表示、約束用什麼表示),就能很容易在上面擴展自己設計的新進化算法。

此外由代碼3可以看到使用Geatpy時可以更加專注於待優化模型的建立,即在做進化算法的應用時,不用管進化算法的細節,而專注於把待優化模型寫成一個自定義問題類。

代碼1、2、3的運行結果如下圖:

3.2 用NSGA3 優化 DTLZ1

下面用NSGA3算法來優化上面的DTLZ1,【種羣個體數91,進化代數500,其他相關參數均設爲一樣】,代碼4、代碼5、代碼6分別爲用deap、pymoo和geatpy的nsga3來優化DTLZ1:

代碼4:deap_nsga3.py

from math import factorial
import random
import matplotlib.pyplot as plt
import numpy as np
from deap import algorithms
from deap import base
from deap import creator
from deap import tools
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d as Axes3d
import time

# Problem definition
NOBJ = 3
K = 5
NDIM = NOBJ + K - 1
P = 12
H = factorial(NOBJ + P - 1) / (factorial(P) * factorial(NOBJ - 1))
BOUND_LOW, BOUND_UP = 0.0, 1.0

def DTLZ1(Vars, NDIM): # 目標函數
    XM = Vars[:,(NOBJ-1):]
    g = 100 * (NDIM - NOBJ + 1 + np.sum(((XM - 0.5)**2 - np.cos(20 * np.pi * (XM - 0.5))), 1, keepdims = True))
    ones_metrix = np.ones((Vars.shape[0], 1))
    f = 0.5 * np.hstack([np.fliplr(np.cumprod(Vars[:,:NOBJ-1], 1)), ones_metrix]) * np.hstack([ones_metrix, 1 - Vars[:, range(NOBJ - 2, -1, -1)]]) * (1 + g)
    return f

##
# Algorithm parameters
MU = int(H)
NGEN = 500
CXPB = 1.0
MUTPB = 1.0/NDIM
##
# Create uniform reference point
ref_points = tools.uniform_reference_points(NOBJ, P)
# Create classes
creator.create("FitnessMin", base.Fitness, weights=(-1.0,) * NOBJ)
creator.create("Individual", list, fitness=creator.FitnessMin)
# Toolbox initialization
def uniform(low, up, size=None):
    try:
        return [random.uniform(a, b) for a, b in zip(low, up)]
    except TypeError:
        return [random.uniform(a, b) for a, b in zip([low] * size, [up] * size)]
toolbox = base.Toolbox()
toolbox.register("attr_float", uniform, BOUND_LOW, BOUND_UP, NDIM)
toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.attr_float)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("mate", tools.cxSimulatedBinaryBounded, low=BOUND_LOW, up=BOUND_UP, eta=30.0)
toolbox.register("mutate", tools.mutPolynomialBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0, indpb=1.0/NDIM)
toolbox.register("select", tools.selNSGA3, ref_points=ref_points)
##
def main(seed=None):
    random.seed(seed)
    # Initialize statistics object
    pop = toolbox.population(n=MU)
    # Evaluate the individuals with an invalid fitness
    invalid_ind = [ind for ind in pop if not ind.fitness.valid]
    ObjV = DTLZ1(np.array(invalid_ind), NDIM)
    for ind, i in zip(invalid_ind, range(MU)):
        ind.fitness.values = ObjV[i, :]
    # Begin the generational process
    for gen in range(1, NGEN):
        offspring = algorithms.varAnd(pop, toolbox, CXPB, MUTPB)
        # Evaluate the individuals with an invalid fitness
        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        ObjV = DTLZ1(np.array([ind for ind in offspring if not ind.fitness.valid]), NDIM)
        for ind, i in zip(invalid_ind, range(MU)):
            ind.fitness.values = ObjV[i, :]
        # Select the next generation population from parents and offspring
        pop = toolbox.select(pop + offspring, MU)
    return pop

if __name__ == "__main__":
    start = time.time()
    pop = main()
    end = time.time()
    fig = plt.figure()
    ax = fig.add_subplot(111, projection="3d")
    p = np.array([ind.fitness.values for ind in pop])
    ax.scatter(p[:, 0], p[:, 1], p[:, 2], marker="o", s=10)
    ax.view_init(elev=30, azim=45)
    plt.grid(True)
    plt.show()
    print('耗時:', end - start, '秒')

代碼5:pymoo_nsga3.py

from pymoo.algorithms.nsga3 import NSGA3
from pymoo.factory import get_problem, get_reference_directions
from pymoo.optimize import minimize
from pymoo.visualization.scatter import Scatter
import time

# create the reference directions to be used for the optimization
M = 3
ref_dirs = get_reference_directions("das-dennis", M, n_partitions=12)
N = ref_dirs.shape[0]
# create the algorithm object
algorithm = NSGA3(pop_size=N,
                  ref_dirs=ref_dirs)
start = time.time()
# execute the optimization
res = minimize(get_problem("dtlz1", n_obj = M),
               algorithm,
               termination=('n_gen', 500))
end = time.time()
Scatter().add(res.F).show()
print('耗時:', end - start, '秒')

代碼6:geatpy_nsga3.py

# -*- coding: utf-8 -*-
import numpy as np
import geatpy as ea

class DTLZ1(ea.Problem): # 繼承Problem父類
    def __init__(self, M):
        name = 'DTLZ1' # 初始化name(函數名稱,可以隨意設置)
        maxormins = [1] * M # 初始化maxormins(目標最小最大化標記列表,1:最小化該目標;-1:最大化該目標)
        Dim = M + 4 # 初始化Dim(決策變量維數)
        varTypes = np.array([0] * Dim) # 初始化varTypes(決策變量的類型,0:實數;1:整數)
        lb = [0] * Dim # 決策變量下界
        ub = [1] * Dim # 決策變量上界
        lbin = [1] * Dim # 決策變量下邊界(0表示不包含該變量的下邊界,1表示包含)
        ubin = [1] * Dim # 決策變量上邊界(0表示不包含該變量的上邊界,1表示包含)
        # 調用父類構造方法完成實例化
        ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)
    def aimFunc(self, pop): # 目標函數
        Vars = pop.Phen # 得到決策變量矩陣
        XM = Vars[:,(self.M-1):]
        g = 100 * (self.Dim - self.M + 1 + np.sum(((XM - 0.5)**2 - np.cos(20 * np.pi * (XM - 0.5))), 1, keepdims = True))
        ones_metrix = np.ones((Vars.shape[0], 1))
        f = 0.5 * np.hstack([np.fliplr(np.cumprod(Vars[:,:self.M-1], 1)), ones_metrix]) * np.hstack([ones_metrix, 1 - Vars[:, range(self.M - 2, -1, -1)]]) * (1 + g)
        pop.ObjV = f # 把求得的目標函數值賦值給種羣pop的ObjV

if __name__ == "__main__":
    """================================實例化問題對象============================="""
    problem = DTLZ1(3)        # 生成問題對象
    """==================================種羣設置================================"""
    Encoding = 'RI'           # 編碼方式
    NIND = 91                 # 種羣規模
    Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges, problem.borders) # 創建區域描述器
    population = ea.Population(Encoding, Field, NIND) # 實例化種羣對象(此時種羣還沒被初始化,僅僅是完成種羣對象的實例化)
    """================================算法參數設置==============================="""
    myAlgorithm = ea.moea_NSGA3_templet(problem, population) # 實例化一個算法模板對象
    myAlgorithm.MAXGEN = 500  # 最大進化代數
    myAlgorithm.drawing = 1   # 設置繪圖方式(0:不繪圖;1:繪製結果圖;2:繪製過程動畫)
    """===========================調用算法模板進行種羣進化===========================
    調用run執行算法模板,得到帕累托最優解集NDSet。NDSet是一個種羣類Population的對象。
    NDSet.ObjV爲最優解個體的目標函數值;NDSet.Phen爲對應的決策變量值。
    詳見Population.py中關於種羣類的定義。
    """
    NDSet = myAlgorithm.run() # 執行算法模板,得到非支配種羣
    # 輸出
    print('用時:%f 秒'%(myAlgorithm.passTime))
    

上面代碼6調用了Geatpy內置的NSGA3算法模板,見鏈接:

https://github.com/geatpy-dev/geatpy/blob/master/geatpy/templates/moeas/nsga3/moea_NSGA3_templet.py

代碼4、5、6的運行結果如下圖:

3.3 用MOEA/D 優化 ZDT1

下面用MOEA/D算法來優化上面的ZDT1,【種羣個體數40,進化代數300,其他相關參數均設爲一樣】,由於deap並無提供MOEA/D的代碼,因此這裏只比較pymoo和geatpy。

值得注意的是:理論上MOEA/D會比NSGA2快,但由於python語言的限制,MOEA/D的算法設計在python上的執行效率會大打折扣。這種情況同樣存在於Matlab中。如果是純C/C++或者Java等,MOEA/D的高效率才能夠真正展現出來。當然,假如在C/C++或Java中採用細粒度的並行來執行進化,那麼MOEA/D的執行效率是遠遠比不上NSGA2的,這是因爲MOEA/D的算法設計天然地不支持細粒度的並行。

下面的代碼7、代碼8分別爲用pymoo和geatpy的MOEA/D來優化ZDT1的代碼:

代碼7:pymoo_moead.py

from pymoo.algorithms.moead import MOEAD
from pymoo.factory import get_problem, get_reference_directions
from pymoo.optimize import minimize
import matplotlib.pyplot as plt
import time
problem = get_problem("ZDT1")
algorithm = MOEAD(
    get_reference_directions("das-dennis", 2, n_partitions=40),
    n_neighbors=40,
    decomposition="tchebi",
    prob_neighbor_mating=1
)
start = time.time()
res = minimize(problem, algorithm, termination=('n_gen', 300))
end = time.time()
plt.scatter(res.F[:, 0], res.F[:, 1], marker="o", s=10)
plt.grid(True)
plt.show()
print('耗時:', end - start, '秒')

代碼8:geatpy_moead.py

# -*- coding: utf-8 -*-
import numpy as np
import geatpy as ea

class ZDT1(ea.Problem): # 繼承Problem父類
    def __init__(self):
        name = 'ZDT1' # 初始化name(函數名稱,可以隨意設置)
        M = 2 # 初始化M(目標維數)
        maxormins = [1] * M # 初始化maxormins(目標最小最大化標記列表,1:最小化該目標;-1:最大化該目標)
        Dim = 30 # 初始化Dim(決策變量維數)
        varTypes = [0] * Dim # 初始化varTypes(決策變量的類型,0:實數;1:整數)
        lb = [0] * Dim # 決策變量下界
        ub = [1] * Dim # 決策變量上界
        lbin = [1] * Dim # 決策變量下邊界(0表示不包含該變量的下邊界,1表示包含)
        ubin = [1] * Dim # 決策變量上邊界(0表示不包含該變量的上邊界,1表示包含)
        # 調用父類構造方法完成實例化
        ea.Problem.__init__(self, name, M, maxormins, Dim, varTypes, lb, ub, lbin, ubin)
    def aimFunc(self, pop): # 目標函數
        Vars = pop.Phen # 得到決策變量矩陣
        ObjV1 = Vars[:, 0]
        gx = 1 + 9 * np.sum(Vars[:, 1:], 1) / (self.Dim - 1)
        hx = 1 - np.sqrt(np.abs(ObjV1) / gx) # 取絕對值是爲了避免浮點數精度異常帶來的影響
        ObjV2 = gx * hx
        pop.ObjV = np.array([ObjV1, ObjV2]).T # 把結果賦值給ObjV

if __name__ == "__main__":
    """================================實例化問題對象============================="""
    problem = ZDT1()          # 生成問題對象
    """==================================種羣設置================================"""
    Encoding = 'RI'           # 編碼方式
    NIND = 40                 # 種羣規模
    Field = ea.crtfld(Encoding, problem.varTypes, problem.ranges, problem.borders) # 創建區域描述器
    population = ea.Population(Encoding, Field, NIND) # 實例化種羣對象(此時種羣還沒被初始化,僅僅是完成種羣對象的實例化)
    """================================算法參數設置==============================="""
    myAlgorithm = ea.moea_MOEAD_templet(problem, population) # 實例化一個算法模板對象
    myAlgorithm.Ps = 1.0 # (Probability of Selection)表示進化時有多大的概率只從鄰域中選擇個體參與進化
    myAlgorithm.MAXGEN = 300  # 最大進化代數
    myAlgorithm.drawing = 1   # 設置繪圖方式(0:不繪圖;1:繪製結果圖;2:繪製過程動畫)
    """===========================調用算法模板進行種羣進化===========================
    調用run執行算法模板,得到帕累托最優解集NDSet。NDSet是一個種羣類Population的對象。
    NDSet.ObjV爲最優解個體的目標函數值;NDSet.Phen爲對應的決策變量值。
    詳見Population.py中關於種羣類的定義。
    """
    NDSet = myAlgorithm.run() # 執行算法模板,得到非支配種羣
    print('用時:%f 秒'%(myAlgorithm.passTime))

上面代碼8調用了Geatpy內置的MOEA/D算法模板,見鏈接:

https://github.com/geatpy-dev/geatpy/blob/master/geatpy/templates/moeas/moead/moea_MOEAD_templet.py

代碼7、8的運行結果如下:

這裏比較奇怪的是pymoo的基於切比雪夫法的MOEA/D經常會出現一個很差的解,如上面左圖的最左邊的點,應該是該版本(0.4.0)有BUG。

3.3 更多的比較

下面加大計算規模,比較deap、pymoo和geatpy在大規模種羣下的表現。

先將代碼1、2、3中種羣規模調整爲500,進化代數依舊是300,運行結果如下:

再將代碼2、3、4中種羣規模調整爲496,進化代數依舊是500,運行結果如下:

更多比較結果見下表,這裏增添三個經典的EA框架/平臺:Pygmo、JMetal和Platemo來共同參與對比,各實驗參數均保持一致。

 

 

Deap

Pymoo

Geatpy

Pygmo (C++)

JMetal (Java)

Platemo (Matlab)

ZDT1

NSGA2

1000個體300代

585.83秒

41.95秒

1.22秒

19.45秒

15.18秒

4.28秒

NSGA2

10000個體300代

>13小時

3587.65秒

20.70秒

2047.3秒

2738.85秒

86.16秒

3維DTLZ1

NSGA3

990個體500代

143.33秒

123.54秒

13.47秒

不支持

53.182秒

31.86秒

NSGA3

9870個體500代

15635.71秒

11542.05秒

1297.25秒

不支持

6316.710秒

2576.09秒

在上面的實驗中沒有計算多目標優化的各項評價指標,但從圖便可大致推斷效果的好壞。其中性能大幅領先於其他的是國產的Geatpy和Platemo,這是因爲Geatpy的種羣進化過程採用的是Geatpy團隊自主研發的超高性能矩陣庫進行計算(注:由於Geatpy尚未提供一個統一的標籤來設置是否採用內核並行,上面的實驗爲了圖方便均沒使用Geatpy的內核並行);而Platemo利用Matlab自帶的高性能矩陣庫MKL進行計算(在大規模下會智能地採用CPU並行計算)。

總結

本文比較了幾種經典的進化算法框架/工具箱/平臺在多目標優化上的表現。由於都具有權威性和各自的特色,這裏我不作好壞對比,即不評價孰優孰劣,也不強行說推薦一定要用哪個,一切以自己習慣的編程語言爲主。

不過如果讀者有將進化計算與機器學習、神經網絡、深度學習結合的需求,我在此建議使用Python的進化算法框架。用Python的話編程效率高,並且Python作爲一種膠水語言,可以很容易嵌套進各種實際應用項目中,同時由上面的實驗也可看出某些Python進化算法框架/工具箱也能達到乃至超越Java、C/C++的進化算法框架的執行速度(如Geatpy)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章