The Nature of Code閱讀心得與Python實作:9.5 Customizing Genetic...

更新於 發佈於 閱讀時間約 13 分鐘
這一節的標題是
9.5 Customizing Genetic Algorithms
因為方格子標題字數限制,所以沒完整顯現

GA的步驟很單純,所以在使用GA解決不同的問題時,基本上程式需要修改的部分很少,像挑選、繁殖等核心步驟,是完全不需要修改的。事實上,不管是要用GA解決什麼樣的問題,只把三個關鍵部分調整、修改一下就可以了。接下來就來看看,這三個關鍵部分,要怎麼因應問題來調整、修改。

Key 1: The Global Variables

族群規模及突變率是影響GA行為甚鉅的兩個參數。那這兩個參數要用多大的數值才能得到最好的效果呢?這問題沒有標準答案。不過,倒是有些基本的方向可供參考。

族群的規模大小,會對GA的行為有什麼影響呢?簡單來說,族群的規模越大,越容易在比較少的世代數量下演化出解答。既然如此,那是不是一律都用很大的族群就好了呢?恐怕不是這樣的!因為族群規模越大,每個世代所需處理的個體數量就會越多,處理所需的時間也會越長。使用比較小規模的族群,雖然需要演化的世代數量比較多,但因為每個世代所需處理的個體數量比較少,所以演化出解答的時間,並不見得會比大規模族群所需的時間來得長。因此,當想加快演化速度時,可別一味地加大族群規模,而應該試著調整另一個對GA效能也有顯著影響的參數:突變率。

突變率的範圍為0~1;突變率越大,代表演化的隨機性越高。突變率為0時,代表演化是確定性的;能不能演化出解答,完全靠運氣。以電腦猴打字的例子來說,如果一開始的時候所有正確的字元都存在於族群的元素中,那很快就能演化出正確的答案;如果不是這樣的話,那演化到地老天荒也演化不出解答來。至於突變率為1,則代表系統的演化完全是隨機的;雖然最終可以得到解答,但有可能所需的時間會讓人無法接受。所以,想減少演化的時間,也別一味地加大突變率,那有可能會因為引入過多的隨機性,反而使得演化時間變長。

總之,族群規模和突變率並非越大越好,當然也不是越小越好。這兩者應該互相搭配、調整,才能有效地提高GA的演化效能。

Key 2: The Fitness Function

適應度函數是否設計得當,是GA能否成功演化出解答的關鍵。道理很簡單,因為適應度函數的作用就在告訴GA,演化有多成功、離目標有多遠,這樣GA才會朝目標所在的方向演化,最後也才能到達目標所在的位置。

設計適應度函數時,最主要的目標,是要能用數值清楚地告訴GA演化有多成功。那要怎麼設計呢?這沒有標準答案;不過有些設計技巧倒是可以學一下,除了可以避免在設計時踩雷,設計出效果不好的適應度函數,另外也可以藉由各種設計技巧,提高適應度函數的效果。

在設計適應度函數時,應該要避免鑑別度過低的情形;這也就是說,好、壞之間的分數差距不要太小,免得看不出來好壞之間的差異。就以電腦猴打字的例子來說,如果目標是要演化出含有「to be or not to be」這個字串,因為共有18個字母,所以如果把適應度函數定為

fitness = 在正確位置上的正確字元數量 / 總字元數量

這時每相差一個正確字元,適應度函數的值就會有

1 / 18 = 0.0556

的差距。可是,如果目標是要演化出一個由1000個字元所構成的字串,那每相差一個正確字元,適應度函數的值就只會有

1 / 1000 = 0.001

的差距。同樣是只差了一個正確字元,可是適應度函數值的差距,確足足相差了將近56倍。所以,如果目標是要演化出一個由1000個字元所構成的字串,那使用這樣子的適應度函數,其實並不是那麼的恰當。

要避免上述的情況,方法很簡單,把適應度函數改成

fitness = 在正確位置上的正確字元數量

就可以了。這樣子不管目標是要演化出多長的字串,每相差一個正確字元,適應度函數值的差距就都是1。

如果這樣還不夠,那就把適應度函數定為

fitness = (在正確位置上的正確字元數量)2

