Resolución de problemas TSP basada en algoritmo genético (implementación de python)

Descripción del problema

Suponiendo que un hombre de negocios que viaja quiere visitar N ciudades, debe elegir el camino que quiere tomar. El límite del camino es que cada ciudad solo se puede visitar una vez, y al final debe regresar a la ciudad de origen original. El objetivo de la selección de ruta es que la distancia de ruta requerida sea el valor mínimo entre todas las rutas

Requisitos: consulte el código central del algoritmo genético y tome el problema TSP como ejemplo para realizar el programa de solución del algoritmo genético

En este ejemplo de problema, generamos aleatoriamente 20 ciudades y usamos las coordenadas (x, y) en el sistema de coordenadas cartesianas planas para representar la ubicación de la ciudad.

Soluciones

El algoritmo genético se utiliza para resolver el problema TSP, y utiliza las características de que es más probable encontrar la solución óptima global; a través de la reproducción continua de la población, simula el proceso de encontrar iterativamente el valor mínimo del camino. Los pasos específicos de implementación son los siguientes:

  1. Inicialice la población: la población está compuesta de individuos, y un individuo corresponde a una solución candidata efectiva en el problema; en el algoritmo genético, el cromosoma se usa para representar al individuo, y cuando se asigna al problema TSP, un camino efectivo representa un individuo; por lo tanto, podemos asegurarnos de que, en la siguiente descripción, el cromosoma y representan un camino válido

  1. Calcule la aptitud de la población: la aptitud refleja la probabilidad de que los individuos sobrevivan en la población y produzcan descendencia.En el problema TSP, debido a que el propósito de cada iteración es encontrar un camino más corto, cuanto más corta sea la longitud del camino del individuo, más fácil será retener en el proceso iterativo, y la aptitud es mayor

  1. Selección natural: Selección de individuos ventajosos de la población actual; utilizo el método de selección por torneo:

  1. Cada vez que se seleccionan aleatoriamente tres individuos de la población (es decir, método de selección de triple torneo) como muestra

  1. Seleccione el que tenga la mejor aptitud (es decir, el esquema con el camino más corto) en la muestra e ingrese la población de la próxima generación

  1. Repita los pasos ab hasta que la nueva población tenga el mismo tamaño que la población original

  1. Crossover: seleccionar individuos en la nueva población generada por selección natural, y crear descendencia.En el algoritmo genético, se realiza mediante el intercambio de cromosomas entre dos individuos seleccionados;

El "cruce de un solo punto" se selecciona en la operación de cruce de esta pregunta:

  1. establecer la probabilidad de un cruce

  1. Seleccionar un individuo de cada una de las dos nuevas poblaciones como padre del individuo descendiente

  1. Si no hay cruce, uno de los padres se selecciona al azar como el individuo descendiente

  1. Si se produce un cruce, realice los siguientes pasos:

  1. Elija un padre (o madre), cuyos cromosomas se copian para la descendencia

  1. Interceptar aleatoriamente un trozo de cromosoma de la madre (o del padre) para reemplazar el cromosoma en la posición correspondiente de la descendencia

  1. Generar nueva descendencia a partir de este

  1. Repita los pasos abcd hasta que la población de descendientes sea del mismo tamaño que la población de la generación anterior.

  1. Mutación: Cambiar algunos genes de cada individuo recién creado (dado que los cromosomas están formados por genes, lo que cambia es una determinada posición en el cromosoma); la probabilidad de mutación suele ser baja

La operación de mutación para este problema es "mutación de intercambio": seleccionar al azar dos genes en un cromosoma, intercambiar sus posiciones y formar un nuevo cromosoma

  1. Estrategia elitista: puede garantizar que los mejores individuos de la población entren en la siguiente generación; a través de la selección, el cruce y la mutación, la descendencia generada a partir de la población de la generación anterior no se utiliza directamente para formar la población descendiente, sino que se compara con la de la población de la generación anterior. población de la generación anterior Se comparan los individuos y se seleccionan más individuos excelentes para ingresar a la población de descendientes, a fin de garantizar que la población de descendientes generada por cada iteración esté compuesta por los mejores individuos, y la solución óptima se puede obtener más rápido

  1. Selección de condiciones de terminación: establezca el número de iteraciones, el algoritmo terminará cuando se alcance el número de iteraciones y se generará la mejor solución

