简介

遗传算法(Genetic Algorithm,GA)是一种自然启发式的优化算法,基于自然界中的进化和遗传原理。遗传算法在搜索和优化问题中表现出色,特别是在复杂、高维度和非线性问题中。它们可以广泛应用于函数优化、机器学习、约束满足问题和组合优化等领域。

遗传算法的基本原理如下:

  1. 编码:首先,将问题的解表示为染色体,通常使用二进制字符串。每个染色体都代表一个潜在的解决方案。

  2. 初始种群:生成一组随机染色体作为初始种群。种群规模可以根据问题复杂性和计算资源来确定。

  3. 适应度评估:评估每个染色体(解决方案)的适应度。适应度函数根据问题的特性设计,以衡量染色体对应解的质量。

  4. 选择:根据适应度函数,选择具有较高适应度的染色体进入下一代。通常采用轮盘赌选择、锦标赛选择等方法。

  5. 交叉(重组):从选择出的染色体中随机选择两个进行交叉操作,生成新的后代。交叉点可以是单点、多点或均匀交叉。交叉操作增加了种群的多样性,有助于搜索解空间的不同区域。

  6. 变异:以一定的概率随机改变染色体的某些基因(二进制位)。变异引入新特征并保持种群多样性,有助于避免陷入局部最优解。

  7. 终止条件:重复进行选择、交叉和变异操作,直到满足终止条件。终止条件可以是达到预设的最大迭代次数、找到满足要求的解或适应度不再明显改善。

  8. 解码:将找到的最佳染色体解码为问题的最优解。

遗传算法的优势在于其全局搜索能力和适应性。然而,遗传算法也存在一定的局限性,如收敛速度较慢、参数调整困难以及对于某些问题可能陷入局部最优。针对这些局限性,可以采用不同的改进方法和启发式策略,以提高算法性能。

一个遗传算法的例子

以下是一个使用遗传算法解决旅行商问题(Travelling Salesman Problem,TSP)的例子。旅行商问题是一个经典的组合优化问题,目标是找到一条访问所有城市的最短路径,使得旅行商从某个城市出发,经过所有其他城市后回到原点,且每个城市仅访问一次。

  1. 编码:使用整数数组表示染色体,数组中的每个元素代表一个城市的编号。例如,对于5个城市的问题,染色体可以表示为0, 1, 2, 3, 4。

  2. 初始种群:创建一个随机的染色体种群。可以通过随机打乱城市编号的顺序来生成染色体。

  3. 适应度评估:对于每个染色体,计算旅行商的总行程距离。适应度函数可以定义为距离的倒数(因为目标是最小化距离),即适应度 = 1 / 总行程距离。

  4. 选择:采用轮盘赌选择或锦标赛选择等方法,根据适应度函数选择染色体进入下一代。

  5. 交叉:使用有序交叉(Order Crossover,OX)或部分匹配交叉(Partially Matched Crossover,PMX)等方法,从选择的染色体中生成新的后代。

  6. 变异:以一定概率对染色体进行变异。常用的变异操作有交换(swap)两个城市的位置、颠倒(reverse)一段城市序列等。

  7. 终止条件:当达到预设的最大迭代次数、找到满足要求的解或适应度不再明显改善时,终止算法。

  8. 解码:解码找到的最佳染色体,得到最短路径。

遗传算法能有效地在解空间中搜索最优解,但可能需要多次尝试和参数调整来找到满意的结果。对于实际问题,可能需要根据问题特点设计特定的编码、适应度函数、交叉和变异操作。
以下是一个使用Python实现的简单遗传算法来解决旅行商问题的示例:

import numpy as np
import random
import operator

# 生成随机城市坐标
num_cities = 10
cities = {i: (np.random.randint(0, 100), np.random.randint(0, 100)) for i in range(num_cities)}


# 计算两个城市间的距离
def distance(city1, city2):
    return np.sqrt((city1[0] - city2[0]) ** 2 + (city1[1] - city2[1]) ** 2)


# 计算路径总距离
def total_distance(route):
    return sum(distance(cities[route[i]], cities[route[i - 1]]) for i in range(len(route)))


# 适应度函数
def fitness(route):
    return 1 / total_distance(route)


