开头我接了一个智能制造工厂的生产排产优化项目,产线规模不大,但机器多、工序多,每天还有各种临时情况。传统排产系统上线后效果并不差,但真正跑起来之后,机器一故障、插单一进来,原有的排产计划就成了一堆废数据。那段时间我几乎天天泡在车间看调度员怎么干活,然后意识到,真正难的不是算出一个最优静态方案,而是如何在动态扰动下保持方案的可用性。后来我决定把方向转向深度强化学习的柔性作业车间动态调度——让调度策略学会应对不确定性,这也是这篇文章想完整聊清楚的事。
这个项目包含了几个关键命题:什么叫柔性作业车间,动态调度里的"动态"到底指什么,以及深度强化学习在调度问题中究竟改写了哪些原有规则。我会从问题定义、方案选型、MDP建模、训练部署、实测效果和踩坑记录几个角度展开,尽量把细节写透,让想往这个方向做的朋友少走一些弯路。
1. 产线排产遇到的真问题:静态计划算得好,一扰动就作废
1.1 车间现状与排产痛点
这个项目背景是某机加工车间的数字化改造。车间面积不大,但工位交错,产线共涉及20多台设备,主要分为粗加工、精加工、热处理、表面处理几个工艺段,每一类设备还有同构的多台机器。订单以中小批量为主,每一批零件要经过五六道到十几道不等的工序,不少工序能够在多种设备上完成,但设备加工时间不同,轻则差10%,重则差出一倍。这就是典型的柔性作业车间调度问题(FJSP,Flexible Job-Shop Scheduling Problem)——不只是决定工序顺序,每一个工序还要决策"到底用哪台机器做"。
我接到手的第一版需求,其实是上一家供应商留下的一个静态排产系统,核心算法是遗传算法。它的逻辑非常简单:每天下班前,把次日所有订单数据汇总进系统,跑一次优化,然后把排产表发给车间执行。在订单稳定、设备正常的理想情况下,这套系统的表现是可以接受的,尤其在小规模问题里,它甚至能找到质量不错的结果。
但现实永远是走样的。第二天一开机,某台数控机床报警故障停机,原计划里排到这台机器上的所有工序瞬间成了无头苍蝇;工艺员又强制插入了一个紧急返工单;夜班的实际加工时长比系统预估慢了一截,导致下一个班次交接时,队列里积压了没干完的活。调度员只能对着Excel手工挪工序,一边挪一边和计划员吵架。静态系统算得再好,在这种局面下也只是个摆设。
1.2 动态事件成为压垮传统方案的最后一根稻草
那个项目最让我印象深刻的,是一次设备故障导致全线停工4个小时。按照原有计划,故障设备的任务应该顺延到2号机上,但2号机当时正在加工一批交期很紧的零件,调度员不敢动,只能逐个人工协调,前后花了一个多小时才敲定一个勉强能跑的新方案。那天晚上的生产会议开得很压抑,车间主任直言不讳——如果系统不能应对这种变化,那它就是一堆无用的图表。
需求由此变了。不再是"每天算一次最优计划",而是"系统必须在扰动发生时,快速给出可执行的调度策略"。这种需求本质上就是动态调度:在已经执行了一部分工序的情况下,当机器故障、紧急插单、交期变更、物料延迟等事件发生后,系统需要重新决策后续工序的设备与顺序。
1.3 从静态到动态,难度为什么指数级上升
做静态调度,你可以把问题看成一次性的组合优化:给定所有订单、工序、设备、时间约束,找出最优或近似最优的调度方案。标准做法是数学规划、分支定界或者元启发式。但动态调度和静态调度在数学结构上有一个本质区别——动态调度必须处理状态的时间依赖性。
举个例子。静态调度里,"工序i使用设备j"是一个决策变量,所有约束一次性满足即可;而在动态情境下,任何一台设备的故障都会造成在制品库存变化、交货期风险重分配,过去的执行结果会影响未来所有决策空间的形状。更麻烦的是,突发事件的发生时间、影响程度通常是随机的,你无法在问题建模阶段就把所有可能分支全部枚举进去。
这解释了为什么学术界对动态调度问题研究了那么多年,真正落地的产品还不多:它不是一个纯粹的离线优化问题,而是一个带随机扰动的序贯决策问题。而序贯决策,恰恰是强化学习的本行。这件事是我最终决定换技术路线,去尝试深度强化学习的最直接原因。
2. 把问题说清楚:柔性作业车间动态调度的数学模型与复杂度来源
2.1 FJSP的数学描述:工序、设备与约束
做算法的人有一个习惯,问题没定义清楚之前不动手。所以得先把FJSP的数学骨架搭出来。
柔性作业车间里有若干个工件(Job),每个工件有多道工序(Operation),每道工序可以在一组候选机器(Machine Set)上加工,但不同机器上的处理时间不同。调度要解决两个相互耦合的子问题:
- 工序排序:每个工件的工序之间有先后约束,但不同工件之间的工序顺序是可以交叉的,需要决定先加工谁。
- 机器分配:每一道工序到底放在候选机器集合中的哪台设备上做,不同选择直接影响完工时间、设备负载甚至能耗。
约束大体有几点:同一工件内部工序必须按顺序执行;一台设备同一时刻只能加工一个工序;每道工序一旦开始不能中断。优化的目标在不同厂里不一样,有的是最小化最大完工时间(makespan),有的是最小化总拖期,有的是多目标权衡。在这个项目里,我把最小化加权总拖期+最小化最大完工时间作为两个主指标,因为客户既要订单及时交付,也关心产线整体效率。
2.2 动态扰动到底从哪来
动态调度里的"动态"不是理论上的名词,日常车间里最常见就这么几类:
- 机器故障:随机停机,短则20分钟,长则半天,是影响最大的一类扰动。
- 紧急插单:临时插入一个交期很短的订单,调度员的第一反应是拆原有计划。
- 交期变更:客户改交期,原本不急的活突然变成最高优先级。
- 物料延迟:上游工序或外购件没到,后续工序无法按计划开工。
- 加工时间偏差:实际工时和工艺定额不一致,导致后继计划失真。
每一种扰动,都会使原有的调度方案不再可行或不再最优。动态调度的核心,就是在这些事件发生之后,以"什么样的频率、什么样的策略"重新生成调度方案。
2.3 复杂度来源:NP-hard底子上的序贯决策
如果不考虑动态,FJSP本身已经被证明是NP-hard问题。原因在于机器分配子问题让每道工序的候选路径呈乘积式爆炸,像6台设备、10个工件、平均每道工序3个候选设备这种规模,彻底的枚举法已经不可能。
而动态调度在NP-hard之上还叠加了一层复杂度——决策需要在实时性约束下执行。静态问题给你一小时跑遗传算法没问题,动态场景里,现场要求的是"事件发生后几秒到几分钟内必须给出反应"。与此同时,由于扰动带来的状态是无限多的,你不可能为每一个状态预先计算一个最优计划存储起来;必须有一个策略模型,输入当前状态,直接输出决策。
很多人没有意识到的一点是,动态调度还对"解的质量评估"提出了新问题:一个调度方案好不好,往往只有在执行完以后才知道。但在没有执行之前,你需要用某种方式评估它的"未来收益",这正是强化学习所擅长的事情——利用与环境的大量交互,学习一个能够最大化长期累积回报的决策策略。
3. 方法选型复盘:为什么最终选择深度强化学习
3.1 三条技术路线的横向对比
立项初期我做了一个认真的技术选型,把主流方法分成三类来比较:数学规划/约束规划、元启发式算法(GA/PSO等)、深度强化学习。
先说数学规划。OR-Tools的CP-SAT求解器在中小规模静态调度上效果其实很好,很多场景下能在几十秒内给出不错的可行解。但这个方案的最大问题是动态适应能力太差——每发生一次扰动,就要重新建模、重新求解,时间上很难满足实时决策,更麻烦的是,在模型规模扩大时求解时间呈现超线性增长,不稳定。
再看遗传算法。遗传算法解FJSP是研究得很成熟的路线,工程上也容易实现。它和数学规划相比,优势在于对问题规模不敏感、能较快找到近似最优解。但它的两个致命短板在动态场景下暴露得很明显:一是每次扰动后需要重新跑整个种群演化,计算开销大;二是它没有"记忆能力",之前算过再好的调度经验,在下一轮重调度时完全用不上,相当于每次都是从头再来。
第三条路就是深度强化学习。它的思路是完全不同的:把调度过程看作一个序贯决策过程,通过在与仿真环境的交互中学习策略,能够在状态输入后毫秒级输出决策。更关键的是,它学到的决策规则是可以泛化的——遇到没见过的机器故障分布、没见过的订单组合,模型是有可能给出合理决策的。
三条路对比下来,我的判断是:生产现场对实时响应的需求太硬,传统方法在扰动频发的场景下撑不住。深度强化学习不一定能保证全局最优,但在动态+实时这个前提条件下,是三者里最可行的方案。
3.2 为什么不用传统强化学习
从强化学习内部来看,还有一个分层选择:传统强化学习方法(如Q-learning、Sarsa)和深度强化学习。在最早期的原型里,我试着用标准Q-table实现过一个非常小的调度问题,规则是"状态包含当前队列中每个工件的进度 + 每台设备的忙闲",动作是"下一道工序分配给哪台设备"。
Q-table方案的崩溃点非常直接:状态空间实在太大。哪怕把全厂状态粗粒度离散化到10个特征,每个特征5档,状态数就是5的10次方接近一千万,每个状态下还要遍历几十个动作,建表学习和存储都不现实。更不用说特征粒度太粗,会导致很多相似但实际不同的状态被归为一类,决策质量一塌糊涂。
深度神经网络的意义就在这里——用函数逼近器替代表格,一亿种状态也能用一套网络参数来表征,并且依靠深度学习模型的表征能力,从原始状态特征中自动提取对决策真正有用的信息。
3.3 评估标准的确定
选型时我给自己定了几条评估标准,后来发现这些标准在项目验收时也很有用:
| 维度 | 具体要求 |
|---|---|
| 实时性 | 单次调度决策耗时小于100毫秒 |
| 扰动适应 | 在机器故障、插单发生时,无需重新训练即可给出可用方案 |
| 方案质量 | 静态稳定场景下,不差于遗传算法解质量10%以内 |
| 数据要求 | 只需历史订单与加工数据,不需要真实车间试错 |
| 工程复杂度 | 具备可维护性,算法迭代不需要重新编写整个系统 |
事实证明,除了"方案质量"这个指标需要反复调优之外,其他几条深度强化学习都天然满足,特别是实时性——神经网络前向推理的时间几乎是恒定毫秒级,完全能嵌入现场调度系统。
4. MDP建模的核心设计:状态、动作、奖励与仿真环境的工程细节
4.1 状态空间:把车间特征压缩成向量
深度强化学习的第一个核心工作就是把车间状态变成一个神经网络可以吃进去的向量。这里不是简单地把所有数据堆在一起,而是要设计出一个既信息充足又维度可控的状态表示。
在这个项目里,我把状态分成四层:
- 工序进度层:每个工件当前进行到第几道工序、下一道工序的候选设备数量、该工件的剩余工序总加工时间、交期剩余时间。
- 设备状态层:每台设备当前是否空闲/加工/故障、设备当前排队长度、设备近期的负载率、设备已完成加工的总工序数。
- 时间特征层:当前仿真时刻、已经完成的总工序比例、各工件交期紧迫度的统计量。
- 任务队列特征层:当前等待队列里的工序数量、队列里各工序的预期加工时长分布、紧急订单在队列中的占比。
处理后,我把每个工件、每台设备的状态组织成一个固定长度的特征矩阵,再通过一个嵌入层送入网络。状态维度过大时要考虑归一化,否则神经网络的训练极不稳定。一个很实际的教训是:交期剩余时间这类特征,量纲差异巨大,必须缩放到(0,1)区间,否则前期训练loss曲线完全不动。
4.2 动作空间:分步决策与动作掩码
动作空间的设计决定了模型能学到什么。我见过不少项目把动作定义为"同时为一组工序分配设备",这个设计在柔性作业车间里很容易陷入指数级动作空间,训练策略几乎收敛不了。
我采用的是一种"分步决策"方式,也叫复合调度规则分解:每一步,模型先选择一个当前可加工的工序,再为该工序从候选设备集合中选择一台机器。这样动作空间就被拆成"选工序"和"选设备"两步,每一步的候选数量都被限制在车间规模级别,而不是组合爆炸级别。
分步决策还有一个工程上的好处——可以做动作掩码(action masking)。很多决策在物理上是不合法的,比如选择一台正在故障检修的设备,或者选择一台和当前工序工艺不匹配的机器。传统强化学习里,这类非法动作只能靠负奖励惩罚来避免,但那样训练收敛极慢;我直接在网络输出层用掩码把所有非法动作的概率置零,训练效率立刻提升了一大截。
4.3 奖励函数:从稀疏到密集的塑形过程
奖励函数是DRL调度项目里改动次数最多的部分。一开始,我用的是最朴素的稀疏奖励——每一个完整调度周期(即所有工件都完工)结束后,把负的makespan作为奖励信号。这样逻辑上完全正确,但训练过程几乎无法进行:一个周期要执行几百个决策步骤,只有在最后一步才拿到一个非零奖励,梯度信息根本无法有效地回传。
后来我把奖励改成了事件驱动的密集奖励。具体做法是,在每个决策步结束时计算:
- 当前完成工序数相对上一步的变化量,完成一道工序给予正向奖励。
- 设备空闲率的反向,设备不必要的空闲时间给予负向惩罚。
- 如果某工件拖期风险上升,给予相应惩罚。
这样做之后,模型在每个决策步都能获得反馈,训练收敛速度提升了一个量级。不过这里也有一个坑:奖励塑形过细会导致模型"短视",比如它会倾向优先把简单的工序做完,而不顾整体交期,最终的全局目标反而不好。最后我用了两层奖励叠加的办法,事件级奖励保证了探索效率,全局makespan稀疏奖励保证了优化方向不偏。
4.4 仿真环境:离散事件驱动的车间模拟器
强化学习需要和环境进行大量交互,真实车间不可能拿来做试错。所以核心基建是一个自研的离散事件仿真器(Discrete Event Simulator),它负责精确模拟车间中每一道工序的加工过程、设备占用、故障发生和队列变化。
仿真器要具备以下几个模块:
- 订单生成器:根据历史订单数据的分布,随机生成不同的任务集,保证训练时见过足够多样的负载和交期组合。
- 故障模型:每台设备按一定的失效率随机发生故障,故障持续时间符合某种概率分布(我用的是威布尔分布的简化形式)。
- 设备加工模拟:准确计算每个工序在指定设备上的加工时长,并记录设备的占用/释放时间。
- 执行回放:记录每一步的完整状态,用于后续分析和调试。
这个仿真器的准确度,直接决定了训练出来的策略在真实车间的表现。我后来专门花了两周时间,拿车间历史三个月的工单数据去校验模拟器的数据分布,确保加工时间、序间等待、故障概率等关键参数与现场统计基本吻合。这一块做得越扎实,模型上线后越靠谱。
5. 训练阶段的关键设置与性能调优
5.1 算法选型:PPO作为主力的理由
深度强化学习近年来的算法非常多,DQN家族、DDPG、TD3、SAC、PPO等各有特点。在调度场景里,我的选择是PPO(Proximal Policy Optimization),加起来一共试了将近两周时间,最后PPO在综合表现上明显占优。
核心原因有三点。第一,PPO是策略梯度类算法,天然支持离散动作空间,和前面的"分步决策"动作设计完美匹配;DQN虽然也支持离散动作,但在动作数量较大时价值函数逼近的误差会累积。第二,PPO通过裁剪(clip)限制了每一步策略更新的幅度,训练稳定性强,不容易出现突然的性能崩塌。这点在复杂调度环境里差异巨大,DDPG这类确定性策略算法在初期的策略崩溃问题几乎让人想摔键盘。第三,PPO天然适合并行采样,可以让多个车间实例同时跑采集数据,训练吞吐量大。
5.2 分布式采样架构设计
训练强化学**习模型最耗时间的是数据采集。一次"回合"意味着仿真器要把整个车间从早到晚运行一遍,而一轮训练要成千上万次回合。为了让训练速度可控,我搭了一套轻量的分布式采样架构:
- 一台训练机跑PPO的参数更新,负责梯度计算和策略网络同步。
- 6台采样Worker并行跑仿真环境,每个Worker独立维护一个车间仿真实例,按当前策略网络持续采样并收集经验数据。
- 采样Worker定期把收集的轨迹数据传到共享的Replay Buffer/Experience Buffer中,训练机批量取数据训练。
这套架构不算复杂,但效率提升非常明显。采样吞吐量从单机的每秒十几个决策步,提升到了每秒一两百个决策步。更重要的一点是,并行仿真实例天然增加了环境多样性,多个车间实例同时跑不同的随机场景,训练数据的覆盖度更好,模型最终泛化能力也更强。
5.3 关键参数配置与调优细节
以下是我在这套系统中最常调的一组参数,提供参考:
| 参数 | 配置值 | 备注 |
|---|---|---|
| 策略网络结构 | 两个256单元的全连接层,ReLU激活 | 状态特征量不大,深层网络收益有限 |
| 学习率 | 3e-4,采用线性衰减 | 初期过大容易崩溃,后期过小收敛慢 |
| PPO裁剪系数 | 0.2 | 这个值在调度问题上很稳定 |
| GAE λ | 0.95 | 平衡偏差与方差 |
| 折扣因子γ | 0.99 | 调度周期较长,需要长远考虑 |
| 每个batch大小 | 4096个决策步 | 太小噪声大,太大更新慢 |
| 采样回合数 | 每轮训练平行跑32个完整车间日程 | 保证状态覆盖度 |
| 训练总步数 | 约200万决策步 | 小规模问题在这个量级开始收敛 |
训练曲线上最容易观察到的信号是奖励均值。前期会有一段平台期,大约20万步内噪声较大,这是模型在探索阶段;过了这个阶段奖励会开始爬升,但中途可能会有突然下跌的情况。这里我后来总结出一个经验——不要一看到奖励下跌就调参,先确认是不是环境随机种子导致的波动,95%的情况是采样的方差问题,跑几轮就恢复了。
6. 动态事件重调度:扰动条件下模型能力的验证
6.1 事件驱动加周期触发的混合重调度机制
动态调度在工程实现上还有一个很关键的问题:什么时候触发一次重调度。学术界通常把它分成两类方式,一类是事件驱动(有扰动就立刻重新调度),一类是周期驱动(每隔固定时间重新调度)。在这个项目里,我采用的是一种混合机制。
- 事件驱动层:重要扰动事件(设备故障停机、紧急插单)一旦发生,系统立即触发一次重调度,重新从当前状态生成后续决策序列。
- 周期检查层:每30分钟执行一次状态检查和滚动窗口计划更新,用于消化加工时间偏差、小物料延迟等轻微扰动。
混合机制的最大好处,是避免了两种极端。如果什么事件都重调度,系统会陷入"决策抖动",一上午重排几十次,车间更乱;如果只靠周期调度,又可能漏掉关键的紧急插单。实测下来,重要事件立即响应、普通偏差周期性平滑修正的组合,是现场接受度最高的方案。
6.2 领域随机化与课程学习
训练时如果只在一种固定工况下训练,模型一上线换个工况就容易"懵"。我用的技巧是领域随机化(Domain Randomization),在训练时随机采样各种扰动强度和频率:
- 设备故障率按0.5%/小时到8%/小时之间随机。
- 紧急插单占订单总数比例从0%到20%随机。
- 加工时间偏差系数按±10%到±30%随机。
这种做法让模型在训练阶段见过各种极端情况,上线之后无论现场是平稳期还是混乱期,它都能给出稳定决策。
另一个训练策略是课程学习(Curriculum Learning)。初期让模型在无扰动的小规模场景中学习基础调度逻辑,收敛到一定水平后,再逐步提高问题规模与扰动强度。这个过程有点类似人学东西先易后难,模型的训练曲线更平滑,最终性能也更好。
6.3 实测效果:抗扰动能力的提升
我们做了一组对照实验:在同样的故障频率和插单分布下,分别用静态最优计划+人工重排、调度规则(EDD优先交期最短)、以及训练好的DRL模型来应对相同场景。结果显示,在扰动频繁的场景中,DRL模型的加权拖期指标比调度规则降低了18.7%,比静态计划+人工重排降低了23.5%;设备平均利用率也提高了近7个百分点。
DRL模型最明显的行为特征,是它在故障发生后会自动优先把"受影响工件"挪到其他空闲且工艺兼容的设备上,同时会刻意保持一台备用设备的轻负载状态——这是为了给后续可能的扰动预留缓冲。这些调度策略我们从来没有显式编程教过它,完全是从数据中自己学到的,这是这个项目最深的一个体验:深度学习能做到超越人类直觉的决策规律。
7. 完整实验对比与部署中的坑
7.1 实验设计与对比结果
为了验证方案的全面性,我设计了三层对比实验:
第一层:静态场景基准测试。在无扰动条件下,和遗传算法、CP-SAT求解器对比。小规模场景(6台设备10个工件),DRL结果与CP-SAT最优解差距在3%以内,略优于GA;在较大规模场景(20台设备80个工序),DRL在makespan上比GA平均好7%,但比CP-SAT略差。这一层结论很清楚:静态场景DRL不是最强的,但已经处于可用级别。
第二层:动态扰动鲁棒性测试。在训练时设定的扰动分布内,DRL远超对比算法;在分布外的极端场景(比如故障率突然加倍),其他方法的表现明显恶化,DRL仍能维持相对稳定的决策质量。
第三层:泛化性测试。用训练时没见过的订单数量和工件组合测试,模型推理出的调度方案仍然保持可用。换到问题规模变化时,性能有下降,但不会失效。
7.2 试运行现场的模型失效问题
上线试运行阶段,我踩过两个印象深刻的坑。
第一个坑是特征分布偏移。训练时仿真的加工时间都是按工艺定额设置的,但现场实际工件装夹、换刀、测量时间会和定额差不少。训练模型没见过这种偏移量级的状态特征,上线后输出的决策明显变差。解决办法是给状态特征加入"实时校正偏差"项,同时用现场数据持续做仿真校准。
第二个坑是奖励函数与现场目标不一致。实验室里的奖励函数偏重makespan,但车间主任最关心的是"周五之前能出多少货"。一个不匹配的优化目标,再好的模型也等于零。我在试运行两周后把奖励函数里的交期权重上调,重新训练了一轮,指标才明显改善。这件事对我的触动很大——算法再高级,先对齐业务目标,永远是第一优先级。
7.3 工程部署的几条经验
最后分享几条工程部署层面的实际经验:
环境一致性。训练时的仿真器和部署时的仿真器必须是同一套代码、同一个配置,否则训练环境学到的策略在部署环境上会打折扣。我把仿真器的版本管理纳入了CI流程,任何改动都必须跑回归测试。
模型监控与回退。模型上线后不能完全不管。我搭了一个简单的监控面板,实时显示模型输出的调度方案在仿真空跑预估中的表现——如果连续多次决策预估效果明显低于阈值,系统自动回退到预设的调度规则兜底,并触发人员介入。这个"人机协同兜底"机制,让现场管理人员对系统的信任度大幅提升。
持续学习通道。现场每运行一个月,都能积累一批真实的调度执行数据,我会清洗后加入训练集做增量更新。这样做一方面缓解了特征分布偏移,另一方面也让模型能适应车间长期的变化(比如增加了新设备、产品结构变化)。增量更新不需要每次都从头训,加载旧模型权重训练一个epoch左右就能完成。
最后再说几句
这个项目从选型、建模、训练到上线,前后经历了将近五个月。回头看,深度强化学习解决柔性作业车间动态调度的核心价值,并不仅仅在于它能在某个指标上比传统方法强多少,而是在于它把调度问题的求解范式从"一次性离线优化"转变成了"持续在线决策"。设备故障不会打垮系统,紧急插单不会让计划作废,模型每时每刻都在根据最新状态重新评估怎么做最好。
如果你也想在这个方向尝试,我的建议是先别急着堆网络结构、调超参数,把仿真环境做扎实,把MDP建模想清楚,这两个环节决定了80%的上限。剩下的20%是训练技巧和工程打磨,那些可以慢慢调。另外,永远记住一个原则:算法只是工具,车间里的真问题才是我们需要低头仔细研究的对象。