← 返回 PaperDaily 大模型与智能体

ICFP 2026|牛津大学Imp:不精确概率编程,BDD编译零改动,三种推理一次搞定

面对一个装有30个红球和60个绿蓝球(比例未知)的瓮,大多数人宁愿赌红——因为已知风险可接受,未知风险让人发怵。这就是Ellsberg悖论揭示的:我们讨厌模糊性。牛津大学在ICFP 2026上提出Imp,一个内嵌于Haskell的不精确概率编程语言,让计算机也能处理这种“说不准”的概率,而且最关键的是:编译基础设施一点不用改!

ICFP 2026|牛津大学Imp:不精确概率编程,BDD编译零改动,三种推理一次搞定
原论文信息如下:
论文标题:
Imprecise Probabilistic Programming, Precisely (Functional Pearl): Credal Sets via Graded Monads, BDDs, and Semiring-Parametric Inference

发表日期: 2026年8月
发表单位: University of Oxford
原文链接: https://arxiv.org/pdf/2607.20801v1.pdf

1. 从Ellsberg悖论到不精确概率编程

先聊一个思想实验。假设一个瓮里装着90个球:30个红的,剩下60个是绿的或蓝的,但比例完全未知。现在两种赌博任你选:赌红球你赢钱,或者赌绿球你赢钱。大多数人在面对这种选择时会毫不犹豫地选赌红——因为红球的概率是确定的1/3,而绿球的概率在0到2/3之间飘忽不定,这种“不确定的不确定性”让人本能地抗拒。这就是著名的Ellsberg悖论。
传统概率论面对这种情况非常尴尬:它无法用单一概率分布来捕捉这种偏好。为了解决这个矛盾,不精确概率(Imprecise Probability)理论应运而生。它的核心思想很简单:既然说不准,那就别逼自己选一个分布,而是给出一个信义集合(Credal Set)——所有合理分布的凸集。对于Ellsberg瓮,红球的概率是固定的1/3,而绿球的概率可以在[0, 2/3]之间任意游走。这个信义集合就是一条线段,如图1左侧所示。
图1:概率单纯形上的信义集合。左图:Ellsberg瓮(ellsberg):一条固定𝑃(R) = 1/3的线段,绿/蓝比例自由变化。右图:simpleRobot和complexRobot移动两次后的结果位置。尽管信义顶点数为4和16,但凸包仅分别由其中3个和6个顶点支撑。
图1:概率单纯形上的信义集合。左图:Ellsberg瓮(ellsberg):一条固定𝑃(R) = 1/3的线段,绿/蓝比例自由变化。右图:simpleRobot和complexRobot移动两次后的结果位置。尽管信义顶点数为4和16,但凸包仅分别由其中3个和6个顶点支撑。
由此产生的关注量不再是单一概率,而是下概率上概率——在全集中求最小和最大。这套体系被称作自然扩展(Natural Extension),贝叶斯规则和期望也可以自然推广,唯一需要额外强调的是:信义集合必须是凸的,这保证了取值范围总是连续的。
不精确概率在统计、决策理论和机器学习里其实早有讨论,但真正把它变成一门编程语言,让程序员可以用代码描述这种“模糊的概率”,是这篇ICFP 2026论文的贡献。牛津大学的Jack Liell-Cock和Sam Staton带来了Imp——一个内嵌于Haskell的领域特定语言。Imp程序可以同时处理普通抛硬币(概率已知)和Knightian抛硬币(概率未知),并通过类型系统保证两者的独立性。
先看一个用Imp编写的Ellsberg瓮的例子:
    data Ball = Red | Green | Blue
    
    ellsberg :: Imp '["split"] Ball
    ellsberg = Imp.do
      isRed <- flip (1/3)
      isGreen <- knight @"split"
      Imp.return $ if isRed then Red
                   else (if isGreen then Green else Blue)
    这个程序看着和普通概率编程语言没区别,但有个关键不同:`flip (1/3)`是已知概率1/3的普通硬币,而`knight @"split"`则是一个Knightian硬币,它的真实概率是未知的,写在类型里——`Imp '["split"] Ball`中的`"split"`就是这枚Knightian硬币的名字。类型列表`'["split"]`表明整个程序只有一个不确定源。如果程序里有多个独立的Knightian硬币,它们的名字不会重叠,类型系统会通过分级单子(Graded Monad)自动追踪和检查。

    2. 分级单子:用类型系统保证不确定性独立性

    如果只是把已知概率硬币和未知概率硬币混在一起,那Imp还不算特别。真正的难点在于:如何保证两个独立的不精确概率源在组合时不互相干扰?
    标准的概率单子(如`m a = 分布`)天然是交换的——先抛硬币再掷骰子,和先掷骰子再抛硬币,结果一样。但对于Knightian硬币,事情就不一样了。原因是,如果用一个函数作用于两个独立的Knightian硬币,不同的组合顺序可能产生不同的信义集合。举个例子:一个分支中,Knightian硬币"a1"控制左边,另一个分支中同样的"a1"又控制右边,那这两个分支的Knightian不确定性其实是相关的,它们来自同一个未知源。
    论文用两个程序来演示这一点:
      dependent :: Imp '["a1"] Three
      dependent = Imp.do
        x <- flip 0.5
        if x then Imp.do
          y <- knight @"a1"
          Imp.return (if y then R else G)
        else Imp.do
          y <- knight @"a1"
          Imp.return (if y then R else B)
      
      independent :: Imp '["a1", "a2"] Three
      independent = Imp.do
        x <- flip 0.5
        if x then Imp.do
          y <- knight @"a1"
          Imp.return (if y then R else G)
        else Imp.do
          y <- knight @"a2"
          Imp.return (if y then R else B)
      `dependent`在两个分支里都使用了同一个名字`"a1"`,这意味着两处的Knightian硬币共享同一个未知概率,所以最终的集合是它们凸组合形成的线段。但`independent`在第二个分支用了不同的名字`"a2"`,两个未知源互不影响,最终的信义集合是一个四边形(图2),明显大于线段。对于线段来说,不可能产生R和G各一半的结果,因为那样需要`"a1"`在一个分支里是True,另一个里是False——矛盾。而对四边形来说,这完全可能。
      图2:{R, G, B}概率单纯形上的信义集合。dependent(一个Knightian名字)的语义是一条线段。independent(两个Knightian名字)的语义是一个四边形。增加独立Knightian选择的个数会扩大信义集合。
      图2:{R, G, B}概率单纯形上的信义集合。dependent(一个Knightian名字)的语义是一条线段。independent(两个Knightian名字)的语义是一个四边形。增加独立Knightian选择的个数会扩大信义集合。
      Imp是如何实现这种精细化控制的?核心在于分级单子(Graded Monad)。传统的单子是一个类型构造器`m`加上`return`和`bind`。分级单子则是带“刻度”的单子:`Imp g a`中的`g`是一个类型级列表,记录了当前计算涉及的所有Knightian名字。`Imp`的每个构造子都在不同程度上修饰`g`:
      `ImpReturn` 引入纯值,刻度为空`'[]`;`ImpFlip` 已知概率抛硬币,刻度也为空;`ImpKnight` 引入一个Knightian硬币,刻度为单元素列表`'[n]`;`ImpBind` 把两个计算合并,刻度为两个刻度的不相交并集`Merge g1 g2`。
      Merge是一个类型族,它接受两个类型级符号列表,以有序方式合并,同时检查是否有重复——如果发现重复名字,GHC直接编译报错。这意味着所有对确定性源重合的误用在编译时就被截断了。这样,当两个子程序的名字集不相交时,它们的组合是交换的:
      `do x <- p; y <- q; f x y` 当且仅当 `p` 和 `q` 的Knightian名字集不相交时,结果与换序相同。
      作者还实现了一个`tag`构造子,它可以把一个计算中所有名字都加上一个前缀,这样当你多次调用同一个子程序时,可以通过不同标签保证不确定性不互相干扰——这正是复杂机器人导航(见下文)中的关键。顺便说一句,Imp利用GHC的`QualifiedDo`扩展,让用户写`Imp.do { ... }`就能自动用上增强版的`>>=`,看起来和普通Haskell一样自然。

      3. BDD编译:概率与Knightian硬币一视同仁

      设计一套优雅的类型系统固然漂亮,但最终要做推理——计算具体概率或上下界——还得靠真正的计算基础设施。Imp的编译策略给了一个震撼弹:不需要任何新的编译器基础架构。一个普通的概率编程编译器(比如Dice)是怎么工作的?它把每个抛硬币的布尔结果编译为BDD(二元决策图)中的一个变量,然后通过加权模型计数(WMC)计算每个终局的概率。Imp发现:一个Knightian硬币和普通硬币在编译阶段毫无区别——它也是BDD的一个布尔变量。区别只在于推理阶段:普通硬币的变量权重是编译时就固定的常数,而Knightian硬币的变量权重是自由的,留到推理时再决定。
      具体来说,Imp的编译函数`compileM`递归遍历Imp语法树,对每个可能的返回值构造一个`(value, BDD)`对——BDD编码了达到该值的所有硬币结果组合。以`dependent`和`independent`程序为例,图3展示了它们编译出的BDD。在`dependent`中,两个条件分支都使用同一个Knightian变量`a1`,因此BDD可以合并为一个节点;而`independent`中两个分支用不同变量`a1`和`a2`,因此BDD必须保留两个不同的分支路径。从图3可以直观看出,共享名字带来的紧凑性。
      图3:从dependent和independent编译出值R的BDD。实线箭头指向变量的高子节点,虚线箭头指向低子节点。在dependent中,产生R的两条路径共享Knightian变量𝑎1,将BDD压缩为单一变量。在independent中,两条路径使用不同的Knightian变量,因此保持分离。
      图3:从dependent和independent编译出值R的BDD。实线箭头指向变量的高子节点,虚线箭头指向低子节点。在dependent中,产生R的两条路径共享Knightian变量a1,将BDD压缩为单一变量。在independent中,两条路径使用不同的Knightian变量,因此保持分离。
      论文使用的是一个标准BDD表示,带哈希合并和补边优化。编译过程中,`flipVar`为每次普通抛硬币分配一个新变量,并在状态中记录权重对`(1-p, p)`;`knightVar`则查找或分配Knightian变量,但不给它任何权重——它就是一个纯粹的未赋值变量。编译状态`CompileState`里包含一个`csKnightVars :: [(String, VarLabel)]`列表,记录每个Knightian名字对应的BDD变量标签。
      这种设计的优雅之处在于,编译是一次性的,推理可以无数次。普通概率推理只需要在BDD上做一次WMC,把所有变量权重乘起来求和。不精确概率推理则需要遍历Knightian变量的所有极端赋值(每个变量取0或1),共2的k次方轮WMC。好在WMC本身是权重的多元线性多项式,所以编译好的BDD可以重复使用。接下来我们就看Imp如何通过半环参数化实现一次性编译、多种推理。

      4. 半环参数化推理:一次编译,三种推理方法

      加权模型计数(WMC)是一个多线性多项式的求值问题。通常,我们把它放在实数半环(ℝ, 0, 1, +, ×)上计算:一个BDD的真值赋值赋予`(1-p)`或`p`的概率,乘积求和。但Imp的贡献在于,它把这个计算参数化在任意半环(Semiring)上。只要半环支持加法(⊕)和乘法(⊗)运算,同一个BDD就能计算出不同语义下的结果。作者提供了三种实例化:

      枚举半环:这是朴素的方法。枚举所有2^k个Knightian变量的极端赋值(每个取0或1),对每个赋值在实数半环上做一次标准WMC,得到k维立方体顶点的概率分布集合。k较小时(比如k <= 10)很直接,但k增大后指数爆炸。

      区间(Interval)半环:这是近似方法。给每个Knightian变量一个区间`[lo, hi]`表示其概率的可能范围,WMC过程在区间算术(Interval Arithmetic)下进行。以区间作为权重,BDD上的加法和乘法变成区间的加法和乘法,最后得到每个结果的一个概率区间。这个区间一定能包含真实上下界(即Sound),但不一定紧(可能偏宽)。优点是计算一次即可,不需要枚举,复杂度为O(BDD大小)的多项式时间。

      对偶数(Dual Number)半环:这是一种差分推理方法。对偶数`a + b·ε`能同时计算值和导数。如果把Knightian变量的权重换成对偶数形式`w + 1·ε`,那么一次WMC就能同时得到该点的概率和对该权重的梯度。这样,就可以在信义集合上做梯度下降(或上升),寻找最优(或最劣)期望值,而不需要遍历所有顶点。这也是多项式时间的——固定步数的WMC调用。

      论文用一个区间值MDP(IMDP)示例展示了这三种推理方法的应用场景。假设一个机器人要从P0穿过马路到P2,中间有风险区P1(停在路中间会被车撞)。每次移动,它成功前进的概率已知在一个区间[0.6, 0.9]内,但具体值未知。用Imp就可以写:
        simpleRobot :: Imp '["move1", "move2"] Position
        simpleRobot = Imp.do
          move1 <- interval @"move1" 0.6 0.9
          let pos1 = step P0 move1
          move2 <- interval @"move2" 0.6 0.9
          Imp.return $ step pos1 move2
        调用`credalVertices simpleRobot`输出一个四行表格(表1),每行对应一个极端赋值。
        表1:credalVertices simpleRobot的输出。每行对应一个Knightian变量的极端赋值。前两列为两个移动成功的概率,后三列为机器人处于各位置的最终概率。
        表1:credalVertices simpleRobot的输出。每行对应一个Knightian变量的极端赋值。前两列为两个移动成功的概率,后三列为机器人处于各位置的最终概率。
        当机器人导航变得更复杂,比如引入后退和停滞动作(`complexRobot`,使用`tag`来复用子程序并保持名字不重叠),Knightian变量数增加到4个,顶点数达到16。虽然枚举仍然可行,但作者进一步演示了使用梯度下降找到最坏情况下的期望分数:调用`credalOptimizeExpectation 200 (-0.1) score complexRobot`,只需若干次WMC计算,就找到了分数为4.5的最坏情况配置,无需遍历所有16个顶点。

        5. 实验与性能:枚举、区间、梯度下降的权衡

        论文用n元Ellsberg瓮作为基准测试来对比三种推理方法的性能。n元Ellsberg瓮是经典版的扩展:有n种颜色,其中一种颜色的数量已知(设为1/n),其余n-1种颜色的数量完全未知。这样引入了n-1个Knightian变量(因为未知比例的和固定,自由度为n-2,但编程上需要n-1个独立Knightian选择)。Knightian变量数为k = n - 1。枚举方法要计算2^(n-1)个顶点,指数量级;区间WMC和梯度下降都是多项式时间。
        实验在GHC 9.4.4、`-O2`优化下进行,取10次运行的中位数。结果如图4所示。
        图4:推理时间 vs 颜色数量N(n元Ellsberg瓮,双对数坐标轴)。精确枚举呈指数增长(2个N减2个Knightian估值);区间WMC为多项式时间。梯度下降为多项式时间(每查询固定次数的WMC传递,因此常数较大);当前优化器在N=9以内保持准确。中位数,10次运行,GHC 9.4.4 -O2。
        图4:推理时间 vs 颜色数量N(n元Ellsberg瓮,双对数坐标轴)。精确枚举呈指数增长(2的N减2次方个Knightian估值);区间WMC为多项式时间。梯度下降为多项式时间(每查询固定次数的WMC传递,因此常数较大);当前优化器在N=9以内保持准确。中位数,10次运行,GHC 9.4.4 -O2。
        从图4可以看出,枚举的时间随着N增长急剧上升(几乎是垂直向上),在N=16时已经接近100秒。区间WMC的时间曲线几乎是平坦的,始终在0.1秒以下——相比之下,枚举就是几何级数噩梦。梯度下降的时间线也稳定平滑,虽然因为需要多次WMC调用(本例中为200步梯度下降),常数项较大,在N较小时比区间方法慢,但随N增加的斜率同样非常平缓。论文指出梯度下降在N<=9时结果准确(即能收敛到精确极值),但对于更大规模的Knightian空间,是否收敛取决于优化器设置和问题的凸性。
        实验结果分析:这个实验设计合理,因为n元Ellsberg瓮是完美的不精确概率场景——精准可控、Knightian变量数可调、上界下界已知,可以清晰地比较方法在不同规模下的伸缩性。枚举是指数级,这完全符合预期(暴力穷举所有极端赋值)。区间WMC之所以如此快,是因为它实质上只做了一次区间算术下的WMC,而区间算术的成本和普通算术几乎一样,所以与问题规模不成正比。梯度下降虽然快,但需要注意它不是保紧的——它只给出一个局部极值(取决于初始点),但并不保证全局最优。在n元Ellsberg瓮这个凸问题上,梯度下降能够收敛到正确结果,但作者也承认,对于非凸问题(比如有if-then-else嵌套的复杂程序),梯度下降可能陷入局部最优。区间WMC给出的区间则是保序的(永远是真实区间的超集),但可能偏宽。所以这三种方法本质上是权衡:枚举是精确但不可扩展,区间是快速但粗略,梯度是近似且依赖问题性质。Imp把选择权留给用户,而且因为BDD只编译一次,用户可以先用区间方法快速估算,再用梯度下降做更细致的优化,最后用枚举验证关键配置。

        6. 总结与展望:函数式编程武装不精确推理

        Imp这个工作的最大亮点在于它展示了如何用类型系统优雅地驯服模糊性。不是依赖新的编译器、新的运行时或者复杂的算法,而是仅仅在已有的BDD+WMC管道上,通过半环参数化把不精确推理附加进去。Haskell的泛型编程和类型级计算在这里发挥了淋漓尽致的作用——谁说FP只能做纯函数?这分明是AI基础设施。
        从工程角度看,Imp的设计给了现实决策支持系统一个非常有吸引力的方案:理论上,只要你能把问题建模为一组布尔变量,你就能在不精确概率框架下获得“说不准”的推理结果。这对于那些在数据稀少或专家意见冲突的领域(如医疗诊断、灾害风险评估、金融模型)很有潜力。
        不足之处也很明显:目前Imp只支持离散、布尔的变量。连续的不精确概率(比如参数在某个连续区间内变化?)还不能直接处理。另外,区间推理方法虽然快,但结果偏宽,在实际决策中可能过于保守;梯度下降的收敛性和精度也需要更多理论保证。还有,目前的BDD规模对某些复杂程序可能爆炸——尽管作者提到了动态变量排序(sifting),但没有深入讨论实际应用的瓶颈。最后,Imp目前只是一个研究原型,它是否能扩展成可落地的库、是否能够与其他Haskell概率编程库(如monad-bayes)无缝对接,还需要更多工作。

        龙迷三问

        下面是龙哥对于大家可能的一些问题的解答:

        Q1: 什么是信义集合(Credal Set)?直观理解为什么它比单一分布更有用?A1: 信义集合就是所有可能概率分布组成的闭凸集。在Ellsberg瓮中,红球的概率是1/3,很确定;而绿球的概率可以是0到2/3的任意值。你无法选出“正确”的那个值,但你可以说“合理的分布存在于一条线段上”。信义集合让决策者能够量化这种模糊性:决策时既可以看最好情况(上概率),也可以看最坏情况(下概率)。在工程中,这让系统设计者避免了对单个“先验分布”的过度依赖,从而对未知更具鲁棒性。

        Q2: 什么是分级单子(Graded Monad),它与普通单子有什么区别?A2: 普通单子是一个类型构造器m,有return :: a -> m a和bind :: m a -> (a -> m b) -> m b。分级单子多加了一个“分级”索引,例如Imp g a,其中的g是类型级列表。return的类型是a -> Imp '[] a,bind的类型是Imp g1 a -> (a -> Imp g2 b) -> Imp (Merge g1 g2) b。这可以让你在类型系统中记录计算所使用的“Knightian命名空间”,并在合成时自动检查重叠。普通单子无法区分这些细节,而分级单子让类型系统做了本应在编译期完成的工作。

        Q3: 半环参数化推理的好处是什么?区间半环和对偶数半环有什么区别?A3: 半环参数化的好处是一次编译得到的BDD可以用于多种计算,只需要替换WMC中的加法、乘法实现。区间半环把每个权重替换为区间[lo, hi](表示该Knightian变量的概率可能范围),计算过程使用区间算术,最终输出一个必定包含真实概率的区间——快速但偏保守。对偶数半环把权重替换为形如w + 1·ε的对偶数,一次WMC能同时得到概率值和对该Knightian变量的导数,因此可以用梯度下降找到极值点。区间方法给出的是“宽度”,对偶数方法给出的是“梯度”——前者适合快速扫视,后者适合优化。

        如果你还有哪些想要了解的,欢迎在评论区留言或者讨论~

        龙哥点评

        论文创新性分数:★★★★★

        不精确概率与BDD/WMC管道的结合并非首创,但用分级单子和半环参数化做到“一次编译三种推理”,且类型系统自动检查不确定性独立性,这个组合非常新颖且有实践价值。

        实验合理度:★★★★☆

        以n元Ellsberg瓮作为基准清晰展示了三种推理方法的伸缩性,实验设计合理。但缺少与noDice等同类工具的直接定量对比,也缺少更多真实世界的应用案例支撑。

        学术研究价值:★★★★★

        不仅为不精确概率编程提供了一个干净、可组合的架构,还向函数式编程社区展示了类型级计算+GADT+半环在AI推理中的实际应用,对Haskell和编程语言理论领域都有很大启发意义。

        稳定性:★★★☆☆

        代码和推理方法是确定的(枚举和区间方法都有精确的数学定义)。梯度下降依赖于初始点和步数,不保证收敛到全局最优。整体上对理论模型稳定,但作为库对外输出时需要小心处理边界情况。

        适应性以及泛化能力:★★★☆☆

        目前只支持离散、布尔变量。任何非布尔变量必须手动编码为布尔序列,增加了编程负担。连续的不精确概率无法直接处理,这在一些现实场景中受限。

        硬件需求及成本:★★★★☆

        编译本身是常量成本且极快;推理因方法而异——区间方法几乎零成本,梯度下降需要多次WMC但也是多项式时间,枚举受限于k值。整体上推理对CPU友好,无需GPU,但大BDD可能消耗不少内存。

        复现难度:★★★☆☆

        论文提供了Haskell代码和实验设置(GHC 9.4.4),理论上可以在拥有Haskell环境的机器上复现。但由于涉及大量GHC扩展(GADTs, DataKinds, TypeFamilies等),需要一定的Haskell进阶知识,且目前未提供现成cabal包或Docker镜像,环境搭建可能需要一些精力。

        产品化成熟度:★☆☆☆☆

        目前还是研究原型,不是一个可开箱即用的库。缺少文档、错误处理、更友好的用户接口,以及与其他概率编程生态的集成。作为Functional Pearl(功能珍珠)论文,它的定位本身就是优雅的设计而非产品,所以这一项打分低并不意外。

        可能的问题:1) 缺乏与noDice、Gen等概率编程工具的直接性能对比;2) 梯度下降的收敛性分析未深入;3) 代码未开源,可复现性受限;4) 只能处理离散布尔分布,连续空间和不精确高斯等更复杂的不精确模型不在范围内。


        主要参考文献

        [1] Imp: Imprecise Probabilistic Programming, Precisely. Jack Liell-Cock, Sam Staton. ICFP 2026. ACM DOI: 10.1145/3828698.
        [2] Dice: Compiling Discrete Probabilistic Programs for Scalable Inference. Holtzen et al. OOPSLA 2020.
        [3] noDice: Non-Deterministic Extensions to Dice. Holtzen et al. OOPSLA 2023.
        [4] Ellsberg, D. "Risk, Ambiguity, and the Savage Axioms." Quarterly Journal of Economics, 1961.
        [5] Walley, P. "Statistical Reasoning with Imprecise Probabilities." Chapman & Hall, 1991.
        [6] Graded Monads. Katsumata. 2014.

        *本文仅代表个人理解及观点,不构成任何论文审核或者项目落地推荐意见,具体以相关组织评审结果为准。欢迎就论文内容交流探讨,理性发言哦~ 想了解更多原文细节的小伙伴,可以点击"阅读原文",查看更多原论文细节哦!       

        end
        概率编程也能如此精准?快来龙哥读论文粉丝群,和志同道合的小伙伴一起探讨不精确概率的奥秘!扫描下方二维码或者添加龙哥助手微信号加群:kangjinlonghelper。一定要备注:研究方向+地点+学校/公司+昵称(如 概率编程+牛津+龙哥),根据格式备注,可更快被通过且邀请进群。
        wechat_helper dianzan
        转发文章 微博 X LinkedIn Facebook
        龙哥读论文 · PaperDaily

        本文基于龙哥读论文 PaperDaily 数据库整理,结合论文原文与工程视角进行解读。