遗传算法(附代码)

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

这一问题叫旅行商问题(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)

运行效果:

遗传算法的优缺点

优点:

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

缺点:

  • 计算量大, 不适合纬度高的问题
  • 算法稳定性差
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容