遗传算法(GA)介绍与Python实现

github代码:https://github.com/dongniu0927/algorithm-hub/blob/master/%E4%BC%98%E5%8C%96%E7%AE%97%E6%B3%95/ga.ipynb

概述

GA算法可以运用在求解复杂的找最优解的问题上,但它不保证一定能找到全局最优解。

问题描述

定性描述

我们通过0-1背包问题来介绍GA算法,0-1背包问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。

定量描述

  • 物体总数: N
  • 背包可容纳总重量: W
  • 第i件物体的重量:w[i]
  • 第i件物体的价格: v[i]

进化论知识

GA算法参考了进化论,我们有必要来了解下相关知识:

  1. 种群(Population):生物的进化以群体的形式进行,这样的一个群体称为种群。种群的生物学定义为:指在一定时间内占据一定空间的同种生物的所有个体。
  2. 个体:组成种群的单个生物。
  3. 基因(Gene):DNA分子上的一个小片段,一个遗传因子。
  4. 染色体(Chromosome):由DNA和蛋白质组成,包含一组基因。
  5. 进化过程:生物在繁殖过程中,会发生基因交叉( Crossover ) ,基因突变 ( Mutation ) ,适应度( Fitness )低的个体会被逐步淘汰,而适应度高的个体会越来越多。那么经过N代的自然选择后,保存下来的个体都是适应度很高的。

GA算法

简介

遗传算法是受达尔文的进化论的启发,借鉴生物进化过程而提出的一种启发式搜索算法。借鉴生物进化论,遗传算法将要解决的问题模拟成一个生物进化的过程,通过复制、交叉、突变等操作产生下一代的解,并逐步淘汰掉适应度函数值低的解,增加适应度函数值高的解。

算法步骤

  1. 编码
  2. 选择
  3. 交叉
  4. 变异

编码

编码是为了模拟进化论中的基因与染色体部分。这里每一个物体可以被取或者不被取,则染色体可以用长度为N的二进制表示,它有N个基因,每个基因有两个可能的状态(0 or 1)。
N=4,则一个个体<取,取,不取,不取>可以表示为:1(未编码) 0001(编码后)

#  假设一个未编码的个体表示为:取,取,不取,不取,可使用10进制数12表示
def encode(N, unit):  #  N:染色体长度(如4);unit:个体表示(如12)
    unit = int(unit)
    unit_str = str(bin(unit))[2:].zfill(N)  # 左侧补0
    unit_list = []
    for s in unit_str:
        unit_list.append(s)
    return unit_list
def decode(unit_list):
    l = ll = len(unit_list) - 1
    c = 0
    while l>=0:
        if unit_list[l] == '1':
            c +=  pow(2, ll - l)
        l -= 1
    return c

print(encode(4, 1))
print(decode(['0', '0', '0', '1']))
['0', '0', '0', '1']
1

选择

按照某些策略选择个体来产生后代,常用的策略有如:轮盘赌算法(Roulette Wheel Selection), 锦标赛, 精英保留策略等。我们使用轮盘赌算法。
轮盘赌算法的思路为:个体被选中的概率与其适应度函数值成正比
本题的适应度函数即为衡量总价值的函数:$f(x_i)$(其中$x_i$为第i个个体,它可能为:1100)
轮盘赌中每个个体被抽中的概率为:$p_i=\dfrac{f(x_i)}{\sum_{j=1}^{n}f(x_j)} $ (本文个体总数$n=2^N$)

轮盘赌算法的基本实现思路为:通过区间长度来表示概率,再通过随机数选择。详细可参考:https://www.cnblogs.com/gaosheng12138/p/7534956.html

import random

# 计算种群的适应性概率
def getRWSPList(population, w, v, W):  # population:总群;w:物体重量list;v:物体价值list;W:背包的重量阈值
    n = len(population)  # 群体总数
    v_list = []  # 价值list
    for i in population:
        unit_code = encode(N, i)  # 获得编码
        unit_w = 0  # 个体的总量
        unit_v = 0  # 个体的价值
        for j in range(N):
            unit_w += int(unit_code[j]) * w[j]
            unit_v += int(unit_code[j]) * v[j]
        if unit_w <= W:
            v_list.append(unit_v)
        else:
            v_list.append(0)  # 超重
    p_list = []  # 每一个个体的概率
    v_all = sum(v_list)
    for i in range(n):
        p_list.append(v_list[i] * 1.0 / v_all)
    return p_list

# 根据适应性概率随机选择一个个体
def RWS(population, plist):  # plist为总群个体抽中概率list
    random.seed()
    r = random.random()  # 获得随机数
    c = 0
    # print plist, r
    for (index, item) in enumerate(plist):
        c += item
        if r < c:
            return population[index]

