启发式算法之遗传算法

刚开学便被拉去参加了研究生数模比赛,赛题是一个航班排班的优化问题,所以第一反映便是遗传算法,比赛期间三个问题都使用单目标遗传算法,趁着还比较熟悉,特此记录,以便后续复习。本篇文章使用Python进行实现。

启发式算法

启发式算法是一种技术,这种技术使得在可接受的计算成本内去搜寻最好的解,但不一定能保证所得的可行解和最优解,甚至在多数情况下,无法阐述所得解同最优解的近似程度。

就是说这种算法的全局最优解只是理论上可行,大多数情况下都是一个局部最优解。启发式算法用的比较多的有模拟退火算法(SA)、遗传算法(GA)、列表搜索算法(ST)、进化规划(EP)、进化策略(ES)、蚁群算法(ACA)、人工神经网络(ANN)。这里重点介绍一下遗传算法(GA)。

遗传算法准备

遗传算法(Genetic Algorithm, GA)起源于对生物系统所进行的计算机模拟研究。它是模仿自然界生物进化机制发展起来的随机全局搜索和优化方法,借鉴了达尔文的进化论和孟德尔的遗传学说。其本质是一种高效、并行、全局搜索的方法,能在搜索过程中自动获取和积累有关搜索空间的知识,并自适应地控制搜索过程以求得最佳解。

具体来说,在写算法之前,有四个很重要的步骤:

  1. 确定编码方式
  2. 如何设计编码
  3. 确定约束条件
  4. 如何实现约束

确定编码方式

对于编码方式来说,影响到交叉算子、变异算子等遗传算子的运算方法,大很大程度上决定了遗传进化的效率。 总的来说,编码方式可以分为三大类:二进制编码法、浮点编码法、符号编码法。

  1. 二进制编码法:将求解的数从十进制转换成二进制表示,这样便于编码与解码,方便交叉和变异算子的实现,但对于一些连续函数的优化问题,由于其随机性使得其局部搜索能力较差,如对于一些高精度的问题,当迭代接近最优解后,由于其变异后表现型变化很大,不连续,所以会远离最优解,达不到稳定。而格雷码能有效地防止这类现象。
  2. 格雷码编码法:我们先来复习一下格雷码:
    十进制 格雷码   二进制
    
    0       0000    0000
    1       0001    0001
    2       0011    0010
    3       0010    0011
    4       0110    0100
    5       0111    0101
    6       0101    0110
    7       0100    0111
    8       1100    1000
    9       1101    1001
    10      1111    1010

    维基百科对格雷码解决误码的说明:

    傳統的二進位系統例如數字3的表示法為011,要切換為鄰近的數字4,也就是100時,裝置中的三個位元都得要轉換,因此於未完全轉換的過程時裝置會經歷短暫的,010,001,101,110,111等其中數種狀態,也就是代表著2、1、5、6、7,因此此種數字編碼方法於鄰近數字轉換時有比較大的誤差可能範圍。葛雷碼的發明即是用來將誤差之可能性縮減至最小,編碼的方式定義為每個鄰近數字都只相差一個位元,因此也稱為最小差異碼,可以使裝置做數字步進時只更動最少的位元數以提高穩定性。

    连续两个整数所对应的编码值之间仅仅只有一个码位是不同的,当迭代次数很多接近最优值时,相邻两个值之间变化是最大的,当一个染色体变异后,它原来的表现现和现在的表现型是连续的,这样可以将误差的可能性缩减至最小。

    格雷码的转换为:

    G为格雷码,B为二进制码,n为当前计算位,\(\oplus\)为异或

    就有\(G(n) = B(n+1) \oplus B(n)\)

    即\(G(n) = B(n+1) + B(n)\)

  3. 浮点编码法

    对于一些多维、高精度要求的连续函数优化问题,使用二进制编码来表示个体时将会有一些不利之处。

    二进制编码存在着连续函数离散化时的映射误差。个体长度较知时,可能达不到精度要求,而个体编码长度较长时,虽然能提高精度,但却使遗传算法的搜索空间急剧扩大。

    所谓浮点法,是指个体的每个基因值用某一范围内的一个浮点数来表示。在浮点数编码方法中,必须保证基因值在给定的区间限制范围内,遗传算法中所使用的交叉、变异等遗传算子也必须保证其运算结果所产生的新个体的基因值也在这个区间限制范围内。

  4. 符号编码法

    这种编码法其实在解决实际问题时问题时是用的最多的,例如这次的航班优化问题我就使用的这种方法,这种方法要结合实际问题来进行编码,然后交叉和变异也要结合实际进行编写,这里我就举个具体例子来讲解一下符号编码法。