# 选择操作(轮盘赌选择)
def roulette_wheel_selection(population, fitnesses):
    total_fitness = sum(fitnesses)
    pick = random.uniform(0, total_fitness)
    current = 0
    for i in range(len(population)):
        current += fitnesses[i]
        if current > pick:
            return population[i]


# 交叉操作(有序交叉)
def ordered_crossover(parent1, parent2):
    size = len(parent1)
    start, end = sorted(random.sample(range(size), 2))

    child = [None] * size
    child[start:end] = parent1[start:end]

    for gene in parent2:
        if gene not in child:
            for i in range(size):
                if child[i] is None:
                    child[i] = gene
                    break
    return child


# 变异操作(交换)
def swap_mutation(route, mutation_rate):
    mutated_route = route.copy()
    for i in range(len(route)):
        if random.random() < mutation_rate:
            j = random.randint(0, len(route) - 1)
            mutated_route[i], mutated_route[j] = mutated_route[j], mutated_route[i]
    return mutated_route


# 遗传算法主函数
def genetic_algorithm(cities, population_size=100, generations=500, crossover_rate=0.8, mutation_rate=0.1):
    # 初始化种群
    population = [random.sample(list(cities.keys()), len(cities)) for _ in range(population_size)]

    for gen in range(generations):
        # 计算适应度
        fitnesses = [fitness(route) for route in population]

        # 选择
        selected = [roulette_wheel_selection(population, fitnesses) for _ in range(population_size)]

        # 交叉
        offspring = []
        for i in range(0, population_size, 2):
            parent1, parent2 = selected[i], selected[i + 1]
            if random.random() < crossover_rate:
                child1, child2 = ordered_crossover(parent1, parent2), ordered_crossover(parent2, parent1)
            else:
                child1, child2 = parent1, parent2
            offspring += [child1, child2]

        # 变异
        mutated_offspring = [swap_mutation(route, mutation_rate) for route in offspring]

        # 更新种群
        population = mutated_offspring

    # 找到最佳路径
    best_route = min(population,key=total_distance)
    best_distance = total_distance(best_route)

    return best_route, best_distance

# 运行遗传算法
best_route, best_distance = genetic_algorithm(cities)
print("最佳路径:", best_route)
print("最短距离:", best_distance)

roulette_wheel_selection

这段代码定义了一个名为roulette_wheel_selection的函数,实现了轮盘赌选择(Roulette Wheel Selection)方法。轮盘赌选择是一种遗传算法中常用的选择操作,它根据染色体的适应度值为每个染色体分配选择概率,适应度高的染色体有更高的概率被选中。这种方法模拟了自然界中适者生存的原则。

函数接收两个参数:population表示当前种群,fitnesses表示种群中每个染色体的适应度值。

以下是该函数的主要步骤:

  1. 计算总适应度:total_fitness = sum(fitnesses)。这一步将所有染色体的适应度值相加,得到总适应度值。

  2. 生成随机数:pick = random.uniform(0, total_fitness)。在区间[0, total_fitness)内生成一个均匀分布的随机数。

  3. 遍历种群:for i in range(len(population))。遍历种群中的每个染色体。

  4. 累计适应度值:current += fitnesses[i]。将当前染色体的适应度值累加到变量current

  5. 检查累计值:if current > pick。如果累计适应度值大于随机数pick,则选择当前染色体并返回:return population[i]。这意味着适应度高的染色体更有可能在累计值超过随机数时被选中。

通过这种方式,轮盘赌选择方法为种群中的染色体分配了一个基于适应度值的选择概率。适应度高的染色体更有可能被选中进入下一代,从而提高算法的搜索效率。

ordered_crossover

这段代码定义了一个名为ordered_crossover的函数,实现了有序交叉(Ordered Crossover,OX)方法。有序交叉是一种遗传算法中针对排列编码问题的交叉操作,用于生成新的后代染色体。在旅行商问题中,城市序列是一个排列编码问题。

函数接收两个参数:parent1和parent2,分别表示两个参与交叉操作的父代染色体。

