Python 遗传算法路径规划

        了却一个心愿

        遗传算法(Genetic Algorithm,GA)最早是由美国的 John holland于20世纪70年代提出,该算法是根据大自然中生物体进化规律而设计提出的。是模拟达尔文生物进化论自然选择遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。相信对于路径规划来说,这种方法其实也是一种目前较好的寻找最优解的方法。


一、遗传算法原理

        原理都是一样的,有很多博客都写得很好,其理论的都是万变不离其宗。

二、主要内容

        虽然理论摆在那里,有人能够看懂,有人看不懂,我也是只能看懂一点点,这就使得理论运用到代码当中就变得相当困难了,所以对于路径规划这样的题目,我想自己编出一个合适的遗传算法几乎就不可能了,还好在github里面发现了一个关于遗传算法TSP问题的代码,代码简单易懂(注释多),我自己试着运行了一下,发现效果也还不错,运行时间短,结果也不会总变来变去(几乎就是正确答案)。

GItHub链接

大哥的视频讲解

三、使用步骤

1.将压缩包下载解压

会发现有如下三个代码文件:

config.py:各参数配置
ga.py:遗传算法实现
main.py:程序入口,数据预处理,效果展示

 ga.py:就用了Python的一两个基础库,强我只能说。

from config import ConfigParser
import random

city_dist_mat = None
Cf = ConfigParser(city_nums=101, gen_nums=10000)
config = Cf.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, ifplot=False):
        # 初代种群
        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

 config.py:我为了运行时调参,将其改为类帮助我不用每次都要到隔壁去修改参数

# -*- coding: utf-8 -*-
import argparse

class ConfigParser:
    def __init__(self, city_nums=101, pos_dimensions=2, individual_nums=60, gen_nums=1000, mutate_probs=0.25):
        '''
        参数调整,默认值可修改
        :param city_num: 城市数量
        :param pos_dimension: 坐标维度
        :param individual_num: 个体数
        :param gen_num: 迭代轮数
        :param mutate_prob: 变异概率
        :return: 参数说明
        '''
        self.parser = argparse.ArgumentParser(description='Configuration file')
        self.arg_lists = []
        # Data
        data_arg = self.add_argument_group('Data')
        data_arg.add_argument('--city_num', type=int, default=city_nums, help='city num')  # 城市数量
        data_arg.add_argument('--pos_dimension', type=int, default=pos_dimensions, help='city num')  # 坐标维度
        data_arg.add_argument('--individual_num', type=int, default=individual_nums, help='individual num')  # 个体数
        data_arg.add_argument('--gen_num', type=int, default=gen_nums, help='generation num')  # 迭代轮数
        data_arg.add_argument('--mutate_prob', type=float, default=mutate_probs, help='probability of mutate')  # 变异概率

    def add_argument_group(self, name):
        arg = self.parser.add_argument_group(name)
        self.arg_lists.append(arg)
        return arg

    def get_config(self):
        self.config, self.unparsed = self.parser.parse_known_args()
        return self.config

    def print_config(self):
        print('\n')
        print('Data Config:')
        print('* city num:', self.config.city_num)
        print('* individual num:', self.config.individual_num)
        print('* generation num:', self.config.gen_num)
        print('* probability of mutate:', self.config.mutate_prob)

if __name__ == '__main__':
    Cf = ConfigParser(city_nums=101, gen_nums=10000)
    config = Cf.get_config()
    print("路径个数:", config.city_num)
    print("迭代次数:", config.gen_num)

main.py:我将ga.py里的代码拷到这里来了,所以没有调用ga,py,方便调参 

import numpy as np
import sys
sys.path.append(r'D:\86176\PycharmProjects\pythonProject\Interesting argrism\ga-tsp-main')
from config import ConfigParser
import matplotlib.pyplot as plt
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['KaiTi']  # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False  # 解决保存图像是负号'-'显示为方块的问题
from haversine import haversine
import time
import random

