这不是一个完整的答案,但这里有一个可能的编码示例网络来自
wikipedia article。每个节点都有一个名称、一个后继列表(子节点)和一个概率表:
(defn node [name children fn]
{:name name :children children :table fn})
此外,这里有一些用于构建真/假概率的小助手函数:
(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)))}))
每个节点的概率表都是根据父节点的状态给出的函数,并返回
true
和
false
值的概率。例如,
((: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。