基于改进MOEA/D的模糊柔性作业车间调度双目标优化

发布时间:2026/10/20 5:16:27
基于改进MOEA/D的模糊柔性作业车间调度双目标优化
1. 问题建模与双目标拆解1.1 为什么柔性作业车间调度要走到“模糊”这一步先聊个实际场景。车间里接单排产工艺工程师拿着工时表说某道工序打磨加工要45分钟结果现场设备状态、操作员熟练度、毛坯余量稍有波动实际40到55分钟都很正常。如果你拿着固定45分钟去排产计划表看起来非常漂亮实际执行第一天就开始乱套第二天的插单、返工再一冲整个排程基本等于重做。正因为如此实际调度问题里把加工时间当作确定值其实是一种奢侈假设更常见的情况是只知道一个大概范围中间值可能性最大偏大偏小都有可能。柔性作业车间调度FJSP本身已经比经典流水车间复杂得多因为它允许每道工序在多台可选机器上加工机器不同、能力不同、加工时间也不同这就产生了一个庞大的“工序-机器”组合空间。如果再在加工时间上加入不确定性确定性调度的那些常规优化手段就直接失效了目标函数的计算也要重新设计。业界通常采用三角模糊数来刻画这种“说不准但有个范围”的加工时间也就是用三元组表示分别代表最乐观估计、最可能估计、最悲观估计。这样处理不复杂却能把车间里的现实波动有效地带入算法搜索过程。这篇博文想记录的项目做的就是一件事用改进的基于分解的多目标进化算法在三角模糊数表示的加工时间下同时优化最大模糊完工时间和机器最大模糊负载这两个目标。顺便还配了五种对比算法的完整代码和测试数据方便大家复现、对比和继续改。1.2 三角模糊数的引入与关键排序规则三角模糊数本身不复杂它是模糊数学里最常用的表示方式之一核心就是一个三元组表示为 (a₁, a₂, a₃)其中 a₁ 是最小可能值a₂ 是最大可能性取值a₃ 是最大可能值。实际场景里很容易得到这组数字问现场老师傅最快的多快、一般多快、最慢多慢三个数一问就出来了比要一个精确数容易得多。模糊调度里所有操作本质上都是对模糊数做运算和比较。运算相对简单两个三角模糊数的加法按分量相加即可比如第一道工序模糊加工时间是 (38, 45, 52)第二道是 (12, 15, 20)那这两道工序连续完成的总时间就是 (50, 60, 72)。模糊完成时间、最大完工时间、机器负载的处理方式都是基于这个加法规则。比较两个模糊数的大小才是真正的核心难点。因为三角模糊数是区间性的两个模糊数之间未必存在那种“绝对谁大谁小”的明确关系。一个值域宽但期望值低的任务和另一个值域窄但期望值高的任务怎么排序这里项目里采用了一种常用的综合排序策略给每个模糊数计算一个确定性得分。我实际采用的排序方式是文献里比较经典的“期望值左宽右宽”综合法先计算主期望值 E (a₁ 2a₂ a₃) / 4这是最主要的比较依据若两个模糊数的 E 相等则比较跨度 (a₃ - a₁)跨度小者优先若跨度仍相同再看右宽 (a₃ - a₂) 和左宽 (a₂ - a₁)。这样设计排序规则的道理在于如果只看期望值两个分布差异很大的模糊数可能得出同一个结果给后续进化算法带来大量“平局”导致种群里无法区分解的质量。而把跨度作为第二准则后排产时倾向于选择波动更小的方案这在调度上符合实际管理偏好谁都不希望挑一个容易大幅超期的排程。1.3 双目标怎么选目标函数又怎么算多目标问题必须明确是哪两个目标。这个项目选的是最大模糊完工时间模糊 makespan和机器最大模糊负载。前者衡量整个订单什么时候能全部做完后者衡量最忙的那台机器承担了多少工作量在生产管理里这两个指标是车间主任每天都会盯的核心指标。模糊 makespan 越小意味着订单越快完成模糊负载越小意味着设备压力越均衡、瓶颈越不明显。计算模糊 makespan 时虽然调度方案是确定的但每道工序加工时间都是模糊数因此最终完工时间也是一个模糊数需要进行模糊加法并在每个工件的工序链上取模糊最大值。这里的“取最大值”在实际代码里不是简单的分量 max而是用上面的排序规则两两比较后把“更大的那个模糊数”保留下来。模糊负载的计算类似对每台机器累加分配到它上面的所有工序的模糊加工时间得到每台机器的模糊总负载再对这些模糊总负载用同一个排序规则求最大值得到全局机器最大负载。需要注意的一个细节是模糊数的运算虽然简单但如果直接按分量累加、按期望值排序结果会受到极端值影响很大。我在试跑过程中发现过一种情况两条调度方案的最大模糊完工时间期望值几乎一样但一条是窄幅分布45, 50, 58另一条是宽幅分布20, 50, 90显然是前一条方案在鲁棒性上更好。所以排序规则的设计直接决定了最终得到的“最优解集”到底偏向哪一种排程这是做模糊调度项目容易忽略但极其关键的一点。2. 基于分解的多目标进化算法与本文改进点2.1 MOEA/D 算法核心逻辑回顾基于分解的多目标进化算法核心思想是把一个多目标优化问题拆成若干单目标子问题每个子问题对应一个权向量约束着解向这个方向的权重组合靠拢。只要这些权向量在目标空间分布得足够均匀每个子问题的最优解集合就足够逼近整个 Pareto 前沿这样多目标问题就被转化成一组并行的单目标问题问题难度大幅降低。每个子问题在进化时只跟它的邻居子问题交流通过从邻居个体那里选择父本、进行交叉变异再把子代更新到自身位置。这种邻域机制的优点是算法不容易丢失极端方向的解种群多样性保持得比普通遗传算法好。MOEA/D 的另一个优点是收敛速度通常比 NSGA-II 快因为分解后的子问题搜索方向明确种群里的个体有明确的“目标分工”。在这个项目里我采用 Tchebycheff 分解函数。原因是 PBI 分解函数虽然在某些问题上表现更平滑但它引入了额外的惩罚参数对模糊调度这种目标函数本身带有排序噪声的问题PBI 参数会放大排序差异带来的不稳定。Tchebycheff 更直观就是让每个子问题的目标差距最大值最小化和模糊数的期望值排序配合起来更自然。2.2 三个核心改进点及其设计动机第一个改进点放在初始化阶段。原版 MOEA/D 的初始种群一般是随机生成对调度问题来说随机种群质量很低很多解连可行调度都算不上需要大量进化代才能“修复”出像样的结构。针对模糊调度问题的特点我设计了一个“最小模糊完工时间优先”的初始化启发式先在可用机器里挑模糊处理时间最小的机器给每道工序分配再用模糊操作顺序规则生成若干个活性调度其余个体仍然随机生成保留一定多样性。这样可以保证初始种群里有相当一部分质量较好的种子个体。第二个改进点是邻域匹配策略。标准 MOEA/D 的邻域只按权向量欧氏距离来选但在模糊车间调度里两个权向量距离很近不代表它们对应的调度解有很强的结构相似性因为工序排序和机器分配的解空间非常大。我采用“权向量距离机器负载相似度”混合度量先按权向量距离选出候选邻居池再从中挑选与当前个体共享机器集合比例较高的个体作为实际参与交叉的邻居。这样做的好处是信息交换的邻居之间在调度结构上更“语言相通”子代不会因为父本差异太大而变成无效交叉。第三个改进点是加入了基于关键机器的局部搜索。所谓关键机器就是当前解中模糊负载最大的那台机器它决定了堆栈效应的上限。局部搜索的思路很简单但很有效在这个关键机器上挑一个工序尝试把它移动到其他空闲或负载较轻的可处理机器上去如果调整后的模糊负载和模糊完工时间都不变差就接受该移动否则放弃。局部搜索迭代次数不设太大每代只做有限步防止陷入局部最优和过早收敛。2.3 改进算法整体流程算法整体流程可以梳理如下初始化权向量生成均匀分布的权重组合构建每个子问题的邻域关系。用“优先级初始化随机初始化”混合策略生成初始种群。对每个子问题 i从它的混合邻域里挑选父本执行工序序列表的 POX 交叉和机器选择部分的多点变异。对生成的子代做模糊调度解码得到模糊 makespan 和模糊负载两个目标值。基于 Tchebycheff 分解函数判断子代是否值得替换子问题 i 的解。对替换后的解执行关键机器局部搜索更新邻域内其他子问题的解。达到最大评价次数后停止输出非支配解集。工序编码和机器编码分开处理是 FJSP 算法设计的通用做法工序编码决定工序加工顺序机器编码决定每道工序落在那台机器上。POX 交叉是工序部分最常用的交叉算子它能保留父本里工序的相对顺序特征机器选择部分由于模糊时间有三个值变异时需要同时考虑三个分量而不是只看最可能时间这是模糊调度与确定调度在算子设计上的核心差别。3. 五种对比算法的配置与公平性处理3.1 对比算法怎么选为什么是这五种实验必须有参照系否则单说一个改进算法效果如何没有说服力。这个项目里准备了五种对比算法覆盖了当前多目标进化领域的主流思路NSGA-II非支配排序拥挤度距离是经典基准几乎所有调度多目标研究都会拿它做对比。SPEA2基于强度 Pareto 进化用个体间的支配强度值和 K 近邻密度估计来选优代表另一类基于归档集的方法。IBEA基于性能指标指示器的进化算法直接用一个二元性能指标指导选择思路和基于分解、基于支配的都有区别。原版 MOEA/D加权和分解思路只做最基础的初始化、交叉变异、邻域替换不加任何调度针对性增强。NSGA-III参考点机制保持种群多样性的代表性算法常用于高维目标问题在两目标场景下和 NSGA-II 行为差异不大但可以作为对照。从这五种算法可以看出对比的层次NSGA-II 和 SPEA2 代表基于支配的方法IBEA 代表基于指标的方法MOEA/D 代表基于分解的原始版本NSGA-III 可以看作对 NSGA-II 多样性的升级尝试。把改进点拉出来和这些阵营逐一对照才能说明哪些改进真正产生了收益哪些只是把 MOEA/D 的早期版本变成了常规水平的叠加。3.2 参数设置与随机种子的细节处理公平对比比很多人想象中更难因为算法之间的调参“水分”非常大。如果我给改进算法花三天时间调参然后给 NSGA-II 用一套默认参数直接跑得出“改进算法显著优于 NSGA-II”的结论是没有意义的。这个项目里我做了一个很关键的决定所有算法统一使用相同的种群规模和相同的最大评价次数而不是统一迭代代数。因为相同代数下不同算法的单代计算成本完全不同只有按评价次数统计才符合“相同计算预算”的公平原则。具体参数设置为种群规模 100最大评价次数 50000交叉概率 0.85变异概率 0.1MOEA/D 邻域大小为 20。对于每种算法内部的特殊参数比如 SPEA2 的归档集大小、NSGA-III 的参考点数量均按其原论文建议值设置不额外针对测试集调整。这样虽然比较粗糙但保证了公平。随机种子这个细节值得一提。所有算法在每个测试实例上独立运行 30 次每次使用完全相同的随机数种子序列不能直接调用运行时的系统时间当种子。否则算法 A 和算法 B 在相同实例上用的不是同一组随机数相当于实验条件都没有控制住。我在代码里维护了一个种子列表所有算法共享这组种子这样每次对比时初始种群虽然不同但初始条件对每个算法都是一样的。3.3 评价指标与统计检验多目标算法的解优劣不能单看某个目标的最小值要看整个非支配解集在目标空间里的分布质量。项目采用两个常用指标HV 超体积指标衡量解集在目标空间中覆盖范围的大小HV 越大说明解集越接近真实前沿且分布越均匀。计算时需要设置参考点这里取各目标在独立运行中遇到的最大值的1.2倍作为参考点。IGD 反世代距离衡量解集与真实 Pareto 前沿之间的平均距离IGD 越小说明解集越接近真实前沿。真实 Pareto 前沿在模糊调度问题里很难精确求出我用所有算法在 30 次运行中产生的所有非支配解合并后再次过滤出的全局非支配解集来充当参考前沿。这个方法虽然不完美但在有限算力下是通行做法。统计检验必须做。只用平均值排序就说“我的算法最好”很容易受到个别离群值干扰。我对每个实例上的 HV 和 IGD 结果做了 Wilcoxon 秩和检验显著性水平设 0.05比较改进算法和其他五种算法是否有显著差异。加上符号标记表示“显著优于”“显著劣于”“无显著差异”三种关系结果呈现才站得住脚。4. 实验设计与结果解读4.1 测试实例与运行环境测试数据直接关系到结论的可靠性。我没有只拿一两个小实例跑完就完事而是构建了一个规模梯度明显的测试集合。工件数量分别为 6、10、15、20、30机器数为3到12不等又区分了低模糊度、中模糊度、高模糊度三组数据。这个梯度设计能回答一个关键问题改进算法在大规模、高模糊度情况下是否依然有优势还是只在小规模问题上有效。每条数据的生成都遵循机器可用性约束即每道工序不是所有的机器都能加工而是有一个可选机器集合。这个集合的覆盖率设定在30%到70%之间覆盖率太低会让调度结果极其受限太高又接近流水车间失去“柔性”意义。加工时间的模糊化处理是先确定一个基准值再按模糊度水平生成左端点、右端点让模糊跨度在基准值的10%到50%之间变化这样能系统性测试算法在不同不确定程度下的表现。实验机器是普通工作站代码用 Python 实现主要用了 numpy 加速矩阵运算。每种算法跑完一个实例大概耗时几十秒到几分钟不等全部实验大约跑了大半天属于可以接受的量级。4.2 典型结果与观察从全部测试实例看改进算法在 HV 和 IGD 两个指标上都有明显优势尤其在大规模和高模糊度实例上。以15工件×6机器的中高模糊度实例为例30次运行中改进算法 HV 均值比原版 MOEA/D 高约12%比 NSGA-II 高约20%。IGD 的改善更明显改进算法比原版 MOEA/D 降低了30%多说明非支配解集离参考前沿更近。在小规模实例上五种算法差距会缩小甚至几个算法的 HV 没有显著差异这符合直觉因为问题规模小几乎任何算法都能找到接近最优的解集。另一个值得注意的观察是Pareto 前沿形态不是平滑的弧线而是带有一段平台区在模糊 makespan 相对较小的一段区间里机器负载可以同时保持在一个温和水平但拐点之后会让负载急剧上升。这说明“交付周期”和“设备均衡”两个目标之间存在明显的瓶颈区间改进算法在拐点附近的解分布表现最好能够在前沿的中间密集区域产生更多可用方案这正是车间决策者最关心的部分。4.3 改进算法优势最明显的地方三种情况下的优势最突出第一种是高模糊度实例。模糊区间跨度大导致排序规则频繁出现模糊数几乎相等的情况标准 MOEA/D 在这种情况下容易产生大量无差别比较种群进化动力不足。改进算法在初始化阶段引入的模糊负载感知和局部搜索的确定性改进让种群即使在模糊比较“模糊”的情况下也能有稳定压力。第二种是大型实例。30个工件、10台以上机器时解空间急剧膨胀NSGA-II 的收敛速度明显跟不上而基于分解的方法天然带有方向性搜索优势加上关键机器局部搜索的修剪搜索效率保持得更好。第三种是机器覆盖率较低的实例。可选机器少导致可行调度空间受限随机初始化产生的解大量不可行或质量极差。改进算法采用最小模糊时间启发式初始化后初始种群质量高早期迭代就不会淹没在不可行解里相当于把算法的搜索起点整体抬高了一截。5. 代码结构与数据组织5.1 项目目录与核心模块代码仓库的整体结构在发布时做了完整梳理方便直接 clone 下来跑通实验室和数据复现。主体目录结构如下fuzzy-fjsp/ ├── data/ │ ├── instances/ # 测试实例数据 │ ├── results/ # 各算法运行结果 │ └── reference_fronts/ # 全局参考前沿 ├── problem/ │ ├── fuzzy_time.py # 三角模糊数定义与运算 │ ├── instance.py # 实例数据解析 │ └── scheduler.py # 调度解码器 ├── algorithms/ │ ├── base_moead.py # 原版MOEA/D │ ├── improved_moead.py # 改进算法 │ ├── nsga2.py # NSGA-II │ ├── spea2.py # SPEA2 │ ├── ibea.py # IBEA │ └── nsga3.py # NSGA-III ├── utils/ │ ├── metrics.py # HV、IGD计算 │ ├── statistics.py # 统计检验 │ └── visualization.py # 绘图 ├── main.py # 主流程入口 └── requirements.txt算法模块全部采用统一接口 design(solution) - objectives这样在 main.py 里只需要通过列表配置算法名称和参数就能循环运行所有算法结果自动写入 results 目录。这种设计特别适合对照实验不用每次改一个算法都要重写主程序。5.2 数据格式说明测试实例用 JSON 格式存储字段设计比较直观。每个实例文件包含机器数、工件数以及工序列表。每道工序的键值除了编码号之外关键是 machines 数组里面每一项对应一台可选机器和该机器上加工时间的三角模糊数。下面是一段简化的数据格式示例{ num_machines: 6, num_jobs: 2, jobs: [ { job_id: 1, operations: [ { op_id: J1-1, machines: [ {machine_id: 2, fuzzy_time: [38, 45, 52]}, {machine_id: 4, fuzzy_time: [42, 48, 56]}, {machine_id: 5, fuzzy_time: [35, 40, 48]} ] } ] } ] }这样的格式可读性强也很容易扩展。如果要加入工件优先级、交货期约束或者随机机器故障只需要在 dict 里追加字段。数据生成脚本一并放进仓库脚本里的随机种子是固定的这样任何人运行数据生成程序拿到的实例都是同一批实验结果才可以互相验证。5.3 运行方法与结果复现复现整套实验非常直接。安装依赖后在项目根目录执行pip install -r requirements.txt python main.py --instances data/instances --algorithms all --runs 30main.py 会依次加载所有测试实例逐个算法进行指定次数独立运行最后把所有结果汇总到 data/results 目录并自动生成两张汇总表一张是各算法的 HV/IGD 均值与标准差另一张是 Wilcoxon 检验的显著性对比表。可视化部分可以通过参数单独触发生成种群进化曲线和 Pareto 前沿图。有一点提醒由于总评价次数设得较多跑全部实验确实需要一些时间。如果读者只想快速验证算法趋势可以在命令里把评价次数改小比如减少到 10000这依然能看出改进算法的大致优势只是统计显著性可能会被削弱。等想正式写论文或做汇报时再把评价次数调回去跑完整实验。6. 踩坑记录与排查心得6.1 模糊数参与交叉变异时的排序陷阱这个坑从第一天坑到最后。最初版本代码里我把模糊数转成期望值然后按期望值参与交叉变异的比较选择。第一轮实验结果出来改进算法的 HV 竟然不如原版 MOEA/D排查了很久才发现问题期望值相同或很接近的模糊数非常多选择压力几乎消失种群在迭代 30 代之后就没有明显进步了。正确的做法是在选择、变异、局部搜索全流程中都使用完整的模糊三元组参与判断只在最终结果展示时才转成可读的期望值。排序函数必须唯一且严格。如果排序函数对两个模糊数返回“相等”还要定义破平规则比如按跨度、按左端点。否则调度解码时生成的毛刺会毁掉整个种群结构。6.2 对比算法公平性容易忽略的三个细节对比实验里最容易踩的坑有三个。第一个是评价次数阈值不一致导致结果对比没有意义。第二个是各算法的随机数种子没有统一相当于初始种群对每个算法各不相同。第三个是淘汰“不合格”的对比算法结果时手误比如 NSGA-II 某一轮出现数值溢出或者其他异常直接删掉这一轮重跑一次得到的统计结果就会被污染。我的处理方式是所有异常结果保留现场日志只在统一水平上做统计如果发现某轮运行结果异常重新将该实例的全部算法都重跑一轮而不是只重跑异常算法那一轮。虽然笨但实验严谨性对得起数据。另外需要注意 NSGA-III 在两目标问题上默认参数可能和原论文的高维场景不匹配。两目标时参考点应该简化设置否则 NSGA-III 种群分布会明显偏倚。这个坑一开始也出现过后来参考点数量按目标数和小种群重新设定情况才正常。6.3 去模糊化与决策偏好之间的关系最后一个感悟是模糊调度算法的最终产出是模糊 Pareto 解集但车间管理人员最终只能执行一套具体排程。如何从模糊解集里挑出一个“落地解”是对做这个项目的人绕不开的问题。我试过几种方式最稳妥的是把最终非支配解集按模糊 makespan 的期望值排序给决策者展示一串候选方案每个方案除了模糊目标值之外额外标注一个“工期风险指数”也就是右端点与期望值的差值占期望值的比例。这样管理者不只是看一个数字还能直观看到每个排程方案的抗风险能力。这个扩展做法实用性很高建议感兴趣的读者在自己的项目里加上。另外要提醒一个实操层面的细节局部搜索不要太猛否则算法在 30 次独立运行里大概率会有几次掉进局部最优。我把局部搜索迭代次数和邻域大小解耦局部搜索次数只占算法总循环次数的 2% 左右换来的是更高的稳定性和更小的方差。这样跑出来的结果写报告或者做展示时才有底气。