如何设计编码

对于一般的数学函数优化问题都是将求解的值转换成二进制或者格雷码进行迭代然后计算适应度(fitness)即可,这里我主要讲解一下如果不是一个传入值直接求解方程的问题解决。

举个栗子

假如我有10架航班(a,b,c,...),和5个登机口(1,2,3,4,5),我现在要让排班更加紧凑,使得使用的登机口数尽量少。每架飞机有个一个出发时间(departure_time),在出发时间之前45分钟飞机必须到达登机口,于是我们设计的一个最小化函数即为

\[ F = \sum_{i=0}^5\sum_{j=0}^{10}S_{ij}\]

这里i即为登机口数,j为航班数,这样\(S_ij\)就是对应登机口的航班与上一架航班的时间之差,使得时间和最小我们可以使得安排更加紧凑。

那我就可以这样进行编码

10个染色体就是对应的10架航班,里面的内容就是其对应的登机口号,这样我们就可以计算同一登机口的相邻飞机的起飞间隔时间差了。

例如2号登机口对应的就是飞机a,飞机i,那么我们使用$ i_{arrival_time} - a_{arrival_time}$就可以得到对应的时间差了,统计计算1号,3号,4号和5号,加在一起就是我们的目标函数的值了。

确定约束条件

对于不同的问题求解会有不同的约束方式,常见的约束方式有

  • 范围限制
  • 条件限制

范围限制,例如我们的求值只可在(0,0.3]与[0.5,0.8)之间,那么我们在求解的时候要么加入惩罚项,即计算到非对应范围的时候乘上一个很大的值来限制,要不就在对应交叉和变异的时候先判断在交叉变异进行限制。

条件限制,例如上题,我们假如约束条件,即为飞机有宽与窄之分,我们的登机口也有对应的宽窄之分,对应的飞机只能停到对应的登机口处,所以这里我们编码时也要在满足约束进行编码,编写交叉变异时也针对此进行编写。

如何实现约束

实现约束即在三个地方实现,分别为

  1. 基因可行解初始化(inital)
  2. 基因交叉(crossover)
  3. 基因变异(mutate)

这里简单讲解一下可行解初始化的约束如何实现,我们可以写一个简单的while循环来找到可行解:

POP_SIZE = 100 # 可行解数目
DNA_SIZE = 10 #dna长度,这里为航班数量,即为10
all_airport_id = [1,2,3,4,5] # 登机口id
aircraft_to_airport_dict = {
    1:[1,2,3],
    2:[2,3,4],
    ...
}# 这里是每架飞机对应的登机口id,这里我就简写了
def inital(self):
    pop = [] # 可行解list
    while len(pop) != POP_SIZE:
        dna = [0 for i in range(DNA_SIZE)]
        all_aircraft_id = list(range(1, 11)) # 所有飞机id
        temp_airport_id_set = set() # 遍历过的存储飞机对应的可行的飞机场
        for aircraft_id in all_aircraft_id:
            workable_airport = aircraft_to_airport_dict[aircraft_id] #可行的登机口id
            while 1: #直到要找到合适的机场停机
                airport_id = random.choice(list(workable_airport)) # 随机生成一个登机口id
                temp_airport_id_set.add(airport_id)
                if len(temp_airport_id_set) == len(workable_airport):
                    dna[aircraft_id-1] = 0 #如果全都不合适,就跳出
                    break
                if constraint(aircraft_id, airport_id): #如果满足约束
                    dna[aircraft_id-1] = airport_id

这里的constraint约束函数可以是很多约束,比如时间约束,区域约束等等。

