用python实现遗传算法的编码及初始化

柔性车间调度(FJSP)问题描述如下:n个工件要在m台机器上加工;每个工件包含一道或多道工序;工序顺序是预先确定的;每道工序可以在多台不同加工机器上进行加工;工序的加工时间随加工机器的不同而不同;调度目标是为每到工序选择最合适的机器,确定每台机器上各道工序最佳加工顺序及开工时间,使整个系统的某些性能指标达到最优。

FJSP包括两个子问题:机器选择和工序排序。

机器选择解决的是每道工序在可选机器集中选择哪台机器进行加工的问题;

     工序排序解决的是所有工序在确定加工机器后的排序问题。

注:我个人认为初始化以及明晰的编码方式是每个初学者进入智能优化算法学习的敲门砖,因此,本文从次开始,详细讲解编码及初始化方式,帮初入门的同学,能更快的入门。


编码

编码就是将问题的解用一段码来表示,从而将问题的状态空间与算法的编码空间相对应。         

编码的目的是为了实现算法的交叉、变异等类似生物界的遗传操作。

FJSP的编码方式最常见的是MSOS编码,其由两部分组成:机器部分(machines selection,MS)和工序排序部分(operations sequencing,OS)。

如下图,图中

python 生产计划调度 制造业生产计划排产python_python 生产计划调度

表示对应编码的长度,

python 生产计划调度 制造业生产计划排产python_python 生产计划调度

为一半编码的长度,

python 生产计划调度 制造业生产计划排产python_python 生产计划调度

=总工序数。

python 生产计划调度 制造业生产计划排产python_Max_04

如下图,每个基因位用整数表示

python 生产计划调度 制造业生产计划排产python_初始化_05

其中,

        MS段依次按照工件和工件的工序进行排列,每个整数表示当前工序选择的加工机器在可选机器集的顺序,并不是对应的机器号(即工序的第几个可选机器)。

       OS段中每个基因用工件号直接编码,工件号出现的顺序表示该工件工序间的先后加工顺序,即对染色体从左到右进行编译,对于第

python 生产计划调度 制造业生产计划排产python_算法_06

次出现的工件号

python 生产计划调度 制造业生产计划排产python_初始化_07

,表示该工件

python 生产计划调度 制造业生产计划排产python_初始化_07

的第

python 生产计划调度 制造业生产计划排产python_算法_06

道工序,并且工件号的总出现次数等于该工件的工序总数。

案例 

那么编码在程序中怎么实现呢, 以下面FJSP问题的示例为例

python 生产计划调度 制造业生产计划排产python_初始化_10

# 加工数据

#加工时间
pro_t=[
    [    #工件1
        [1,3,4],    #工序O11
        [5,2,3],    #工序O12
        [2,5,4]     #工序O13
    ],
    [   #工件2
        [3,5,2],    #工序O21
        [3,2,9],    #工序O22
        [7,4,2,3]   #工序O23
    ],
    [   #工件3
        [3,2,7],    #工序O31
        [2,6,1]     #工序O32
    ]
]

#加工机器
pro_m=[
    [    #工件1
        [1,2,3],    #工序O11
        [2,3,5],    #工序O12
        [2,3,4]     #工序O13
    ],
    [   #工件2
        [1,3,5],    #工序O21
        [2,3,4],    #工序O22
        [1,3,4,5]   #工序O23
    ],
    [   #工件3
        [1,2,4],    #工序O31
        [3,4,5]     #工序O32
    ]
]

import random
import copy

#OS随机编码
def encode_OS(pro_t):
    # OS段的编码准备
    os_list=[_ for _ in range(len(pro_t)) for __ in range(len(pro_t[_]))]
    #通过打乱os_list获得一个随机OS段
    random.shuffle(os_list)
    return os_list

#MS段随机编码
def encode_RMS(pro_t):
    # MS段编码准备
    Max_M_num = [len(pro_t[_][__]) for _ in range(len(pro_t)) for __ in range(len(pro_t[_]))]
    # 从可选加工机器中随机选择一个
    ms_list = [random.randint(0, _ - 1) for _ in Max_M_num]
    return ms_list

os_list = encode_OS(pro_t)
ms_list=encode_RMS(pro_t)
print("OS:{},MS:{}".format(os_list,ms_list))

//输出:OS:[1, 1, 0, 2, 1, 0, 2, 0],MS:[1, 0, 2, 1, 1, 2, 1, 0]

注:为方便python运行,基因位的整数从0开始,于是工件1的在列表中则以0来表示。

