元啟發式演算法庫 MEALPY 初體驗-遺傳演算法為例

songjiefa發表於2024-04-11

簡介

官網: MealPY官網

開源許可: (GPL) V3

MEALPY簡介

官網簡介翻譯

MEALPY (MEta-heuristic ALgorithms in PYthon) 是一個提供最新自然啟發式元啟發演算法的Python模組,它是最大的此類Python模組之一。這些演算法模仿自然界中的成功過程,包括生物系統以及物理和化學過程。mealPy 的目標是免費向所有人分享元啟發領域的知識,幫助各領域的研究者儘快接觸到最佳化演算法,並且實現從經典到最先進的元啟發演算法,涵蓋了元啟發演算法的全部歷史。

MEALPY 的用途廣泛,你可以使用它來分析演算法引數、進行演算法的定性和定量分析、分析演算法的收斂速率、測試和分析演算法的可擴充套件性和健壯性。該庫目前的版本為 3.0.1,共包含 215 種演算法,其中包括 190 種官方演算法(原始演算法、混合演算法、變體演算法)和 25 種開發演算法。

MEALPY 的特點在於支援解決連續和離散問題,並且在新版本中,所有功能都被封裝在類和物件之中,使得定義模型一次後,就可以解決多個問題。

一句話總結

MEALPY 是一個強大的元啟發式演算法Python庫,它適合用於研究和解決各種最佳化問題,特別是對於那些複雜的NP難題

安裝

安裝軟體和庫

  • python 3.7或以上
  • vscode 或 pycharm (我自己用vscode)
  • vscode 外掛:Python Extension Pack(pycharm 忽略)
  • python 庫: numpy, scipy, pandas, matplotlib, mealpy( 其他遇到缺失報錯的再安裝也不遲)

python庫直接在外掛裡搜就是了
image

基本概念

二話不說,先上一個簡單程式碼:

from mealpy import FloatVar, GA

def objective_func(solution):
    return solution**2

problem_dict = {
    "obj_func": objective_func,
    "bounds": FloatVar(lb=[-10000], ub=[10000]),
    "minmax": "min",
}

optimizer = GA.BaseGA(epoch=100, pop_size=50, pc=0.85, pm=0.1)
optimizer.solve(problem_dict)

print(optimizer.g_best.solution) 
print(optimizer.g_best.target.fitness) 

程式碼解釋

匯入

from mealpy import FloatVar, GA
  • 匯入了 mealpy庫裡面的 FloatVar類,說明輸入輸出的是 float 型別的變數
  • 匯入了 mealpy庫裡面的 GA(Genetic Algorithm)遺傳演算法(下面解釋)

定義目標函式(objective_func)

def objective_func(solution):
    return solution**2

obj_func 是一個在最佳化問題中用來評估解決方案好壞的目標函式(objective function)。在元啟發式演算法中,obj_func 通常是一個接收單個解決方案(一個代表可能解的numpy向量)作為輸入,並返回一個單一的實數值(在單目標最佳化問題中)或一個實數值列表(在多目標最佳化問題中)的函式。
上面objective_func 目的是找到 \(f(x)=x^2\),的極值(在問題字典裡定義求最大還是最小值)
那麼有人就會問, \(f(x)=x^2\)的極值不是 x=0的時候,極值也是0嗎?
image

我想說,是的,你說的對,這不是為了舉簡單例子嘛(比官網例子還簡單一點),如果我拿出下面公式,閣下如何應對?
\(f(x)=\sqrt[3]{x^3-x^2-x+1+y^3-xy^2}\)
所以現在我們假裝不知道\(y=x^2\)的極值公式和影像,求一下x等於多少的時候,y的最小值。

建立問題字典(problem_dict)

problem_dict = {
    "obj_func": objective_func,
    "bounds": FloatVar(lb=[-10000], ub=[10000]),
    "minmax": "min",
}

