遗传算法学习与python实现


1.遗传算法,算法原理

​ 现在我有一个问题,这个问题可以描述为N个点,这些点相互可以计算权值,变更点的组合获得一个目标的最优解,或者已知一些点,需要对这些点的权值进行变动,这些权值在某些计算下可以获得一个目标值,这个目标值有一些最优解。例如,我有ABCD四个城市,我知道或者可以计算任意两个城市的距离,现在我需要计算出一条路径,能够遍历ABCD,使得路径长度最短,这是一个图优化问题,可以通过遗传算法进行解决。或者在一些成像优化问题上,比如相差校正的时候,我需要校正4-11阶相差,我可以使用卷积核来评价校正后的图像,但是4-11阶的相差需要多少权值,我并不清楚,这时候遗传算法就给了一个可行性的方案。或者对于神经网络来说,神经节点的权值需要更换,这种情况就可以简化为上述问题,这就是一些神经网络模型结合遗传算法后,用于更新权值的方式。

​ 遗传算法模拟的是自然界中的种群更替,一定数量的生物在组合或者变异出一些新的形状后,更加有利于当前环境的生存,或者更加不适应当前环境的生存,一代一代的选择将合适的基因变得更多,不适宜的被淘汰,被淘汰与被留下是一个概率问题,瘸腿的角马也可能会留下后代,而一个强壮的角马可能会被狮子捕猎留不下后代。当然被淘汰了也没事,只要代数足够多,这个角马的性状是可能会重新出现的。

​ 结合平时看的动物频道,龙女之声这些,来理解遗传算法就简单的多了。下面给一些基本概念:

1.种群:

​ 包含大量个体的集合称为种群,进化的单位不是个体,而是一个种群。

2.个体

​ 一个个体拥有一个染色体,比如我需要对ABCD四个城市进行排序,那么我将四个城市编号为0 1 2 3,那么0 1 2 3这个组合可以看作一个个体,0 2 3 1也可以看作一个个体,通常在开始的时候使用随即顺序来生成大量个体。

3.适应度

​ 适应度决定了优化的方向,根据不同的需求选取适应度:

​ 例如:针对最短路径问题,选取路径长度之和的倒数为适应度,适应度越大路程越近

​ 针对清晰度,选取横向纵向边缘卷积后图像之和作为适应度,适应度越大图像越清晰

4.交叉

​ 交叉是指染色体中基因的重新交叉组合,交叉不涉及变异,交叉有如下方式:

​ **a:**简单交叉

​ 随机选择交叉位点: p1: 1 2 |3 4 5 6| 7 8 9 p2: 5 4 |6 9 2 1 |7 8 3

​ 简单交叉: 12 6921 789 54 3456 783

​ 交换重复基因: c1:35 6921 784 c2: 29 3456 781

b: ox交叉

​ 随机选择交叉位点: p1: 1 2 |3 4 5 6| 7 8 9 p2: 5 4 |6 9 2 1 |7 8 3

​ 找位置补上: c1: 3 4 6921 578 c2: 9 2 | 3 4 5 6 | 1 7 8 9

c: pbx交叉

​ 随机选择交叉位点: p1: 1 |2 |3 4 |5 6| 7 8 |9 | p2: 5 4 |6 9 2 1 |7 8 3

​ 从p2随机拿出5 6 9 2,放在p1的位置,即2 5 6 9,然后按照顺序插入p2剩余的数字,即

​ 4 2 3 9 5 6 7 8 9

d: cx交叉:

​ p1: 1 2 3 4 5 6 7 8 9

​ p2: 5 4 6 9 2 1 7 8 3

​ 父代中有:1->5->2->4->9->3->6->1

​ 排序为1 2 3 4 5 6 9,按照p1间隔为1 2 3 4 5 6| | |9|

​ 5 4 6 9 2 1 7 8 3

​ c1为1 2 3 4 5 6 7 8 9,同理得到c2

