在机器人控制、库存管理和算法策略上线这类场景里,最让人头疼的往往不是“结果不确定”,而是“连估计用的概率都不确定”。标准强化学习通常假设马尔可夫决策过程(MDP)中的转移概率已知,可实际工程中我们拿到的要么是有限样本估计值,要么是离线收集的历史数据。模型一换,策略就崩,这在工业界太常见了。
如果要在这种“参数本身不可靠”的环境里做决策,一个自然思路是鲁棒 MDP:假设真实模型落在一个不确定性集合内,然后选一个在最坏情况下表现最好的策略。但“最坏情况最优”也有问题,它可能过度保守,导致策略在绝大多数常见场景里都偏弱。于是很多研究者开始转向另一个决策标准:Minimax Regret,也就是最小化最大遗憾。它要回答的不是“最坏情况下我能拿多少”,而是“在真实模型未知的前提下,我的策略和那个事后诸葛亮最优策略相比,还差多少”。
这篇文章会讲清楚三件事:Minimax Regret 在不确定 MDP 里的目标函数究竟长什么样;为什么把候选策略集限制成一个小集合反而有理论价值和工程价值;以及如何用一份可运行的 Python 代码骨架,把“优化最小最大遗憾”这件事落地到自己的实验里。读完以后,你可以直接复用这套流程,去验证自己的策略在参数扰动下到底稳不稳。
1. 为什么不确定 MDP 需要 Minimax Regret
先回到基础问题。一个标准 MDP 可以写成五元组:
MDP = (S, A, P, r, γ)- S:状态集合
- A:动作集合
- P(s'|s,a):从状态 s 执行动作 a 后转移到状态 s' 的概率
- r(s,a):状态 s 下执行动作 a 得到的即时奖励
- γ:折扣因子
常规强化学习会假设 P 是已知的。大多数算法也只是在样本中去估计这个 P,然后基于估计结果做规划。问题在于,当 P 有误差时,最优策略往往会发生剧烈变化。尤其在一些安全敏感任务里,一个小的估计偏差可能导致策略完全失效。
不确定 MDP(Uncertain MDP)就是为了解决这个问题。它不再假设 P 是一个固定值,而是假设真实模型 P* 落在某个集合 U 中,这个集合被称为不确定性集合。决策者知道的是:
P* ∈ U但不知道具体是 U 中的哪一个。
在这种设定下,有两种常见决策标准。第一种是最小化最坏情况成本,也就是 Robust MDP 常用的思路:
min_{π} max_{P ∈ U} V_P(π)它保证策略在 U 中最差的那个模型里也能达到一定的表现。这种做法在理论上很干净,但在实际业务中,往往会得到过于保守的策略。因为真实世界里的坏模型通常不会以 100% 概率出现,如果你总是按最坏模型去设计策略,那在正常工况下就很可能损失性能。
第二种就是 Minimax Regret:
Regret_U(π) = max_{P ∈ U} [ V_P(π_P^*) - V_P(π) ]其中:
π_P^* = argmax_{π' ∈ Π_all} V_P(π')理解起来很简单:如果真实模型是 P,那么存在一个“事后最优策略” π_P^*。如果你的策略 π 在这个模型下拿到 80 分,而事后最优策略能拿到 100 分,差距就是 20。真实模型越未知,这个差距越难控制。Minimax Regret 要做的,就是选一个策略,让“最坏情况下的事后悔恨”尽量小。
这个标准的现实意义非常直观。做库存管理时,你不会只关心最坏需求下能不能活下来,更希望自己的补货策略和“如果早知道真实需求就采用的补货策略”相比,损失不至于太大。做动态定价时也一样,你既不是纯乐观,也不是纯保守,而是希望在“模型没猜准”时依然能输得不太难看。
所以我的判断是:在实际工程决策中,Minimax Regret 往往比纯鲁棒优化更符合业务直觉。它天然带着“估值错误后的担责”意识,也是分布鲁棒优化和相关领域近几年的研究热点。
2. 不确定 MDP 与 Minimax Regret 的核心概念
理解 Minimax Regret 之前,必须先理解两个概念:不确定性集合 U 和策略空间 Π。
2.1 不确定性集合 U
不确定性集合是鲁棒决策的灵魂。它刻画了“真实模型可能出现在什么位置”。常见构造方式包括:
| 集合类型 | 定义 | 特点 |
|---|---|---|
| 区间集合 | 每个转移概率落在一个区间内 | 简单直观,但忽略了概率之间的相关性 |
| 范数球 | 以名义模型为中心,限制距离 | 控制扰动幅度,常见 L1/L2 范数 |
| KL 散度球 | 限制与名义模型的 KL 散度 | 适合统计意义上的置信区域 |
| 数据驱动集合 | 由经验分布周围的置信区间生成 | 更贴合离线数据场景 |
对于实际项目,最常用的是“区间集合”和“L1 范数球”。区间集合的好处是便于解释,每个参数都能直接看到上下界;L1 范数球的好处是理论上容易分析,而且在概率单纯形上做投影也方便。
值得注意的是,不确定性集合并不是越宽越好。集合越宽,说明你对模型的信心越低,策略也会越保守;集合太窄,又可能把真实模型排除在外。比较好的做法是用统计置信区间来标定 U 的宽度,比如根据历史样本量计算转移概率的置信范围。
2.2 策略空间与事后最优策略
策略空间是所有可能策略组成的集合。在标准强化学习里,策略空间是巨大的,可以是所有从状态到动作分布的映射。但在 Minimax Regret 目标中,你需要回答一个问题:在某个模型 P 下,最优策略到底是什么?
为了定义 regret,我们总是需要“事后最优策略”作为参照物。对某个固定模型 P,可以求解一个普通 MDP,得到 π_P^。这个策略在理论上可以任意复杂,但实际计算中,我们并不一定需要显式求出它,只需要得到它对应的价值函数 V_P^。
所以整个 Minimax Regret 问题的内部结构是:
外循环:候选策略 π 内循环:不确定性集合中的每个模型 P 参照值:每个 P 下的最优价值 V_P^*这个结构和普通强化学习完全不同。普通强化学习只需要优化一个目标函数,而 Minimax Regret 需要同时考虑“模型的坏”和“策略的相对损失”。
2.3 为什么不能把 min 和 max 直接交换
很多读者第一次看到 Minimax Regret 时,会想当然地以为它和 Robust MDP 差不多,可以直接交换 min 和 max。其实不行。
在 Robust MDP 里,目标函数是:
min_π max_P V_P(π)它的最优策略可以理解为“在 U 中挑一个对我不利的模型,然后我来防守”。
在 Minimax Regret 里,目标函数是:
min_π max_P [ V_P(π_P^*) - V_P(π) ]最坏模型会自动变化。同一个 π 在模型 P1 下可能拿到 90 分,在模型 P2 下可能只拿到 60 分;而事后最优值的差距也随 P 变化。这意味着,即使你找到一个比较保守的策略,它也不能只靠“压制坏模型下的价值”来取胜,还必须保证在其他模型下不至于损失太多。
所以 Minimax Regret 的核心难点,恰恰在于策略必须同时兼顾多个候选模型中的表现差异。这也让它在理论上比 Robust MDP 更有意思,在计算上更困难。
3. “策略集要小”到底在说什么
标题里最容易被忽略的是 “Small Sets of Policies”。为什么策略集要小?很多人第一反应是:少一点策略,求解更快。这个理解部分正确,但远不是全部。
3.1 小策略集的工程动机
在实际部署中,我们不会把一个神经网络策略直接裸奔上线。更常见的做法是,用离线数据训练一批候选策略,做评估、审计、灰度,然后只放行风险最低的那个。
这时候“策略集”就是人工准备好的候选列表:
Π̂ = { π_1, π_2, ..., π_K }K 可以很小,比如 5、10、20。这个集合里的每个策略可以是不同风格的人工规则,也可以是不同训练阶段产出的模型,甚至可以是不同调参组合下的策略。
为什么说小策略集是优势?因为 Minimax Regret 是一个需要“枚举或近似枚举所有模型-策略组合”的问题。策略空间越大,计算代价越高,审计难度也越高。当你把候选策略限制成一个小集合时,你可以对每个策略做详尽的模型扰动测试,甚至可以逐条解释每个策略在哪些模型下表现好、在哪些模型下后悔值高。这对于安全审查和业务解释非常友好。
3.2 小策略集里的“包围”问题
但小策略集也有一个致命风险:如果 Π̂ 里根本没有接近最优的策略,那就算你在这个小集合里选了最小遗憾策略,它和全局最优策略相比也可能差很远。这就像你只带了三把螺丝刀去维修一台机器,如果真正需要的型号不在其中,你怎么选都是错。
所以小策略集的设计准则不是“少”,而是“用尽量少的策略去覆盖不确定性集合中的关键行为模式”。好的候选策略集通常会包含:
- 非常保守的策略,比如高成本触发维护的策略;
- 非常激进的策略,比如尽量不干预、靠低动作频率盈利的策略;
- 几个中间策略,用于覆盖不同的干扰场景。
这样定义出来的 Π̂,虽然只有几个元素,但能撑起一个比较完整的决策边界。
3.3 数学上有意义的限制
从理论角度,“小策略集”也带来了可分析性提升。当 Π̂ 是有限集合,Minimax Regret 优化问题就退化为一个有限的枚举问题:
π^* = argmin_{π ∈ Π̂} max_{P ∈ U} [ V_P(π_P^*) - V_P(π) ]这个形式虽然仍然是双层优化,但外层的枚举工作量只有 |Π̂| 个候选。真正剩下的难点,是内层要在 U 上做 max。如果 U 也是有限集或离散采样集,那么整个问题就能用最朴素的方式精确求解。
这也是这篇论文标题的策略性所在:不追求在全策略空间上求解 Minimax Regret,而是把注意力放到“小策略集”上,用有限候选策略换取计算可行性。这种思路在工程落地中非常有价值,因为先解决“算得动”的问题,才有资格谈“算得准”。
4. 核心流程:从不确定性集到策略选择
下面我把整个优化流程拆成五步。这套流程无论你是做 MDP 研究,还是做实际强化学习项目,都可以复用。
4.1 第一步:构造不确定性集合 U
先根据离线数据或业务经验,确定名义转移概率矩阵 P0。然后围绕 P0 构造不确定性集合。
如果采用 L1 范数球,数学形式是:
U = { P : ||P - P0||_1 ≤ ε, P 满足概率分布条件 }其中 ε 是扰动半径。在代码里,你可以通过随机扰动 P0,再把每一行归一化,生成一组候选转移矩阵:
import numpy as np def generate_uncertainty_set(P0, eps, K=5, seed=42): rng = np.random.default_rng(seed) nS, nA, _ = P0.shape U = [] for _ in range(K): P = P0.copy() for s in range(nS): for a in range(nA): noise = rng.uniform(-eps, eps, nS) row = P[s, a] + noise row = np.maximum(row, 0.0) row = row / row.sum() P[s, a] = row U.append(P) return U这里需要注意的是,噪声不要加得过大,否则归一化后的概率离 P0 太远,可能超出你的业务容忍范围。
4.2 第二步:准备候选策略集 Π̂
候选策略可以来自多个来源:
- 基于规则的经验策略;
- 独立训练得到的多个强化学习模型;
- 对已有策略做微调后产生的变体。
在小策略集模式下,建议 K 不超过 10。候选策略必须是确定性策略或者固定分布策略,这样你才能在某个固定模型下非常准确地求出价值函数。
4.3 第三步:计算每个候选模型下的最优价值函数 V_P^
这一步其实是在解决一个常规 MDP 规划问题。对 U 中的每个 P,使用值迭代或策略迭代求出最优价值函数:
def optimal_value(P, r, gamma=0.95, theta=1e-8): nS, nA, _ = P.shape V = np.zeros(nS) for _ in range(10000): Q = np.zeros((nS, nA)) for s in range(nS): for a in range(nA): Q[s, a] = r[s, a] + gamma * np.dot(P[s, a, :], V) V_new = Q.max(axis=1) if np.max(np.abs(V_new - V)) < theta: return V_new V = V_new return V如果你使用的是连续状态或大规模状态空间,可以替换为近似动态规划或离线强化学习评估方法,但原理相同:先得到“事后最优”的基准。
4.4 第四步:计算每个候选策略在 U 上的遗憾
对每个候选策略 π、每个模型 P,评估它的价值函数 V_P(π),然后和 V_P^* 做差:
Regret(π) = max_P [ V_P^* - V_P(π) ]这一步是整个流程的核心。如果不确定性集合 U 是连续的,无法直接枚举,你可以使用采样、梯度优化或轻量级搜索技术来逼近最坏模型。但是“先枚举、后取 max”仍然是解释问题最基础的方式。
4.5 第五步:选出最小遗憾策略
最后在候选策略集 Π̂ 里,直接比较每个 π 的 Regret 值,取最小的那个作为最终决策。
这个流程之所以工程友好,是因为每一步都很透明。你可以随时看到某个策略在某个模型下后悔了多少,甚至可以画一张“候选策略 × 候选模型”的遗憾热力图,让业务方直接参与决策。
5. 用 Python 实现一个小型 Minimax Regret 选择器
下面我给出一个完整的最小实现。这个例子不是为了演示大规模求解,而是为了说明“小策略集 + 有限不确定性集”下的精确求解流程。运行环境只需要 Python 3.8+ 和 NumPy。
5.1 完整代码
假设状态数 S=2,动作数 A=2。状态 0 表示正常,状态 1 表示故障;动作 0 表示不干预,动作 1 表示维修。奖励矩阵按业务习惯设定:正常运行有正收益,维修有成本,故障有惩罚。
import numpy as np # 文件路径:minimax_regret_mdp.py class MDPValueSolver: def __init__(self, P, r, gamma=0.95): self.P = P self.r = r self.gamma = gamma self.nS = P.shape[0] self.nA = P.shape[1] def evaluate_policy(self, policy, theta=1e-8): V = np.zeros(self.nS) for _ in range(10000): V_new = np.zeros(self.nS) for s in range(self.nS): a = policy[s] V_new[s] = self.r[s, a] + self.gamma * np.dot( self.P[s, a, :], V ) delta = np.max(np.abs(V_new - V)) V = V_new if delta < theta: break return V def optimal_value(self, theta=1e-8): V = np.zeros(self.nS) for _ in range(10000): Q = np.zeros((self.nS, self.nA)) for s in range(self.nS): for a in range(self.nA): Q[s, a] = self.r[s, a] + self.gamma * np.dot( self.P[s, a, :], V ) V_new = Q.max(axis=1) if np.max(np.abs(V_new - V)) < theta: break V = V_new return V def create_demo_uncertainty_set(): # 名义模型:正常运行概率较高 P0 = np.array([ [[0.90, 0.10], [0.80, 0.20]], [[0.00, 1.00], [0.60, 0.40]] ]) # 扰动模型1:维修效果较差 P1 = np.array([ [[0.85, 0.15], [0.75, 0.25]], [[0.00, 1.00], [0.40, 0.60]] ]) # 扰动模型2:故障率整体偏高 P2 = np.array([ [[0.80, 0.20], [0.70, 0.30]], [[0.00, 1.00], [0.50, 0.50]] ]) return [P0, P1, P2] def main(): r = np.array([ [10.0, 5.0], [-50.0, -2.0] ]) gamma = 0.95 U = create_demo_uncertainty_set() # 小策略集:3个确定性策略 policies = { "不干预": np.array([0, 0]), "总是维修": np.array([1, 1]), "故障才修": np.array([0, 1]) } # 1. 对每个模型计算事后最优价值 best_values = [] for P in U: solver = MDPValueSolver(P, r, gamma) best_values.append(solver.optimal_value()) # 2. 对每个候选策略计算遗憾 print("候选策略在不同模型下的遗憾:") results = {} for policy_name, policy in policies.items(): regret_over_models = [] for idx, P in enumerate(U): solver = MDPValueSolver(P, r, gamma) V_pi = solver.evaluate_policy(policy) regret = np.max(best_values[idx] - V_pi) regret_over_models.append(regret) print(f"{policy_name} 在模型{idx + 1}下的遗憾 = {regret:.4f}") results[policy_name] = max(regret_over_models) # 3. 选择最小遗憾策略 best_policy = min(results, key=results.get) print("\n每个候选策略的最坏遗憾:") for name, regret in results.items(): print(f"{name}: {regret:.4f}") print(f"\n最终选择:{best_policy}") if __name__ == "__main__": main()5.2 关键逻辑解释
这段代码分为三个层次。
第一个层次是 MDPValueSolver。它封装了策略评估和最优价值求解。这个类是整个流程的底座,因为无论哪个候选模型进来,都需要用值迭代求出 V_P^* 和 V_P(π)。
第二个层次是不确定性集合的构造。这里采用的是“离散枚举”方式。我把 U 拆成了三个显式模型:名义模型、维修效果偏差模型、整体故障率偏高模型。这样做的好处是代码直观,方便对照验证。
第三个层次是遗憾计算与策略选择。对每个候选策略,我遍历 U 中的所有模型,分别计算遗憾,再取最大值作为该策略的“最坏遗憾”。最终在所有候选策略里,选择最坏遗憾最小的那个。
5.3 运行方式
在命令行执行:
python minimax_regret_mdp.py不需要额外安装第三方库,NumPy 是唯一依赖。如果你还没有 NumPy:
pip install numpy6. 运行结果分析与效果验证
运行脚本后,你会在控制台看到两部分信息:每个策略在每个模型下的遗憾,以及最终选择结果。
6.1 预期结果判断
对于上面这个设备维护示例,比较符合直觉的结果是:单一“不干预”策略会在故障率高的模型下后悔值很大;单纯“总是维修”策略会在正常运行为主的模型下后悔值偏大;而“故障才修”策略更可能在所有模型下都拿到比较均衡的遗憾。
你不需要照搬这个结论,关键是判断逻辑是正确的。你应该能看到最终选择的策略,恰好是那个在所有候选策略里,最坏遗憾最小的一个。
6.2 验证方法
验证一个 Minimax Regret 决策是否合理,可以从三个角度检查。
第一,检查最优价值函数是否一致。如果某个模型的 V_P^* 明显低于其他模型,说明这个模型本身很“差”,它的遗憾值天然容易被放大。这是正常的。
第二,检查候选策略是否存在支配关系。如果某个策略在所有模型下的价值都高于另一个策略,那低价值策略不应该被选中。如果有这种情况,建议调整候选策略集。
第三,做敏感性分析。你可以把扰动模型的数量从 3 个扩大到 10 个,观察最终选择是否发生变化。如果变化很大,说明候选策略集的覆盖度不够,需要补充更具差异性的策略。
6.3 失败排查
如果运行后某个策略的遗憾出现负数,几乎可以肯定是价值函数计算逻辑出了问题。遗憾的定义是 V_P^* - V_P(π),其中 V_P^* 是整个策略空间中最优价值,因此它一定大于等于 V_P(π)。出现负数,多半是值迭代没有收敛,或者策略评估时使用了错误的模型参数。
另外,如果所有候选策略的遗憾都一样,说明模型扰动范围太小,或者候选策略差异不够大。这时应该放宽 U 的扰动半径,或者重新设计策略集。
7. 常见问题与排查思路
在实际写代码和做实验时,下面几个问题出现概率很高。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 某项遗憾为负数 | 最优价值函数与策略评估使用了不同模型 | 检查代码中 P 是否被意外修改 | 优先保存原始 P,评估时用副本 |
| 所有策略遗憾几乎一样 | 不确定性集合太小,或策略差异太小 | 打印 V_P^* 和 V_P(π) 看差距 | 增大扰动半径,重新设计策略集 |
| 计算时间过长 | U 中模型数量太多,或状态空间过大 | 分析瓶颈在 V_P^* 在线计算 | 使用缓存,或先对 U 抽样 |
| 最终策略在真实环境中表现不佳 | 不确定性集合不贴合真实分布 | 对比真实环境数据与 U 的覆盖范围 | 用统计置信区间重新标定 U |
| 候选策略覆盖不足 | “故障才修”等中间策略缺失 | 查看遗憾热力图 | 加入更多中间策略或随机策略 |
| 值迭代不收敛 | 折扣因子 γ 过大,或奖励尺度异常 | 检查 V 的更新趋势 | 适当降低 γ 或统一奖励数值范围 |
对于第七八节问题,最重要的是先搞清楚“到底是算错了,还是候选集设计得不好”。把这两个问题分开,工作流会清晰很多。
8. 工程落地与最佳实践建议
Minimax Regret 不只是理论模型。在实际项目中,它特别适合那些模型更新频繁、业务方对“策略为什么这么做”要求很高的场景。比如动态定价、库存补货、风控决策、运维调度等。
下面是我实际落地这类方案时的几条建议。
8.1 用置信区间去校准不确定性集合
很多团队做鲁棒优化,最大的错误是随便拍一个扰动半径。更好的做法是:用转移概率估计的置信区间构造 U。比如,名义转移概率是从 1000 条样本估计出来的,那方差就比较小;如果只有 50 条样本,U 就应该更宽。
一个简单公式是:
ε = c / sqrt(n)其中 n 是用于估计转移概率的样本数量,c 是根据置信水平选择的常数。虽然这个公式比较粗糙,但在早期实验中足够用。
8.2 候选策略要“风格迥异”而不是“参数微调”
小策略集的核心价值是覆盖不确定性。如果你的候选策略之间只是学习率不同,那它们大概率会在同一类模型上表现相似。真正应该做的是保证行为风格差异:有一个极保守,有一个极激进,再有几个中间档。这样你才能通过 Minimax Regret,看哪种风格在模型的未知扰动中最扛得住。
8.3 先离线跑完再考虑在线优化
第一次实现时,不需要上大规模优化器。用有限离散的 U,把所有策略都算一遍,先建立“模型-策略-遗憾”三张表。这个表本身就有很大的决策价值。你觉得你理解了模型敏感度之后,再考虑把它扩展到连续扰动集和更大策略空间。
8.4 保留审计能力
工业环境里,最终选择的策略不一定是最优遗憾最小,也可能是遗憾最小但行为最难解释。这时候建议把“遗憾热力图”附带在决策报告里。业务方关心的是:这个策略在哪种模型下最可能后悔,后悔会有多大。这些信息,恰好是 Minimax Regret 流程里天然产出的,不用额外开发。
9. 总结:值得继续深入的方向
这篇内容表面上讲的是一个理论问题:不确定 MDP 下如何用小策略集优化 Minimax Regret。实际上,它传递的是一套更通用的工程思维:当模型不可靠、参数会漂移、上线要审计时,应该把问题建模成“和事后最优之间的差距”来优化,而不是单点追求最优或单点追求最坏。
如果你接下来想往深处走,比较有价值的方向有三个。第一个是连续不确定性集合上的 regret 优化,从有限枚举过渡到凸优化或在线优化;第二个是大规模状态空间下如何用强化学习做策略候选生成,让小策略集里的每个成员都更有代表性;第三个是数据驱动的不确定性集构造,如何从离线数据中精确估计出“足够诚实”的 U。
建议先把这篇文章中的最小实现跑一遍,把“模型-策略-遗憾”表做出来。你会明显感觉到,相比直接训练一个策略然后祈祷模型没估计错,这种“先列出后悔上限、再选策略”的路径,在真实业务里踏实得多。收藏备用,后面做鲁棒策略上线时一定能用上。