在mealpy庫中,problem_dict 是一個字典,它定義了最佳化問題的所有必要資訊。這個字典通常包含下列關鍵字:

  • obj_func:這是目標函式(objective function),它接收一個候選解(通常是一個numpy陣列)並返回一個評估值(對於最小化問題來說越小越好,對於最大化問題來說越大越好)。上面已經定義好了objective_func。
  • bounds:這是變數的邊界,它定義瞭解空間的上下界。在mealpy中,你可以使用不同的型別來定義邊界,如FloatVar, IntVar, BoolVar等,以適應不同型別的最佳化問題。假設我們不知道\(f(x)=x^2\)的影像,但看樣子,x可以取值\((-\infty,\infty)\),我們程式不可能取\((-\infty,\infty)\),不做特殊處理肯定會溢位,我現在就小小試一下,設\(x \in [-10000,10000]\), 然後x是實數,所以bounds屬於FloatVar型別,lb,ub表示上下界,是個陣列,但因為我們現在的例子只有一個輸入,所以[]裡面只有一個邊界。
  • minmax:這是一個字串,它指示最佳化問題是最小化問題("min")還是最大化問題("max")。我們這裡是例子求\(f(x)\)最小值, 所以設定為min

初始化最佳化程式並執行 (以遺傳演算法為例)

optimizer = GA.BaseGA(epoch=100, pop_size=50, pc=0.85, pm=0.1)
optimizer.solve(problem_dict)

這段程式碼做了以下兩件主要事情:

  • 初始化遺傳演算法最佳化器。
  • 使用該最佳化器解決定義好的最佳化問題。

讓我們解釋這段程式碼之前,先說一下什麼是遺傳演算法

遺傳演算法(覺得太長可以跳過不看)

遺傳演算法(Genetic Algorithm,簡稱GA)是計算數學中用於解決最佳化和搜尋問題的一種啟發式演算法。它是由約翰·霍蘭德(John Holland)在20世紀70年代初期發展起來的,其設計靈感來源於生物進化中的自然選擇和遺傳學原理。

遺傳演算法的基本概念包括個體、種群、適應度、選擇、交叉(或稱為雜交,英文為crossover)、變異等,以下是這些概念的詳細介紹:

個體(Individual):

在遺傳演算法中,每一個可能的解都稱為一個“個體”,通常表示為一串“基因”,這可以是二進位制值、實數或其他編碼形式。
我們上面例子裡,因為輸入只有一個,那麼個體就是那個公式裡的\(x\)浮點本身,或者是程式碼裡目標方法(objective_func)的 solution引數本身。

種群(Population):

種群是一組個體的集合,演算法從這個種群中選擇個體來進行遺傳操作併產生新一代種群。
我們上面例子裡,pop_size=50 就是一開始有 50個x(一般初始化,都是隨機的,[-10000,10000]範圍內隨機生成50個x)

適應度(Fitness):

適應度是評價個體優劣的標準,它是一個函式,用於衡量個體解決問題的能力。個體的適應度越高,它被選中並遺傳到下一代的機會就越大。
因為我們設定了求最小值"minmax": "min",,可以認為Fitness等價於例子裡的 objective_func。

選擇(Selection):

選擇是根據個體的適應度從當前種群中選出一部分個體,用於生成下一代。常見的選擇方法有輪盤賭選擇、錦標賽選擇, 隨機選擇等。
BaseGA 類透過 selection_process__ 方法實現了幾種不同的選擇策略:

  • 輪盤賭選擇(Roulette Wheel Selection):使用 get_index_roulette_wheel_selection 方法,根據適應度比例選擇個體。
  • 隨機選擇(Random Selection):隨機選擇兩個不同的個體作為父母。
  • 錦標賽選擇(Tournament Selection):使用 get_index_kway_tournament_selection 方法,隨機選擇一組個體,然後從中挑選出最好的個體。k_way 引數決定了錦標賽的大小。
    BaseGA 類的初始化方法 init 允許使用者設定選擇策略(selection 引數),預設是錦標賽選擇
class BaseGA(Optimizer):
	def __init__(self, epoch: int = 10000, pop_size: int = 100, pc: float = 0.95, pm: float = 0.025, **kwargs: object) -> None:
	# ... 初始化程式碼
	self.selection = "tournament"
	if "selection" in kwargs:
			self.selection = self.validator.check_str("selection", kwargs["selection"], ["tournament", "random", "roulette"])
	# ... 初始化程式碼

選擇策略,如果不指定 selection 引數,則會使用預設的錦標賽選擇。如果你想使用輪盤賭選擇或隨機選擇,需要在建立 BaseGA 例項時透過 selection 引數明確指定。