初始化

            种群初始化是进化算法中是一个关键问题,当然也是可以采用随机的方式生成初始种群。

例如,随机生成5个初始解,只需运行上述encode函数5次即可:

import numpy as np

OS,MS=[],[]
for i in range(5):
    os_list = encode_OS(pro_t)
    ms_list = encode_RMS(pro_t)
    OS.append(os_list)
    MS.append(ms_list)
print("OS:\n{0}\nMS:\n{1}".format(np.array(OS),np.array(MS)))

//输出:
OS:
[[0 0 2 1 1 1 2 0]
 [0 0 0 1 2 2 1 1]
 [0 0 1 0 2 1 1 2]
 [0 2 2 0 0 1 1 1]
 [1 0 2 1 0 1 0 2]]
MS:
[[0 1 2 2 0 1 1 2]
 [1 0 0 2 1 3 1 2]
 [2 2 0 0 1 3 2 2]
 [1 2 2 2 0 2 1 0]
 [1 2 1 2 0 2 1 0]]

        然而,初始解的质量对遗传算法求解的速度和质量有非常大的影响,因此根据问题特点,对种群初始化方式合理设计是使得到优秀算法一个重点。

针对FJSP问题,将介绍几种常见的初始化方式:随机选择、全局选择和局部选择。

注:三种初始化方式由张国辉老师提出,原文可参考华科09年张国辉博士论文[1]第二章,同时其论文发表在SCI二区的Expert Systems with Applications[2]上,并对三种初始化方式做了相应的实验验证,感兴趣的可以看看。

这三种初始化方式主要是作用在机器选择(MS)段编码上的,即工序编码依然采用随机生成,但机器编码的生成则根据调度规则。

由于原文步骤复杂,此处将取其精华去其糟粕,简述之如下:

随机选择

        与上述方式一样,MS段和OS段都随机生成。

局部选择

        OS段随机生成。

        MS段中,局部选择以工件为单位,从第1个工件到最后一个工件顺序进行,选择可选机器集中累计加工时间与对应机器加工该工序的加工时间之和最小的机器。

        其中,机器累计加工时间在换工件后要置零

注:这种情况下此种方式生成的MS段可能完全相同!

#局部选择
def encode_LMS(pro_t,pro_m,M_num):  # M_num:机器数
    ms_list=[]
    for i in range(len(pro_t)):
        ms_load = [0 for _ in range(M_num)]    #以工件为机器累计负荷
        for j in range(len(pro_t[i])):
            # 以工件为机器累计负荷+对应机器加工该工序的加工时间
            ms_load_add = [ms_load[pro_m[i][j][_] - 1] + pro_t[i][j][_] for _ in
                           range(len(pro_t[i][j]))]
            ms_load[pro_m[i][j][np.argmin(ms_load_add)] - 1] = min(ms_load_add)  # 更新机器负荷
            ms_list.append(np.argmin(ms_load_add))
    return ms_list

全局选择

        OS段随机生成。

        MS段中,全局选择以随机工件顺序,选择可选机器集中累计加工时间与对应机器加工该工序的加工时间之和最小的机器。        

          与局部选择不同的是,机器累计加工时间在换工件后不需要置零

def encode_GMS(pro_t,pro_m,M_num,n): # M_num:机器数,n:工件数
    ms_dict={}
    n_list=[_ for _ in range(n)]    #工件集
    ms_load = [0 for _ in range(M_num)]  # 以工件为机器累计负荷
    while n_list!=[]:
        i=random.choice(n_list)     #随机选择一个工件
        n_list.remove(i)   #将选择后的工件从未选工件集中移除
        ms_i=[]
        for j in range(len(pro_t[i])):
            # 以工件为机器累计负荷+对应机器加工该工序的加工时间
            ms_load_add = [ms_load[pro_m[i][j][_] - 1] + pro_t[i][j][_] for _ in
                           range(len(pro_t[i][j]))]
            ms_load[pro_m[i][j][np.argmin(ms_load_add)] - 1] = min(ms_load_add)  # 更新机器负荷
            ms_i.append(np.argmin(ms_load_add))
            ms_dict[i]=ms_i
    ms_sorted=sorted(ms_dict.items(),key=lambda x:x[0])
    ms_list=[]
    for msi in ms_sorted:ms_list.extend(msi[1])
    return ms_list

最后,为方便大家使用,将其规整为类如下:若要运行,请把上述FJSP示例的数据沾下来!

import random
import numpy as np