N = 4
n = pow(2, N)  # 种群个体总数
w = [2, 3, 1, 5] 
v = [4, 3, 2, 1]
W = 6
population = []
# 初始化种群
for i in range(n):
    population.append(i)
print("Original population:",population)
# 种群选择
plist = getRWSPList(population, w, v , W)  # 获得总群概率list
new_population = []
for i in range(n):  # 适者生存
    new_population.append(RWS(population, plist))
new_population = list(set(new_population))
print("New population:",new_population)
Original population: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
New population: [3, 4, 6, 10, 12, 14]

交叉

交叉指的是两个个体交换染色体片段然后产生两个新的后代。其步骤如下:

  1. 随机匹配多对父母
  2. 每对个体按照一定概率一定规则交叉染色体
  3. 新产生的个体组成新的种群

交叉操作存在着多种方式,例如:多点杂交、均匀杂交,离散杂交、中间杂交、线性杂交和扩展线性杂交等算法

#  获得随机couple组
def getRandomCouple(n):  # n:个体总数
    random.seed()
    selected = [0]*n  # 是否被选择了
    couples = []  # 配对list
    for i in range(n//2):
        pair = []
        while len(pair) < 2:
            unit_index = random.randint(0, n-1)
            if not selected[unit_index]:
                pair.append(unit_index)
                selected[unit_index] = True
        couples.append(pair)
    return couples

couples = getRandomCouple(len(new_population))  # 获得随机配对
print("Random pair:", couples)

def crossover(population, couples, cross_p, N):  # cross_p为交叉概率;N为编码长度
    random.seed()
    new_population = []
    for pair in couples:
        unit_one = encode(N, population[pair[0]])
        unit_two = encode(N, population[pair[1]])
        p = random.random()
        if p >= (1 - cross_p):
            # 交叉使用从随机位置交叉尾部
            random_loc = random.randint(0, N-1)  # 获得随机位置
            new_population.append(unit_one[0:random_loc] + unit_two[random_loc:])
            new_population.append(unit_two[0:random_loc] + unit_one[random_loc:])
        else:
            new_population.append(unit_one)
            new_population.append(unit_two)
    for (index, unit) in enumerate(new_population):
        new_population[index] = decode(unit)  # 解码
    return list(set(new_population))

new_population = crossover(new_population, couples, 0.8, N)
print("After crossover:", new_population)
Random pair: [[1, 0], [4, 2], [6, 7], [5, 3]]
After crossover: [0, 3, 6, 8, 10, 12, 14]

变异

变异是指某一些个体的染色体上的基因发生突变,如个体1100->1000(单点突变)。

def mutation(population, N, mutation_p):
    # print(population, N, mutation_p)
    new_population = []
    random.seed()
    for unit in population:
        unit_code = encode(N, unit)
        p = random.random()  # 获得随机概率
        if p > (1 - mutation_p):
            random_loc = random.randint(0, N-1) 
            v = unit_code[random_loc]
            unit_code[random_loc] = '0' if v=='1' else '1'
        new_population.append(decode(unit_code))
    return list(set(new_population))

print("After mutation:",mutation(new_population, N, 0.1))
After mutation: [0, 3, 6, 8, 12, 14]

整体编码

下面展示整个代码流程。

# 变量设置
generation_count = 50  # 迭代次数
N = 4  # 物体总数
n = pow(2, N)  # 种群个体总数
w = [2, 3, 1, 5]  # 每个物体重量
v = [4, 3, 2, 1]  # 每个物体价值
W = 6  # 重量阈值
population = [] 

# 初始化种群
for i in range(n):
    population.append(i)
print("Original population:",population)

# 算法开始
c = 0 # 当前迭代次数
while c < generation_count:
    print('-'*10+str(c)+'-'*10)
    
    # 种群选择
    plist = getRWSPList(population, w, v , W)  # 获得总群概率list
    new_population = []
    for i in range(n):  # 适者生存
        new_population.append(RWS(population, plist))
    new_population = list(set(new_population))
    print("After selection:",new_population)
    if len(new_population) == 1:
        population = new_population
        break
    
    # 种群交叉
    couples = getRandomCouple(len(new_population))  # 获得随机配对
    new_population = crossover(new_population, couples, 0.8, N)
    print("After crossover:", new_population)
    if len(new_population) == 1:
        population = new_population
        break
    
    # 种群变异
    new_population = mutation(new_population, N, 0.1)
    print("After mutation:"+ str(new_population))
    if len(new_population) == 1:
        population = new_population
        break
    
    population = new_population
    
    c += 1

print(population)

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

推荐阅读更多精彩内容