例子使用方式,例如:

model = BaseGA(epoch=100, pop_size=50, pc=0.85, pm=0.1, selection="roulette")
交叉(Crossover):

交叉是遺傳演算法中產生新個體的主要方式。它模擬生物學中的雜交現象,透過組合兩個“父母”個體的部分基因來產生“子代”個體。
例子中 pc=0.85 表示交叉機率是 85%,意味著每一代中有 85% 的個體將透過交叉來產生新的後代。
在BaseGA類中,交叉(Crossover)是透過crossover_process__方法實現的。該方法定義了幾種不同的交叉策略:

  • 算術交叉(Arithmetic Crossover):透過算術運算結合兩個父代的基因來產生子代。在這個實現中,可能是按照一定權重將兩個父代的基因線性組合。

  • 單點交叉(One-Point Crossover):選擇一個隨機的位置(切點),然後交換兩個父代在這個切點位置以前和以後的基因段,從而產生兩個子代。

  • 多點交叉(Multi-Point Crossover):選擇多個切點,並在這些切點位置將父代的基因段交叉組合,產生子代。

  • 均勻交叉(Uniform Crossover):對於每個基因位點,隨機決定該位置的基因是來自父代1還是父代2。這個決策是獨立於其他基因位點的。

    以下是crossover_process__方法的程式碼實現:

def crossover_process__(self, dad, mom):
	if self.crossover == "arithmetic":
		w1, w2 = self.crossover_arithmetic(dad, mom)
	elif self.crossover == "one_point":
		cut = self.generator.integers(1, self.problem.n_dims-1)
		w1 = np.concatenate([dad[:cut], mom[cut:]])
		w2 = np.concatenate([mom[:cut], dad[cut:]])
	elif self.crossover == "multi_points":
		idxs = self.generator.choice(range(1, self.problem.n_dims-1), 2, replace=False)
		cut1, cut2 = np.min(idxs), np.max(idxs)
		w1 = np.concatenate([dad[:cut1], mom[cut1:cut2], dad[cut2:]])
		w2 = np.concatenate([mom[:cut1], dad[cut1:cut2], mom[cut2:]])
	else:           # uniform
		flip = self.generator.integers(0, 2, self.problem.n_dims)
		w1 = dad * flip + mom * (1 - flip)
		w2 = mom * flip + dad * (1 - flip)
	return w1, w2

在這個方法中,dad和mom參數列示兩個父代的基因串。根據self.crossover的值,它決定使用哪種交叉策略。每種策略都會計算並返回兩個子代w1和w2的基因串。

注意,實際的交叉操作是否發生是由交叉機率self.pc控制的,這個機率決定了在種群中有多少比例的個體會經歷交叉過程。如果隨機數小於self.pc,那麼呼叫crossover_process__方法進行交叉;否則,子代直接繼承父代的基因。

簡單來說,BaseGA類中的交叉是透過拼接兩個父代的某些部分來產生子代,具體的拼接方式取決於所選的交叉策略。
BaseGA預設使用均勻交叉uniform,對於浮點數的均勻交叉(Uniform Crossover),方法與處理二進位制串或整數串的方式類似,但是要考慮到浮點數的連續性。在均勻交叉中,每個基因位點都有一個獨立的機率決定是從父代1繼承還是從父代2繼承。

對於浮點數列表的均勻交叉,可以按如下方式進行:

  1. 對於個體中的每一個浮點數位點(在這個案例中,由於只有一個浮點數,所以只有一個位點)生成一個0到1之間的隨機數。
  2. 如果這個隨機數小於預設的閾值(通常是0.5),則從父代1中選取該位點的值;否則,從父代2中選取該位點的值。

因為在上面例子中,每個個體只包含一個浮點數,所以均勻交叉將簡化為以下步驟:

  1. 生成一個0到1之間的隨機數。
  2. 如果隨機數小於0.5,子代將繼承父代1的值;如果隨機數大於或等於0.5,子代將繼承父代2的值。
變異(Mutation):

變異是在個體的基因序列中隨機改變某些基因的過程,這增加了種群的多樣性,有助於演算法跳出區域性最優解,探索更廣泛的搜尋空間。
在BaseGA類中,變異(Mutation)是透過mutation_process__方法實現的。該方法根據所選的變異策略來對子代進行變異操作。變異操作的目的是在遺傳演算法的演化過程中引入一些隨機性,以避免演算法過早收斂到區域性最優解,並增加搜尋全域性最優解的可能性。

