Genetic Algorithms with python 學習筆記ch6
阿新 • • 發佈:2020-08-06
Card Problem
Card Problem 要求將給定的10個數1~10分成A、B兩個集合,要求其中一個集合的和為36,另一個集合的乘積為360。
其中 genetic.py 完整程式碼修改後如下:
import random import statistics import sys import time def _generate_parent(length, geneSet, get_fitness): genes = [] while len(genes) < length: sampleSize = min(length - len(genes), len(geneSet)) genes.extend(random.sample(geneSet, sampleSize)) fitness = get_fitness(genes) return Chromosome(genes, fitness) def _mutate(parent, geneSet, get_fitness): childGenes = parent.Genes[:] index = random.randrange(0, len(parent.Genes)) newGene, alternate = random.sample(geneSet, 2) childGenes[index] = alternate if newGene == childGenes[index] else newGene fitness = get_fitness(childGenes) return Chromosome(childGenes, fitness) def _mutate_custom(parent, custom_mutate, get_fitness): childGenes = parent.Genes[:] custom_mutate(childGenes) fitness = get_fitness(childGenes) return Chromosome(childGenes, fitness) def get_best(get_fitness, targetLen, optimalFitness, geneSet, display, custom_mutate=None): if custom_mutate is None: def fnMutate(parent): return _mutate(parent, geneSet, get_fitness) else: def fnMutate(parent): return _mutate_custom(parent, custom_mutate, get_fitness) def fnGenerateParent(): return _generate_parent(targetLen, geneSet, get_fitness) for improvement in _get_improvement(fnMutate, fnGenerateParent): display(improvement) if not optimalFitness > improvement.Fitness: return improvement def _get_improvement(new_child, generate_parent): bestParent = generate_parent() yield bestParent while True: child = new_child(bestParent) if bestParent.Fitness > child.Fitness: continue if not child.Fitness > bestParent.Fitness: bestParent = child continue yield child bestParent = child class Chromosome: def __init__(self, genes, fitness): self.Genes = genes self.Fitness = fitness class Benchmark: @staticmethod def run(function): timings = [] stdout = sys.stdout for i in range(100): sys.stdout = None startTime = time.time() function() seconds = time.time() - startTime sys.stdout = stdout timings.append(seconds) mean = statistics.mean(timings) if i < 10 or i % 10 == 9: print("{} {:3.2f} {:3.2f}".format( 1 + i, mean, statistics.stdev(timings, mean) if i > 1 else 0))
上面這段程式碼,最大的改變就是對於get_best過程的改變。由於本次的問題如果每次只更新一個的話,就會長時間得不到最優解,因此本次程式碼需要自定義一個突變函式,因此這個過程get_best中,需要對時候需要使用自定義的突變函式進行判斷並呼叫對應的突變函式。後續過程稍微做了一些調整,但是目的不變。
此外 cardTests.py 的完整程式碼如下:
import datetime import functools import operator import random import unittest import genetic def get_fitness(genes): group1Sum = sum(genes[0:5]) group2Product = functools.reduce(operator.mul, genes[5:10]) duplicateCount = (len(genes) - len(set(genes))) return Fitness(group1Sum, group2Product, duplicateCount) def display(candidate, startTime): timeDiff = datetime.datetime.now() - startTime print("{} - {}\t{}\t{}".format( ', '.join(map(str, candidate.Genes[0:5])), ', '.join(map(str, candidate.Genes[5:10])), candidate.Fitness, timeDiff)) def mutate(genes, geneset): if len(genes) == len(set(genes)): count = random.randint(1, 4) while count > 0: count -= 1 indexA, indexB = random.sample(range(len(genes)), 2) genes[indexA], genes[indexB] = genes[indexB], genes[indexA] else: indexA = random.randrange(0, len(genes)) indexB = random.randrange(0, len(geneset)) genes[indexA] = geneset[indexB] class CardTests(unittest.TestCase): def test(self): geneset = [i + 1 for i in range(10)] startTime = datetime.datetime.now() def fnDisplay(candidate): display(candidate, startTime) def fnGetFitness(genes): return get_fitness(genes) def fnMutate(genes): mutate(genes, geneset) optimalFitness = Fitness(36, 360, 0) best = genetic.get_best(fnGetFitness, 10, optimalFitness, geneset, fnDisplay, custom_mutate=fnMutate) self.assertTrue(not optimalFitness > best.Fitness) def test_benchmark(self): genetic.Benchmark.run(lambda: self.test()) class Fitness: def __init__(self, group1Sum, group2Product, duplicateCount): self.Group1Sum = group1Sum self.Group2Product = group2Product sumDifference = abs(36 - group1Sum) productDifference = abs(360 - group2Product) self.TotalDifference = sumDifference + productDifference self.DuplicateCount = duplicateCount def __gt__(self, other): if self.DuplicateCount != other.DuplicateCount: return self.DuplicateCount < other.DuplicateCount return self.TotalDifference < other.TotalDifference def __str__(self): return "sum: {} prod: {} dups: {}".format( self.Group1Sum, self.Group2Product, self.DuplicateCount) if __name__ == '__main__': unittest.main()
接下來對上面的重要部分進行上述程式碼的重要部分簡單的講解:
class Fitness: def __init__(self, group1Sum, group2Product, duplicateCount): self.Group1Sum = group1Sum self.Group2Product = group2Product sumDifference = abs(36 - group1Sum) productDifference = abs(360 - group2Product) self.TotalDifference = sumDifference + productDifference self.DuplicateCount = duplicateCount def __gt__(self, other): if self.DuplicateCount != other.DuplicateCount: return self.DuplicateCount < other.DuplicateCount return self.TotalDifference < other.TotalDifference def __str__(self): return "sum: {} prod: {} dups: {}".format( self.Group1Sum, self.Group2Product, self.DuplicateCount)
適應值包含了第一粗資料的和、第二組資料的積、重複資料個數,以及總差異(即第一組資料的和與36的差的絕對值+第二組資料的積和360的差的絕對值)。重複數越小越好,如果重複值相同,則總差異越小越好。
另一個比較重要的更改是,上述程式自定義了突變函式,並在呼叫engine是傳入了該函式。
def mutate(genes, geneset):
if len(genes) == len(set(genes)):
count = random.randint(1, 4)
while count > 0:
count -= 1
indexA, indexB = random.sample(range(len(genes)), 2)
genes[indexA], genes[indexB] = genes[indexB], genes[indexA]
else:
indexA = random.randrange(0, len(genes))
indexB = random.randrange(0, len(geneset))
genes[indexA] = geneset[indexB]
在函式mutate中,首先判斷這個基因是不是不包含重複的字母,如果包含就隨機選擇一個位置進行替換,如果不包含,則進行1~4次的突變,這個突變為講兩個位置的數進行交換。