遗传算法实现

个人认为遗传算法总体分为5个部分:

  1. 初始化可行解
  2. 目标函数,即计算适应度(fitness)
  3. 适者生存
  4. 交叉进化
  5. 变异

具体流程为:

生成n组可行解dna
迭代m次:
    计算每个dna的fitness
    适者生存
    循环n个dna:
        交叉进化
        变异

接下来主要讲一下适者生存、交叉进化和变异

适者生存

物竞天择,适者生存

适者生存的意思就是通过计算fitness,然后作为随机选择的概率,这样概率越大的就被选择到概率越大。具体实现如下,我对fitness做了个倒数,这里是因为我计算的是最小化,所以越小对于我来说是最优的,所以取倒数再计算概率才符合逻辑。

def select(self, pop, fitness):
        fitness = 1 / fitness
        idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True,
                           p=fitness/fitness.sum())
        return pop[idx]

交叉进化

交叉进化有很多种方式,具体可以看这篇文章遗传算法中几种交叉算子小结,讲的已经很全了。

这里我主要讲一下单点交叉,即随机在双亲(parent)dna上选择一个点p,然后与孩子(child)dna上p点后的基因进行交换,实现起来也十分简单。如下图,o点为2,然后就将将蓝色部分与黄色部分互相交换。

CROSS_RATE = 0.8 #交叉概率
def crossover(self, parent, pop):
    if np.random.rand() < CROSS_RATE:
        i_ = random.randint(0, POP_SIZE - 1) # 孩子基因,child
        p = np.random.randint(0, DNA_SIZE)  # 单点交叉
        parent[p:DNA_SIZE] = pop[i_][p:DNA_SIZE]
        return parent
    return parent

变异

人间总有那么多出其不意的突变,很难说我们怎样才算是到了穷途末路,人只要一息尚存,对什么都可抱有希望。

——蒙田《蒙田随笔全集》

许多突变都是中性的,但有些可能会对后代造成损伤,而有些则可能是有益的。控制基因的突变可以对生物体造成极大的影响。有益的基因突变可以帮助生物更好地适应环境,并很可能遗传给后代。

变异就是在dna上随机选择一个点进行基因变异,这样做的原因是防止局部最优,因为如果这个世界有人突变,那么就不排除会比我们现在更加适应这个世界的可能。c点就是变异点。

MUTATION_RATE = 0.01 #突变概率
def mutate(self, child):
    for c in range(DNA_SIZE):
        if np.random.rand() < MUTATION_RATE:
            workable_airport = aircraft_to_airport_dict[aircraft_id] #可行的登机口id
            random_airport_id = random.choice(workable_airport) #随机一个机场
            child[c] = random_airport_id

原文地址:https://www.cnblogs.com/harrylyx/p/12397319.html

时间: 2024-08-01 11:59:01

启发式算法之遗传算法的相关文章

用遗传算法进行容量管理 让虚拟机放置策略更优

by 王国兵 随着互联网技术的高速发展,云计算已经成为各行各业的"水电煤",成为"互联网+"的基础设施,而数据中心则是云服务背后的刚性保障.无论是传统的数据中心,还是云形态的数据中心,虚拟化技术都是提升其资源利用率.降低管理成本的重要方式.据统计,早在2016年,AWS的服务器规模已经到达55万台,而虚拟机数量则超过了800万.随着虚拟机数量急剧增加,虚拟机的放置方案自然成为了决定数据中心资源利用率及其他需求的关键. 如果想提高数据中心的资源利用率,就需要采用合适的

启发式算法(Heuristic Algorithm)

背景: 李航的<统计学习方法>一书中提到:决策树算法通常采用启发式算法,故了解之 问题解答: 时间有限,这里也只是将算法和启发式算法的区别和简单发展摘录如下: 一.算法和启发式方法之间的差别很微妙,两个术语的意思也有一些重叠.就本书的目的而言,它们之间的差别就在于其距离最终解决办法的间接程度:算法直接给你解决问题的指导,而启发式方法则告诉你该如何发现这些指导信息,或者至少到哪里去寻找它们. 二.发展 40年代:由于实际需要,提出了启发式算法(快速有效). 50年代:逐步繁荣,其中贪婪算法和局部