BaseGA類提供了以下幾種變異策略:

  • 翻轉變異(Flip Mutation):隨機選擇基因串中的一個或多個位點,並將其值改變。對於浮點數,這通常意味著重新生成一個在給定範圍內的隨機值來替換當前的基因值。
  • 交換變異(Swap Mutation):隨機選擇基因串中的兩個位點,並交換它們的值。
  • 擾亂變異(Scramble Mutation):隨機選擇基因串中的一段序列,並將這段序列中的基因值進行隨機打亂。
  • 逆序變異(Inversion Mutation):隨機選擇基因串中的一段序列,並將這段序列中的基因值逆序排列。

在BaseGA類的mutation_process__方法中,根據變異機率self.pm來決定是否對子代進行變異操作(預設是翻轉變異Flip)。以下是mutation_process__方法的程式碼實現(部分):

def mutation_process__(self, child):
    if self.mutation_multipoints:
        if self.mutation == "swap":
            # ... Swap mutation logic ...
        else:  # "flip"
            mutation_child = self.problem.generate_solution()
            flag_child = self.generator.uniform(0, 1, self.problem.n_dims) < self.pm
            return np.where(flag_child, mutation_child, child)
    else:
        if self.mutation == "swap":
            # ... Swap mutation logic for single point ...
        elif self.mutation == "inversion":
            # ... Inversion mutation logic ...
        elif self.mutation == "scramble":
            # ... Scramble mutation logic ...
        else:  # "flip"
            idx = self.generator.integers(0, self.problem.n_dims)
            child[idx] = self.generator.uniform(self.problem.lb[idx], self.problem.ub[idx])
            return child

在上述程式碼中,self.problem.n_dims表示個體中基因的數量。self.problem.generate_solution()是生成一個新的隨機解,該解可能會用於替換當前子代的某些基因。self.generator.uniform(0, 1, self.problem.n_dims)生成一個0到1之間的隨機數陣列,與變異機率self.pm比較,確定哪些基因位點將發生變異。np.where函式根據這個條件選擇性地替換基因值。

對於例子裡單浮點數的個體,翻轉變異將簡化為在給定的值範圍內重新生成一個隨機浮點數。如果變異策略是swap、scramble或inversion,由於只有一個浮點數,這些策略將不適用或不產生任何效果。

回到例子

optimizer = GA.BaseGA(epoch=100, pop_size=50, pc=0.85, pm=0.1)

這行程式碼建立了一個遺傳演算法最佳化器的例項,並設定了幾個重要的引數:

  • epoch=100:這指定了演算法的迭代次數,即演算法將執行100代。
  • pop_size=50:這指定了種群的大小,即每一代中有50個候選解。
  • pc=0.85:這是交叉機率(crossover probability),指在每一代中,有多少比例的候選解將透過交叉(即基因的重組)來產生新的後代。在這裡,設定為85%。
  • pm=0.1:這是變異機率(mutation probability),指在每一代中,有多少比例的候選解將經歷變異(即基因的隨機改變)。在這裡,設定為10%。
    這些引數是遺傳演算法效能的關鍵,它們需要根據具體問題進行調整以達到最佳效果。
optimizer.solve(problem_dict)

這行程式碼呼叫最佳化器的 solve 方法,並傳遞之前定義的 problem_dict 作為引數。solve 方法將使用遺傳演算法來尋找最優解或者儘可能接近最優解的解決方案。

problem_dict 包含了目標函式 obj_func(用於評估解的質量)、解的邊界 bounds(定義瞭解的搜尋空間),以及最佳化目標 minmax(指明是最小化問題還是最大化問題)。

透過執行這兩行程式碼,遺傳演算法將執行指定的迭代次數(在本例中為100代),並在每一代中使用遺傳操作(選擇、交叉、變異)來改進解,最終返回找到的最佳解。這個最佳解將是根據目標函式評估得到的最優質的解,同時受到解空間邊界的約束。

列印結果

print(optimizer.g_best.solution)
print(optimizer.g_best.target.fitness)

