
作业车间调度是制造系统里最经典的优化问题之一。这些年我一直在帮工厂做排产相关的算法落地最深的体会是调度算法从理论到能用中间隔着一条叫状态表达的河。模型选得再花哨超参调得再好状态特征设计不到位整个深度强化学习方案就是纸上谈兵。这篇我用实际项目的视角系统梳理一下作业车间调度中深度强化学习的状态特征设计框架把踩过的坑、验证过的方案和可以直接抄作业的代码结构都摊开讲。先说清楚这篇文章适合谁正在做车间调度、柔性产线排程、制造执行系统MES里智能决策模块的算法工程师以及想入深度强化学习落地制造场景的研究生。如果你对作业车间调度问题Job Shop Scheduling Problem, JSP有基本概念读起来会非常顺畅。如果完全没接触过我建议先稍微了解一下工序、机器、完工时间这些基础概念再来看不然有些细节会接不上。1. 项目目标与问题边界1.1 我们在解决什么问题作业车间调度的核心难点一句话说就是给定一批工件每个工件有固定的多道工序每道工序必须按顺序在某台特定机器或某组机器上加工怎么安排加工顺序和开工时间才能让某个目标最优最常见的目标是最小化最大完工时间Makespan也有工厂更关心延迟率、设备利用率或者能耗。传统方法比如遗传算法、禁忌搜索、分支定界在小规模问题上表现不错。但一旦订单数量上百个、工序上千道这些方法的求解时间会指数级爆炸。深度强化学习Deep Reinforcement Learning, DRL天然适合这种场景它把调度过程建模成马尔可夫决策过程MDP智能体每到一个决策点看一眼当前车间的快照决定下一步做什么动作。只要状态特征设计得够好训练出来的策略网络一次前向推理就能给出非常好的调度方案快、可扩展、还能针对不同实例做迁移。1.2 为什么状态特征设计是成败的关键我在做第一个调度DRL项目时一开始把注意力全放在网络结构上用了一堆Transformer、图注意力效果却很一般。后来复盘发现问题出在状态特征上。DRL的核心公式是状态-动作-奖励状态是智能体做决策的唯一信息来源。如果特征本身含混、冗余、重复、缺失关键信号再强的网络也学不出来。打个比方你让一个从没进过厨房的人做菜他的感官接收信息的能力是有限的。如果他只看得到食材看不到锅的温度闻不到香味听不到油爆的声音那就算他有米其林大厨的头脑也白搭。状态特征就是智能体的感官决定的不是它多聪明而是它能感知到什么、感知得是否准确。1.3 状态特征设计要满足的三个核心属性我把多年的经验浓缩成三个词信息完备性、结构规整性、训练稳定性。信息完备性是指状态要包含足够的信息来支撑决策。对于作业车间调度正在加工的机器状态、每个工件的工序进度、哪些设备空闲、哪些工件即将可用这些不能漏。结构规整性是指状态特征是固定维度的向量或者说至少在一个batch内是规整的。因为深度神经网络的输入维度是固定的你不能这次给100维、下次给120维。调研中发现很多论文用图神经网络处理这类变长问题但是工程落地时如果你能通过精巧的固定维度特征设计让网络在简单MLP或全连接网络上跑出效果部署成本会低非常多。训练稳定性是指特征的数值范围不能剧烈波动。状态特征里如果有一部分是几百、几千的大数值另一部分是0到1的小数值网络收敛会非常慢甚至发散。特征归一化不是可选项是必需项。2. 状态特征体系设计方法论2.1 第一层工序级特征工序级特征是所有特征中最贴近问题本质的一层它回答的问题是当前这些待排工序在什么状态下。每个工件由多道工序构成每道工序都有几个天然属性工序所属的工件编号或者更准确地说工件在当前排序中的序号这道工序是工件的第几道工序该工序需要哪一台机器加工在对应机器上的加工时间该工序的剩余处理时间占总加工时间的比例该工件本身的剩余工序数在这一层设计上最容易犯的毛病是直接采用工件是哪个的ID作为特征比如工件编号0到N-1。这种离散ID特征对神经网络来说是灾难因为它没有任何语义上的顺序性或相似性。我见过有项目把工件ID one-hot编码后拼进状态结果训练出的策略对训练集中的工件ID有严重的过拟合换一个数量级的实例就彻底崩盘。我更推荐的做法是把工件的关键属性抽象成时间、比例这类连续量。比如该工件剩余总加工时间是一个好特征该工件当前工序序号占其总工序数的比例是一个好特征但该工件是第7个工件不是一个好特征。2.2 第二层机器级特征机器级特征描述的是每台机器当前的工作负荷和忙碌情况。在决策时我们需要知道每台机器上当前正在加工的工序剩余时间是多少该机器的等待队列长度该机器的总负载所有被分配但未开工工序的加工时间之和该机器已累计完成的有效工作量该机器未来一段时间内是否会被大量占用这一层特征我经历过最深刻的教训是只用当前机器忙不忙这种二值特征远远不够。调度中最关键的决策往往发生在机器从忙转闲的瞬间——这时候你要决定下一个放入机器的是哪个工件。如果你只知道机器忙或闲就无法评估放入一个新工序会带来多大的挤占效应决策质量会大幅度下降。我最终采用的方案是用一组滑动窗口的未来负载曲线作为机器特征。把时间轴分成若干等份比如未来10个时间窗口每个窗口内该机器的累计加工负荷就是一项特征。这样一个机器用10个数就能表达出它的忙碌节奏。可以做一个小的对比帮助读者直观理解设计思路特征示例表达能力问题所在基础版是否空闲弱丢失了负载量信息进阶版队列长度总负载中丢失了时间分布信息推荐版滑动窗口负载曲线强需要多调一个窗口数量参数2.3 第三层全局级特征全局级特征描述的是整个车间的宏观状态它的作用是让智能体跳出局部纵览全局。这层特征包括当前系统时间归一化后所有机器平均空闲率所有工序的平均延迟程度当前完成率已完成工序占总工序的比例归一化剩余总工作量为什么需要全局特征因为纯粹的局部特征会让智能体陷入短视。比如两台机器一台队列很长但是总负载很大另一台队列短但是即将空闲只看局部特征算法可能倾向于清空短队列但全局最优解可能是优先处理长队列里最关键的瓶颈工序。我在部署时发现全局特征加入后策略网络的决策质量有一个非常明显的跃升尤其是在实例规模扩大时。这个现象后来想明白了全局特征提供了对当前调度阶段的一种定位感网络可以从中判断现在处于调度早期的宽松期还是晚期的紧张期从而选择合适的策略模式。3. 核心特征编码实操3.1 维度规整的处理办法作业车间调度问题的难点还在于不同实例的工件数量、机器数量都不同。如果每台机器都生成一份特征那么机器数是10时特征维度是100机器数是50时特征维度是500网络很难弹性地处理。我比较常用的办法有两种第一种是最大规模对齐法在数据集构建阶段统计所有实例的最大机器数和最大工序长度每个实例的特征向量都按最大维度生成不足的部分用0填充再用一个Mask向量标明哪些位是有效的。这种方法简单、粗暴、有效但缺点是特征维度会被最大实例拉得很高小实例上有大量0填充浪费计算。第二种是特征聚合法不直接对每台机器单独建特征而按排名聚合。比如把机器的负载从高到低排序取Top-K台的负载值再取Bottom-K台的负载值配合平均值、中位数、方差。这样不管机器总数是10还是200只要K固定特征维度就固定了。我测试下来两种方法各有长短。如果模型使用注意力机制或卷积最大规模对齐法是优选因为保留了完整的空间关系。但如果模型只是全连接网络在部署时经常这样做特征聚合法更稳定、更轻量。我实际落地时两种方法用了不同项目给车间规模波动特别大的集团级排产平台用特征聚合给单一工厂固定机群的排程模块用最大对齐。3.2 时间相关特征的归一化策略调度问题里的时间特征加工时间、剩余时间、等待时间有一个特点倍数差异极大。一道工序可能只需要30秒另一道可能要用8小时。如果直接把这些原始值塞进网络特征空间会被大数值主导小数值特征几乎学不到东西。推荐的归一化策略是最大完工时间预估归一化在所有工件静态数据已知的前提下我们可以用简单的启发式方法比如最短加工时间优先规则跑一个快速仿真得到一个参考最大完工时间T_ref然后把所有时间类特征除以T_ref。这样让所有时间特征落在0到1区间附近且保留了绝对时间的变化信息。这一步我强烈建议在数据管道里做而不要在损失函数里做。把归一化和特征提取绑定成一个模块训练和推理共用同一套代码可以避免最大的坑推理和训练时用了不同的归一化基准导致模型完全失效。我踩过这个坑具体情况是这样的训练时用的归一化基准是所有训练实例的全局最大时间上线测试时某个新实例恰好有更长的大工件结果模型输出的调度方案在完工时间上莫名其妙翻了倍。后来把归一化基准改成每批实例的启发式预估T_ref问题才彻底消失。3.3 Mask特征与决策边界处理在作业车间调度中不是所有工件在任何时候都能被放上机器。一个工件必须等它的前道工序完成才能开下一道工序一台机器同一时间只能加工一道工序。如果特征里没有Mask信息智能体可能输出一个非法动作把不该放的工序放上去或者在她还没准备好时就强行开工。模型设计时我将状态特征拆成两段全候选集特征和合法动作Mask。全候选集特征是全量的、不区分合法性的原始状态合法动作Mask是一个变长二进制数组标明哪些候选工序此刻是可以被选择进入对应机器的。训练时损失函数里对非法动作的Q值直接做遮蔽处理这样网络只需要在合法动作空间里做选择。实际操作中有个小技巧Mask不只是二值0/1还可以加入偏好引导的半Mask。比如如果某台机器当前空闲且一个工件的下一道工序恰好可以在该机器上加工那么这个动作的Mask值设为1.2比常规1略大相当于给网络一个轻微的倾向性先验。这样在不显著限制探索空间的前提下加速了初始训练阶段的有效动作访问。4. 网络结构衔接与训练细节4.1 从特征向量到策略输出的完整链路状态特征设计好了以后还要考虑它和网络的衔接方式。我经常用的一套组合是这样的原始状态特征工序级 机器级 全局级拼接成一维向量过一个两层全连接特征编码器输出一个256维的隐向量隐向量分别进入两个头策略头输出每个动作的概率或者Q值和值函数头输出当前状态的估计价值训练时用TD误差更新决策时直接取Q值最大的合法动作这套结构本质上是一个Dueling DQN的变体。选择Dueling结构的原因在于作业车间调度的奖励很多时候非常稀疏状态价值V(s)比动作优势A(s,a)的波动要平滑得多把两者分开建模可以加速训练。网络层数不需要很深我测试过2层和6层的对比效果差异不大但推理速度差了将近3倍。在车间场景里决策时延直接关系到产线等待时间所以除非特殊必要尽量不要堆深度。4.2 奖励函数与状态特征的联动设计很多人忽略了状态特征和奖励函数是唇亡齿寒的关系。我在设计奖励函数时刻意让奖励信号和特征中包含的信息保持对齐。举个具体例子如果状态特征中包含机器空闲时间占比那么奖励函数里可以加一项惩罚惩罚所有机器的平均空闲时间这样智能体在优化奖励时就会调整机器负载分布而这个调整又会反过来体现为状态特征的变化形成一个闭环的学习信号。但如果状态特征里压根没有机器空闲信息你却在奖励里要求最小化机器空闲智能体就处于盲飞状态它接收不到与奖励对应的信息学习会非常缓慢且不鲁棒。工业界常用的一种奖励组合r -α × Makespan增量 - β × 平均机器空闲率增量 - γ × 超期工序惩罚在实际项目中α、β、γ的取值要边训练边调。我一般先从α主导开始训练到策略基本收敛后再逐步提高β和γ的权重让模型从只关注最终完工时间过渡到同时关注过程中间状态。4.3 实测经验状态特征维度与训练收敛速度的关系这块我做了不少对比实验用同一份数据集包含200个中等规模的作业车间实例训练的观察结果可以整理成一个参考表特征维度网络规模收敛所需环境交互步数最终评价Makespan偏差vs. 最优解32维极小网络150k12.4%128维标准MLP100k6.7%512维加深MLP80k5.8%1024维大网络140k6.2%有意思的地方在于512维是表现最佳的1024维反而出现了轻微的过拟合和训练不稳定。特征太多并不总是好事尤其当部分特征是冗余或高度相关时网络会花费额外的容量去理解这种相关性而不是去学习真正的调度策略。我的建议是特征设计要像做减法一样。先用42个核心特征跑通链路然后每增加一个特征都要用A/B测试去验证它是否带来真实的决策质量提升。宁可特征少而精不要大而全。5. 实战踩坑与排查速查表5.1 问题一训练不收敛或震荡剧烈表现损失函数上下跳动策略指标忽高忽低。我排查过这个现象很多次最可能的原因是状态特征中的数值范围没有处理好。车间调度的工序时间常常跨越几个数量级一次归一化处理不到位梯度更新就会出现被大数值特征主导的情况。另一个原因经常出现在最大规模对齐法的特征填充上大量0填充的Mask位如果没在损失函数里做遮蔽模型的梯度会被无意义的0特征稀释。我刚开始就犯了这个错后来把Mask乘到特征向量和损失上问题立刻缓解了大半。5.2 问题二换一批实例就失效泛化性差表现训练集里效果很好换一组新到的订单数据就明显退化。泛化性差的根源往往在特征里带了实例特有信息。比如把工件ID、具体工件名称直接当成特征或者把每一台机器的编号抽象成独立的槽位编号。正确的做法是只保留物理含义明确的信息加工时间、所需机器类型、工序序号等。我个人的一个补充经验是训练数据集里故意混入不同规模、不同工序分布的实例模拟现实中的订单波动。状态特征设计得当与否在这种乱序混合的训练方式下会非常直观地暴露出来。如果换了数据分布就崩先别急着加正则化和早停回过头检查特征里有没有不该出现的实例特有变量。5.3 问题三调度动作犹豫不决表现网络输出的概率分布非常平坦各动作Q值差距很小调度决策像在随机猜测。这种情况往往发生在训练早期属于模型还没找到规律。但我发现一个很多人忽略的细节状态特征里如果同时放了过多高度相关的特征比如机器负载信息同时出现了三次虽然形式略为不同网络很容易陷在伪相关性里迟迟无法找到真正的决策信号。解决方式就是做特征筛选。计算各特征之间以及特征与最终最优动作标签如果用最优解做监督辅助的互信息把互信息极低和特征间相关系数超过0.9的冗余特征剔除。5.4 常见问题速查表现象主要原因方向性解决建议训练不收敛特征数值范围未归一用启发式预估T_ref做时间特征归一换实例就崩特征带实例ID信息删除ID保留物理量特征收敛太慢奖励与特征不对齐检查奖励项是否都有对应特征支撑策略平坦特征冗余互信息低做互信息筛选减少相关特征推理时延高网络过深或特征过大压层数到2-3层特征压到512维以内非法动作频出Mask未接入损失函数确保动作Mask遮蔽到策略输出层5.5 独家避坑状态特征设计要面向部署做冻结在很多项目里状态特征设计在训练阶段是一套代码到了部署阶段又因为要对接不同的MES系统而修改了一部分。这是非常危险的动作。我的建议是特征提取函数的输入输出接口在项目启动初期就尽量固定训练代码和推理解析代码共用同一个特征提取模块。有一次我把特征提取函数做了接口重构从字典输入改成数据库查询输入忘记同步更新训练端的构造方式结果整个策略模型的决策逻辑完全错乱了但报错信息非常隐蔽排查了两天才发现是特征喂错了。从那以后我对特征提取模块一律采用单一函数、单一定义、双端共用的铁律。6. 落地效果评估与扩展方向思考6.1 基准评估的方法与经验状态特征设计得好不好必须用数据说话。我通常分三层评估第一层是训练曲线的对比。观察不同特征设计方案下训练回合逐步增加时累积奖励的变化曲线。正常情况应该是单调上升然后趋于平稳如果在某个位置长期平台期说明特征表达能力到了瓶颈。第二层是离线指标对比。用标准测试实例集比如作业车间调度公共基准里不同规模的数据对比DRL方案的Makespan与最优解或上界解的差距。我要求自己的方案在不同规模下普遍保持在10%以内的差距才算有实用价值。第三层是仿真平滑度测试。用离散事件仿真器跑一遍调度方案统计机器的平均利用率、工序平均等待时间等过程指标。这一步最容易暴露看起来解好实际产线跑不顺的问题。比如生产的调度方案Makespan很低但机器切换频繁实际换模时间远超预估总完工时间反而更高。6.2 后续可以这样扩展整套框架里最核心的可扩展点是面向大规模工件的图结构特征。如果工件数量非常大比如超过500个统一维度的全局特征表达会比较紧张这时候可以考虑在状态特征中插入图神经网络的节点嵌入表达——每个工件是图上的一个节点工序之间的先后关系就是图的边用GNN层把节点信息做消息传递后汇总成固定维度的特征向量输出给全连接网络。另一种我非常看好的扩展方向是基于元学习的跨工厂迁移。状态特征设计得好的标准之一是同一个特征提取模块可以适配不同工厂的差异。元学习则更进一步让策略能够在看到几个示范实例后快速调整特征提取的权重适应新产品结构和新机器配置。不过不管怎么扩展我始终认为状态特征设计是DRL调度方案的根本。它决定了整个系统的信息天花板网络结构只是在这个天花板下面做拟合。与其花大量精力去改造网络结构不如踏踏实实把特征体系设计好。我个人在实际项目中的体会是状态特征设计这项工作就像给一个资深调度员做岗前培训你不能只告诉他尽量用最短时间做完活你得把他日常经验里判断优先级的所有信号都完整告诉他。机器在响、工件在排队、瓶颈在形成、紧急单在插队这些信号汇总成他的判断力。DRL里的状态特征就是把这种判断力从隐性经验变成显性数据的过程。只要特征框架搭对后面的训练和部署都会顺利很多。