e: sec交叉

​ p1: 1 2 3 4 5 6 7 8 9

​ p2: 3 4 9 7 8 5 2 1 6

​ 找出随机片段: 4 5 6 7=>按照p2进行排列为:4 7 5 6

​ c1: 1 2 3 4 7 5 6 8 9

​ c2: 3 4 9 5 8 6 2 1 7

5.染色体编码与解码

​ 对于一些问题,需要对基因进行映射,常见的编码方式有二进制编码、格雷码、浮点数编码、各参数级联编码、多参数交叉编码 ,这里不一一举例,主要给出一下二进制编码,这种东西用的时候再查即可。

X[2,4]使5000000000100010...10101101011+0+4+0+16=212+2142251=3.3548X\in[2,4] \\ 使用5位置二进制编码\\ 00000、00001、00010、...10101 \\ 10101 对应十进制为:1+0+4+0+16=21\\ 对应编码数字为:2+21\frac{4-2}{2^5-1}=3.3548

6.变异:

​ 变异的目的是出现一些意料之外的基因,很多情况下仅靠排列组合来获得最优解是异想天开,比如对于波前矫正来说,仅仅通过数字排列组合根本出不来新东西,就需要对权值进行变异。

​ 变异不能太快,即变异率不能太大,不然优秀的基因几代可能就变异没了。

​ 随机指定需要变异的点位,然后把这个地方的数字在定义域类随机变成一个值,完成一次变异。

7. 选择:

​ 常用轮盘赌法,适应度高的个体存活下来的概率更大,轮盘赌更像是一块按概率分割的靶子,然后向靶子上扔飞镖,看看哪个分割区域落得飞镖多。

2.算法模块与实现

"""本代码用于模拟遗传算法"""
"""
遗传算法需要:
种群大小
遗传代数
交叉概率
编译概率
"""
import numpy as np

class Gen:

    def __init__(self,code,code_style,nind,maxgen,pc,pm,ggap):
        """这里是遗传算法需要的参数"""
        self.code=code
        #传入需要计算的对象
        self.code_style=code_style
        #设置编码方法
        self.nind=nind
        #设置种群大小
        self.maxgen=maxgen
        #设置遗传代数
        """选择合适的个体后进行交叉,不一定所有基因能找到对象,给一个概率"""
        self.pc=pc
        #交叉概率
        """变异概率,遗传后代出现不一样的概率"""
        self.pm=pm
        #变异概率,一般较小
        self.ggap=ggap
        #代沟,选择下个种群的大小
        self.chorm=0


    def init_group(self):
        """初始化种群,就是得到100个个体"""
        long_gene=self.code.shape[0]
        """给出基因的长度"""
        #print(self.long_gene)
        chorm=np.zeros([self.nind,long_gene])
        """chorm用于储存种群"""
        for i in np.arange(self.nind):
            chorm[i,:]= np.random.permutation(long_gene)
            """随机排列组合"""
        self.chorm=chorm
        """chorm为初始种群"""
        #print(chorm)
        #return chorm

    def optimize(self):
        """优化部分"""
        gen=0
        dis=distance(self.code)
        #print(self.chorm)
        #len_road=path_lengeth(dis,self.chorm)
        #return len_road
        chorm=self.chorm
        """设置代数"""
        while gen<self.maxgen:
            """当遗传代数小于预定代数的时候循环"""
            """计算适宜度,适者生存,对于优化路线问题了,适宜度就是距离了"""
            len_road=path_lengeth(dis, chorm)
            """计算路线长度"""
            fitv=fitness(len_road) #适应率,越大越好
            #print('fitv:',fitv)
            """选择符合要求的部分,剔除不需要的"""
            selch=select(chorm,fitv,self.ggap)
            #print("1",selch)
            """对基因进行交叉"""
            selch=recombin(selch,self.pc)
            #print("2",selch)
            """输入轮盘赌后选中的个体与交叉概率"""
            selch=variation(selch,self.pm)
            """接下来进行一波变异,输入交叉后的数组和变异概率"""
            #print("3",selch)
            #print(self.chorm.shape)
            selch=reverse(selch,dis)
            """逆操作"""
            chorm=reins(chorm,selch,len_road)
            """重新插入,上一轮的种群,这一轮种群,路径长度(这里的适应度)"""
            gen+=1
        return chorm,dis