最後,最佳化演算法運算結束,列印最佳方案即\(x\)和最佳適應度,本例子等價於\(f(x)\)最小值。
結果如下:
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: Solving single objective optimization problem.
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 1, Current best: 509877.0525830909, Global best: 509877.0525830909, Runtime: 0.00927 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 2, Current best: 432309.9094269061, Global best: 432309.9094269061, Runtime: 0.00697 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 3, Current best: 432309.9094269061, Global best: 432309.9094269061, Runtime: 0.00829 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 4, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00681 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 5, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00785 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 6, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00749 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 7, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00863 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 8, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00734 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 9, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00702 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 10, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00653 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 11, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00696 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 12, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00776 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 13, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00768 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 14, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00854 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 15, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00748 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 16, Current best: 48716.68478834449, Global best: 48716.68478834449, Runtime: 0.00735 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 17, Current best: 17626.98464372988, Global best: 17626.98464372988, Runtime: 0.00716 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 18, Current best: 17626.98464372988, Global best: 17626.98464372988, Runtime: 0.00768 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 19, Current best: 17626.98464372988, Global best: 17626.98464372988, Runtime: 0.00796 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 20, Current best: 17626.98464372988, Global best: 17626.98464372988, Runtime: 0.00805 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 21, Current best: 17626.98464372988, Global best: 17626.98464372988, Runtime: 0.00838 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 22, Current best: 17626.98464372988, Global best: 17626.98464372988, Runtime: 0.00754 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 23, Current best: 17626.98464372988, Global best: 17626.98464372988, Runtime: 0.00818 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 24, Current best: 17626.98464372988, Global best: 17626.98464372988, Runtime: 0.00731 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 25, Current best: 17626.98464372988, Global best: 17626.98464372988, Runtime: 0.00890 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 26, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00742 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 27, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00778 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 28, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00720 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 29, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00776 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 30, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00761 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 31, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00767 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 32, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00734 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 33, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00767 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 34, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00835 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 35, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00910 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 36, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00857 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 37, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00941 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 38, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00676 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 39, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00808 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 40, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00701 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 41, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00844 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 42, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00701 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 43, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00953 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 44, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00694 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 45, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00839 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 46, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00770 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 47, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00756 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 48, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00758 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 49, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00846 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 50, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00739 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 51, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00702 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 52, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00773 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 53, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00734 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 54, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00775 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 55, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00721 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 56, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00710 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 57, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00803 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 58, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00802 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 59, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00802 seconds
2024/04/11 11:29:05 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 60, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00728 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 61, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00769 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 62, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00647 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 63, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00755 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 64, Current best: 5265.4477712475245, Global best: 5265.4477712475245, Runtime: 0.00766 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 65, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00891 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 66, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00743 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 67, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00778 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 68, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00698 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 69, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00776 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 70, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00697 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 71, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00864 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 72, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00715 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 73, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00817 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 74, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00633 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 75, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00836 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 76, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00716 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 77, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00754 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 78, Current best: 2293.6227363517078, Global best: 2293.6227363517078, Runtime: 0.00699 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 79, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00819 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 80, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00794 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 81, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00770 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 82, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00727 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 83, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00771 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 84, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00749 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 85, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00729 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 86, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00784 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 87, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00805 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 88, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00724 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 89, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00733 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 90, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00749 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 91, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00745 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 92, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00724 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 93, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00797 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 94, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00968 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 95, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.01172 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 96, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.01594 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 97, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.01259 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 98, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00722 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 99, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00835 seconds
2024/04/11 11:29:06 AM, INFO, mealpy.evolutionary_based.GA.BaseGA: >>>Problem: P, Epoch: 100, Current best: 1232.002800987481, Global best: 1232.002800987481, Runtime: 0.00735 seconds
[35.09989745]
1232.002800987481

結果分析

就看結果的最後兩行,不要看中間過程,中間過程可以拿來分析,如果不想看到中間過程,可以在問題字典裡新增"log_to": None, 鍵值對。

 # 定義問題字典
    problem_dict = {
        "obj_func": objective_func,
        "bounds": FloatVar(lb=[-10000], ub=[10000]),
        "minmax": "min",
        "log_to": None,
    }