class Encode:

    def __init__(self,pro_t,pro_m,M_num,n,Pop_size,p_G=0.6,p_L=0.3,p_R=0.1):
        '''
        :param p_G: 全局选择概率
        :param p_L: 局部选择概率
        :param p_R: 随机选择概率
        '''
        self.pro_t=pro_t
        self.pro_m=pro_m
        self.M_num=M_num
        self.n=n
        self.Pop_size=Pop_size
        self.p_G,self.p_L,self.p_R=p_G,p_L,p_R

    def encode_OS(self):
        # OS段的编码准备
        os_list=[_ for _ in range(len(self.pro_t)) for __ in range(len(self.pro_t[_]))]
        #通过打乱os_list获得一个随机OS段
        random.shuffle(os_list)
        return os_list

    #随机选择
    def encode_RMS(self):
        # MS段编码准备
        Max_M_num = [len(self.pro_t[_][__]) for _ in range(len(self.pro_t)) for __ in range(len(self.pro_t[_]))]
        # 从可选加工机器中随机选择一个
        ms_list = [random.randint(0, _ - 1) for _ in Max_M_num]
        return ms_list

    #局部选择
    def encode_LMS(self):  # M_num:机器数
        ms_list=[]
        for i in range(len(self.pro_t)):
            ms_load = [0 for _ in range(self.M_num)]    #以工件为机器累计负荷
            for j in range(len(self.pro_t[i])):
                # 以工件为机器累计负荷+对应机器加工该工序的加工时间
                ms_load_add = [ms_load[self.pro_m[i][j][_] - 1] + self.pro_t[i][j][_] for _ in
                               range(len(self.pro_t[i][j]))]
                ms_load[self.pro_m[i][j][np.argmin(ms_load_add)] - 1] = min(ms_load_add)  # 更新机器负荷
                ms_list.append(np.argmin(ms_load_add))
        return ms_list

    #全局选择
    def encode_GMS(self): # M_num:机器数,n:工件数
        ms_dict={}
        n_list=[_ for _ in range(self.n)]    #工件集
        ms_load = [0 for _ in range(self.M_num)]  # 以工件为机器累计负荷
        while n_list!=[]:
            i=random.choice(n_list)     #随机选择一个工件
            n_list.remove(i)   #将选择后的工件从未选工件集中移除
            ms_i=[]
            for j in range(len(self.pro_t[i])):
                # 以工件为机器累计负荷+对应机器加工该工序的加工时间
                ms_load_add = [ms_load[self.pro_m[i][j][_] - 1] + self.pro_t[i][j][_] for _ in
                               range(len(self.pro_t[i][j]))]
                ms_load[self.pro_m[i][j][np.argmin(ms_load_add)] - 1] = min(ms_load_add)  # 更新机器负荷
                ms_i.append(np.argmin(ms_load_add))
                ms_dict[i]=ms_i
        ms_sorted=sorted(ms_dict.items(),key=lambda x:x[0])
        ms_list=[]
        for msi in ms_sorted:ms_list.extend(msi[1])
        return ms_list

    #生成初始种群
    def Pop_Gene(self):
        Pop=[]
        for i in range(int(self.p_G*self.Pop_size)):
            os_list=self.encode_OS()
            ms_list=self.encode_GMS()
            Pop.append([os_list,ms_list])
        for i in range(int(self.p_L * self.Pop_size)):
            os_list = self.encode_OS()
            ms_list = self.encode_LMS()
            Pop.append([os_list, ms_list])
        for i in range(int(self.p_R * self.Pop_size)):
            os_list = self.encode_OS()
            ms_list = self.encode_RMS()
            Pop.append([os_list, ms_list])
        return Pop

E=Encode(pro_t,pro_m,M_num=5,n=3,Pop_size=10)
Pop=E.Pop_Gene()
print(np.array(Pop))

不知所讲内容是否讲述清楚,若有不甚清楚的地方可见原文内容如下:(但我个人认为原文有些过分复杂化了!)

1、全局选择:工件为随机从工件集中选择一个工件并且机器时间数组从头到尾都不归零。具体过程如下:

python 生产计划调度 制造业生产计划排产python_算法_11

2、局部选择:从工件集的第一个工件开始取,按顺序取出所有工件,机器时间数组每次都要归零

python 生产计划调度 制造业生产计划排产python_算法_12

3、随机选择

python 生产计划调度 制造业生产计划排产python_python 生产计划调度_13

 参考文献

[1]张国辉. 柔性作业车间调度方法研究[D].华中科技大学,2009.

[2]Zhang Guohui,Gao, Liang,Shi, Yang.An effective genetic algorithm for the flexible job-shop scheduling problem[J].Expert Systems with Applications,2011,38(4):3563-3573