《Match Phrase.py》详解

"""
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()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值