código fuente del algoritmo

import math
import random
import time
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pylab import mpl
import numpy as np


# 计算适应度
def compute_fitness(pop_line,distance):
    sum_dis = 0
    temp_dis = 0
    nums = len(pop_line)
    for i in range(nums):
        if i < nums - 1:
            temp_dis = distance[pop_line[i],pop_line[i+1]]
            sum_dis += temp_dis
        else:
            temp_dis = distance[pop_line[i],pop_line[0]]
            sum_dis += temp_dis
    return sum_dis

# 轮盘赌选择(不太好实现,先不用了)
# def select(pops,fitness):
#     fitness = np.array(fitness)
#     index = np.random.choice(np.arange(POP_SIZE), size=1, replace=True,p=( fitness / fitness.sum()))[0]
#     print(fitness[index])
#     return pops[index]

# 自然选择,采用锦标赛选择
def tournament_select(pops,pop_size,fitness,tournament_size):
    new_pops = []
    new_fitness = []
    # 直到新的种群规模到达当前种群规模
    while len(new_pops) < len(pops):
        # 从原始样本中选出的样本
        checked_list_pop = [random.randrange(0,pop_size) for i in range(0,tournament_size)]
        checked_list_fitness = np.array([fitness[i] for i in checked_list_pop])
        min_fitness = checked_list_fitness.min() # 最小适应度
        idx = np.where(checked_list_fitness == min_fitness)[0][0] # 获取索引
        min_pop = pops[idx] #获取对应的个体
        new_pops.append(min_pop) #放入新的种群中
        new_fitness.append(min_fitness)
    # print(new_pops)
    return new_pops #返回新的种群

# 交叉算子 选择单点交叉
def crossover(pop_size,pops_parent1,pops_parent2,trans_rate):
    pops_children = []
    # 双亲交叉一次得到一个子代
    for i in range(0,pop_size):
        child = [None] * len(pops_parent1[i])
        parent1 = pops_parent1[i]
        parent2 = pops_parent2[i]
        # print("双亲为:")
        # print(parent1)
        # print(parent2)
        if random.random() > trans_rate:
            # 不发生交叉,则随机保留父代中的一个
            if random.random() > 0.5:
                child = parent1.copy()
            else:
                child = parent2.copy()
        else:
            #发生交叉
            #确定交叉的位置
            begin = random.randrange(0,len(parent1))
            end = random.randrange(0,len(parent1))
            if begin > end:
                temp = begin
                begin = end
                end = temp
            child[begin:end+1] = parent1[begin:end+1].copy()
            list1 = list(range(end+1,len(parent2)))
            list2 = list(range(0,begin))
            list_idx = list1 + list2
            j = -1
            for i in list_idx:
                for j in range(j+1,len(parent2)):
                    if parent2[j] not in child:
                        child[i] = parent2[j]
                        break
            #print("子代为:",child)
        pops_children.append(child)
    return pops_children

#变异算子 采用交换突变
def mutate(pop_size,pops,mutation_rate):
    pops_mutated = []
    for i in range(pop_size):
        child = pops[i].copy()
        if random.random() < mutation_rate:
            # 发生变异
            pos_first = random.randrange(0,len(child))
            pos_second = random.randrange(0,len(child))
            if pos_first != pos_second:
                temp = child[pos_first]
                child[pos_first] = child[pos_second]
                child[pos_second] = temp
        pops_mutated.append(child)
    return pops_mutated

# elitism策略处理(精英注意)
def elitism(pop_size,pops,children_pops,pop_fitness,children_fitness):
    #如果父代适应度更高的话,则不会被淘汰
    for i in range(0,pop_size):
        if pop_fitness[i] > children_fitness[i]:
            pops[i] = children_pops[i]
            pop_fitness[i] = children_fitness[i]
    return pops,pop_fitness

# 画路径图
def draw_path(line, CityCoordinates):
    x, y = [], []
    for i in line:
        Coordinate = CityCoordinates[i]
        x.append(Coordinate[0])
        y.append(Coordinate[1])
    x.append(x[0])
    y.append(y[0])
    plt.plot(x, y)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.show()


