1. 多目标强化学习与规划为什么值得啃透第一次看到《A Practical Guide to Multi-Objective Reinforcement Learning and Planning》这个标题我下意识以为又是一篇堆公式的综述。真正读进去才发现它更像一份写给工程师的“落地手册”——把多目标强化学习MORL和多目标规划MOP这两个经常被混为一谈的方向从问题定义、算法分类到实验设计一条线捋清楚了。如果你正在做资源调度、推荐策略、机器人路径规划或者任何需要同时权衡多个指标的决策系统这篇内容基本绕不开。多目标强化学习和单目标最大的区别在于“最优”这个概念本身变了。单目标里奖励是一个标量谁大谁好排序天然存在。多目标里奖励是一个向量比如同时考虑“耗时”和“能耗”你没法说(3, 5)和(5, 3)哪个更好——它们可能都是帕累托最优解。这个看似简单的变化直接导致策略空间从一条曲线变成一个高维曲面算法设计、评估方式、甚至“什么叫收敛”都要重新定义。这篇指南的价值在于它没有停留在“帕累托前沿”这种概念层面而是把当前主流方法拆成了几大类基于单策略的、基于多策略的、基于分解的、基于偏好条件的。每一类都讲清楚了适用场景和代价。比如单策略方法把多目标压缩成加权和跑得快但只能拿到前沿上的一个点多策略方法能一次性逼近整条前沿但计算开销和存储成本成倍上升。这种取舍在真实项目里几乎天天遇到指南里给了很实用的判断依据。我读这篇内容时最大的感受是它把“偏好”这个模糊的东西工程化了。在MORL里偏好就是你告诉算法“我更在乎哪个目标”可以是一个权重向量也可以是一条约束。指南里专门讨论了偏好如何注入训练过程——是在奖励函数里加权还是在策略网络里做条件化还是在回放缓冲区里做筛选。不同注入方式对最终前沿的覆盖度和均匀性影响很大这部分是我认为最值得反复看的地方。适合读这篇内容的人我建议至少满足两个条件之一要么你已经做过单目标强化学习项目想扩展到多目标场景要么你在做多目标优化但想引入序贯决策的框架。如果两者都不沾直接啃会有点吃力因为里面默认你理解MDP、值函数、策略梯度这些基础概念。不过指南本身写得比较务实公式推导不多更多是讲“为什么这么设计”和“什么时候该用哪个方法”所以有一定工程背景的人也能跟上。2. 多目标问题的核心框架拆解2.1 从单目标MDP到多目标MDP的关键变化单目标MDP用一个五元组描述(S, A, P, R, γ)其中R是标量奖励。多目标MDP把这个R换成了向量R (R₁, R₂, ..., Rₙ)每个分量对应一个目标。这个改动看似只是把标量换成向量但连锁反应很大。首先是值函数的定义。单目标里值函数V(s)是一个标量表示从状态s出发的期望折扣回报。多目标里值函数变成一个向量V(s) (V₁(s), ..., Vₙ(s))每个分量对应一个目标的期望回报。这时候比较两个状态的好坏就不能用“大于”了得用帕累托支配关系如果V(s₁)在每个分量上都不差于V(s₂)且至少有一个分量严格更好才说s₁支配s₂。其次是策略的优劣判断。单目标里策略π₁优于π₂当且仅当V^π₁(s) ≥ V^π₂(s)对所有s成立。多目标里一个策略可能在某些目标上更好在另一些目标上更差于是产生了“帕累托最优策略集”的概念。这个集合里的策略互不支配最终选哪个取决于决策者的偏好。注意很多初学者会直接把多目标奖励加权求和变成单目标然后用标准RL算法跑。这样做不是不行但每次只能得到一个偏好下的最优策略想覆盖整条前沿就得反复训练成本极高。指南里明确把这种方法归为“单策略方法”并指出了它的适用边界。2.2 帕累托前沿与偏好空间的关系帕累托前沿Pareto Front是多目标优化的核心概念指的是所有帕累托最优解在目标空间里形成的曲面。在MORL里这个前沿是由策略参数决定的每个策略对应前沿上的一个点。偏好空间则是决策者表达“我想要哪个权衡”的空间。最常见的偏好表示是权重向量w (w₁, ..., wₙ)满足wᵢ ≥ 0且Σwᵢ 1。给定一个权重向量标量化后的奖励是r Σwᵢrᵢ对应的最优策略就是前沿上在该权重方向上的“支撑点”。这里有个容易踩的坑均匀采样权重向量并不一定能得到均匀分布的帕累托前沿点。当目标之间的尺度差异很大或者前沿形状非凸时均匀权重可能让大部分点挤在某一小段前沿上。指南里专门讨论了这个问题并给出了几种改进策略比如用切比雪夫标量化代替线性加权或者在训练过程中动态调整权重采样。2.3 多目标规划与多目标学习的边界标题里同时出现了“Reinforcement Learning”和“Planning”这两者在多目标语境下的边界值得说清楚。多目标规划通常假设环境模型已知比如状态转移概率和奖励函数都是显式给出的。这时候可以用动态规划、线性规划或者多目标进化算法来直接求解帕累托前沿。规划方法的优势是理论保证强能精确刻画前沿的形状但缺点是依赖模型模型不准就全盘皆输。多目标强化学习则不要求模型已知通过与环境交互来学习策略。它的优势是适应性强能处理复杂、未知的环境但代价是样本效率低前沿的逼近质量受探索策略影响很大。指南里把这两者放在一起讲是因为很多实际系统是混合的一部分动态已知可以用规划另一部分未知需要学习。比如机器人导航里地图和运动学模型可能已知但人类偏好和动态障碍物的行为未知。这时候就需要规划和学习协同工作。3. 主流算法分类与选型逻辑3.1 单策略方法与多策略方法的取舍单策略方法的核心思想是把多目标压缩成单目标然后用标准RL算法求解。最常见的压缩方式是线性加权r Σwᵢrᵢ。给定一个权重向量w跑一次单目标RL得到一个策略。想得到多个策略就换不同的w反复跑。这种方法的优点是实现简单可以直接复用现有的单目标RL代码库调参经验也能迁移。缺点是每次训练只能得到一个偏好下的解而且当帕累托前沿非凸时线性加权无法覆盖前沿的凹陷部分。多策略方法则试图在一次训练中逼近整条前沿。典型代表包括多目标Q学习维护一个向量值函数用帕累托支配关系更新Q值。多目标策略梯度输出一组策略每个策略对应不同的偏好条件。进化算法结合RL用种群搜索来探索策略空间每个个体对应一个偏好。多策略方法的优势是一次训练覆盖多个偏好缺点是计算和存储开销大而且随着目标数量增加前沿的维度爆炸逼近难度急剧上升。实操心得如果目标数量不超过3个且计算资源充足优先考虑多策略方法。如果目标数量超过5个或者资源紧张单策略方法配合合理的权重采样往往更实际。我试过在4目标场景下强行跑多策略结果训练时间翻了6倍前沿覆盖度还不如单策略跑20个权重。3.2 基于分解的方法怎么用基于分解的方法借鉴了多目标进化算法里的思想把多目标问题拆成一组单目标子问题每个子问题对应一个权重向量或参考点。然后并行或交替求解这些子问题利用子问题之间的邻域关系来共享信息。在MORL里常见的分解策略包括加权和分解每个子问题用不同的权重向量做线性加权。切比雪夫分解用最大偏差来定义子问题能更好地处理非凸前沿。边界交叉分解适合目标数量较多的情况能均匀覆盖前沿。分解方法的优势是能并行化而且子问题之间的信息共享可以加速收敛。缺点是子问题的数量随目标数量指数增长需要合理设置邻域大小和更新频率。指南里给了一个很实用的建议先用少量子问题快速得到一个粗略的前沿估计然后在前沿稀疏的区域增加子问题密度。这种自适应分解策略比均匀分解效率高很多。3.3 偏好条件化策略网络的设计要点偏好条件化是多策略方法里很优雅的一种思路训练一个策略网络π(a|s, w)输入状态s和偏好w输出动作。这样一次训练就能覆盖整个偏好空间推理时只需要改变w就能得到不同的策略。设计这种网络时有几个关键点偏好编码方式w可以直接拼接在状态后面也可以通过一个嵌入层映射到更高维空间。对于目标数量较多的情况嵌入层能更好地捕捉偏好之间的非线性关系。训练时的偏好采样训练时w的采样分布直接影响前沿覆盖度。均匀采样简单但可能不够高效可以结合前沿估计来动态调整采样密度。网络容量偏好条件化会增加网络的输入维度需要适当增加隐藏层宽度或深度否则不同偏好下的策略会互相干扰。我踩过的一个坑是偏好向量直接拼接时如果不同目标的尺度差异很大网络会偏向数值大的目标。解决办法是先对偏好做归一化或者在网络里加一层可学习的缩放。4. 实操流程与关键环节实现4.1 环境搭建与依赖选择要复现指南里的实验环境搭建是第一步。我建议用Python生态核心依赖包括pip install gymnasium numpy torch matplotlib如果要做多目标规划部分还需要pip install pymoopymoo是多目标优化库提供了丰富的帕累托前沿计算和可视化工具。gymnasium是OpenAI Gym的维护版本接口稳定社区支持好。torch用于实现策略网络和价值网络。注意不要用太老的Gym版本接口变动很大很多教程里的代码跑不通。Gymnasium的API和旧版Gym基本兼容但修复了不少bug。4.2 多目标环境的定义与奖励设计定义一个多目标环境关键是设计奖励向量。以经典的“资源分配”场景为例一个智能体需要把有限的资源分配给多个任务每个任务有独立的收益曲线。import gymnasium as gym from gymnasium import spaces import numpy as np class ResourceAllocationEnv(gym.Env): def __init__(self, n_tasks3, total_resource100): super().__init__() self.n_tasks n_tasks self.total_resource total_resource self.action_space spaces.Box( low0, high1, shape(n_tasks,), dtypenp.float32 ) self.observation_space spaces.Box( low0, high1, shape(n_tasks,), dtypenp.float32 ) self.state np.ones(n_tasks) / n_tasks def reset(self, seedNone): super().reset(seedseed) self.state np.ones(self.n_tasks) / self.n_tasks return self.state, {} def step(self, action): action np.clip(action, 0, 1) allocation action / (action.sum() 1e-8) * self.total_resource rewards np.array([ np.log1p(allocation[i]) for i in range(self.n_tasks) ]) self.state allocation / self.total_resource terminated False truncated False return self.state, rewards, terminated, truncated, {}这个环境里奖励是一个向量每个分量对应一个任务的收益。收益函数用对数是为了模拟边际递减效应这在资源分配里很常见。4.3 单策略基线实现与权重采样先用单策略方法跑一个基线。核心思路是采样一个权重向量w把向量奖励加权成标量然后用PPO或DQN训练。import torch import torch.nn as nn import torch.optim as optim class PolicyNet(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, 64), nn.ReLU(), nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, action_dim), nn.Softmax(dim-1) ) def forward(self, x): return self.net(x) def scalarize(rewards, weights): return np.dot(rewards, weights) def train_single_policy(env, weights, episodes500): state_dim env.observation_space.shape[0] action_dim env.action_space.shape[0] policy PolicyNet(state_dim, action_dim) optimizer optim.Adam(policy.parameters(), lr1e-3) for ep in range(episodes): state, _ env.reset() log_probs [] rewards [] for step in range(50): state_tensor torch.FloatTensor(state) probs policy(state_tensor) action torch.multinomial(probs, 1).item() log_probs.append(torch.log(probs[action])) state, reward_vec, terminated, truncated, _ env.step( np.eye(action_dim)[action] ) rewards.append(scalarize(reward_vec, weights)) if terminated or truncated: break returns [] G 0 for r in reversed(rewards): G r 0.99 * G returns.insert(0, G) returns torch.FloatTensor(returns) returns (returns - returns.mean()) / (returns.std() 1e-8) loss -torch.stack(log_probs) * returns optimizer.zero_grad() loss.sum().backward() optimizer.step() return policy权重采样策略直接影响前沿覆盖度。我一般先用均匀采样跑一轮看看前沿的大致形状然后在稀疏区域加密采样。def sample_weights(n_objectives, n_samples): weights np.random.dirichlet(np.ones(n_objectives), n_samples) return weightsDirichlet分布能保证权重和为1且非负比手动归一化方便。4.4 多策略偏好条件化网络实现偏好条件化网络的关键是把偏好向量作为额外输入。网络结构上我习惯用两个分支一个处理状态一个处理偏好然后在中间层拼接。class PreferenceConditionedPolicy(nn.Module): def __init__(self, state_dim, pref_dim, action_dim): super().__init__() self.state_net nn.Sequential( nn.Linear(state_dim, 64), nn.ReLU() ) self.pref_net nn.Sequential( nn.Linear(pref_dim, 32), nn.ReLU() ) self.fusion nn.Sequential( nn.Linear(96, 64), nn.ReLU(), nn.Linear(64, action_dim), nn.Softmax(dim-1) ) def forward(self, state, pref): s self.state_net(state) p self.pref_net(pref) x torch.cat([s, p], dim-1) return self.fusion(x)训练时每个episode采样一个偏好向量然后跑完整的轨迹。损失函数和单策略类似只是标量化时用的是当前采样的偏好。def train_preference_conditioned(env, episodes2000): state_dim env.observation_space.shape[0] action_dim env.action_space.shape[0] pref_dim 3 policy PreferenceConditionedPolicy(state_dim, pref_dim, action_dim) optimizer optim.Adam(policy.parameters(), lr1e-3) for ep in range(episodes): pref np.random.dirichlet(np.ones(pref_dim)) state, _ env.reset() log_probs [] rewards [] for step in range(50): state_tensor torch.FloatTensor(state) pref_tensor torch.FloatTensor(pref) probs policy(state_tensor, pref_tensor) action torch.multinomial(probs, 1).item() log_probs.append(torch.log(probs[action])) state, reward_vec, terminated, truncated, _ env.step( np.eye(action_dim)[action] ) rewards.append(np.dot(reward_vec, pref)) if terminated or truncated: break returns [] G 0 for r in reversed(rewards): G r 0.99 * G returns.insert(0, G) returns torch.FloatTensor(returns) returns (returns - returns.mean()) / (returns.std() 1e-8) loss -torch.stack(log_probs) * returns optimizer.zero_grad() loss.sum().backward() optimizer.step() return policy4.5 帕累托前沿的可视化与评估训练完之后需要评估前沿的覆盖度和均匀性。常用的指标有两个超体积Hypervolume前沿与参考点围成的体积越大越好。间距Spacing前沿上相邻点距离的标准差越小说明分布越均匀。from pymoo.indicators.hv import HV import matplotlib.pyplot as plt def evaluate_frontier(policies, env, n_weights50): weights np.random.dirichlet(np.ones(3), n_weights) front [] for w in weights: state, _ env.reset() total_reward np.zeros(3) for step in range(50): state_tensor torch.FloatTensor(state) pref_tensor torch.FloatTensor(w) with torch.no_grad(): probs policies(state_tensor, pref_tensor) action torch.argmax(probs).item() state, reward_vec, terminated, truncated, _ env.step( np.eye(3)[action] ) total_reward reward_vec if terminated or truncated: break front.append(total_reward) front np.array(front) ref_point np.array([0, 0, 0]) hv HV(ref_pointref_point) hypervolume hv(front) plt.scatter(front[:, 0], front[:, 1], cfront[:, 2], cmapviridis) plt.colorbar(labelObjective 3) plt.xlabel(Objective 1) plt.ylabel(Objective 2) plt.title(fPareto Front (HV{hypervolume:.2f})) plt.show() return front, hypervolume实操心得超体积的参考点选择很关键。参考点太近所有前沿点的超体积都差不多区分度低参考点太远超体积数值很大但比较意义不大。我一般取所有前沿点在每个目标上的最小值再乘以0.9作为参考点。5. 常见问题与排查技巧实录5.1 前沿覆盖不均匀怎么办这是多目标RL里最常见的问题。表现是某些区域的帕累托前沿点很密集另一些区域几乎空白。原因通常有三个权重采样不均匀均匀采样权重向量但前沿形状非凸导致某些权重方向上的最优解挤在一起。目标尺度差异大某个目标的数值范围远大于其他目标网络训练时被这个目标主导。探索不足策略网络陷入局部最优无法覆盖整个偏好空间。解决办法我试过几种效果从好到差排列自适应权重采样先跑一轮粗粒度采样估计前沿形状然后在稀疏区域加密采样。实现上可以用核密度估计来判断稀疏区域。目标归一化在计算标量化奖励之前对每个目标的奖励做归一化让它们的数值范围接近。归一化统计量可以用滑动平均来更新。增加偏好条件化网络的容量如果网络太小不同偏好下的策略会互相干扰导致某些偏好被忽略。适当增加隐藏层宽度通常有帮助。5.2 训练不收敛或震荡严重多目标RL的训练稳定性比单目标差因为奖励向量的不同分量可能给出冲突的梯度信号。常见表现是损失函数震荡策略在几个极端偏好之间跳来跳去。排查思路检查奖励尺度如果某个目标的奖励范围是[0, 1000]另一个是[0, 1]梯度会被第一个目标主导。先做归一化。降低学习率多目标场景下梯度冲突更严重学习率通常需要比单目标小一个数量级。使用梯度裁剪限制梯度范数防止某个目标的梯度爆炸影响整体更新。增加批量大小更大的批量能平均掉不同偏好下的梯度噪声训练更稳定。我踩过的一个坑是用PPO跑多目标时优势函数的估计用了标量化奖励但不同episode的偏好不同导致优势估计的基线漂移。解决办法是为每个偏好维护独立的基线或者用偏好条件化的价值网络。5.3 目标数量增加后的维度灾难当目标数量超过5个时帕累托前沿的维度急剧上升逼近整条前沿所需的样本量呈指数增长。这时候多策略方法基本不可行必须换思路。指南里提到的应对策略包括降维如果某些目标高度相关可以合并或剔除。用主成分分析或者相关性分析来识别冗余目标。偏好聚焦不追求覆盖整条前沿只关注决策者最关心的区域。用少量偏好向量引导训练。分层优化把目标分成几组先优化组内再优化组间。比如先分别优化“效率”和“公平”再在两者之间做权衡。注意目标数量超过3个时可视化前沿就很困难了。我一般用平行坐标图或者雷达图来展示高维前沿比散点图直观。5.4 常见问题速查表问题现象可能原因排查方法解决措施前沿覆盖不均匀权重采样不合理绘制前沿散点图自适应权重采样训练震荡奖励尺度差异大打印各目标奖励范围目标归一化收敛慢梯度冲突检查梯度范数降低学习率、梯度裁剪某些偏好被忽略网络容量不足测试不同偏好下的策略差异增加网络宽度超体积不增长探索不足检查策略熵增加熵正则化系数前沿非凸区域缺失线性加权局限检查前沿形状改用切比雪夫标量化6. 从指南到项目的落地建议6.1 如何选择合适的算法起点如果你刚开始做多目标RL项目我建议按这个顺序试单策略线性加权先用最简单的方案跑通流程确认环境、奖励、评估都没问题。这一步的目的是建立基线不是追求最优。单策略切比雪夫标量化如果发现前沿非凸换切比雪夫标量化通常能覆盖更多区域。偏好条件化多策略如果计算资源允许且需要覆盖多个偏好上偏好条件化网络。分解方法如果目标数量较多3-5个且需要并行加速考虑基于分解的方法。这个顺序的好处是每一步都有明确的对比基线能清楚知道新方法带来了多少提升。6.2 评估指标的选择与陷阱超体积是最常用的指标但它有两个陷阱参考点敏感参考点稍微变一下超体积数值可能差很多。报告结果时一定要说明参考点怎么选的。不可比不同环境、不同目标数量下的超体积没有可比性。只能在同一环境内比较不同算法。除了超体积我还建议看前沿覆盖率在偏好空间里均匀采样一批偏好看有多少偏好能找到对应的非支配解。覆盖率低说明前沿逼近不完整。6.3 工程化落地的注意事项把多目标RL用到真实系统里有几个工程问题必须提前考虑偏好获取真实场景里决策者的偏好往往不是显式给出的需要通过交互来推断。可以设计一个偏好 elicitation 模块让决策者在几个候选策略里做选择逐步缩小偏好范围。在线适应偏好可能随时间变化。偏好条件化网络天然支持在线切换偏好但需要监控切换后的策略表现防止突变导致系统不稳定。安全约束某些目标可能是硬约束比如“能耗不能超过阈值”。这时候不能简单加权要把约束目标单独处理用拉格朗日乘子或者约束RL的方法。我个人在实际项目里的体会是多目标RL的算法部分其实只占工作量的30%剩下70%都在处理偏好获取、评估设计和工程集成。指南里对算法讲得很透但工程落地的细节需要结合具体场景来补。建议在项目初期就拉上领域专家一起定义目标和偏好不然后面返工成本很高。最后分享一个小技巧如果目标数量是2个可以直接用散点图看前沿非常直观。如果是3个用颜色编码第三个目标。如果是4个以上用平行坐标图每个轴代表一个目标每条线代表一个策略。这种可视化方式在跟非技术背景的决策者沟通时特别有用他们能一眼看出不同策略在各个目标上的权衡。
阅读完成 · 觉得有帮助?