遗传算法

本篇文章讲解遗传算法,主要从三个方面入手:

  • 遗传算法的思想:物竞天择
  • 遗传算法的实例:函数优化,寻找句子,最短路径(TSP)问题
  • 遗传算法的优化:精英主义思想
    (第一部分刚开始有点迷糊不要紧,建议把过一下第一部分,看完第二部分再回过来品味第一部分)

一、遗传算法的思想:
1,遗传算法:
(1),遗传算法认识:遗传算法是模仿自然界生物进化的一种随机全局搜索和优化方法,它是一种自适应的优化算法。模拟物竞天择的生物进化过程,通过维护一个潜在解的群体执行了多方向的搜索,并支持这些方向上的信息构成和交换。是以面为单位的搜索,比以点为单位的搜索,更能发现全局最优解。

(2)遗传算法的过程: 遗传算法其实过程很简单,就是一直迭代到收敛。
这里写图片描述

基本遗传算法伪代码
/*
* Pc:交叉发生的概率
* Pm:变异发生的概率
* M:种群规模
* G:终止进化的代数
* Tf:进化产生的任何一个个体的适应度函数超过Tf,则可以终止进化过程
*/
初始化Pm,Pc,M,G,Tf等参数。随机产生第一代种群Pop

do
{ 
  计算种群Pop中每一个体的适应度F(i)。
  初始化空种群newPop
  do
  {
    根据适应度以比例选择算法从种群Pop中选出2个个体
    if ( random ( 0 , 1 ) < Pc )
    {
      对2个个体按交叉概率Pc执行交叉操作
    }
    if ( random ( 0 , 1 ) < Pm )
    {
      对2个个体按变异概率Pm执行变异操作
    }
将2个新个体加入种群newPop中
} until ( M个子代被创建 )
用newPop取代Pop
}until ( 任何染色体得分超过Tf, 或繁殖代数超过G )

2,遗传算法细节: 遗传算法流程性问题很简单,只有部分有差别(选择、交叉、变异);算法重点应该关注问题解空间的编码、解码,以及适应度函数设计。

(1)基因编码方式:基于编码是对问题潜在解进行“数字化”编码的方案,建立表现型和基因型的映射关系。常用的编码方式有三种:

  • 二进制编码:二进制编码是指采用二进制位串对问题解进行编码;对应的解码则是先把二进制转换为十进制(可以利用矩阵的乘积进行计算),然后在把这个十进制归一化到问题解空间中;本文以函数优化为例介绍。
  • 符号编码: 符号编码一般是指采用ascii码对字符问题解进行编码;对应的解码则是把ascii码转化为字符;本文以配对句子为例介绍。
  • 序列编码: 序列编码是指采用数字序列对问题解进行编码;解码没有固定说法,即是把这个序列翻译成对应的实际问题;本文以TSP问题为例介绍。

(2)适应度函数: 适应度函数是你对问题的认识,因为我们是根据适应度进行选择的。设计的原则是:对于好的个体,适应度要大;反之,适应度小。

  • 函数优化:可以用函数值作为适应度;如果设计轮盘选择(概率选择),则要把适应度修正,转换为正数。
  • 配对句子:用匹配的字符数。
  • TSP:用路径总长的倒数作为适应度。

(3)选择、交叉、变异 遗传算子: 遗传算子其实对于大部分的情况都是固定写法,只是有细微的差别,这里简单的介绍一下相同的固定思路,具体差异看TSP问题实例。(这里所说的固定是指当你选定的选择、交叉、变异的策略方法确定后,写法大部分一样,并非指方法策略是唯一的。)

  • 选择:常用轮盘法:就是根据每个个体的适应度大小给每个个体赋予一个选择概率,然后根据这个概率随机的选择。
  • 交叉:选择两个父亲,交换一部分基因编码,产生新的个体
  • 变异:改变某个基因的一部分。

三者的作用:
选择的作用:选择好的个体群;优胜劣汰,适者生存;
交叉的作用:产生新的个体;保证种群的稳定性,朝着最优解的方向进化
变异的作用:产生新的个体;保证种群的多样性,避免交叉可能产生的局部收敛

