遗传算法 |运筹优化

1、遗传算法简介(Genetic Algorithm,GA)

借鉴了生物学中的遗传学和进化论的思想,将待优化问题的解看作进化中的个体,通过基因重组、突变和选择等遗传操作来实现逐代进化,最终找到最优解。

2、概念

种群(Population)–候选解集合

遗传算法保持大量的个体(individuals)——
针对当前问题的候选解集合。由于每个个体都由染色体表示,因此这些种族的个体(individuals)可以看作是染色体集合:
例子中:
某次出现几只猴子可以爬到20米的山,那么他们的后代得到这些猴子的基因的重组和变异,有希望爬到更高,比如30米,以此迭代,最终希望有一些猴子通过基因重组和变异,能够爬到最高地方!
适应度函数(Fitness function)
在算法的每次迭代中,使用适应度函数(也称为目标函数)对个体进行评估。适应度得分更高的个体代表了更好的解,其更有可能被选择繁殖并且其性状会在下一代中得到表现。随着遗传算法的进行,解的质量会提高,适应度会增加,一旦找到具有令人满意的适应度值的解,终止遗传算法。(因为会有父带和母,选择哪一个?)
交叉率:
交叉率决定了当前种群中能够进行交叉的个体数目所占的比例。一般来说,交叉率的设置在0.6-0.9之间比较合理,但是具体数值需要根据具体问题在实验中进行调整。
种群大小:
种群大小应该越大越好,但是太大会影响算法的效率,因此需要根据实验结果进行调整。
选择算子:
选择算子决定了哪些个体应该被选中进行交叉和变异。常用的选择算子有轮盘赌选择、锦标赛选择和随机选择等

3、求解过程

初始化-选择-交叉-变异-选择-(往复循环至迭代次数)

  1. 初始化种群:
    随机生成一组初始个体并构成一个种群。
  2. 评价函数:
    对每个个体进行适应度评价。
  3. 选择操作:
    按照适应度大小,选出优秀个体参与下一轮进化。
    计算可行解间的适应度(TSP中对应的是距离,如果距离最小,那肯定是比较好的结果),结果会生成一个父代和母代,在这个过程中,基因片段是否可以重复。
    在计算出种群中每个个体的适应度后,使用选择过程来确定种群中的哪个个体将用于繁殖并产生下一代,具有较高值的个体更有可能被选中,并将其遗传物质传递给下一代。
  4. 交叉操作:
    选出的优秀个体进行基因交叉操作生成新的子代。
    那么在操作过程中,为什么需要用到父代和母代呢?
    遗传算法将待优化问题看作进化中的个体,随机生成一个初始种群,每一个种群成员对应着问题的一个解。对于每一代种群,都需要进行进化操作以获取新的一代种群。

这时,遗传算法就需要用到父代和母代了。在交叉操作中,需要从当前的种群中选择两个个体作为父母,通过基因重组生成新的个体(子代)。在变异操作中,也需要选择一个个体进行变异操作。这样,遗传算法通过父代和母代来实现基因的遗传和变异。

父代和母代的选择通常是按照适应度大小来进行的,适应度较高的个体更有可能成为父母,从而更有可能传递其优秀基因;而适应度较低的个体则被淘汰,从而防止不良基因的继承。
为了创建一对新个体,通常将从当前代中选择的双亲样本的部分染色体互换(交叉),以创建代表后代的两个新染色体。此操作称为交叉或重组:
5. 变异操作:
对新生成的子代进行基因突变操作。
它可以在基因编码上引入一定程度的随机性和新颖性,以增加种群的多样性。通常,变异操作会随机选择一个或多个个体的某个基因位,并对其进行一定的改变。
下面是一个简单的示例代码,演示了如何在二进制编码上实现单点变异

