从小鸡仔开始的遗传算法

1.遗传算法的简单介绍

    基本思想:适者生存,基因交流

    遗传算法是智能优化算法的一种,通过不断缩小解集的搜索域来寻找最优解。

    先来以一个简单直观的的自然现象来掀开遗传算法的面纱。

    一个叫张三的大学生回乡创业,开了个养鸡场,初期购入1000只小鸡仔(公母各半)。每只小鸡仔的状况都是未知的(把这些小鸡仔称为初始解集),长大后可能有的生蛋多,有的长得肥,有的肉质好......市场上好的肉鸡能买个好价钱(把市场需求叫目标函数),所以张三倾向于多一点肉鸡,春天到了,小鸡仔长成了大公鸡和大母鸡,张三为了能让自己的养鸡场里多获得一些好的肉鸡鸡仔,他开始想办法了。他把不同的鸡按照肉质进行分级(假设不用杀鸡就能看出肉质)。级数越高(肉质越好,即在目标函数的评价标准之下越接近最优解,适应度越高)的有更多交配机会优质肉的基因就有更大概率能遗传下去)。这样,下一代的鸡中的肉鸡大大增加,且肉质更好的鸡可能产生(优质肉的基因可能突变为更优质的,或者优质肉基因组合在一起变得更优质)。张三赚到了钱,就继续这样的操作,最后,肉鸡在鸡群中的比例越来越大,并且肉质逐代提高(即较优解占比提高,并向最优解靠拢)。

      这个故事虽然简单,但是,我们能够看到张三清晰的解题思路即:
获取初始解集→根据目标确定适应度函数→按适应度大小给予基因遗传概率→交配带来基因的组合和突变基因的遗传→获得更好的下一代

话不多说,接下来我们就开始实践吧:

2.实战搭建遗传算法

我们会用到python 、numpy 、matplotlib(用来画图,可以不用)

我们以鸡为出发点,来尝试培养一群有趣的飞鸡

假设我们的飞鸡能够瞬间长大,也就是根据基因直接获得适应度

我们给予飞鸡两个不同的运动方向,每个运动方向有着不同的运动速度

让他们从同一起点出发,飞向目标点,假设他们不会在途中有速度的改变

示意图如下:

我们的目标是飞鸡飞的准。

我们设置的起点坐标为(0,50),目标点坐标为(100,30)。

下面开始编程实战:

第一步编码

我们把飞鸡的两个不同的速度方向用简单的二进制编码来表示,假设单向最大速度为15,且我们只取整数,则每个速度方向我们用4位来编码,因为2的四次方减一是15。

由于比较简单,我们初始种群为10只飞鸡注意:一般来说种群太小是极其不利的,只有10往往会过早收敛,即得出的解只是较优解

import numpy as np
import matplotlib.pyplot as plt
start=(0,0)
end=(100,30)
def encode():
    raw=np.random.randint(0,2,(20,4))#生成一个20*4的矩阵,每两行确定一只飞鸡,先X后Y
    return raw

第二步计算适应度

我们的适应度很容易计算,用飞鸡在x轴运动到100时所对应的y轴量与目标点的差值来代表,当然此时要求距离越小越好,对于极端情况,比如x轴速度为零的直接淘汰。注意:一定要用到归一化,使适应度加起来是1。下一步要用。

def adapt(n):
    n1=np.array(range(4))
    n1=2**n1#n1[1 2 4 8],n1.T表示n1的转置
    n=n.dot(n1.T)#获取一个40*1的矩阵,每一行有一个速度分量
    vx=np.zeros((10,1))
    vy = np.zeros((10, 1))
    for i in range(10):
        vx[i]=n[2*i-1]#获取x速度
        vy[i]=n[2*i]#获取y速度
    adp=np.zeros((10,1))#生成一个新的矩阵来保存适应度
    for i in range(10):
        if vx[i]!=0:
            t=(end[0]-start[0])/vx[i]#飞行时间
            dety=abs(start[1]+t*vy[i]-(end[1]-start[1]))#距离差
            adp[i]=1/(dety+1)#交配概率,加一是为了避免除0
        else:
            adp[i] = 0
    adp=adp/sum(adp)#归一化
    return adp

第三步.生成下一代

生物学的研究表明,遗传与染色体的行为有着密不可分的关系。

把我们获取的初始解集中不同的编码当作染色体上不同的基因,为了操作简单,我们在这个例子中只考虑自由组合与变异,不考虑交叉,因为编码比较短,交叉意义不大。

组合方式就按随机组合,在这里我们通过不同的适应度给出不同的遗传概率,使得优秀的基因有更大的概率传给下一代

这里我们采用保留上一代中最优个体的进化方法(这样会在理论上收敛到最优解,已经被论证过了,有兴趣的读者可以去找一找证明过程。)。

def new(n,adp):
    byl=0.1#0.1的变异率
    new=np.zeros((20,4))
    x = np.zeros((10,4))
    y = np.zeros((10, 4))
    for i in range(10):
        x[i,:] = n[2 * i ,:]  # 获取x
        y[i,:] = n[2 * i+1,:]  # 获取y
    new[0, :] = x[np.where(adp==max(adp))[0][0]]#找到上一代最优的那一个的x
    new[1, :] = y[np.where(adp==max(adp))[0][0]]#找到上一代最优的那一个的y
    for j in range(1,10):
        p1=np.random.choice(list(range(10)),1,p=adp[:,0])#x速度
        p2 = np.random.choice(list(range(10)),1,p=adp[:,0])#y速度
        new[2*j+1,:]=y[p1]
        new[2 * j, :] = x[p2]
    if np.random.randint(0,20,1)<10*byl:#变异操作
        k=np.random.randint(0,20,1)#20个速度(包括两个方向)中选一个进行变异
        new[k,0]=abs(new[k,0]-1)#将0变1,1变0,选择0号位置是为了减小误差,使变化小一些
    return new

