信念网络的概率推理与桶消除算法实现

信念网络,也称为贝叶斯网络或图形模型,是一种图,其中节点带有条件概率表(CPT)代表随机变量,节点之间的链接或箭头表示影响。如图1所示的信念网络,可以通过1-P(X=F)来获得P(X=T)。信念网络在数学上等同于联合概率分布,它为提供了一种自然的方式来模拟一组感兴趣变量之间的概率关系。由于其概率性质,信念网络在编码不确定知识方面非常有用。一旦掌握了网络,就可以从中进行推理,无论是诊断性的(从效果到原因),还是因果性的(从原因到效果),或者是混合的。然后可以根据推理结果做出决策或建议。这就是信念网络在人工智能中找到其应用方式的原因。

信念网络是一个在理论和应用上都值得探索的丰富领域。然而,由于篇幅有限,本文不会讨论任何细节。相反,将通过一个简单的例子来引导,而不提供证明。希望这篇文章能让感受到信念网络是如何工作的。如果感兴趣,可以阅读像[1]这样的教科书,或者在互联网上搜索教程。

推理

条件概率,写作P(A|B,C),告诉在事件B和C发生的情况下事件A发生的概率。一个联合概率,如P(A, B),表示事件A和B同时发生的概率。根据概率演算的乘法规则,一个联合概率可以被分解为先验概率和条件概率的乘积,P(A, B) = P(A|B)P(B),其中P(B)被称为先验或无条件概率。

推理的任务是计算一组查询变量的条件下概率P(query|evidence),给定一些证据变量。考虑图1中给出的信念网络,如果想知道草湿时不多云的概率,将计算P(cloudy=0|wetGrass=1)。通过重新排列公式(1),有:P(c=0|w=1) = P(c=0, w=1)/P(w=1)。

为了计算P(w=1),必须包括W节点的父节点的影响,这在信念网络中用箭头表示。W节点有两个父节点S和R。W的父节点也受到它们父节点的影响。重复这个过程直到达到先验概率,得到P(w=1) = ∑s,rP(s,r)P(w=1|s,r) = ∑c,s,rP(c)P(s|c)P(r|c)P(w=1|s,r)。

以类似的方式计算P(c=0, w=1),有P(c=0|w=1) = P(c=0)∑s,rP(s|c=0)P(r|c=0)P(w=1|s,r)/P(w=1)。现在所有右侧的概率都在信念网络的CPT中明确定义了。

桶消除算法

从上面的例子中可以看到,进行推理就是计算联合概率。如果有一个算法来计算联合概率P(c,s,r,w),就可以进行信念网络中的任何推理。

让将公式(3)重写为P(w=1) = ∑cP(c)∑sP(s|c)∑rP(r|c)P(w=1|s,r)。桶消除算法[2],或者更一般的变量消除算法的思想是将求和分布在乘积上。从公式(5)的最内层(最右边)开始,P(w=1) = ∑cP(c)∑sP(s|c)∑rP(r|c)λw(s,r),其中λw(s,r) = P(w=1|s,r) = ∑cP(c)∑sP(s|c)λr(c,s),其中λr(c,s) = ∑rP(r|c)λw(s,r) = ∑cP(c)λs(c),其中λs(c) = ∑sP(s|c)λr(c,s) = λc

现在节点被替换为包含相应节点的CPT和子节点桶的桶λ。计算过程以相反的顺序进行。它从最外层的节点开始,即根节点。如果一个节点是证据或查询,使用提供的值,否则对所有可能的值求和。然后这个过程可以递归实现。

实现

实现有一个如图2所示的静态结构。下载的源代码是一个简化版本。它只适用于二元节点,即节点只取0和1两个值。也移除了所有的异常控制,如try-catch和asserts,并且移除了所有的优化,尽管其中一些是显而易见和直接的。目的是使代码更容易阅读。一旦理解了它,调整代码应该不会太困难。

节点在XML文件中定义。有两个文件,BN_WetGrass.xml和BN_Alarm.xml,包含在下载中,都取自[1]。它们具有完全不同的拓扑结构。可以设计自己的节点。只要记住节点的顺序很重要,任何节点的父节点必须在节点之前定义。方法BNet::Build("nodes.xml")从XML文件中加载节点,然后使用这些节点创建一个信念网络。

要进行推理,可以这样做:

C# BNet net = new BNet(); net.Build("bn_wetGrass.xml"); BNInference infer = new BElim(net) double pr = infer.GetBelief("sprinkler=1; cloudy=0", "WetGrass=0; Rain=1");

桶在BElim::PrepareBucket()中初始化。仔细阅读例子公式(6)后,会发现每个桶不仅需要从其对应节点的父节点输入,还需要从其子桶询问的节点输入。

算法的核心在BElim::Sum(int id, int para)中,如下所示:

C# protected double Sum(int node_id, int para) { // skipped ... double pr = 0.0; foreach (int value in theNode.Range) { if (theNode.IsEvidence && theNode.Evidence != value) continue; double tmpPr = theNode.CPT; foreach (Bucket nxtBuck in theBuck.childBuckets) { int next_para = 0; foreach (BNode node in nxtBuck.parentNodes) { // calculate next_para; } tmpPr *= Sum(nxtBuck.id, next_para); } pr += tmpPr; } return pr; }

在这里利用了二元节点的优势。整数para用作大小为32的位数组。它允许对其进行位操作,从而使代码更有效和紧凑。求和遍历所有可能的值,除非节点有提供的值。然后它考虑其子桶的贡献。要调用子的贡献,求和必须提供参数,并让下一个桶知道哪个节点有什么值,等等。不会进一步解释,因为这些事情最好由代码而不是文字来解释。

使用信念网络

WetGrass网络非常简单,但它已经展示了一些微妙之处。例如,可以计算在草湿的情况下喷水器工作的概率。有P(sprinkler=1|wetGrass=1) = 0.43。这种推理是诊断性的,因为它告诉从效果到原因。另一方面,如果知道是否下雨,那么P(sprinkler=1|wetGrass=1; rain=1) = 0.19,或者P(sprinkler=1|wetGrass=1; rain=0) = 1.0。看到额外的下雨信息改变了sprinkler=1的概率,尽管喷水器和雨并不直接相互影响。这种效应被称为解释性。

可以玩代码,尝试WetGrass和Alarm网络的不同推理。可能会有惊喜。推理并不总是直接的。常识并不总是正确的。

编码笔记

使用C#的原因是因为想学习它。使用C#作为更好的C++,而不是更好的C :)。作为一个长期的C++程序员,花了一些时间来适应使用new而不考虑在哪里放置delete。唯一让烦恼的是ArrayList。在看来,似乎没有办法在声明时指定类型;类型是在添加一个项目时决定的。这可能是一个很好的特性。然而,如果要将设计和实现分开,必须依赖注释来提醒实现者列表包含的项目类型。也许错过了一些东西。

代码是用Emacs编辑器编辑的,并由从Microsoft网站下载的C# SDK中的csc.exe编译。Emacs编辑器限制了编写更好的UI。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485