```python
import random

def mutation(individual, mutation_rate):
    """
    对个体进行单点变异
    :param individual: 个体
    :param mutation_rate: 变异概率
    :return: 变异后的个体
    """
    for i in range(len(individual)):
        # 对每个基因位进行变异
        if random.random() < mutation_rate:
            # 随机选取一个比特位进行翻转
            individual[i] = 1 - individual[i]
    return individual

在这个函数中,individual是一个二进制编码的个体,mutation_rate表示变异概率。函数会遍历个体的每个基因位,如果当前基因位被选中,则将基因位的值进行翻转(0变成1,1变成0)。通过调整mutation_rate的大小,可以控制变异的强度和频率。当mutation_rate较小时,变异操作不容易发生,个体可以保持相对稳定的状态。当mutation_rate较大时,变异操作更容易发生,种群的多样性也会相应增加。

需要注意的是,在实际应用中,变异还可以采取不同的方式和形式,比如多点变异、区间变异、高斯变异等,具体方法要根据问题域的特点和需求进行选择。
6. 评价函数:
对新生成的子代进行适应度评价。
7. 替换操作:
将新生成的子代与当前种群中适应度较低的个体进行替换。
8. 检查进化结束条件:
如果满足终止条件,则算法结束,否则回到第3步。
两种最常用的停止条件是:
已达到最大世代数(迭代轮数)。这也用于限制算法消耗的运行时间和计算资源。
在过去的几代中,个体没有明显的改进。这可以通过存储每一代获得的最佳适应度值,然后将当前的最佳值与预定的几代之前获得的最佳值进行比较来实现。如果差异小于某个阈值,则算法可以停止。

4、遗传算法的优缺点

4.1主要优点

1、全局搜索能力强:
由于遗传算法具有随机性和多样性,在搜索空间中可以遍历并发现多个局部最优解,从而增加了找到全局最优解的可能性。为什么呢?
遗传算法跳出当前最优解的局部最优解,通过以下两种策略实现:
(1)变异
它可以在基因编码上引入一定程度的随机性和新颖性,使得搜索空间更加多样化。在遗传算法迭代过程中,如果所有个体都按照交叉方式进行繁衍,那么新个体只是基于现有个体的组合而来,很难获得全新的信息。因此,为了增加多样性,需要对部分个体执行变异操作,将其中的一个或几个基因进行改变,从而达到跳出当前最优解的局部最优解的目的。
(2)精英保留
精英保留是一种通过维护优秀个体来保持种群多样性的策略。通过将前几代中最好的个体直接复制到下一代,可以尽量保存这些优秀的特征和变量组合,并且将它们与其它个体进行交配,加速种群的收敛。由于精英个体具有较高的适应度值和多样性,它们的后代也可望在搜索空间中探索到新的领域,从而跳出当前最优解的局部最优解。
2、适用范围广:
遗传算法适用于各种类型的优化问题,无需对问题域或目标函数做出任何特殊的假设
(遗传算法不需要特殊假设的原因:
遗传算法的核心思想是借鉴自然界中的进化和遗传机制,通过模拟自然选择和遗传演化的过程来实现问题求解。在这个过程中,遗传算法只需要给定一个适应度评价函数来衡量每个个体的适应度,而不需要对目标函数或问题域做出任何特殊假设
(线性规划算法:
它需要将问题转化成线性规划模型,即目标函数和约束条件都是线性的。这就要求问题域和目标函数都必须是线性的。)。
3、并行计算能力强:
遗传算法非常适合并行化和分布式处理。适应度是针对每个个体独立计算的,这意味着可以同时评估种群中的所有个体。
另外,选择、交叉和突变的操作可以分别在种群中的个体和个体对上同时进行。通过并行计算,可以大大加快求解速度,特别是在搜索空间较大的情况下。
并行计算方式有如下:
(1)遗传算子并行
遗传算子并行是一种将遗传算法各个操作(交叉、变异、选择等)进行并行计算的方法。具体做法是将种群分为多个子群,每个子群分别在独立的处理器上进行遗传算法操作,并互相交换个体信息。该方法可减小遗传算法优化过程中的瓶颈,并提高搜索效率。

4.2主要缺点

  1. 依赖于参数设置
    需要根据问题的性质和搜索空间特征设置一系列参数,包括交叉率、变异率、种群大小等。这些参数的选择对算法的性能影响很大,需要进行仔细的调整。

  2. 可能会陷入局部最优解:
    由于遗传算法具有一定的随机性,在搜索过程中可能会陷入局部最优解而无法跳出。以下是遗传算法陷入局部最优解的原因:
    (1)种群初始化不佳
    种群的初始化对于遗传算法的性能有着非常重要的影响。如果初始种群中的个体都比较相似,那么在交叉和变异操作中,子代往往也会接近父代,这样就很容易陷入局部最优解。
    (2)遗传操作参数设置不当
    遗传算法中的交叉、变异、选择等遗传操作的各种参数设置也会影响算法的搜索效果。如果交叉率过高,种群多样性会下降;如果变异率过低,种群难以跳出局部最优解。

(3)评价函数设计不合理
遗传算法的成功与否还与所选的评价函数密切相关。只有设计合理的评价函数才能指导算法的搜索方向,避免陷入局部最优解。

(4)停止准则选择不当
停止准则也会影响算法的搜索效果。如果停止准则设置得过早或过于宽松,遗传算法的搜索可能会终止在一个局部最优解处。

综上所述,遗传算法陷入局部最优解的原因有多方面,包括种群初始化、遗传操作参数设置、评价函数设计和停止准则选择等方面。选择合适的参数、合理的评价函数、恰当的停止准则以及优质的初始种群等可以帮助克服这些问题。
4. 算法复杂度高:
遗传算法中涉及到基因编码、交叉变异等操作,使得算法的复杂度较高,处理复杂问题时需要更多的计算资源。
遗传算法的时间复杂度主要取决于什么呢?
种群大小、进化代数以及遗传操作的复杂度。
通常情况下,遗传算法的时间复杂度是随问题规模增加而增加的。若种群大小为n,遗传算法进化t代,其时间复杂度约为O(nt),其中n通常是问题规模的多项式级别,t取决于算法实现的细节,也通常是问题规模的多项式级别。因此,遗传算法的时间复杂度通常是多项式级别的。

然而,遗传算法的空间复杂度可能不止多项式级别,还与种群大小有关。如果种群非常大,遗传算法存储所有个体的信息可能会占用大量的内存。另外,遗传算法需要进行交叉操作和变异操作等,这些操作涉及到数据结构的复制和操作,所以空间复杂度也可能比简单的迭代算法高。

5、TSP案例

1.初始群体设定

一般都是随机生成一个规模为 N 的初始群体(可行解的个数,一般取值为100-200)。染色体基因数 N=25(指的城市个数),迭代次数 maxIter=1000,变异概率 Pm=0.1。
计算每个个体的适应度值(即路径长短),执行交叉、变异、选择操作,生成新的种群

2.适应度函数

用距离的总和作为适应度函数,来衡量求解结果是否最优。

3.选择

指以一定的概率从群体中选择优胜个体的操作,它是建立在群体中个体适应度评估基础上的。为了加快局部搜索的速度,在算法中采用最优保存策略的方法,即将群体中适应度最大的个体直接替换适应度最小的个体。它们不进行交叉和变异运算,而是直接复制到下一代,以免交叉和变异运算破坏种群中的优秀解答。

4.交叉算子

是产生新个体的主要手段。它是指将个体进行两两配对,以交叉概率 Pc 将配对的父代个体的部分结构加以替换重组生成新个体的操作。本文中采用有序交叉法来实现。有序交叉法的步骤描述如下:

5.变异操作

是以较小的概率 Pm 对群体中个体编码串上的某位或者某些位作变动,从而生成新的个体。本文中采用倒置变异法:假设当前个体 X为(1 3 7 4 8 0 5 9 6 2),如果当前随机概率值小于 Pm,则随机选择来自同一个体的两个点mutatepoint(1) 和 mutatepoint(2),然后倒置两点的中间部分,产生新的个体。例如,假设随机选择个体 X 的两个点“7”和“9”,则倒置该两个点的中间部分,即将“4805”变为“5084”,产生新的个体 X 为(1 3 7 5 0 8 4 9 6 2)。

6.终止条件为循环一定的代数。

class Individual:
def init(self, genes=None):
# 随机生成序列
if genes is None:
genes = [i for i in range(gene_len)]
random.shuffle(genes)
self.genes = genes
self.fitness = self.evaluate_fitness()

def evaluate_fitness(self):
# 计算个体适应度
#在tsp问题中,依次根据距离矩阵计算前后两点间的路程后求和得出适应度
fitness = 0.0
for i in range(gene_len - 1):
# 起始城市和目标城市
from_idx = self.genes[i]
to_idx = self.genes[i + 1]
fitness += city_dist_mat[from_idx, to_idx]
# 连接首尾
fitness += city_dist_mat[self.genes[-1], self.genes[0]]
return fitness

不同参数下的结果与分析
随着种群数量的增长,遗传算法寻优的结果也越来越好;随着迭代次数的越来越多,遗传算法寻优的结果越来越好。

https://blog.csdn.net/LOVEmy134611/article/details/111639624

import config as conf
import random

city_dist_mat = None
config = conf.get_config()
# 各项参数
gene_len = config.city_num
individual_num = config.individual_num
gen_num = config.gen_num
mutate_prob = config.mutate_prob


def copy_list(old_arr: [int]):
    new_arr = []
    for element in old_arr:
        new_arr.append(element)
    return new_arr


# 个体类
class Individual:
    def __init__(self, genes=None):
        # 随机生成序列
        if genes is None:
            genes = [i for i in range(gene_len)]
            random.shuffle(genes)
        self.genes = genes
        self.fitness = self.evaluate_fitness()

    def evaluate_fitness(self):
        # 计算个体适应度
        fitness = 0.0
        for i in range(gene_len - 1):
            # 起始城市和目标城市
            from_idx = self.genes[i]
            to_idx = self.genes[i + 1]
            fitness += city_dist_mat[from_idx, to_idx]
        # 连接首尾
        fitness += city_dist_mat[self.genes[-1], self.genes[0]]
        return fitness


class Ga:
    def __init__(self, input_):
        global city_dist_mat
        city_dist_mat = input_
        self.best = None  # 每一代的最佳个体
        self.individual_list = []  # 每一代的个体列表
        self.result_list = []  # 每一代对应的解
        self.fitness_list = []  # 每一代对应的适应度

    def cross(self):
        new_gen = []
        random.shuffle(self.individual_list)
        for i in range(0, individual_num - 1, 2):
            # 父代基因
            genes1 = copy_list(self.individual_list[i].genes)
            genes2 = copy_list(self.individual_list[i + 1].genes)
            index1 = random.randint(0, gene_len - 2)
            index2 = random.randint(index1, gene_len - 1)
            pos1_recorder = {value: idx for idx, value in enumerate(genes1)}
            pos2_recorder = {value: idx for idx, value in enumerate(genes2)}
            # 交叉
            for j in range(index1, index2):
                value1, value2 = genes1[j], genes2[j]
                pos1, pos2 = pos1_recorder[value2], pos2_recorder[value1]
                genes1[j], genes1[pos1] = genes1[pos1], genes1[j]
                genes2[j], genes2[pos2] = genes2[pos2], genes2[j]
                pos1_recorder[value1], pos1_recorder[value2] = pos1, j
                pos2_recorder[value1], pos2_recorder[value2] = j, pos2
            new_gen.append(Individual(genes1))
            new_gen.append(Individual(genes2))
        return new_gen

    def mutate(self, new_gen):
        for individual in new_gen:
            if random.random() < mutate_prob:
                # 翻转切片
                old_genes = copy_list(individual.genes)
                index1 = random.randint(0, gene_len - 2)
                index2 = random.randint(index1, gene_len - 1)
                genes_mutate = old_genes[index1:index2]
                genes_mutate.reverse()
                individual.genes = old_genes[:index1] + genes_mutate + old_genes[index2:]
        # 两代合并
        self.individual_list += new_gen

    def select(self):
        # 锦标赛
        group_num = 10  # 小组数
        group_size = 10  # 每小组人数
        group_winner = individual_num // group_num  # 每小组获胜人数
        winners = []  # 锦标赛结果
        for i in range(group_num):
            group = []
            for j in range(group_size):
                # 随机组成小组
                player = random.choice(self.individual_list)
                player = Individual(player.genes)
                group.append(player)
            group = Ga.rank(group)
            # 取出获胜者
            winners += group[:group_winner]
        self.individual_list = winners

    @staticmethod
    def rank(group):
        # 冒泡排序
        for i in range(1, len(group)):
            for j in range(0, len(group) - i):
                if group[j].fitness > group[j + 1].fitness:
                    group[j], group[j + 1] = group[j + 1], group[j]
        return group

    def next_gen(self):
        # 交叉
        new_gen = self.cross()
        # 变异
        self.mutate(new_gen)
        # 选择
        self.select()
        # 获得这一代的结果
        for individual in self.individual_list:
            if individual.fitness < self.best.fitness:
                self.best = individual

    def train(self):
        # 初代种群
        self.individual_list = [Individual() for _ in range(individual_num)]
        self.best = self.individual_list[0]
        # 迭代
        for i in range(gen_num):
            self.next_gen()
            # 连接首尾
            result = copy_list(self.best.genes)
            result.append(result[0])
            self.result_list.append(result)
            self.fitness_list.append(self.best.fitness)
        return self.result_list, self.fitness_list

原文链接:https://blog.csdn.net/LOVEmy134611/article/details/111639624

你可能感兴趣的:(python)