Cf = ConfigParser(city_nums=101, gen_nums=10000)
config = Cf.get_config()
print("路径个数:", config.city_num)
print("迭代次数:", config.gen_num)
# 各项参数
gene_len = config.city_num
individual_num = config.individual_num
gen_num = config.gen_num
mutate_prob = config.mutate_prob

def build_dist_mat(input_list):
    n = config.city_num
    dist_mat = np.zeros([n, n])
    for i in range(n):
        for j in range(i + 1, n):
            d = input_list[i, :] - input_list[j, :]
            # 计算点积
            dist_mat[i, j] = np.dot(d, d)
            dist_mat[j, i] = dist_mat[i, j]
    return dist_mat

def dist_ance(sj):
    n = config.city_num
    distance = np.zeros([n, n])
    for i in range(n):
        for j in range(i + 1, n):
            distance[i, j] = haversine(sj[i, :], sj[j, :])
            distance[j, i] = distance[i, j]
    return distance

#######################################
# 遗传算法主要部分GA
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]
        # 迭代
        # plt.figure()
        # plt.ion()
        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)
            # plt.plot(self.fitness_list)
            # plt.pause(0.01)
        # plt.title(u"适应度曲线")
        # plt.legend()
        return self.result_list, self.fitness_list

if __name__ == '__main__':
    Start = time.time()
    # 城市坐标
    # 读取数据
    city_pos_list = np.loadtxt(r'D:\86176\PycharmProjects\pythonProject\sj.txt', dtype=np.float32)
    city_pos_list = np.vstack([
        city_pos_list[:, [2 * i, 2 * i + 1]]
        for i in range(4)
    ])
    d0 = np.array([70, 40])
    city_pos_list = np.vstack([d0, city_pos_list])
    # 城市距离矩阵
    city_dist_mat = dist_ance(city_pos_list)

    # print(city_pos_list)
    # print(city_dist_mat)

    # 遗传算法运行
    ga = Ga(city_dist_mat)
    result_list, fitness_list = ga.train()
    result = result_list[-1]
    idx = result.index(0)
    result = result[idx:-1]
    result.extend(result_list[-1][:idx])
    result.append(0)
    result_pos_list = city_pos_list[result, :]



    # fig = plt.figure()
    # plt.plot(result_pos_list[:, 0], result_pos_list[:, 1], 'o-r')
    # plt.title(u"路线")
    # plt.legend()
    # fig.show()

    # 画图
    # plt.figure()
    # plt.ion()
    # # plt.plot(fitness_list)
    # xx = [fitness_list[0]]
    # for i in range(1, len(fitness_list)):
    #     xx.append(fitness_list[i])
    #     plt.plot(xx)
    #     plt.pause(0.01)
    # plt.title(u"适应度曲线")
    # plt.legend()
    # plt.show()

    plt.figure()
    plt.ion()
    xs = [result_pos_list[0, 0]]
    ys = [result_pos_list[0, 1]]
    for i in range(1, len(result_pos_list)):
        xs.append(result_pos_list[i, 0])
        ys.append(result_pos_list[i, 1])
        plt.plot(xs, ys, '-o')
        plt.pause(0.1)
    End = time.time()

    print("用时", End - Start, '秒')
    print('运行路线为:')
    for i in result:
        print(i, end=' ')

    print("\n最短总距离为:", fitness_list[-1])

2.读入数据

        用的是一个飞机要经过的100个城市坐标(经纬度),所以在做的过程中要考虑经纬度之间距离的直接转化。飞机的起始坐标是(70,40),要经过下面的所有地点并回到起点。