以下是该函数的主要步骤:

  1. 获取染色体长度:size = len(parent1)。

  2. 随机选择两个交叉点:start, end = sorted(random.sample(range(size), 2))。在染色体长度范围内随机选取两个点并排序,得到交叉点的起始和结束位置。

  3. 初始化子代染色体:child = [None] * size。创建一个与父代相同长度的空子代染色体。

  4. 从父代染色体1复制一段序列:child[start:end] = parent1[start:end]。将父代染色体1中的一段序列(从交叉点起始到结束位置)复制到子代染色体相应位置。

  5. 遍历父代染色体2:for gene in parent2。遍历父代染色体2中的每个城市编号(基因)。

  6. 检查城市编号是否已在子代中:if gene not in child。如果父代染色体2中的当前城市编号不在子代染色体中,则执行下一步。

  7. 将城市编号插入子代染色体:遍历子代染色体for i in range(size),当找到一个空位置(None)时,将城市编号插入该位置child[i] = gene,然后终止循环break。

  8. 返回子代染色体:return child。

通过有序交叉操作,我们可以确保子代染色体在保留父代染色体1的一段序列的同时,从父代染色体2中继承其他城市编号。这种方法有助于在子代中保留父代的部分优良特征,同时增加种群的多样性。

swap_mutation

这段代码定义了一个名为swap_mutation的函数,实现了交换变异(Swap Mutation)方法。交换变异是一种遗传算法中的变异操作,用于在染色体中随机交换两个基因的位置,增加种群的多样性。在旅行商问题中,基因表示城市编号。

函数接收两个参数:route表示待变异的染色体,mutation_rate表示变异率。

以下是该函数的主要步骤:

  1. 复制染色体:mutated_route = route.copy()。创建一个新的染色体,以避免修改原始染色体。

  2. 遍历染色体:for i in range(len(route))。遍历待变异染色体中的每个城市编号。

  3. 判断是否进行变异:if random.random() < mutation_rate。生成一个随机数,如果随机数小于变异率,则执行下一步进行变异操作。变异率决定了变异操作发生的概率。

  4. 随机选择另一个城市编号:j = random.randint(0, len(route) - 1)。在染色体长度范围内随机选取一个城市编号的索引。

  5. 交换两个城市编号:mutated_route[i], mutated_route[j] = mutated_route[j], mutated_route[i]。将当前位置的城市编号与随机选择的另一个城市编号进行交换。

  6. 返回变异后的染色体:return mutated_route。

通过交换变异操作,我们可以在种群中引入新的染色体组合,提高种群的多样性。这有助于算法更全面地搜索解空间,避免陷入局部最优解。

genetic_algorithm

这段代码定义了一个名为genetic_algorithm的主函数,实现了用遗传算法求解旅行商问题(TSP)。函数接收一个参数cities,表示城市与其坐标之间的映射字典,以及可选参数population_size(种群大小,默认为100)、generations(迭代次数,默认为500)、crossover_rate(交叉率,默认为0.8)和mutation_rate(变异率,默认为0.1)。

以下是该函数的主要步骤:

  1. 初始化种群:population = [random.sample(list(cities.keys()), len(cities)) for _ in range(population_size)]。随机生成指定数量的不同路径,形成初始种群。

  2. 进行指定次数的迭代:for gen in range(generations)。

    a. 计算适应度:fitnesses = [fitness(route) for route in population]。计算种群中每个染色体(路径)的适应度值。

    b. 选择操作:selected = [roulette_wheel_selection(population, fitnesses) for _ in range(population_size)]。使用轮盘赌选择方法选取适应度较高的染色体。

    c. 交叉操作:按照指定的交叉率进行有序交叉操作,生成子代染色体。

    d. 变异操作:mutated_offspring = [swap_mutation(route, mutation_rate) for route in offspring]。按照指定的变异率进行交换变异操作,增加种群的多样性。

    e. 更新种群:population = mutated_offspring。用变异后的子代替换当前种群。

  3. 找到最佳路径:best_route = min(population,key=total_distance)。在最终种群中找到具有最短总距离的路径。

  4. 计算最短距离:best_distance = total_distance(best_route)。

  5. 返回最佳路径及其总距离:return best_route, best_distance。

通过这个遗传算法主函数,我们可以求解给定城市集合的旅行商问题,找到一条近似最短的路径。遗传算法是一种启发式搜索方法,因此不能保证找到绝对最优解,但在实际应用中通常能找到较好的解。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