第四步.重复操作,进化多代

用一个while循环,或者for循环就好

x=encode()#获取初代,不要写到循环里
for ii in range(20):#20代
 pp=adapt(x)#适应度
 x=new(x,pp)#产生新一代
 huitu(x)#绘图

 最后,上效果图:

可以看到,在3到4代进化后就已经趋于最优解了。而且从第二代开始有愈来愈收敛的趋势。

看起来还不错 ,但其实会出现算法早熟的情况,过早收敛,还没到最优解就进化不动了。这个图是运行了几次后才出来的。如果要避免早熟,可以把种群弄大一些。但是这个问题一共就16*16=256种可能,种群弄大了难免有随机搜索之嫌。

遗传算法要真正的使用其实还有许多要考虑的地方,比如编码肯定不是像这么简单,产生下一代的方法也不会这么简单,进化的代数一般会超过500代。但是该有的思想都在这里面了,希望能给遗传算法的初学者一点点启发。

最后贴上全部代码:

import numpy as np
import matplotlib.pyplot as plt
import time

start = (0, 0)
end = (100, 30)


def encode():
    raw = np.random.randint(0, 2, (20, 4))  # 生成一个40*4的矩阵,每两行确定一只飞鸡,先X后Y
    return raw


def adapt(n):
    n1 = np.array(range(4))
    n1 = 2 ** n1  # n1[1 2 4 8],n1.T表示n1的转置
    n = n.dot(n1.T)  # 获取一个40*1的矩阵,每一行有一个速度分量
    vx = np.zeros((10, 1))
    vy = np.zeros((10, 1))
    for i in range(10):
        vx[i] = n[2 * i]  # 获取x速度
        vy[i] = n[2 * i + 1]  # 获取y速度
    adp = np.zeros((10, 1))  # 生成一个新的矩阵来保存适应度
    for i in range(10):
        if vx[i] != 0:
            t = (end[0] - start[0]) / vx[i]  # 飞行时间
            dety = abs(start[1] + t * vy[i] - (end[1] - start[1]))  # 距离差
            adp[i] = 1 / (dety + 1)  # 交配概率,加一是为了避免除0
        else:
            adp[i] = 0
    adp = adp / sum(adp)  # 归一化
    return adp


def new(n, adp):
    byl = 0.1  # 0.1的变异率
    new = np.zeros((20, 4))
    x = np.zeros((10, 4))
    y = np.zeros((10, 4))
    for i in range(10):
        x[i, :] = n[2 * i, :]  # 获取x
        y[i, :] = n[2 * i + 1, :]  # 获取y
    new[0, :] = x[np.where(adp == max(adp))[0][0]]  # 找到上一代最优的那一个的x
    new[1, :] = y[np.where(adp == max(adp))[0][0]]  # 找到上一代最优的那一个的y
    for j in range(1, 10):
        p1 = np.random.choice(list(range(10)), 1, p=adp[:, 0])  # x速度
        p2 = np.random.choice(list(range(10)), 1, p=adp[:, 0])  # y速度
        new[2 * j + 1, :] = y[p1]
        new[2 * j, :] = x[p2]
    if np.random.randint(0, 20, 1) < 10 * byl:  # 变异操作
        k = np.random.randint(0, 20, 1)  # 20个速度(包括两个方向)中选一个进行变异
        new[k, 0] = abs(new[k, 0] - 1)  # 将0变1,1变0,选择0号位置是为了减小误差,使变化小一些
    return new


def huitu(n):
    n1 = np.array(range(4))
    n1 = 2 ** n1  # n1[1 2 4 8],n1.T表示n1的转置
    n = n.dot(n1.T)  # 获取一个40*1的矩阵,每一行有一个速度分量
    vx = np.zeros((10, 1))
    vy = np.zeros((10, 1))
    mo = np.zeros((10, 1))  # 末位置
    for i in range(10):
        vx[i] = n[2 * i]  # 获取x速度
        vy[i] = n[2 * i + 1]  # 获取y速度
    for i in range(10):
        if vx[i] != 0:
            t = (end[0] - start[0]) / vx[i]  # 飞行时间
            mo[i] = abs(start[1] + t * vy[i])
    print(mo.T)
    plt.scatter(start[0], start[1])
    plt.scatter(end[0], end[1])
    plt.grid()
    plt.ion()
    for i in range(10):
        plt.plot((0, 100), (0, mo[i, :]))
        plt.show()
        plt.pause(0.1)
        f = plt.gcf()  # 获取当前图像
        f.savefig(f'0\{str(time.time())}.png')  # 保存图像
    plt.clf()
    pass


x = encode()
for ii in range(20):  # 20代
    pp = adapt(x)
    x = new(x, pp)
    huitu(x)
n1 = np.array(range(4))
n1 = 2 ** n1  # n1[1 2 4 8],n1.T表示n1的转置
x = x.dot(n1.T)  # 获取一个40*1的矩阵,每一行有一个速度分量
print(x)

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值