ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于非支配排序小龙虾优化算法求解柔性作业车间调度

基于非支配排序小龙虾优化算法求解柔性作业车间调度 在车间里盯过排产的人多少都有过这种经历机器闲着等工件另一头的工件却在排队等机器。你刚把A订单插到前面B订单的交期就报警了。传统调度通常只盯着一个目标——最快把活干完但现实里设备负载、能耗、拖期成本往往互相打架。这就是为什么柔性作业车间调度问题FJSP这些年在智能制造领域特别受关注。它允许工序跨机器选择加工多了柔性也多了决策维度。再往下想一步调度目标往往不止一个这时候就需要一套能同时输出多个折中方案的多目标优化算法。今天要聊的方案很直接用基于非支配排序的小龙虾优化算法NSCOA来求解FJSP。它的思路是把2023年提出的小龙虾优化算法COA从连续优化域搬到离散调度域再借鉴NSGA-II里的非支配排序与拥挤度距离让算法最终输出一组互不支配的Pareto调度方案。下面我会把问题建模、算法改造、编码解码、Matlab代码骨架和实验验证全部串起来讲适合刚入门车间调度的研究生也适合想尝试新算法替换传统排产方案的工程师。1. 从车间现场说开去柔性调度为什么比“排个顺序”麻烦得多1.1 比经典JSP多出来的自由度严格来说柔性作业车间调度问题是经典作业车间调度问题JSP的扩展。JSP里每个工件的工艺路线是固定的每道工序分配到哪台机器也是写死的。到了FJSP里工厂不再只有一组专用设备而是同一台机器能加工多种工序同一道工序也可能被多台机器胜任。于是每道工序前面多了一个选择到底把活放到哪台机器上干。这个“自由度”听起来只是多选一次但影响是连锁的。举个常见场景某道钻孔工序在M1上耗时8分钟在M2上耗时10分钟单看时间肯定选M1。但如果M1接下来要连续加工三个关键工序强行把钻孔塞过去反而会让整体完工时间变差。决策者需要在“局部最短加工时间”和“全局资源配置”之间做权衡这种权衡正是FJSP的难点。1.2 两个互相耦合的子决策从数学模型角度看FJSP要同时回答两个问题第一每一道工序选择哪台机器加工这叫机器分配Assignment第二所有工序在选定的机器上按什么顺序执行这叫工序排序Sequencing。这两件事不是先定一个再定另一个的简单关系。机器分配一旦变化各机器的时间轴就变了最优的工序顺序也得跟着变反过来工序顺序调整后机器负载分布又会改变原本合理的机器分配可能就不合适了。这种耦合关系导致一个很直接的结果不能把FJSP拆成两个独立的单目标子问题然后分别用简单规则求解。很多初学者最先想到的方案是“贪心启发式规则”比如SPT最短加工时间、MWKR最大剩余工作量它们在零件数量少、机器数量小的稳定生产线里很好用。但一旦设备故障、紧急插单、交期变动这些东西出现固定规则马上失灵这时候就需要在更大的解空间里做搜索也才有了后面这几种智能优化算法的用武之地。1.3 组合爆炸里的NP-Hard陷阱关于FJSP的计算复杂度有一句大家常引用的话它是典型的NP-Hard问题。所谓NP-Hard并不需要背定义你只需要理解一件事——当工件数量和机器数量增长时可行解的规模是组合爆炸式增长的。n个工件、m台机器平均每道工序有k台可选机器光机器分配就有k的幂次种组合工序排序又是另一个阶乘量级的排列数。就算是一个20个工件、10台机器的中等规模实例穷举所有调度方案在工程上完全不可行。所以主流做法都不是追求“证明最优”而是“在有限计算时间里找一个足够好的近似解”。这也是为什么小龙虾优化算法、非支配排序这一类元启发式方法会被反复拿来做调度研究的根本原因。它们不保证找到全局最优但能在可接受的时间内给出一批工程上可用的候选方案。2. 小龙虾优化算法从连续优化到离散调度的身份转换2.1 一个“生物小剧场”式的搜索框架第一次看到小龙虾优化算法Crayfish Optimization AlgorithmCOA这个名字我愣了一下后来翻原始论文才发现作者模拟的是小龙虾在高温季节的三类行为避暑、竞争、觅食。算法里有个核心参数R它随迭代次数从高到低衰减相当于“环境温度”。当R偏高时小龙虾倾向于避暑行为——躲进洞穴相当于在解空间里做随机大范围跳转当R适中时个体进入竞争行为两只小龙虾互相比拼弱的一方被推向强的一方当R合适时个体执行觅食行为在当前位置附近精细搜索。这三类行为放在优化视角里非常清晰避暑保证算法前期不会过早陷入局部最优竞争提供了种群内信息交换觅食负责对优质区域做精化搜索。整个框架本质上是一种探索与开发并重的群体智能算法。2.2 温度参数R是怎么控制节奏的原始COA里R的计算常见形式是R2*(1-t/MaxT)t是当前迭代次数MaxT是最大迭代次数。可以看到R是一个从2递减到0的数值配合随机数来判断执行哪类行为。比如当R小于某个阈值时执行觅食当R大于阈值时根据随机数与R的关系选择避暑或竞争。这个设计的巧妙之处在于它把“算法阶段”和“搜索节奏”绑定在了一起。前期R大随机跳转概率高种群在广阔区域里撒网后期R小几乎全部个体都是觅食行为围绕当前最优区域做小步精修。如果R的衰减曲线设计不合理比如下降太快算法会在前期就集火到某个局部区域后面再怎么精修也跳不出来。2.3 为什么不能直接把原始COA搬过来解FJSP原始COA是为连续优化问题设计的。它的个体位置是一个连续实数向量更新公式做的是实数加减、按权重扰动。但FJSP的解空间是离散的机器编号是整数工序顺序是排列。直接把连续位置四舍五入成整数会制造大量机器不可用之类的非法解而且工序顺序的“加一个向量”也完全没有意义。还有一点更关键原始COA是单目标算法跑完一次只输出一个最优解。可FJSP实际生产中经常有多目标需求比如同时优化最大完工时间makespan和机器总负载。单目标算法即使给不同权重跑很多次也很难得到一组分布均匀的Pareto折衷解而这正是多目标进化算法擅长的事情。所以NSCOA在架构上做了一个重要改动保留COA的种群更新算子把选择机制替换成非支配排序和拥挤度距离让算法输出一整条Pareto前沿。3. 非支配排序在这里到底干了什么3.1 先理解什么是“支配”在单目标问题里“你好我好”很好比较目标值小的就是好。多目标问题里却很尴尬两个目标往往互相冲突。这时候需要引入Pareto支配的概念。简单说如果一个解A在所有目标上都不比解B差并且至少在一个目标上严格优于B就说A支配B。反过来如果两个解各有胜负则彼此互不支配它们都属于非支配解。我打个比方买房时同时看“面积”和“总价”。方案A面积更大但价格也更高方案B面积小一点但便宜很多A和B就是互不支配的关系——买哪个纯粹取决于你的偏好。如果方案C面积又大价格又低那C就支配A和B正常人都会选C。FJSP里的场景完全一样一组非支配解就是一批“各有取舍但不被另一方案全面碾压”的调度方案。3.2 拥挤度距离为什么能拉开展布如果只做非支配排序算法很容易出现一个现象所有种群个体都挤在Pareto前沿的某个局部区域因为那里的解恰好相对容易被保留。这样得到的前沿“质量”很差——只有一小段可用方案没法给决策者提供完整信息。NSCOA继承了NSGA-II的解决方案在同一个非支配层内部用拥挤度距离来评估个体的“稀缺程度”。计算方式是先按某个目标值排序然后看每个解和相邻两个解在这个目标轴上的距离再把多个目标上的距离累加。距离越大说明这个解周围越空旷越应该保留距离小的解说明周围已经很密集了优先级降低。这样选出来的下一代不仅层级领先而且在整个前沿上展布均匀。3.3 NSCOA的迭代主循环从种群到下一代把以上机制串起来NSCOA的一个完整迭代过程可以写成下面几个步骤初始化N个调度方案作为初始种群计算每个方案的多目标值。对当前种群做非支配排序得到第一前沿、第二前沿……后续前沿的完整分层。在同一层内计算拥挤度距离得到每个个体的综合优劣排序。根据当前温度R选择COA的避暑、竞争或觅食算子在优势个体附近产生新候选解。把父代种群和子代种群合并重新做非支配排序和拥挤度排序从中选出前N个个体作为下一代。回到第2步直到迭代次数用尽输出最终第一前沿对应的调度方案。第5步的“先合并再选择”就是常说的精英保留策略。它的意义在于当前最好的非支配解不会被随机扰动淘汰掉保证收敛趋势是单调向前的。这是NSGA-II被证明有效的核心设计NSCOA把它完整继承了下来。3.4 精英保留策略为什么这么重要很多初学者在实现多目标算法时会忽略精英保留。如果不合并父代只在子代里选下一代那么上一代大量优秀的非支配解会因为概率原因被丢掉算法收敛会变得非常缓慢甚至振荡。合并选择等于给种群上了一道“保险丝”无论COA算子怎么折腾好的解都能留存。代价是多消耗一点排序时间但换来的是稳定性和收敛速度非常划算。4. 编码解码把车间方案翻译成算法语言4.1 MSOS编码机器选择串与工序排序串有了搜索机制还需要一套编码方案。FJSP领域最常见的编码是MSOS双串结构全称Machine Selection Operation Sequence。它由两条长度相同的串组成长度等于所有工件的工序总数。机器选择串MS按工件的工艺顺序排列每一位表示对应工序选择哪台机器。例如MS[2,1,3,2]表示工序1选2号机工序2选1号机以此类推。 工序排序串OS由工件号组成每个工件号出现次数等于该工件的工序数量。读取时从左到右扫描第几次出现某个工件号就代表该工件的第几道工序。用一个小例子说清楚。假设共有两个工件J1有3道工序J2有2道工序总工序数为5。如果OS[2,1,2,1,1]那么从左到右的映射关系可以这样记OS中的位置出现的工件号对应工序第1个2O21即工件2的第1道工序第2个1O11即工件1的第1道工序第3个2O22即工件2的第2道工序第4个1O12即工件1的第2道工序第5个1O13即工件1的第3道工序这样一串看似重复的数字通过“出现次数”就能唯一确定工序的先后逻辑。MS与OS一一配合就完整描述了一个调度方案。4.2 解码策略追加式与插入式的差距解码就是把MS和OS还原成具体的时间安排表。最简单的做法是追加式解码按OS顺序每道工序放到选定机器的当前末尾。这种解码实现很容易但会浪费机器时间轴上的碎片空隙。比如某台机器在2到5分钟是空闲的下一道工序只需要2分钟追加式解码会把它排到机器当前末尾的10到12分钟白白浪费了3分钟的空档。插入式解码也叫左移解码就要聪明得多——当安排一道工序时先扫描机器的时间轴如果存在一个空闲窗口其开始时间不早于该工序紧前工序的完工时间且窗口长度足够容纳本道工序就直接把这个工序插入窗口。这样能明显压短makespan和机器等待时间。很多论文里FJSP的对比结果差距很大一部分并不是算法多聪明而是解码方式不同造成的。所以实验对比时一定要保持对比算法使用相同的解码策略。4.3 一个可以手推的小算例为了让你彻底搞懂解码我给出一个2×2的小例子你可以在纸上推一遍。两个工件两台机器。J1有2道工序O11在M1上加工3分钟、M2上加工4分钟O12在M1上加工2分钟、M2上加工1分钟。J2有2道工序O21在M1上加工4分钟、M2上加工3分钟O22在M1上加工2分钟、M2上加工2分钟。假设种群个体给出的MS[2,1,1,2]OS[1,2,1,2]第1道工序是O11选M2MS[1]2加工4分钟安排在[0,4]。第2道工序是O21选M1MS[2]1加工4分钟安排在[0,4]。第3道工序是O12选M1MS[3]1加工2分钟。由于O11在M2上的[0,4]已完成O12的紧前约束满足M1 [0,4]被O21占用因此O12插入到[4,6]。第4道工序是O22选M2MS[4]2加工2分钟。O21在M1的[0,4]已完成M2 [0,4]被O11占用因此O22安排在[4,6]。最终makespan6机器总负载442212。你可以试着把OS改成[2,1,2,1]会发现时序不同空闲碎片也完全不同makespan仍然可能不同。这说明了调度序列对结果的影响有多直接。4.4 初始种群别全是“同一种味道”初始化阶段有个容易被忽略的点如果所有个体都选每道工序加工时间最短的机器MS串很快就全部收敛成同一个模板种群多样性会非常差。反过来如果全部随机选择机器初始解质量又太低拖慢后期收敛。常见做法是“混合初始化”一部分个体完全随机生成另一部分个体在随机基础上偏向每道工序的最短加工时间机器还有一部分在随机基础上偏向机器负载较小的机器。这样既保留了多样性又让初始种群的平均质量不至于太差算法后期更容易收敛到好的前沿。5. Matlab实现的关键环节与代码骨架5.1 文件该怎么组织我建议把功能拆成独立脚本工程结构会清晰很多。典型的结构如下main.m参数配置、流程控制、结果绘图loadInstance.m读取算例生成machine_matrix和time_matrixinitPop.m混合初始化种群生成MS与OSdecode.m插入式解码输出每台机器上的工序时间表和目标值nonDominatedSort.m非支配排序输出每个个体的前沿编号crowdingDistance.m计算拥挤度距离COA_update.m避暑、竞争、觅食三类算子eliteSelection.m合并种群并做精英选择plotGantt.m画甘特图plotPareto.m画Pareto前沿学习阶段不用一上来就搞分模块工程化先把decode.m和nonDominatedSort.m写对其他逻辑串起来调试效率更高。5.2 算例数据怎么转成算法矩阵标准FJSP算例一般给出两种表一是每道工序的可选机器列表二是每道工序在各机器上的加工时间。Matlab里常用machine_matrix和time_matrix两个三维矩阵来表示machine_matrix{i}(j,k)第i个工件的第j道工序是否可以在第k台机器上加工0表示不可用1表示可用。time_matrix{i}(j,k)对应加工的时长不可用时置为0或Inf。读入算例后建议先写一个可视化检查函数把每个工件每道工序的可选机器数打印一遍确认数据没有错位。这一步能省下后面大量排查逻辑错误的时间。5.3 非支配排序和拥挤度的Matlab思路非支配排序不一定要写冗长的循环。可以先把每个个体的目标向量都取出来得到N行2列或N行m列的目标矩阵。然后对每两个个体做一次矩阵化的支配比较统计每个个体被多少个其他个体支配。所有“被支配数为0”的个体就是第一前沿去掉第一前沿后再重复统计即可得到第二前沿以此类推。% 非支配排序简化版伪代码风格 for i 1:N for j i1:N if dominates(fitness(i,:), fitness(j,:)) S{i} [S{i}, j]; n(j) n(j) 1; elseif dominates(fitness(j,:), fitness(i,:)) S{j} [S{j}, i]; n(i) n(i) 1; end end end front{1} find(n 0);拥挤度计算相对更简单对每个前沿内的个体按每个目标分别排序边界个体直接赋值为一个大数内部个体用相邻目标值的差除以该目标的最大最小值范围把各个目标的贡献累加。注意目标值的量纲差异会影响拥挤度的数值建议在计算前先做归一化处理否则哪个目标的数值大哪个目标在拥挤度里的主导权就大。5.4 COA算子在调度域里的三个变身原始COA的连续更新公式无法直接套到MS和OS上需要把三类行为改造成离散域的操作避暑行为对应随机大范围扰动。在调度域里可以随机选取OS中的若干位置做重排或随机修改MS中的若干位为其他可用机器。它的作用是让个体跳出局部区域。竞争行为两个个体竞争时弱的个体向强的个体靠拢。在调度域里可以抽取优质个体的机器选择片段替换掉劣质个体的对应片段同时把优质个体的OS中部分工序顺序复制过来。这样劣质个体能快速吸收优质信息。觅食行为对应局部精修。可以围绕当前个体做邻域搜索比如交换OS中两个不同工件的工序位置、把某道工序从当前机器换到另一台加工时间相近的机器等。做完邻域搜索后如果新解支配旧解或新解综合评价更好就替换掉旧解。把这三类算子写清楚后整个NSCOA其实就是反复调用它们产生新解再用非支配排序和拥挤度做筛选的循环。代码结构反而简单。5.5 一个容易踩坑的地方调试多目标算法时我第一次跑NSCOA发现Pareto前沿越变越差排查了很久才发现是解码函数里用了全局变量保存机器完工时间但每次调用前没有清零。这种状态残留问题在Matlab里非常隐蔽建议所有调度相关的中间变量都以局部变量形式在decode.m内部重置避免依赖工作区的全局状态。另一个常见坑是MS和OS经过算子修改后长度改变导致解码越界。建议每次更新完个体后都写一个check_individual函数检查MS和OS的长度是否与原问题一致、MS中的机器编号是否在该工序的可选集合内。6. 用标准算例说话NSCOA的实验设计与评价指标6.1 标准算例从哪来验证算法效果不要自己随便编数据最好用领域内同行公认的标准算例。FJSP方向最常用的是Brandimarte的MK系列MK01到MK10以及Kacem等提出的8×8、10×10算例。MK系列以部分柔性为主工序数量从几十到几百不等规模梯度合理非常适合做算法早期验证。以MK01为例它的规模是10个工件、6台机器总工序数55道部分工序有4到6台可选机器。相对简单适合先跑通流程。等代码稳定后再上MK04、MK06这些算例的工序数和可选机器数量都更复杂能明显拉开算法之间的差距。6.2 三个关键评价指标评价多目标算法性能不能只看最终画出来的Pareto前沿好不好看要量化。常见指标有三个IGD、HV和Spread。IGD反世代距离衡量算法前沿与参考前沿之间的“贴近和覆盖程度”。计算方法是在参考前沿上取若干均匀分布的点对每个点找算法前沿中距离它最近的点然后把所有距离取平均。IGD越小说明算法得到的解集越接近参考前沿且覆盖越完整。HV超体积以参考点与Pareto前沿之间的超立方体体积作为指标HV越大说明算法找到的解在目标空间里占据的支配区域越大代表收敛性和分布性均衡。HV的优点是不需要提前知道真实前沿只需指定一个参考点对算例不熟悉的情况下也能使用。Spread分布度直接衡量解集在目标空间里的展开宽度和均匀程度越小越好。它能发现“前沿虽然收敛但全部挤在一段”的作弊式结果。实际操作时建议三个指标一起看IGD看贴近程度Spread看均匀度HV看整体综合表现。6.3 一套可以直接套用的参数模板下面是我调试时用的比较多的一套配置对MK01到MK04这种中小规模算例比较稳参数推荐值说明种群规模N100大算例可适当提高到150到200最大迭代次数MaxIter300大算例建议提高到500COA温度衰减R 2 * (1 - iter/MaxIter)可改为余弦衰减增强后期精修避暑随机扰动位数3到5位影响前期探索强度竞争复制片段长度取OS总长的1/4太长容易破坏原有优势结构觅食邻域搜索次数1到2个候选邻域增加次数会明显拖慢运行时间独立运行次数10次以上用于统计均值和标准差这套参数不是黄金组合只是起点。换算例时建议用正交试验或简单的网格搜索先固定一半参数扫另一半参数观察IGD和HV的变化趋势再去调整。6.4 实验结果怎么解读才算有说服力一个很容易犯的问题是把单次运行最好的一次结果拿出来和对比算法打擂台。正确做法是多次独立运行报告均值±标准差并做简单的显著性检验。用相同初始种群、相同解码方式、相同最大迭代次数是保证公平对比的基本底线。提示实验对比时务必保证对比算法使用相同的解码方式和相同的初始种群否则结论不成立。从经验上看NSCOA这类算法和NSGA-II对比时在前中期迭代阶段往往收敛速度更快因为COA的竞争算子会让种群快速向优势区域收缩但后期如果温度衰减设计不好容易在最终前沿的端点上分布不足。这个问题可以通过把觅食阶段邻域搜索改成基于Pareto支配的多方向搜索来改善。7. 参数调优心得与延伸方向7.1 温度衰减曲线是最容易“翻车”的地方很多复现COA的人包括我第一次遇到的问题是温度参数R衰减过快。当MaxIter设得偏大而R的衰减速度不变时算法前半段就在大量避暑跳跃后半段又过早进入觅食精修导致整个搜索过程比例失衡。建议把R的衰减公式改造成带斜率系数或余弦衰减的版本例如R2cos(πiter/(2*MaxIter))让前期更平滑、后期更专注。改完最好做一个单变量灵敏度实验绘制IGD随衰减速度的变化曲线比凭空调参靠谱得多。7.2 种群规模不是越大越好FJSP个体长度随工序总数增长每个个体解码一次就要遍历所有工序和机器时间轴复杂度并不低。种群规模设到300迭代500次中小算例还能接受到了几百道工序的大算例一次实验可能要跑几十分钟。我的经验是解码时间占比远高于算法排序时间与其盲目加种群不如优化解码速度。比如用Matlab的向量操作替代逐工序循环或者对机器时间轴的插入操作改用二分查找。7.3 从论文复现走向工程应用NSCOA这类研究算法要落地可以先从目标函数和约束条件两个方向延伸。目标函数可以从两个目标扩展为三个makespan、机器总负载、最大负载或能耗指标还可以加入拖期惩罚、切换成本等。约束方面生产现场常见的机器维护窗口、工件释放时间、紧急插单等在标准FJSP算例里往往都不包含但这些才是工程里最值钱的需求。可以先在Matlab里把这些约束硬编到解码函数中再逐步迁移到更工业化的调度平台。这几年做调度优化我个人的体会是算法创新只是半个故事另一半在实现细节和实验设计里。编码对不对、解码公不公平、对比有没有控制变量随便一个环节放松最终结论都站不住脚。如果你也在复现这个方向建议先跑通MK01并画出正确的甘特图再追求算法层面的改进。那一步走扎实了后面的路会顺很多。
返回列表