"""调用函数定义"""
        

def distance(matr):
    """点坐标矩阵边的距离"""
    m,n=matr.shape
    dis=np.zeros([m,m])
    for i in np.arange(m):
        for j in np.arange(m):
            dis[i,j]=np.sqrt((matr[i,0]-matr[j,0])**2+(matr[i,1]-matr[j,1])**2)
    return dis

def path_lengeth(dis,chorm):
    """计算路径长度,也就是适应度"""
    #print(chorm.shape[0])
    len_road=np.zeros([chorm.shape[0],1])
    #print(len_road)
    for i in np.arange(chorm.shape[0]):
        for j in np.arange(chorm.shape[1]):
            line=chorm[i,:]
            if j<=chorm.shape[1]-2:
                len_road[i]+=dis[np.uint8(line[j]),np.uint8(line[j+1])]
            else:
                len_road[i]+=dis[np.uint8(line[0]),np.uint8(line[j])]
    
    return len_road

def fitness(dis):
    """计算适应率"""
    fitv=1/dis
    return fitv

def select(chrom,fitv,ggap):
    """选择函数:输入图矩阵,适应度,代沟比例
    输出被选择的个体
    选择方法为轮盘读
    """
    nind=chrom.shape[0]
    nsel=max(np.ceil(nind*ggap),2)
    """至少取两个样本"""
    newchrlx=sus(fitv,nsel)

    """轮盘赌代码实现"""
    selch=chrom[newchrlx,:]

    """选出相应的行送回去"""
    return selch
    #轮盘赌算法



def sus(fitv,nsel):
    """轮盘赌算法"""
    """输入
    适应度
    取得个体数量
    输出:
    被选中个体的索引号"""
    nind,ans=fitv.shape#种群数量
    """计算累积概率"""
    cumfit=np.cumsum(fitv)
    cumfit=cumfit.reshape(nind,1)
    #100*1
    #累积和
    nsel=np.uint8(nsel)
    mat=np.linspace(0,nsel-1,nsel)
    mat=mat.reshape(nsel,1)
    trails=cumfit[nind-1]/nsel*(np.random.rand()+mat)
    #产生随机数看落在哪个区间,90*1
    mf=np.tile(cumfit,nsel)
    #print(mf.shape)
    mt=np.tile(trails,nind).T#计算mt落在mf区间里面的数量与对应的标号
    #print(mt.shape)
    #生成的随机数
    #扩展矩阵
    ze=np.zeros([1,nsel])
    tt=np.vstack((ze,mf[0:nind-1,:]))##这一行有问题
    aa,bb=np.where((tt<=mt)&(mt<mf))

    """注意这个傻逼括号一定要"""
    #print('a',aa)
    #tt=tt.reshape(nind,nsel)
    #print(cc)
    """这里输出的cc就是保留的行数"""
    """这里获取随机区间内的坐标索引"""
    al=np.random.permutation(aa)
    #print(al)
    """随机打乱输出的行,用于给下一个",打乱便于交叉"""
    return al


def recombin(selch,pc):
    """对pc概率的个体进行交叉"""
    """先找出需要被交叉的个体"""
    nsel=selch.shape[0]
    for i in range(0,nsel-1-np.mod(nsel-1,2),2):
        if pc>=np.random.rand():
            """使用概率"""
            selch[i,:],selch[i+1,:]=intercross(selch[i,:],selch[i+1,:])
    return selch

    
    """交叉方法"""