這樣每相差一個正確字元,適應度函數值的差距會更大。例如,正確字元數量是5和6的兩個個體,雖然正確字元數量只相差1,但其適應度函數值分別是25和36,足足相差了11。

上述的適應度函數,它的值是以二次增長(quadratic growth)的方式增加,而原先的適應度函數,則是以線性增長(linear growth)的方式增加。另外還有一種增加速度更快的是指數增長(exponential growth)。例如

fitness = 2在正確位置上的正確字元數量

同樣正確字元數量是5和6的兩個個體,其適應度函數值分別是32和64,相差了32,比二次增長時的差距還要大很多。

Exercise 9.8

讓適應度函數呈二次增長。

修改DNA類別的calculate_fitness()方法:

def calculate_fitness(self, target):
score = 0
for i in range(self.length):
if self.genes[i] == target[i]:
score += 1

self.fitness = score*score

Population類別的target_score屬性也要跟著修改成

self.target_score = len(target)**2

適應度函數很難一體適用,通常都必須針對應用的場景或要解決的問題重新設計。例如,用於演化電腦猴打字的適應度函數,就不能用於讓老鼠演化出能找到食物的最好方式,因為去數有幾個正確字元的適應度函數,是不可能讓老鼠演化出找到食物的最好方式的;比較好的設計,應該是像這樣:

fitness = 老鼠和食物之間的距離

另一個應用的場景是電玩中NPC的控制。例如足球電玩中,假設進行的是PK戰,守門員是由玩家所控制,而進攻球員則是由電腦控制。每個進攻球員都具有許多用來決定要怎麼踢球的參數。假設我們把適應度函數設計成

fitness = 總進球數

這樣總進球數越高的球員,其參數會越有機會保留下來而在後續的比賽中發揮功效。這也就是說,隨著一場又一場的比賽,由電腦所控制的進攻球員,會越來越適應玩家的防守策略,並演化出更能進球的進攻策略;即便換了個玩家以完全不同的策略來防守,系統也會察覺適應度函數值下降,從而演化出新的進攻策略來讓適應度函數值上升。這種能讓系統自動調整以適應外在變化的能力,也就是演化式運算的威力之所在。

總之,適應度函數如果不能正確地評估出族群中個別元素的演化有多成功,那族群將不會朝我們所希望的方向進行演化;要針對不同的應用場景或要解決的問題來設計專屬的適應度函數,這樣才能確保演化能夠順利進行,並獲得我們想要的結果。

Key 3: The Genotype and Phenotype

最後一個關鍵部分是關於基因型與表現型的設計;這牽涉到要以怎樣的方式將系統屬性編碼,以及如何將想要表現在外的東西給轉成數字。

在電腦猴打字的例子中,那些放在list中的字元就是編碼後的系統屬性,也就是基因型,而顯示在畫面上的字串,則是表現型。這個例子很簡單、很單純,很容易就能看出該怎麼設計基因型和表現型。但事情並不總是這麼簡單。

在前面談到關於足球PK戰電玩的適應度函數設計時提到,每個進攻球員都具有許多用來決定要怎麼踢球的參數,但並沒有提到這些參數究竟是什麼。事實上,這些參數就是基因型。設計基因型時,就是要決定這些參數是什麼東西,以及要用怎樣的方式來將這些參數編碼;這很明顯不是件簡單的工作,而且也沒有標準答案,一切就看你想要怎麼設計。

接下來,就用個實際的例子來看看,基因型和表現型的設計過程。

在設計類別時,我們會設定一堆變數,例如

class Vehicle:
def __init__(self):
self.max_speed = ?????
self.max_force = ?????
self.size = ?????
self.separation_weight = ?????
:
:

想要讓這些變數能夠在GA中進行演化,就只需要把它們轉變成DNA類別中的方法,如crossover()mutate()等,可以使用的list就可以了。要達到這個目的,一個常見的做法是使用由0~1的浮點數所構成的list,像這樣:

class DNA:
def __init__(self, length):
self.length = length

self.genes = [random.random() for _ in range(self.length)]

這裡要注意一下,之所以選用0~1間的浮點數,就只是因為比較好處理;使用其他範圍的浮點數,也是可以的。透過簡單的計算,在某個範圍的浮點數,可以很容易就映射到不同的範圍中;這個可以參考〈The Nature of Code閱讀心得與Python實作:0.6 A Smoother Approach with Perlin Noise〉〈Noise Ranges〉小節。

