Clojure DAG(贝叶斯网络)

14

我想在Clojure中构建一个贝叶斯网络,因为我没有找到任何类似的项目。

我学了很多BN的理论,但我仍然不知道如何实现网络(我不是人们所谓的“大牛”,尤其不擅长函数式编程)。

我知道BN只是一个DAG和许多概率表(每个节点一个)而已,但是现在我不知道如何实现DAG。

我的第一个想法是使用一组巨大的集合(DAG)和一些小映射(DAG节点),每个映射都应该有一个名称(可能是key a)、一个概率表(另一个映射?)、一个父向量和最后一个后代向量。

现在我不知道如何实现父项和非后代的引用(应该在两个向量中放什么)。我想使用指针,但Clojure缺乏指针;我可以在向量中放置另一个节点的名称,但速度会变慢,不是吗?

我想,与其使用向量,还不如使用更多的集合,这样查找节点的后代会更快。

概率表也存在类似的问题,我仍需要一些对其他节点的引用。

最后,我还想学习BN(从数据开始构建网络),这意味着我将大量更改概率表、边缘和节点。

我应该使用可变类型吗?还是它们只会增加复杂性?


这个SO问题可以帮助你。 - Ankur
1
Chas Emerick在ClojureConj上做了一个贝叶斯网络的演讲。其中包含一些有用的信息,可能可以回答你的一些问题。演讲链接 - John Szakmeister
现在请访问https://www.youtube.com/watch?v=xoSFcSqo1jQ。 - Thumbnail
你看过 Loom 库吗?http://github.com/aysylu/loom - 象嘉道
可能与此不完全相关,但您是否看过http://www.robots.ox.ac.uk/~fwood/anglican/(Clojure中的Church衍生物)还请参见http://www.robots.ox.ac.uk/~fwood/anglican/examples/index.html? - JoelKuiper
3个回答

1
一般来说,计算贝叶斯网络的联合分布的方法是:
prod( P(node | parents of node) ) 

为了实现这一点,您需要一个节点列表,其中每个节点都包含:
  • 节点名称
  • 父节点列表
  • 概率表
  • 子节点列表
当每行值对应于父配置,每列对应于节点值时,平坦化的概率表可能是最容易处理的。这假定您使用记录来保存所有值。节点的值也可以包含在节点中。
没有父节点的节点只有一行。
每行应在规范化后,P(node | parents)= table [row,col]
您不真正需要子节点列表,但是拥有它可以使拓扑排序更容易。 DAG必须能够进行拓扑排序。
最大的问题是概率表中单元格的数量是父级和自身所有维度的乘积。我在C ++中使用稀疏表和行映射来处理这个问题。
查询DAG是一个不同的问题,最好的方法取决于大小以及是否需要近似答案。这里没有足够的空间来涵盖它们。搜索Murphy和Bayes Net Toolbox可能会有所帮助。

我知道你特别想要一个实现,但是只要稍微努力一下,你就可以自己创建。


1
这不是一个完整的答案,但这里有一个可能的编码示例网络来自wikipedia article。每个节点都有一个名称、一个后继列表(子节点)和一个概率表:
(defn node [name children fn]
  {:name name :children children :table fn})

此外,这里有一些用于构建真/假概率的小助手函数:
;; builds a true/false probability map
(defn tf [true-prob] #(if % true-prob (- 1.0 true-prob)))

上述函数返回一个闭包,当给定一个true值(或false值)时,返回事件X=true的概率(对于我们编码的概率变量X)。
由于网络是一个DAG,我们可以直接引用节点之间的关系(就像您提到的指针一样),而不必担心循环引用。我们只需按拓扑顺序构建图形:
(let [gw (node "grass wet" [] (fn [& {:keys [sprinkler rain]}]
                            (tf (cond (and sprinkler rain) 0.99
                                      sprinkler 0.9
                                      rain 0.8
                                      :else 0.0))))

  sk (node "sprinkler" [gw]
           (fn [& {:keys [rain]}] (tf (if rain 0.01 0.4))))

  rn (node "rain" [sk gw]
           (constantly (tf 0.2)))]

  (def dag {:nodes {:grass-wet gw :sprinkler sk :rain rn}
        :joint (fn [g s r]
                 (*
                  (((:table gw) :sprinkler s :rain r) g)
                  (((:table sk) :rain r) s)
                  (((:table rn)) r)))}))

每个节点的概率表都是根据父节点的状态给出的函数,并返回truefalse值的概率。例如,
((:table (:grass-wet dag)) :sprinkler true :rain false)

... 返回 {:真 0.9,:假 0.09999999999999998}

由此产生的联合函数根据以下公式组合概率:

P(G,S,R) = P(G|S,R).P(S|R).P(R)

((:joint dag) true true true) 返回 0.0019800000000000004。实际上,((:table <x>) <args>) 返回的每个值都是一个闭包,其中包含一个 if,它返回知道概率变量状态的概率。我们使用相应的 true/false 值调用每个闭包以提取适当的概率,并将它们相乘。

在这里,我有点作弊,因为我假设联合函数应该通过遍历图形来计算(在一般情况下可以使用宏来帮助)。这也感觉有点混乱,特别是关于节点的状态,它们不一定只是 true 和 false:在一般情况下,您很可能会使用 map。


0

你可以尝试更加扁平化,通过节点ID索引多个映射:一个概率表的映射,一个父节点的映射和一个非后代节点的映射(我不是BN专家:这是什么,它如何使用等等?感觉像是可以从父节点表^W关系^W映射中重新计算出来的东西)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接