二、遗传算法实例:(本文所有代码都是用python实现)
1,函数优化问题: 求函数y(x)=sin(10*x)*x + np.cos(2*x)*x的最值,x取值范围:[0,5]。
实例的完整代码,包括可视化展示:函数优化

(1)编码: 我们采用二进制编码,可以根据你的精确度选定位数,这里采用10位。
解码: 先将二进制转换为十进制这里写图片描述,再将十进制归一化的问题空间内。
这里写图片描述

DNA_SIZE = 10            # 编码长度
POP_SIZE = 100           # 种群大小
CROSS_RATE = 0.8         # 交叉概率
MUTATION_RATE = 0.003    # 遗传概率
N_GENERATIONS = 200      # 迭代次数
X_BOUND = [0, 5]         # x的取值范围
def translateDNA(pop):  # 转换DNA到十进制,并归一化到x得区间内
    return pop.dot(2 ** np.arange(DNA_SIZE)[::-1]) / float(2 ** DNA_SIZE - 1) * X_BOUND[1]
    '''
        这里要注意两个地方:
        1,权重weight=np.arange(DNA_SIZE)[::-1]),这里的数组切片处理容易让人迷惑,[开始:结束:步长],这的负号代表的是从后向前
        2,这里的十进制计算使用pop与weight的内积计算得到,巧妙的运用的矩阵的内积
    '''

(2)适应度: 这里用函数值代表适应度,但是用轮盘选择,所以要进行非负转换。

def get_fitness(pred):  # 计算适应度值
    return pred + 1e-3 - np.min(pred)   # 转化值,使得概率为正
    # return pred

(3)选择: 轮盘法

def select(pop, fitness):
    idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True, p=fitness / fitness.sum())
    return pop[idx]
    '''
        # 这里的选择是调用choice方法进行,p代表的选择的概率大小(轮盘选择法),size是选择的个数,我们选择保持种群大小不变
    '''

(4)交叉: 采用多点交叉:

def crossover(parent, pop):    # 交叉过程
    if np.random.rand() < CROSS_RATE:   # 产生0-1的随机值
        i_ = np.random.randint(0,POP_SIZE,size=1)   # 选择另外一个交叉个体
        cross_points = np.random.randint(0,2,size=DNA_SIZE).astype(np.bool) # 选择交叉点,这里假设每个基因都可以交叉
        parent[cross_points] = pop[i_,cross_points] # 完成交叉
    return parent

(5)变异:每一个位都有几会变异

def mutate(child):
    for point in range(DNA_SIZE):
        if np.random.rand() < MUTATION_RATE:
            child[point] = 1 if child[point] == 0 else 0    # python中的三元运算符,等价于:child[popint] = np.where(child[point]==0],1,0)
    return child

2,配对句子: 就是在所有字符里面构成出你自己设定的句子:TARGET_PHRASE = ‘I love LuoSiHan!。
实例的完整代码,包括可视化展示:配对句子

(1)编码:用ascii码来表示字符串。解码:把ascii码翻译成字符串。ascii的取值范围【32,126】。


TARGET_PHRASE = 'I love LuoSiHan!'  # 目标句子,目标DNA
POP_SIZE = 300  # 种群大小
CROSS_RATE = 0.4    # 交叉概率
MUTATION_RATE = 0.01    # 变异概率
N_GENERATIONS = 1000    # 迭代次数

DNA_SIZE = len(TARGET_PHRASE)   # DNA长度
TARGET_ASCII = np.fromstring(TARGET_PHRASE,dtype=np.uint8)  # 转化为数字
ASCII_BOUND = [32,126]  # 编码的取值范围
def translateDNA(self,DNA): # 解码:转化为字符串就行了
    return DNA.tostring().decode('ascii')

(2)适应度: 计算与目标配对字符的个数

    def get_fitness(self):  # 适应度大小:匹配了多少位
        match_count = (self.pop == TARGET_ASCII).sum(axis=1)
        return match_count

(3)选择、交叉、变异: 选择、交叉和基本的一样;变异也只是随机赋值,[32,126]中的一个值,而不是0,1改变。具体看源码。