另一個要注意的地方是,我們把原始的遺傳數據,也就是基因型,以及這些數據會外顯表現出來的東西,也就是表現型,分別放在不同的類別中。以DNAVehicle這兩個類別來說,DNA類別是基因型,它裡頭就是一堆數據;而Vehicle類別則是表現型,它涉及如何讓基因型的那一堆數據表現在外,最終轉化成動畫以及視覺效果。

藉由在Vehicle類別中納入DNA類別的實例,我們可以將基因型和表現型聯繫起來:

class Vehicle:
def __init__(self):
# 將DNA物件嵌入Vehicle類別
self.dna = DNA(4)
self.max_speed = dna.genes[0]
self.max_force = dna.genes[1]
self.size = dna.genes[2]
self.separation_weight = dna.genes[3]
:
:

基因型並不一定是數字,而可以是任何資料型態。例如,設計具有一系列推進器的火箭時,可以把每個推進器都看成是向量。這時,基因型就可以設計成是向量物件:

class DNA:
def __init__(self, length):
self.length = length

self.genes = []
for _ in range(self.length):
r = random.uniform(0, 10)
theta = random.uniform(0, 360)
# 大小介於0~10,方向為任意方向的推力
thruster = pygame.Vector2.from_polar((r, theta))
self.genes.append(thruster)

至於表現型,則可以設計成一個Rocket類別:

class Rocket:
def __init__(self):
self.dna = ?????
:
:

把基因型和表現型分別放在兩個不同的類別中,最大的好處是,在處理不同的問題或在不同的場景中,放置基因型的那個類別,只要調整、修改放在list中資料的類型,就可以拿來重複使用,不需要整個重新設計或改寫。例如,在火箭的這個例子中,在DNA類別部分,我們就只修改了genes這個list中所存放的資料,而沒有重新設計整個類別。

