Probabilistic Inference

The probabilistic inference algorithm used by py-bbn is an exact inference algorithm. Let’s go through an example on how to conduct exact inference.

Huang Graph

Below is the code to create the Huang Graph [HD99]. Note the typical procedure as follows.

  • create a Bayesian Belief Network (BBN)

  • create a junction tree from the graph

  • assert evidence

  • print out the marginal probabilities

digraph { node [fixedsize=true, width=0.3, shape=circle, fontname="Helvetica-Outline", color=crimson, style=filled] A -> B A -> C B -> D C -> E D -> F E -> F C -> G E -> H G -> H }

Huang Bayesian Belief Network structure.

 1from pybbn.graph.dag import Bbn
 2from pybbn.graph.edge import Edge, EdgeType
 3from pybbn.graph.jointree import EvidenceBuilder
 4from pybbn.graph.node import BbnNode
 5from pybbn.graph.variable import Variable
 6from pybbn.pptc.inferencecontroller import InferenceController
 7
 8# create the nodes
 9a = BbnNode(Variable(0, 'a', ['on', 'off']), [0.5, 0.5])
10b = BbnNode(Variable(1, 'b', ['on', 'off']), [0.5, 0.5, 0.4, 0.6])
11c = BbnNode(Variable(2, 'c', ['on', 'off']), [0.7, 0.3, 0.2, 0.8])
12d = BbnNode(Variable(3, 'd', ['on', 'off']), [0.9, 0.1, 0.5, 0.5])
13e = BbnNode(Variable(4, 'e', ['on', 'off']), [0.3, 0.7, 0.6, 0.4])
14f = BbnNode(Variable(5, 'f', ['on', 'off']), [0.01, 0.99, 0.01, 0.99, 0.01, 0.99, 0.99, 0.01])
15g = BbnNode(Variable(6, 'g', ['on', 'off']), [0.8, 0.2, 0.1, 0.9])
16h = BbnNode(Variable(7, 'h', ['on', 'off']), [0.05, 0.95, 0.95, 0.05, 0.95, 0.05, 0.95, 0.05])
17
18# create the network structure
19bbn = Bbn() \
20    .add_node(a) \
21    .add_node(b) \
22    .add_node(c) \
23    .add_node(d) \
24    .add_node(e) \
25    .add_node(f) \
26    .add_node(g) \
27    .add_node(h) \
28    .add_edge(Edge(a, b, EdgeType.DIRECTED)) \
29    .add_edge(Edge(a, c, EdgeType.DIRECTED)) \
30    .add_edge(Edge(b, d, EdgeType.DIRECTED)) \
31    .add_edge(Edge(c, e, EdgeType.DIRECTED)) \
32    .add_edge(Edge(d, f, EdgeType.DIRECTED)) \
33    .add_edge(Edge(e, f, EdgeType.DIRECTED)) \
34    .add_edge(Edge(c, g, EdgeType.DIRECTED)) \
35    .add_edge(Edge(e, h, EdgeType.DIRECTED)) \
36    .add_edge(Edge(g, h, EdgeType.DIRECTED))
37
38# convert the BBN to a join tree
39join_tree = InferenceController.apply(bbn)
40
41# insert an observation evidence
42ev = EvidenceBuilder() \
43    .with_node(join_tree.get_bbn_node_by_name('a')) \
44    .with_evidence('on', 1.0) \
45    .build()
46join_tree.set_observation(ev)
47
48# print the posterior probabilities
49for node, posteriors in join_tree.get_posteriors().items():
50    p = ', '.join([f'{val}={prob:.5f}' for val, prob in posteriors.items()])
51    print(f'{node} : {p}')

A Bayesian Belief Network (BBN) is defined as a pair, G, P, where

  • G is a directed acylic graph (DAG)

  • P is a joint probability distribution

  • and G satisfies the Markov Condition (nodes are conditionally independent of non-descendants given its parents)

Ideally, the API should force the user to define G and P separately. However, there will be a bit of cognitive friction with this API as we define nodes associated with their local probability models (conditional probability tables) and then the structure afterwards. But this approach seems a bit more concise, no?

Updating Conditional Probability Tables

Sometimes, you may want to preserve the join tree structure and just update the condtional probability tables (CPTs). Here’s how to do so.

 1from pybbn.graph.dag import Bbn
 2from pybbn.graph.edge import EdgeType, Edge
 3from pybbn.graph.node import BbnNode
 4from pybbn.graph.variable import Variable
 5from pybbn.pptc.inferencecontroller import InferenceController
 6
 7# you have built a BBN
 8a = BbnNode(Variable(0, 'a', ['t', 'f']), [0.2, 0.8])
 9b = BbnNode(Variable(1, 'b', ['t', 'f']), [0.1, 0.9, 0.9, 0.1])
10bbn = Bbn().add_node(a).add_node(b) \
11    .add_edge(Edge(a, b, EdgeType.DIRECTED))
12
13# you have built a junction tree from the BBN
14# let's call this "original" junction tree the left-hand side (lhs) junction tree
15lhs_jt = InferenceController.apply(bbn)
16
17# you may just update the CPTs with the original junction tree structure
18# the algorithm to find/build the junction tree is avoided
19# the CPTs are updated
20rhs_jt = InferenceController.reapply(lhs_jt, {0: [0.3, 0.7], 1: [0.2, 0.8, 0.8, 0.2]})
21
22# let's print out the marginal probabilities and see how things changed
23# print the marginal probabilities for the lhs junction tree
24print('lhs probabilities')
25# print the posterior probabilities
26for node, posteriors in lhs_jt.get_posteriors().items():
27    p = ', '.join([f'{val}={prob:.5f}' for val, prob in posteriors.items()])
28    print(f'{node} : {p}')
29
30# print the marginal probabilities for the rhs junction tree
31print('rhs probabilities')
32for node, posteriors in rhs_jt.get_posteriors().items():
33    p = ', '.join([f'{val}={prob:.5f}' for val, prob in posteriors.items()])
34    print(f'{node} : {p}')

Note that we use InferenceController.reapply(...) to apply the new CPTs to a previous one and that we get a new junction tree as an output.