经纬度坐标
经度纬度经度纬度经度纬度经度纬度
53.712115.304651.17580.032246.325328.275330.33136.9348
56.543221.418810.819816.252922.789123.104510.158412.4819
20.10515.45621.94510.205726.495122.122131.48478.964
26.241818.17644.035613.540128.983625.987938.472220.1731
28.269429.001132.1915.869936.486329.72840.971828.1477
8.958624.663516.561823.614310.559715.117850.211110.2944
8.15199.532522.107518.55690.121518.872648.207716.8889
31.949917.63090.77320.465647.413423.778341.86713.5667
43.54743.906153.352426.725630.816513.459527.71335.0706
23.92227.630651.961222.851112.793815.73074.95688.3669
21.505124.090915.254827.21116.2075.144249.24316.7044
17.116820.035434.168822.75719.44023.9211.581214.5677
52.11810.40889.555911.421924.45096.563426.721328.5667
37.584816.847435.66199.933324.46543.16440.77756.9576
14.470313.636819.86615.12243.16164.242818.524514.3598
58.684927.148539.516816.937156.508913.70952.521115.7957
38.438.464851.818123.01598.998323.64450.115623.7816
13.79091.95134.057423.39623.06248.431919.98575.7902
40.880114.297858.828914.522918.66356.743652.842327.288
39.949429.511447.509924.066410.112127.266228.781227.6659
8.083127.67059.155614.130453.79890.219933.6490.398
1.349616.835949.98166.082819.363517.662236.954523.0265
15.73219.569711.511817.388444.039816.263539.713928.4203
6.990923.180438.339219.99524.654319.605736.99824.3992
4.15913.185340.1420.30323.98769.40341.108427.7149

        求最短路径。

3.最终结果

        只需修改运行main.py中的文件就行了

        将结果坐标放入直角坐标中展示

迭代结果:

 

实时迭代结果的展示可能会使得算法在计算时速度会减慢,如果电脑好那就另说了,不建议实时结果展示。

运行路线为:
0 44 66 1 91 81 47 71 13 26 9 83 17 39 78 76 30 96 84 64 63 10 93 69 18 62 61 25 28 33 65 89 85 7 38 77 46 56 27 87 60 48 67 6 24 22 57 80 21 70 36 31 23 12 72 11 52 88 5 95 54 55 20 98 99 43 37 53 4 74 32 3 40 90 15 68 75 59 8 14 49 79 97 50 41 19 29 73 82 86 58 100 51 45 92 42 35 94 34 2 16 0 
最短总距离为: 40546.764132575256


总结

        这是我在之前国赛训练训练时的一个题目,虽然很简单,但是当时是真的觉得很难,记录一下,希望对有需要的同学有帮助,其实我现在是对这个算法理解的也还算深了,因为和它对抗了很久,总是想找一个合适的Python算法没找到,自己写的也不尽人意,这也算是我对自己这一模块结束的标志了吧。

  • 13
    点赞
  • 120
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
遗传算法是一种模拟生物进化过程的优化算法,它可以用来解决路径规划问题。在Python中,我们可以利用遗传算法来找到最优路径。 首先,需要定义问题的目标和限制条件。路径规划问题需要明确起点和终点,定义距离和方向的适应度函数,以及确定遗传算法的各个参数,如种群大小、变异率等。 然后,需要生成初始种群。可以随机生成一些路径作为初始个体,也可以利用启发式算法生成一些较好的初始解。每个个体可以使用列表或数组来表示路径,其中每个元素代表一个节点。 接下来,通过选择、交叉和变异等操作,进行迭代优化。选择操作可以利用适应度函数来评估个体的优劣,选择适应度较高的个体作为父代。交叉操作是将两个个体的某一部分路径进行交换,以生成新的个体。变异操作是对个体的某个路径进行微小的随机变动,以增加种群的多样性。 重复进行选择、交叉和变异操作,直到达到预定的迭代次数或满足终止条件为止。最终得到的最优个体即为路径规划问题的解,即从起点到终点的最优路径。 在Python中,我们可以使用numpy和random等库来实现遗传算法路径规划。需要编写适应度函数、选择函数、交叉函数和变异函数等,并进行迭代优化。最后,可以根据需求输出最优路径。 总之,利用遗传算法可以解决路径规划问题,通过选择、交叉和变异等操作来寻找最优路径。在Python中,我们可以借助numpy和random等库来实现路径规划遗传算法
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值