"""
Visualize Genetic Algorithm to match the target phrase.
Visit my tutorial website for more: https://mofanpy.com/tutorials/
"""
import numpy as np
TARGET_PHRASE = 'You get it!' # target DNA
POP_SIZE = 300 # population size
CROSS_RATE = 0.4 # mating probability (DNA crossover)
MUTATION_RATE = 0.01 # mutation probability
N_GENERATIONS = 1000
DNA_SIZE = len(TARGET_PHRASE)
# np.fromstring从字符串中解码出数据
# >>> np.fromstring('123', np.uint8)
# array([49, 50, 51], dtype=uint8)
TARGET_ASCII = np.fromstring(TARGET_PHRASE, dtype=np.uint8) # convert string to number
ASCII_BOUND = [32, 126]
# 转变为类
class GA(object):
# 初始化
def __init__(self, DNA_size, DNA_bound, cross_rate, mutation_rate, pop_size):
self.DNA_size = DNA_size
# DNA_bound=ASCII_BOUND,126+1 = 127
DNA_bound[1] += 1
self.DNA_bound = DNA_bound
self.cross_rate = cross_rate
self.mutate_rate = mutation_rate
self.pop_size = pop_size
# numpy.random.randint(low, high=None, size=None, dtype='l')
# 函数的作用是,返回一个随机整型数,范围从低(包括)到高(不包括),即[low, high)。
# 如果没有写参数high的值,则返回[0, low)的值
# python变量前加*或者**
# 当函数形参接受元组或者字典参数时,可分别使用*前缀和** 实现快速分配。
# *DNA_bound = [32, 127]
self.pop = np.random.randint(*DNA_bound, size=(pop_size, DNA_size)).astype(np.int8) # int8 for convert to ASCII
# 翻译DNA
def translateDNA(self, DNA): # convert to readable string
# tostring方法是将Python对象序列化为字符串的方法
# Python对bytes类型的数据用带b前缀的单引号或双引号表示:
# decode()方法,把bytes变为str
# print('DNA: \t' + str(DNA))
# print('DNA.tostring(): \t' + str(DNA.tostring()))
# print('DNA.tostring().decode(\'ascii\'): \t' + str(DNA.tostring().decode('ascii')))
return DNA.tostring().decode('ascii')
# 适应度
def get_fitness(self): # count how many character matches
# 匹配某一条DNA与目标句子的差距
# self.pop == TARGET_ASCII 会对比每一条DNA的ASCII码与目标句子的ASCII码每一位对应的字母是否相同
# 相同为1,不同为0。将对比之后的结果相加,数值大的更加接近目标句子
# sum默认的axis=0 就是普通的相加,而当加入axis=1以后就是将一个矩阵的每一行向量相加
match_count = (self.pop == TARGET_ASCII).sum(axis=1)
# 测试
# print('match_count[0]: \t' + str(match_count[0]))
# print('self.pop[0]: \t\t' + str(self.pop[0]))
# print('TARGET_ASCII[0]: \t' + str(TARGET_ASCII))
return match_count
# 优胜劣汰
def select(self):
fitness = self.get_fitness() + 1e-4 # add a small amount to avoid all zero fitness
# np.random.choice函数是numpy库中的一个函数,用于从给定的一维数组中随机选择元素。其具体用法如下:
# np.random.choice(a, size=None, replace=True, p=None)
# 参数说明:
# a:一维数组或整数,表示要从中进行随机选择的数组。如果a是一个整数,则表示从0到a-1的整数中进行选择。
# size:整数或整数元组,表示所需的随机样本的形状。默认为None,表示返回单个随机样本。
# replace:布尔值,表示是否可以重复选择。如果为True,则选择的样本可以重复;如果为False,则不可以重复选择。默认为True。
# p:一维数组,表示每个元素被选择的概率。如果没有给定,则默认为None,表示每个元素被选择的概率相等。
# idx 为 从种群中选择出的DNA序列的索引号,
idx = np.random.choice(np.arange(self.pop_size), size=self.pop_size, replace=True, p=fitness/fitness.sum())
# print('idx: \t' + str(idx))
return self.pop[idx]
# 交叉配对
def crossover(self, parent, pop):
# np.random.rand()产生[0,1)之间的随机浮点数数组,即控制交叉配对率
if np.random.rand() < self.cross_rate:
# np.random.randint() 根据参数中所指定的范围生成随机 整数。
# 随机从[0, self.pop_size)中选择一个数。即,为跟parent进行交叉配对,而随机选择一条DNA。
i_ = np.random.randint(0, self.pop_size, size=1) # select another individual from pop
# 随机从[0, 2)中选择组成一个长度为self.DNA_size的数组。即,随机选择0和1,并将其转化为bool类型的值。
cross_points = np.random.randint(0, 2, self.DNA_size).astype(np.bool) # choose crossover points
# 将随机选择的DNA与parent的DNA进行交叉配对
# print('-'*100)
# print('before parent: \t\t\t' + str(parent))
parent[cross_points] = pop[i_, cross_points] # mating and produce one child
# print('cross_points: \t\t\t' + str(cross_points))
# print('pop[i_]: \t\t\t\t' + str(pop[i_]))
# print('parent[cross_points]: \t' + str(parent[cross_points]))
# print('after parent: \t\t\t' + str(parent))
return parent
# 变异
def mutate(self, child):
# 遍历DNA的每一位
for point in range(self.DNA_size):
# 控制变异的概率
if np.random.rand() < self.mutate_rate:
# 从ASCII码的所有可能值中,选择一个值,来代替目前孩子DNA的某一位
child[point] = np.random.randint(*self.DNA_bound) # choose a random ASCII index
return child
# 进化,选择、繁衍、变异
def evolve(self):
pop = self.select()
pop_copy = pop.copy()
for parent in pop: # for every parent
child = self.crossover(parent, pop_copy)
child = self.mutate(child)
parent[:] = child
self.pop = pop
if __name__ == '__main__':
# 初始化
ga = GA(DNA_size=DNA_SIZE, DNA_bound=ASCII_BOUND, cross_rate=CROSS_RATE,
mutation_rate=MUTATION_RATE, pop_size=POP_SIZE)
for generation in range(N_GENERATIONS):
fitness = ga.get_fitness()
# np.argmax()函数用于一维数组,其返回的是该数组中最大值的索引
best_DNA = ga.pop[np.argmax(fitness)]
# 将ASCII码转化为符号
best_phrase = ga.translateDNA(best_DNA)
# print('Gen', generation, ': ', best_phrase)
# 如果最好的句子等于目标句子就结束循环
if best_phrase == TARGET_PHRASE:
break
# 进化
ga.evolve()
《Match Phrase.py》详解
于 2023-09-03 21:09:47 首次发布