留言
avatar-img
留言分享你的想法!
avatar-img
ysf的沙龍
19會員
157內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2025/08/18
在寫程式之前先來看一下,傳統的GA是透過哪些步驟來讓生物族群一代一代地演化下去的。
2025/08/18
在寫程式之前先來看一下,傳統的GA是透過哪些步驟來讓生物族群一代一代地演化下去的。
2025/08/11
GA可以在不知道正確答案的情況下,隨機地找個起點,然後透過模擬演化的方式,以遠比暴力法更快的速度找出正確的答案,是個可實際解決問題的好工具。
2025/08/11
GA可以在不知道正確答案的情況下,隨機地找個起點,然後透過模擬演化的方式,以遠比暴力法更快的速度找出正確的答案,是個可實際解決問題的好工具。
看更多
你可能也想看
Thumbnail
透過蝦皮分潤計畫,輕鬆賺取零用金!本文分享5-6月實測心得,包含數據流程、實際收入、平臺優點及注意事項,並推薦高分潤商品,教你如何運用空閒時間創造被動收入。
Thumbnail
透過蝦皮分潤計畫,輕鬆賺取零用金!本文分享5-6月實測心得,包含數據流程、實際收入、平臺優點及注意事項,並推薦高分潤商品,教你如何運用空閒時間創造被動收入。
Thumbnail
單身的人有些會養寵物,而我養植物。畢竟寵物離世會傷心,植物沒養好再接再厲就好了~(笑)
Thumbnail
單身的人有些會養寵物,而我養植物。畢竟寵物離世會傷心,植物沒養好再接再厲就好了~(笑)
Thumbnail
不知你有沒有過這種經驗?衛生紙只剩最後一包、洗衣精倒不出來,或電池突然沒電。這次一次補貨,從電池、衛生紙到洗衣精,還順便分享使用心得。更棒的是,搭配蝦皮分潤計畫,愛用品不僅自己用得安心,分享給朋友還能賺回饋。立即使用推薦碼 X5Q344E,輕鬆上手,隨時隨地賺取分潤!
Thumbnail
不知你有沒有過這種經驗?衛生紙只剩最後一包、洗衣精倒不出來,或電池突然沒電。這次一次補貨,從電池、衛生紙到洗衣精,還順便分享使用心得。更棒的是,搭配蝦皮分潤計畫,愛用品不僅自己用得安心,分享給朋友還能賺回饋。立即使用推薦碼 X5Q344E,輕鬆上手,隨時隨地賺取分潤!
Thumbnail
身為一個典型的社畜,上班時間被會議、進度、KPI 塞得滿滿,下班後只想要找一個能夠安靜喘口氣的小角落。對我來說,畫畫就是那個屬於自己的小樹洞。無論是胡亂塗鴉,還是慢慢描繪喜歡的插畫人物,那個專注在筆觸和色彩的過程,就像在幫心靈按摩一樣,讓緊繃的神經慢慢鬆開。
Thumbnail
身為一個典型的社畜,上班時間被會議、進度、KPI 塞得滿滿,下班後只想要找一個能夠安靜喘口氣的小角落。對我來說,畫畫就是那個屬於自己的小樹洞。無論是胡亂塗鴉,還是慢慢描繪喜歡的插畫人物,那個專注在筆觸和色彩的過程,就像在幫心靈按摩一樣,讓緊繃的神經慢慢鬆開。
Thumbnail
在 Python 中,print( ) 函數用於將結果輸出到螢幕上。當你嘗試將不同資料型別(例如字串和數字)混合在一起輸出時,print( )函數無法直接處理這些不同型別的資料,因此你需要先將它們轉換為相同的資料型別。通常,這意味著需要將數字轉換為字串型別,以便與其他字串一同輸出。 雖然我們也可以
Thumbnail
在 Python 中,print( ) 函數用於將結果輸出到螢幕上。當你嘗試將不同資料型別(例如字串和數字)混合在一起輸出時,print( )函數無法直接處理這些不同型別的資料,因此你需要先將它們轉換為相同的資料型別。通常,這意味著需要將數字轉換為字串型別,以便與其他字串一同輸出。 雖然我們也可以
Thumbnail
這一節介紹使用accept-reject algorithm來產生符合特定機率分布的亂數,使得random walker具備Lévy flight的能力。
Thumbnail
這一節介紹使用accept-reject algorithm來產生符合特定機率分布的亂數,使得random walker具備Lévy flight的能力。
Thumbnail
這一節的標題是0.4 A Normal Distribution of Random Numbers,介紹常態分布的基本概念,以及相關亂數產生器的使用方法與應用方式。
Thumbnail
這一節的標題是0.4 A Normal Distribution of Random Numbers,介紹常態分布的基本概念,以及相關亂數產生器的使用方法與應用方式。
Thumbnail
在模擬自然界中的事物時導入隨機性,可以讓結果看起來比較自然,但如果導入的隨機性都是uniform distribution,那未免也太呆板了。這時候,我們需要nonuniform distribution亂數,來讓模擬出來的結果,更像真的一樣。
Thumbnail
在模擬自然界中的事物時導入隨機性,可以讓結果看起來比較自然,但如果導入的隨機性都是uniform distribution,那未免也太呆板了。這時候,我們需要nonuniform distribution亂數,來讓模擬出來的結果,更像真的一樣。
Thumbnail
今天來介紹python的函式 函式在python中是非常重要的一環,因為到了後期,程式會越來越複雜。 而函式可以想成是容易管理的小程式,當我們需要使用時,只需呼叫即可。
Thumbnail
今天來介紹python的函式 函式在python中是非常重要的一環,因為到了後期,程式會越來越複雜。 而函式可以想成是容易管理的小程式,當我們需要使用時,只需呼叫即可。
Thumbnail
在程式中,了解資料型態是相當重要的。 為什麽? 因為許多error,常常都是因為資料型態不正確所導致的。 舉個例子,在python中: a = 1 + 2 print(a) 結果就是3 a = = "1"+"2" print(a) 結果就是12 是不是差很多? 所以今天我來介
Thumbnail
在程式中,了解資料型態是相當重要的。 為什麽? 因為許多error,常常都是因為資料型態不正確所導致的。 舉個例子,在python中: a = 1 + 2 print(a) 結果就是3 a = = "1"+"2" print(a) 結果就是12 是不是差很多? 所以今天我來介
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News