3,TSP最短路径问题: 问题就是要遍历每一个城市,使得路径长度最小。
实例的完整代码,包括可视化展示:最短路径

(1)编码: 采用路径的城市序列编码。
解码:将这个城市序列翻译成,每个城市横、纵坐标,用来计算长度。

    def translateDNA(self,DNA,city_position): # 解码:返回DNA序列代表的城市顺序的x,y坐标
        line_x = np.empty_like(DNA,dtype=np.float64)
        line_y = np.empty_like(DNA,dtype=np.float64)
        for i,d in enumerate(DNA):
            city_coord = city_position[d]
            line_x[i,:] = city_coord[:,0]
            line_y[i,:] = city_coord[:,1]

        return line_x,line_y

(2)适应度: 采用路径长度的倒数。

    def get_fitness(self,lx,ly):    # 计算适应度:距离越大,适应度越小
        total_distance = np.empty((lx.shape[0]),dtype=np.float64)
        for i,(xs,ys) in enumerate(zip(lx,ly)):
            total_distance[i] = np.sum(np.sqrt(np.square(np.diff(xs)) + np.square(np.diff(ys)))) # 计算距离
        fitness = np.exp(self.DNA_size * 2 / total_distance)
        return fitness, total_distance

(3)选择:选择和基本的一样。

(4)交叉:不同的是要保证每一个城市都要访问,不能单纯交叉。

    def crossover(self,parent,pop): # 交叉:不能简单的把两个互换,必须保持所有点都要访问
        if np.random.rand() < self.cross_rate:
            i_ = np.random.randint(0,self.pop_size,size=1)
            cross_points = np.random.randint(0,2,size=self.DNA_size).astype(np.bool)
            keep_city = parent[~cross_points]
            swap_city = pop[i_,np.isin(pop[i_].ravel(),keep_city,invert=True)]
            parent[:] = np.concatenate((keep_city, swap_city))
        return parent

(5)变异:不同的是要保证每一个城市都要访问,不能单纯变异。这里采用交换基因中的两个位置。

    def mutate(self,child): # 变异:不能简单改变某一个值,必须保持所有点都要访问
        for point in range(self.DNA_size):
            if np.random.rand() < self.mutate_rate:
                swap_point = np.random.randint(0, self.DNA_size)
                swapA, swapB = child[point], child[swap_point]
                child[point], child[swap_point] = swapB, swapA
        return child

三、遗传算法的优化:精英主义思想。也就是每次把好的给保留下来。它要优化的进化思路,并没有对交叉、变异做改变。进化过程:每次进化没有统一的选择了,它每次选择两个个体,对这两个进行比较,把好的保留下来,并用好的去改变不好的(交叉)。
实例的完整代码,包括可视化展示:精英主义

(1)进化过程:

    def evolve(self, n):    # 进化过程中,每次悬着两个,对于适应度低的那个,利用使用度高的进行交叉和变异,保留适应度高的
        for _ in range(n):
            sub_pop_idx = np.random.choice(np.arange(0, self.pop_size), size=2, replace=False)
            sub_pop = self.pop[sub_pop_idx]
            product = F(self.translateDNA(sub_pop))
            fitness = self.get_fitness(product)
            loser_winner_idx = np.argsort(fitness)
            loser_winner = sub_pop[loser_winner_idx]    # 排序:第一个是适应度小的,第二个是适应度大的
            loser_winner = self.crossover(loser_winner) # 交叉
            loser_winner = self.mutate(loser_winner)    # 变异
            self.pop[sub_pop_idx] = loser_winner

        DNA_prod = self.translateDNA(self.pop)
        pred = F(DNA_prod)
        return DNA_prod, pred

(2)交叉:

    def crossover(self, loser_winner):      # 交叉:把适应度高的个体,交叉点对应的的值给适应度低的个体
        cross_idx = np.empty((self.DNA_size,)).astype(np.bool)
        for i in range(self.DNA_size):
            cross_idx[i] = True if np.random.rand() < self.cross_rate else False
        loser_winner[0, cross_idx] = loser_winner[1, cross_idx]
        return loser_winner
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值