matlab遗传算法工具箱

转自http://blog.sina.com.cn/s/blog_5ebcc0240101pnrj.html matlab遗传算法工具箱函数及实例讲解 (2014-01-10 13:03:57)   分类: matlab 最近研究了一下遗传算法,因为要用遗传算法来求解多元非线性模型.还好用遗传算法的工具 箱予以实现了,期间也遇到了许多问题.借此与大家分享一下. 首先,我们要熟悉遗传算法的基本原理与运算流程. 基本原理:遗传算法是一种典型的启发式算法,属于非数值算法范畴.它是模拟达尔文的自然 选择

启发式算法

启发式算法(heuristic algorithm)是相对于最优化算法提出的. 一个问题的最优算法求得该问题每个实例的最优解. 启发式算法可以这样定义:一个基于直观或经验构造的算法,在可接受的花费(指计算时间和空间)下给出待解决组合优化问题每一个实例的一个可行解,该可行解与最优解的偏离程度一般不能被预计. 现阶段,启发式算法以仿自然体算法为主,主要有蚁群算法.模拟退火法.神经网络等. 现代启发式算法的各种具体实现方法是相对独立提出的,相互之间有一定的区别.从历史上看,现代启发式算法主要有: 模拟

启发式算法中的合法解与可行解

参考文献: [1] Cheng R , Gen M , Tsujimura Y . A tutorial survey of job-shop scheduling problems using genetic algorithms—I. representation[J]. Computers and Industrial Engineering, 1996, 30(4):983-997. 最近在看一些启发式算法求解经典问题的论文,发现几篇比较新的文献中对于合法解和可行解的定义有一些区别,想起

多目标遗传算法 ------ NSGA-II (部分源码解析) 交叉操作 crossover.c

遗传算法中的交叉操作是 对NSGA-II  源码分析的  最后一部分, 这一部分也是我 从读该算法源代码和看该算法论文理解偏差最大的  函数模块. 这里,首先提一下,遗传算法的  交叉操作.变异操作都是需要设定概率的, 即交叉概率和变异概率. 假设种群个体 大小为  popsize ,  那么交叉操作需要进行 popsize/2 次 ,   变异操作需要进行 popsize 次, 其中每次操作的时候都需要随机生成一个随机数来与给定的概率进行判断,若小于给定的概率则继续执行否则退出该操作. 如果继

用遗传算法GA改进CloudSim自带的资源调度策略(2)

遗传算法GA的核心代码实现: 最核心: private static ArrayList<int[]> GA(ArrayList<int[]> pop,int gmax,double crossoverProb,double mutationRate) { HashMap<Integer,double[]> segmentForEach=calcSelectionProbs(pop); ArrayList<int[]> children=new ArrayL

遗传算法在JobShop中的应用研究(part3:交叉)

2.交叉 交叉是遗传算法中的一个重要操作,它的目的是从两条染色体中各自取出一部分来组合成一条新的染色体这里,在车间调度中一种常见的交叉方法叫Generalized Order Crossover方法(GOX),假设有三个工件A,B,C, 每个工件下面包含三道工序,根据这一信息我们可以利用上一节介绍的编码技术随机生成两条染色体如下: 我们可以用一个list来存储一条染色体, 这个list中的每个元素是一个有序偶,有序偶的第一个元素是工件号,第二个元素是工序号. Parent1用list可以表示为[

遗传算法在JobShop中的应用研究(part 1)

1. 什么是JobShop问题 Job,中文翻译成工件.一个工件又由若干道工序加工完成. resource, 资源.在本文的车间调度中资源指的是机器,每道工序要在某个特定机器上加工. Constraint, 约束.在车间调度中约束主要有以下两种: 同一个工件包含的工序有先后顺序. 每个机器不能同时处理两道工序,因此这台机器上完成工序时要串行,不能并行. Objective,目标.JobShop问题的一个常见目标是使所有工件完成的总时间最小,这个总时间英语叫做Makespan. 一个JobShop