Genetic Algorithms with python 學習筆記ch1
Hello world問題
一直一個字母字元的集合,利用其中的字元和字母猜測出設定的密碼(例如Hello world)。
1.初次嘗試
字元集合與猜測目標密碼如下:
geneSet = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!."
target = "Hello World!"
下面這個函式表示產生一個最初的猜測:
import random def generate_parent(length): genes = [] while len(genes) < length: sampleSize = min(length - len(genes), len(geneSet)) genes.extend(random.sample(geneSet, sampleSize)) return ''.join(genes)
為了便於快速猜測出目標密碼,需要給出一個反饋來告訴猜測者當前猜測是否更接近答案,如果不是,就可以捨棄該猜測。下面的函式用來計算適應值,它反應當前猜測和目標密碼有多少個相同的字母:
def get_fitness(guess):
return sum(1 for expected, actual in zip(target, guess)
if expected == actual)
下面這個函式為變異,即對之前的猜測中的某個字母替換,得到新的猜測。
def mutate(parent): index = random.randrange(0,len(parent)) childGenes = list(parent) newGene, alternate = random.sample(geneSet, 2) childGenes[index] = alternate \ if childGenes[index] == newGene \ else newGene return ''.join(childGenes)
下面這個函式為輸出過程中的結果,輸出器對應的猜測字串、該猜測的適應值、從開始到本次猜測結束的時間。
import datetime
def display(guess, startTime):
timeDiff = datetime.datetime.now() - startTime
fitness = get_fitness(guess)
print("{}\t{}\t{}".format(guess, fitness, timeDiff))
下面是主函式部分,主要包含產生一個初始猜測、以及重要的一個迴圈。迴圈的過程包括:產生一個猜測;計算該猜測的適應值;將該猜測的適應值與與之前最好的適應值相比較;保留最高的適應值。
if __name__ == "__main__":
random.seed()
startTime = datetime.datetime.now()
bestParent = generate_parent(len(target))
bestFitness = get_fitness(bestParent)
display(bestParent, startTime)
while True:
child = mutate(bestParent)
childFitness = get_fitness(child)
if bestFitness >= childFitness:
continue
display(child, startTime)
if childFitness >= len(bestParent):
break
bestParent = child
bestFitness = childFitness
guessPassword.py完整程式碼:
import random
import datetime
geneSet = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!."
target = "Hello World!"
def generate_parent(length):
genes = []
while len(genes) < length:
sampleSize = min(length - len(genes), len(geneSet))
genes.extend(random.sample(geneSet, sampleSize))
return ''.join(genes)
def get_fitness(guess):
return sum(1 for expected, actual in zip(target, guess)
if expected == actual)
def mutate(parent):
index = random.randrange(0,len(parent))
childGenes = list(parent)
newGene, alternate = random.sample(geneSet, 2)
childGenes[index] = alternate \
if childGenes[index] == newGene \
else newGene
return ''.join(childGenes)
def display(guess, startTime):
timeDiff = datetime.datetime.now() - startTime
fitness = get_fitness(guess)
print("{}\t{}\t{}".format(guess, fitness, timeDiff))
if __name__ == "__main__":
random.seed()
startTime = datetime.datetime.now()
bestParent = generate_parent(len(target))
bestFitness = get_fitness(bestParent)
display(bestParent, startTime)
while True:
child = mutate(bestParent)
childFitness = get_fitness(child)
if bestFitness >= childFitness:
continue
display(child, startTime)
if childFitness >= len(bestParent):
break
bestParent = child
bestFitness = childFitness
程式碼執行結果如下圖:
2.可複用的程式碼
首先編寫一個可以複用的進行密碼猜測的檔案genetic.py,其完整程式碼如下:
import random
def _generate_parent(length, genSet):
genes = []
while len(genes) < length:
sampleSize = min(length - len(genes), len(genSet))
genes.extend(random.sample(genSet,sampleSize))
return ''.join(genes)
def _mutate(parent, geneSet):
index = random.randrange(0, len(parent))
childGenes = list(parent)
newGene, alternate = random.sample(geneSet, 2)
childGenes[index] = alternate \
if childGenes[index] == newGene \
else newGene
return ''.join(childGenes)
def get_best(get_fitness, targenLen, optimalFitness, geneSet, dispaly):
random.seed()
bestParent = _generate_parent(targenLen, geneSet)
bestFitness = get_fitness(bestParent)
dispaly(bestParent)
if bestFitness >= optimalFitness:
return bestParent
while True:
child = _mutate(bestParent,geneSet)
childFitness = get_fitness(child)
if bestFitness >= childFitness:
continue
dispaly(child)
if childFitness >= optimalFitness:
return child
bestParent = child
bestFitness = childFitness
其中,函式_generate_parent和函式_mutate兩個函式型別為python中的protected,只能由模組中的其他函式呼叫。對於函式get_best,其形參中的get_fitness,和形參display為引數只有猜測產生的字串,因為get_best不需要知道目標target是什麼,也不需要知道過了多少時間。
上面的程式碼由檔案guess_Password.py中的函式呼叫,其程式碼如下:
import datetime
import genetic
def test_Hello_World():
target = "Hello World!"
guess_password(target)
def guess_password(target):
geneSet = " abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!."
startTime = datetime.datetime.now()
def fnGetFitness(guess):
return get_fitness(guess, target)
def fnDisplay(genes):
display(genes, target, startTime)
optimalFitness = len(target)
genetic.get_best(fnGetFitness, len(target), optimalFitness, geneSet, fnDisplay)
def display(genes, target, startime):
timeDiff = datetime.datetime.now() - startime
fitness = get_fitness(genes, target)
print("{}\t{}\t{}".format(genes, fitness, timeDiff))
def get_fitness(genes, target):
return sum(1 for expected, actual in zip(target, genes)
if expected == actual)
if __name__ == '__main__':
test_Hello_World()
3.使用python的unittest架構
使用unittest架構,需要講主要測試功能一如unittest.TestCase繼承的類中。但是必須將self作為每個函式的第一個引數新增,因為它們將屬於測試類。
這裡只需要修改guessPassword.py修改後的整體程式碼如下:
import datetime
import genetic
import unittest
class GuessPasswordTests(unittest.TestCase):
geneSet = " abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!."
def test_Hello_World(self):
target = "Hello World!"
self.guess_password(target)
def guess_password(self, target):
startTime = datetime.datetime.now()
def fnGetFitness(guess):
return get_fitness(guess, target)
def fnDisplay(genes):
display(genes, target, startTime)
optimalFitness = len(target)
best = genetic.get_best(fnGetFitness, len(target), optimalFitness, self.geneSet, fnDisplay)
self.assertEqual(best,target)
def display(genes, target, startime):
timeDiff = datetime.datetime.now() - startime
fitness = get_fitness(genes, target)
print("{}\t{}\t{}".format(genes, fitness, timeDiff))
def get_fitness(genes, target):
return sum(1 for expected, actual in zip(target, genes)
if expected == actual)
if __name__ == '__main__':
unittest.main()
genetic.py的內容不變,為了方便,再次貼上如下:
import random
def _generate_parent(length, genSet):
genes = []
while len(genes) < length:
sampleSize = min(length - len(genes), len(genSet))
genes.extend(random.sample(genSet,sampleSize))
return ''.join(genes)
def _mutate(parent, geneSet):
index = random.randrange(0, len(parent))
childGenes = list(parent)
newGene, alternate = random.sample(geneSet, 2)
childGenes[index] = alternate \
if childGenes[index] == newGene \
else newGene
return ''.join(childGenes)
def get_best(get_fitness, targenLen, optimalFitness, geneSet, dispaly):
random.seed()
bestParent = _generate_parent(targenLen, geneSet)
bestFitness = get_fitness(bestParent)
dispaly(bestParent)
if bestFitness >= optimalFitness:
return bestParent
while True:
child = _mutate(bestParent,geneSet)
childFitness = get_fitness(child)
if bestFitness >= childFitness:
continue
dispaly(child)
if childFitness >= optimalFitness:
return child
bestParent = child
bestFitness = childFitness
此外,改程式的命令列測試輸入及輸出如下:
4.測試更長的密碼
在上述guessPassword.py中的GuessPasswordTests類中加入一個函式,程式碼如下:
def test_For_I_am_fearfully_and_wonderfully_made(self):
target = "For I am fearfully and wonderfully made."
self.guess_password(target)
部分執行結果如下圖:
5.引入染色體Chromosome類
未完待續。。