遗传算法的Python实现

遗传算法的Python实现



问题简述

  实验中,每代固定m个个体,每个个体的基因是n维向量,该向量通过某种计算F得到每个个体的适应度,适应度通过Softmax计算变为概率,每代中p个适应度最高的个体直接传给下一代,无须基因重组或基因突变,另外(m-p)个个体是通过上一代个体基因两两重组得到,每个个体成为父代的概率是它的适应度转换得到的概率,两个父代贡献不平衡,比例为a:b.此外,子代个体基因有c的概率发生基因突变,突变来源于G分布。超参数的值未进行广泛的优化。

  假设:

m=40,
n=4000,
F=F(),
p=10,
m-p=30,
a=75%,
b=25%,
c=25%,
G为一个标准正态分布。


基因的构建

  在传统的遗传算法中,个体的基因通常为二进制,如在计算某个函数的最大值时对自变量进行二进制转变即可得到对应的基因编码(基本的遗传算法)。(在遗传规划中,基因不再由位串组成,而是采用了由数学运算符和变量构成的计算机代码片段——Richard O.Duda, Peter E.Hart, David G.Stork. Pattern Classification, Second Edition.)

  本例中,已经给出基因编码的表达方式,我主要用了Python中的Numpy数据格式来处理向量。

''' 
@author: 纵心似水
Time: 2019/9/17
Place: Zhongguancun East Road, Haidian District, Beijing
'''
# coding:utf-8

evolution_num = 1000

for evol_num in range(evolution_num):

    # 显示进化的第几代
    print('generation_num_' + str(evol_num + 1))

    # 初始编码是40个4000维的向量,采样于标准正态分布(nz=4000);子代中的初始编码是上一代进化来的编码
    if evol_num == 0:
        people = np.randn(40, nz)
        # people = np.zeros(40, nz)
    else:
        people = people_new
        


适应度与得分的计算

  通过函数F得到每个个体的适应度:

	fitness = F(people)

  得分即是概率,将适应度通过Softmax计算转换为概率,具体计算公式如下所示:
p t a r g e t = e x t a r g e t ∑ i = 0 N e x i p_{target} = \frac{e^{x_{target}}}{\sum_{i=0}^{N}{e^{x_{i}}}} ptarget=i=0Nexiextarget

	scores = np.exp(fitness) / np.sum(np.exp(fitness))

每代最优基因的直接遗传

  每一代得分最高的10个个体的基因直接传递给下一代:

    # 将每代得分最高的10个个体直接传送给下一代
    index = np.argsort(-scores)
    people_max_score_10 = people[index[0:10], :]
    # print(people_max_score_10.shape)
    # print(np.array(people_max_score_10).shape)
    people_new = []
    people_new.extend(poeple_max_score_10)
    # print(np.array(people_new).shape)
    

  np.argsort(scores)找到scores按从小到大排序的索引,np.argsort(-scores)找到scores按从大到小排序的索引,np.sort(scores)scores从小到大排序,np.sort(-scores)scores从大到小排序。

  people_new是list格式,可通过.extend方法增加列表元素。


基因重组

  基因的选择性重组是遗传算法的灵魂,这种交叉运算的引入,将两个不同的基因交叉,提供了一种与随机搜索算法完全不同的搜索方式,它通过选择、反转、重组基因片段的方式运作,如果这些片段能够“忠实地"代表基本功能模块,那么遗传算法可望得到更好的性能。——Richard O.Duda, Peter E.Hart, David G.Stork. Pattern Classification, Second Edition.

  选择时采用了轮盘赌算法,查找时也可采用二分法。轮盘赌算法即将各个个体的单个概率转化为累积概率(最后一个个体累积概率为 1 1 1),然后生成 k k k [ 0 , 1 ] [0, 1] [0,1]区间的随机数( k k k也是超参数),统计这些随机数分布在哪些区间,从而决定了这些区间对应的个体被选择的情况。

  其它一些具体的计算过程已经在代码注释中写得很详细了:

''' 
@author: 纵心似水
Time: 2019/9/17
Place: Zhongguancun East Road, Haidian District, Beijing
'''

    # 这一代的40个个体两两组合出新的个体,成为父代的概率是个体的得分,从3/4处断开重组,选取得分最高的30个子代个体进入下一代
    # 采用轮盘赌算法,先计算各个概率区间
    p_sum = np.zeros(40)
    for i in range(40):
        p_sum[i] = np.sum(scores[:i+1])
    # print(p_sum)

    # 随机生成40个位于0到1区间的浮点随机数
    random_numbers = np.random.rand(40)

    # 查找每个随机数位于轮盘的哪个区间,并取出该区间对应的个体作为父代候选,由此选出40个候选者
    father_handicate = []
    for i in range(40):
        random_number = random_numbers[i]
        if random_number < p_sum[0]:
            father_handicate.append(people[0, :].numpy())
        else:
            for j in range(40 - 1):
                if (random_number > p_sum[j]) and (random_number < p_sum[j+1]):
                    father_handicate.append(people[j+1, :].numpy())
    # print(np.array(father_handicate).shape)

    # 40个候选者个体基因两两组合,共生成40*39/2=780种组合
    iterator = itertools.combinations(range(0, 40), 2)
    list_result_combinations = list(iterator)
    # print(np.array(list_result_combinations).shape)
    people_after_combine = []
    # print(list_result_combinations[1][1])
    # print(np.array(father_handicate)[0, :, :, :])
    for i in range(780):
        people_after_combine_ = np.concatenate((np.array(father_handicate)[list_result_combinations[i][0], :3000],
                                              np.array(father_handicate)[list_result_combinations[i][1], 3000:]))
        people_after_combine.append(people_after_combine_)
    # print(np.array(people_after_combine).shape)    # (780, 4000, 1, 1)

    # 在所有780个组合中选择得分最高的30个作为下一代
    data = people_after_combine
    # print(data.shape)
    optim_values = []
    for i in range(780):
        fitness = F(data[i])
        optim_values.append(fitness)
    scores = optim_values / (np.sum(optim_values))
    index = np.argsort(-scores)
    people_max_score_30 = people[index[0:30], :]
    people_new.extend(people_max_score_30.squeeze(1).numpy())
    # print(np.array(people_new).shape)



基因突变

  基因突变的概率越大,模式更新得越彻底,算法收敛越快,但可能会出现算法发散不收敛的情况;基因突变的概率越小,算法越稳定,但可能收敛速度很慢,很容易收敛到一个局部极值点。超参数的调节与算法的优化是使用机器学习以及深度学习必备的经验与技能。

    # 初步得到的40个子代的基因有1/4的概率发生基因突变,突变来自于一个均值为0,标准差为1的标准高斯(正态)分布
    mutation_index = random.sample(range(0, 4000), 1000)
    # print(mutation_index[0])
    people_new = np.array(people_new)
    people_new[:, mutation_index] = np.random.normal(0, 1, 1000 * 40).reshape((40, 1000))
    # print(people_new.shape)


小结

  虽然深度学习这几年在各个领域十分 h o t hot hot and p o p u l a r popular popular,但经典的机器学习方法也是从事模式识别领域的学习研究者们不可或缺的知识。笔者还是小白一枚,若有不恰当、不正确的地方还请及时指出;如果你有更好的建议与方法,欢迎赐教~

纵心似水
2019.9.17
————在探索世界奥秘的海洋上缓缓荡桨

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值