迭代100代,從-10000到10000 實數里,最終結果算出來,當\(x=35.09989745\)時,\(f(x)\)最小,且等於1232.002800987481
然後,第一次接觸遺傳演算法的,不禁會發出大大的疑問:搞了半天,就這????!!!!我一秒鐘都得出是\(x=0\)了。

我知道你困了累了,但請別困,別累。
從結果來看,那說明咱們上面說的原理沒錯,確實是一步一步的遺傳變異,得到的結果。至於結果不如意,那肯定是需要繼續調優的。不然那些調包俠,調參俠的稱呼怎麼來的?

引數調優分析

GA.BaseGA 它在尋找全域性最優解的過程中可能會找到區域性最優解或者近似解。遺傳演算法的效能(即能否找到全域性最優解以及找到解的速度)取決於多個因素,包括:

  • 目標函式的複雜性和多峰性:如果目標函式有很多區域性最優解,演算法可能陷入其中之一而不是全域性最優解。
  • 種群大小(pop_size):較大的種群可以提供更多的多樣性,有助於探索解空間但同時也會增加計算成本。
  • 交叉機率(pc)和變異機率(pm):這些引數控制演算法的探索和開發的平衡,需要根據具體問題進行調整。
  • 選擇、交叉和變異操作的策略:這些操作如何實施也會影響演算法的表現。
  • 迭代次數(epoch):迭代次數越多,演算法有更多的機會改進當前解,但同時也意味著更高的計算成本。

例子中目標函式是一個簡單的單變數平方函式,它的全域性最小值在 x = 0 處(假裝不知道)。遺傳演算法應該能夠找到這個全域性最小值,或者至少是一個非常接近的近似值。結果不盡如人意,可能有幾個原因:

  • 隨機性:遺傳演算法包含隨機性,每次執行可能得到不同的結果。可能需要多次執行來獲得更好的統計結果。
  • 引數設定:pop_size、pc 和 pm 這些引數可能需要調整以適應特定問題。
  • 初始化:初始種群的生成可能沒有很好地覆蓋解空間。
  • 早熟收斂:演算法可能過早地收斂到了一個區域性最優解而非全域性最優解。
  • 迭代次數(epoch)不足:可能需要更多的迭代次數來允許演算法有足夠的時間來最佳化解。

要改進結果,可以嘗試以下方法:

  • 增加迭代次數:增加epoch的值,允許演算法有更多的時間來改進解。
  • 調整初始種群的生成。
  • 調整種群大小:增加pop_size的值,提供更多的初始解和多樣性。
  • 調整交叉和變異機率:調整pc和pm的值,嘗試找到更好的探索和開發之間的平衡。
  • 多次執行:由於遺傳演算法的隨機性質,多次執行並取最佳結果可能有助於獲得更好的解。

最佳化

根據上面的分析,我們可以挑一兩個引數試試

最佳化1: 大力出奇跡,增加迭代次數

程式碼其他不變,代數從100 代變成 10000代

optimizer = GA.BaseGA(epoch=10000, pop_size=50, pc=0.85, pm=0.1)

結果:
[-0.20769044]
0.04313531784135752

看來結果是-0.2,還是不太能接受,繼續努力最佳化。

最佳化2 : 啟用先知預測功能,邊界值調小

從結果來看,\(x\)越來越小,那麼,\(x\)邊界值就可以不用從[-10000,10000]這麼大範圍篩選了,直接[-10,10]

problem_dict = {
    "obj_func": objective_func,
    "bounds": FloatVar(lb=[-10], ub=[10]),
    "minmax": "min", }
optimizer = GA.BaseGA(epoch=10000, pop_size=50, pc=0.85, pm=0.1)

結果:
[7.35480744e-05]
5.409319244888902e-09

$x= 7.35480744 \times 10^{-5}, f(x)= 5.409319244888902 \times 10^{-9} $
第二次結果看起來非常不錯。運氣不錯哈。(不要臉!)

總結

這只是我對mealpy庫的入門嘗試,還有很多高階特性沒用上,比如多執行緒,微調,畫圖等等,最佳化演算法也只是研究了BaseGA,其他演算法估計也有其獨到之處。
舉的例子也是非常沒有實際應用的可能性。
在這些探索留到日後有空再慢慢看看吧,這篇主要目的是讓大家知道有這麼一個庫。
我也是零基礎入門中,文章中可能有些錯誤,歡迎指正。

相關文章