def intercross(a,b):
    len=a.shape[0]
    r1=np.random.randint(0,len,1)
    r2=np.random.randint(0,len,1)
    """生成随机数"""
    if r1!=r2:
        a0=a.copy()
        b0=b.copy()
        r=np.vstack((r1,r2))
        s=r.min()
        e=r.max()
        #s=np.uint8(s)
        #e=np.uint8(e)
        for i in range(s,e):
            a1= a.copy()
            b1= b.copy()
            a[i]=b0[i]
            b[i]=a0[i]
            x = np.where(a==a[i])
            y = np.where(b==b[i])
            x = np.setdiff1d(x, i)
            y = np.setdiff1d(y, i)
            #剔除相同的地方
            if x.size !=0:
                a[x]=a1[i]
            if y.size !=0:
                b[y]=b1[i]
    return a,b

def variation(selch,pm):
    """输入待变化矩阵和"""
    mm,nn=selch.shape[0],selch.shape[1]
    for i in range(mm):
        if pm>=np.random.rand():
            al=np.random.permutation(nn)
            selch[i,al[0]],selch[i,al[1]]=selch[i,al[1]],selch[i,al[0]]
            """修改这里"""
    return selch

def reverse(selch,dis):
    mm,nn=selch.shape[0],selch.shape[1]
    objv=path_lengeth(dis,selch)
    selch1=selch.copy()
    for i in range(mm):
        r1 = np.random.randint(0, nn , 1)
        r2 = np.random.randint(0, nn , 1)
        r = np.vstack((r1,r2))
        s=r.min()
        e=r.max()
        t=np.linspace(s,e,e-s+1)
        t=np.uint8(t)
        t1=np.flipud(t)
        selch1[i,t]=selch1[i,t1]
        """增加一个selch1交换se的步骤"""
    objv1=path_lengeth(dis,selch1)
    row,col=np.where(objv1<objv)
    """找出路径更小的排列进行替换"""
    selch[row,:]=selch1[row,:]
    return selch
def reins(chrom,selch,objv):
    """选取十个适应度最好的插回selch中,构成新的chrom"""

    index = np.argpartition(objv.ravel(), 10)[:10]
    #print("sss:",index)
    tt=chrom[index,:]
    chrom1=np.vstack((tt,selch))
    return chrom1




if __name__ == '__main__':
    #x=np.random.rand(10,2)*10
    #print(x)
    """待处理矩阵"""
    """x=np.array([[9.10893551, 0.94758485],
 [3.19123719, 7.10030758],
 [8.99780653, 8.46583976],
 [8.59168471 ,8.16506294],
 [8.41959546 ,4.50936991],
 [3.16840804, 2.93466796],
 [6.2796096 , 0.58816662],
 [1.80523243 ,1.61825121],
 [7.3146517  ,5.02325565],
 [7.20971725 ,4.16120105]])"""

    x =np.array( [[16.47, 96.10]
         ,[16.47, 94.44]
         ,[20.09, 92.54]
         ,[22.39, 93.37]
         ,[25.23, 97.24],
          [22.00, 96.05],
         [20.47, 97.02],
         [17.20, 96.29],
         [16.30, 97.38],
         [14.05, 98.12],
         [16.53, 97.38],
         [21.52, 95.59],
         [19.41, 97.13],
         [20.09, 92.55]])
    dis=distance(x)
    #print(dis)
    x_size=x.shape
    heredity=Gen(x,1,100,400,0.9,0.05,0.9)

    """def __init__(self, code, code_style, nind, maxgen, pc, pm, ggap):"""
    heredity.init_group()

    chrom,dis=heredity.optimize()
    len_road = path_lengeth(dis, chrom)
    #print(len_road)
    index = np.argpartition(len_road.ravel(), 1)[:1]
    print("路径为:",chrom[index,:],"距离为:",len_road[index,:])
    #print(heredity.init_group())

文章作者: dlx
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 dlx !
评论