if __name__ == '__main__':

    CITY_NUM = 20  # 城市数量
    ITERATIONS = 1000  # 终止条件的选择 种群繁衍1000代终止
    POP_SIZE = 100  # 种群大小
    TOURNAMENT_SIZE = 2  # 锦标赛采样大小
    TRANS_RATE = 0.7  # 交叉概率
    MUTATION_RATE = 0.1  # 变异概率

    # 获取城市坐标(可以读取文件获取或者随机生成)
    coordinates = [] # 城市坐标
    with open("data.txt","r") as f:
        lines = f.readlines()
    for line in lines:
        line = line.strip()
        coordinates.append(list(map(int,line.split(" "))))
    # coordinates = 100 * np.random.rand(20, 2)  # 随机产生20个城市
    coordinates = np.array(coordinates)
    row,col = coordinates.shape


    #计算各个城市之间的距离
    distance = np.zeros((row,row))
    for i in range(row):
        for j in range(row):
            distance[i,j] = distance[j,i] = math.sqrt((coordinates[i][0] - coordinates[j][0]) ** 2 + (coordinates[i][1] - coordinates[j][1]) ** 2 )
    #print(distance)

    iteration = 0 #迭代次数为0

    #生成种群 100个 每一种路径的选择相当于一条染色体
    pops = [random.sample([i for i in list(range(len(coordinates)))],len(coordinates)) for j in range(POP_SIZE)]
    #print(pops)

    #计算适应度
    pop_fitness = [0] * POP_SIZE
    for i in range(POP_SIZE):
        pop_fitness[i] = compute_fitness(pops[i],distance)
    # print(pop_fitness)

    # 找到初代最优解
    min_pop_fitness = min(pop_fitness)
    optimal_pop = pops[pop_fitness.index(min_pop_fitness)]
    print("第1代的最短距离为:",min_pop_fitness)
    optimal_pops = [optimal_pop]

    start_time = time.perf_counter()
    x = []
    y = []

    # 开始迭代
    while iteration <ITERATIONS:
        pops_parent1 = tournament_select(pops,POP_SIZE,pop_fitness,TOURNAMENT_SIZE)
        pops_parent2 = tournament_select(pops,POP_SIZE,pop_fitness,TOURNAMENT_SIZE)

        # 双亲交叉
        pops_children = crossover(POP_SIZE,pops_parent1,pops_parent2,TRANS_RATE)
        # print(pops_children)

        # 变异
        pops_mutated = mutate(POP_SIZE,pops_children,MUTATION_RATE)
        # print(pops_mutated)

        # 统计发生变异的个体
        # print( "发生变异的个体有%d个"  %(len(np.where(np.array(pops_children) != np.array(pops_mutated))[0]) / 2) )

        # 获取子代种群
        children_fitness = []
        for i in range(POP_SIZE):
            children_fitness.append(compute_fitness(pops_mutated[i],distance))

        # 应用精英策略
        pops,pop_fitness = elitism(POP_SIZE,pops,pops_children,pop_fitness,children_fitness)

        # 找到当代最优解
        if min_pop_fitness > min(pop_fitness):
            min_pop_fitness = min(pop_fitness)
            optimal_pop = pops[pop_fitness.index(min_pop_fitness)]

        optimal_pops.append(optimal_pop)
        print("第%d代的最短距离为:" % (iteration+2),min_pop_fitness)
        # XY是趋势图的横轴和纵轴
        x.append(iteration)
        y.append(min_pop_fitness)
        iteration += 1

    print("最佳路径为:",optimal_pop)
    end_time = time.perf_counter()

    # 根据绘制每一代最优解的走向图
    plt.figure()
    plt.rcParams['font.sans-serif'] = ['SimHei']
    title = "迭代次数:"+str(ITERATIONS)+"种群大小:"+ str(POP_SIZE)+"\n"+"交叉概率:"+  str(TRANS_RATE) +"变异概率:" + str(MUTATION_RATE)
    plt.title(
        label=title,
        fontdict={
            "fontsize": 20,  # 字体大小
            "color": "black",  # 字体颜色
        }
    )
    plt.plot(x, y)
    plt.show()

    #画路径图
    # draw_path(optimal_pop,coordinates)
    print("迭代所需时间为:%f ms" % ((end_time-start_time)*1000))

Supongo que te gusta

Origin blog.csdn.net/qq_51235856/article/details/129737332
Recomendado
Clasificación