美文网首页
遗传算法(附代码)

遗传算法(附代码)

作者: 老居搞机 | 来源:发表于2020-08-15 09:48 被阅读0次

    先看个问题:一个旅行商需要去拜访一系列城市,这些城市之间的距离都不相同,怎么才能访问每个城市一次并回到起始城市最短回路?

    这一问题叫旅行商问题(TSP问题),它是组合优化中的一个NP困难问题,当前有很多种解决算法,比如暴力搜索法、随机法或者模拟退火算法、遗传算法、蚁群算法等启发式算法。

    今天来主要讲讲遗传算法是怎么解决此类问题的。

    什么是遗传算法

    遗传算法是一类受生物进化启发的一种随机搜索与优化的算法,它通过变异交叉两种手段在当前已知的最好解来生成后续的解。

    比如说蜜蜂采密,在亿万年的进化过程中,体内藏有一套高效的在不同花朵中寻找最短路劲的算法

    遗传算法流程

    遗传算法运行中会设置种群的规模、交叉率、变异率、保留胜出比例、进化迭代次数等参数,整体流程如下:

    种群
    种群是每代所产生的染色体总数,一个种群包含了该问题在这一代的一些解的集合

    适应度评价
    适应度评价是评估产生的解好坏的标准,如距离越短越好:f(s) = 1/dintance

    选择
    在种群中我们选择top N的优胜者进入下一轮迭代,一般使用按分数排序或者轮盘赌的方式来选择优胜者

    交叉
    交叉是将种群中两组优胜者的解分别拿出一部分,组合成为新的解

    变异

    变异是将种群中一组优胜者的解做一些随机变化,产生新的解

    代码实现

    # -*- encoding: utf8 -*-
    ​
    import random
    import math
    import matplotlib.pyplot as plt
    ​
    ​
    class GA:
        def __init__(self, scale, num, max_gen, pc, elite):
            # 种群规模
            self.scale = scale
            # 染色体数量(城市数量)
            self.num = num
            # 运行代数
            self.max_gen = max_gen
            # 交叉率
            self.pc = pc
            # 每代多少胜出者
            self.elite = elite
    ​
        def crossover(self, tour1, tour2, data):
            # 交叉
            c1 = random.randint(0, len(tour1['tour']) - 1)
            c2 = random.randint(0, len(tour1['tour']) - 1)
    ​
            while c1 == c2:
                c2 = random.randint(0, len(tour1['tour']) - 1)
    ​
            if c1 > c2:
                c1, c2 = c2, c1
    ​
            tour_part = tour1['tour'][c1:c2]
    ​
            new_tour = []
            index = 0
            for i in range(len(tour2['tour'])):
                if index == c1:
                    for item in tour_part:
                        new_tour.append(item)
    ​
                if tour2['tour'][i] not in tour_part:
                    new_tour.append(tour2['tour'][i])
    ​
                index += 1
    ​
            return self.get_tour_detail(new_tour, data)
    ​
        def mutate(self, tour, data):
            # 变异
            c1 = random.randint(0, len(tour['tour']) - 1)
            c2 = random.randint(0, len(tour['tour']) - 1)
    ​
            while c1 == c2:
                c2 = random.randint(0, len(tour['tour']) - 1)
    ​
            new_tour = []
            for i in range(len(tour['tour'])):
                if i == c1:
                    new_tour.append(tour['tour'][c2])
                elif i == c2:
                    new_tour.append(tour['tour'][c1])
                else:
                    new_tour.append(tour['tour'][i])
    ​
            return self.get_tour_detail(new_tour, data)
    ​
        def sort(self, tours):
            # 排序
            tours_sort = sorted(tours, key=lambda x: x['score'], reverse=True)
    ​
            return tours_sort
    ​
        def get_distance(self, c1, c2):
            # 获取距离
            xd = abs(c1['x'] - c2['x'])
            yd = abs(c1['y'] - c2['y'])
            distance = math.sqrt(xd * xd + yd * yd)
    ​
            return distance
    ​
        def get_score(self, distance):
            # 适应性函数 距离越小适应值越大
            return 1 / (distance + 1)
    ​
        def get_tour(self, data):
            # 随机获得一条路径
            tour = []
            for key, value in data.items():
                tour.append(key)
    ​
            random.shuffle(tour)
    ​
            return self.get_tour_detail(tour, data)
    ​
        def get_tour_detail(self, tour, data):
            tmp = None
            distance = 0
            for item in tour:
                if tmp is not None:
                    distance_tmp = self.get_distance(data[tmp], data[item])
                    distance += distance_tmp
    ​
                tmp = item
    ​
            return {'tour': tour, 'distance': distance, 'score': self.get_score(distance)}
    ​
        def initialise(self, data):
            # 初始种群
            tours = []
            for i in range(self.scale):
                tours.append(self.get_tour(data))
    ​
            return tours
    ​
        def get_fittest(self, tours):
            # 获取当前种群中最优个体
            tmp = None
            for item in tours:
                if tmp is None or item['score'] > tmp['score']:
                    tmp = item
    ​
            return tmp
    ​
        def run(self, data):
            pop = self.initialise(data)
            old_fittest = self.get_fittest(pop)
    ​
            topelite = int(self.scale * self.elite)
    ​
            same_num = 0
            max_same_num = 30
            max_score = 0
            for i in range(self.max_gen):
                ranked = self.sort(pop)
                pop = ranked[0:topelite]
    ​
                fittest = self.get_fittest(pop)
                if fittest['score'] > max_score:
                    same_num = 0
                    max_score = fittest['score']
                else:
                    same_num += 1
    ​
                # 最大分数保持n次一直没有变化则退出
                if same_num > max_same_num:
                    break
    ​
                while len(pop) < self.scale:
                    if random.random() < self.pc:
                        c1 = random.randint(0, topelite)
                        c2 = random.randint(0, topelite)
                        while c1 == c2:
                            c2 = random.randint(0, topelite)
    ​
                        tour = self.crossover(ranked[c1], ranked[c2], data)
                    else:
                        c = random.randint(0, topelite - 1)
                        tour = self.mutate(ranked[c], data)
    ​
                    pop.append(tour)
    ​
            print 'total gen:', i
    ​
            print 'before fittest:'
            print old_fittest
    ​
            print 'after fittest:'
            new_fittest = self.get_fittest(pop)
            print new_fittest
    ​
            return new_fittest
    ​
    ​
    def init_data(num):
        data = {}
        def get_random_point():
            # 随机生成坐标
            return {
                'x': random.randint(1, 99),
                'y': random.randint(1, 99)
            }
    ​
        for i in range(num):
            data[i + 1] = get_random_point()
    ​
        return data
    ​
    ​
    def show(citys, tour):
        # 绘图
        plt.bar(left=0, height=100, width=100, color=(0, 0, 0, 0), edgecolor=(0, 0, 0, 0))
        plt.title(u'tsp')
        plt.xlabel('total distance: %s m' % tour['distance'])
    ​
        x = []
        y = []
        i = 0
        for item in tour['tour']:
            city = citys[item]
            x.append(city['x'])
            y.append(city['y'])
    ​
            i += 1
            if i == 1:
                plt.plot(city['x'], city['y'], 'or')
            else:
                plt.plot(city['x'], city['y'], 'bo')
    ​
        plt.plot(x, y, 'g')
        plt.xlim(0.0, 100)
        plt.ylim(0.0, 100)
        plt.show()
    ​
    ​
    if __name__ == '__main__':
        scale, num, max_gen, pc, elite = 50, 10, 1000, 0.8, 0.2
        data = init_data(num)
        ga = GA(scale, num, max_gen, pc, elite)
        new_fittest = ga.run(data)
        show(data, new_fittest)
    
    

    运行效果:

    遗传算法的优缺点

    优点:

    • 不盲目穷举, 是启发式搜索
    • 与问题的领域无关,可快速切换
    • 可并行计算一组种群

    缺点:

    • 计算量大, 不适合纬度高的问题
    • 算法稳定性差

    相关文章

      网友评论

          本文标题:遗传算法(附代码)

          本文链接:https://www.haomeiwen.com/subject/mbpndktx.html