1. 程式人生 > 實用技巧 >Genetic Algorithms with python 學習筆記ch1

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類

未完待續。。