aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCathy Yeh <cathy@driver.xyz>2018-01-18 21:57:50 -0800
committerCathy Yeh <cathy@driver.xyz>2018-01-18 21:57:50 -0800
commit2366e92bdb9c81bc2bd7132a00ed5c16a5160c5e (patch)
treefc71d343eec17b59d8af81eb768e4fe2eab167c2
parent65d822247e30b6e104a8c09d3b930487b9f20a58 (diff)
parentc93c352b2f68a2bbcde2241e61d9fb52504a67a9 (diff)
downloadbeliefs-2366e92bdb9c81bc2bd7132a00ed5c16a5160c5e.tar.gz
beliefs-2366e92bdb9c81bc2bd7132a00ed5c16a5160c5e.tar.bz2
beliefs-2366e92bdb9c81bc2bd7132a00ed5c16a5160c5e.zip
Merge branch 'generic_discrete_factor'. Implements explicit discrete factor methodsv0.1.0
-rw-r--r--VERSION2
-rw-r--r--beliefs/factors/bernoulli_and_cpd.py10
-rw-r--r--beliefs/factors/bernoulli_or_cpd.py10
-rw-r--r--beliefs/factors/cpd.py45
-rw-r--r--beliefs/factors/discrete_factor.py126
-rw-r--r--beliefs/inference/belief_propagation.py99
-rw-r--r--beliefs/models/base_models.py90
-rw-r--r--beliefs/models/belief_update_node_model.py393
-rw-r--r--beliefs/utils/math_helper.py14
-rw-r--r--beliefs/utils/random_variables.py17
-rw-r--r--conda-build/meta.yaml2
-rw-r--r--examples/compare_pgmpy_belief_propagation.ipynb990
-rw-r--r--tests/test_belief_propagation.py64
13 files changed, 1585 insertions, 277 deletions
diff --git a/VERSION b/VERSION
index bcab45a..6e8bf73 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.0.3
+0.1.0
diff --git a/beliefs/factors/bernoulli_and_cpd.py b/beliefs/factors/bernoulli_and_cpd.py
index fdb0c25..291398f 100644
--- a/beliefs/factors/bernoulli_and_cpd.py
+++ b/beliefs/factors/bernoulli_and_cpd.py
@@ -12,15 +12,17 @@ class BernoulliAndCPD(TabularCPD):
"""
def __init__(self, variable, parents=[]):
"""
- Args:
- variable: int or string
- parents: optional, list of int and/or strings
+ Args
+ variable: int or string
+ parents: list,
+ (optional) list of int and/or strings
"""
super().__init__(variable=variable,
variable_card=2,
parents=parents,
parents_card=[2]*len(parents),
- values=[])
+ values=None,
+ state_names={var: ['False', 'True'] for var in [variable] + parents})
self._values = None
@property
diff --git a/beliefs/factors/bernoulli_or_cpd.py b/beliefs/factors/bernoulli_or_cpd.py
index 12ee2f6..b5e6ae5 100644
--- a/beliefs/factors/bernoulli_or_cpd.py
+++ b/beliefs/factors/bernoulli_or_cpd.py
@@ -12,15 +12,17 @@ class BernoulliOrCPD(TabularCPD):
"""
def __init__(self, variable, parents=[]):
"""
- Args:
- variable: int or string
- parents: optional, list of int and/or strings
+ Args
+ variable: int or string
+ parents: list,
+ (optional) list of int and/or strings
"""
super().__init__(variable=variable,
variable_card=2,
parents=parents,
parents_card=[2]*len(parents),
- values=[])
+ values=None,
+ state_names={var: ['False', 'True'] for var in [variable] + parents})
self._values = None
@property
diff --git a/beliefs/factors/cpd.py b/beliefs/factors/cpd.py
index a286aaa..c7883c9 100644
--- a/beliefs/factors/cpd.py
+++ b/beliefs/factors/cpd.py
@@ -1,32 +1,33 @@
+import copy
import numpy as np
+from beliefs.factors.discrete_factor import DiscreteFactor
-class TabularCPD:
+class TabularCPD(DiscreteFactor):
"""
Defines the conditional probability table for a discrete variable
whose parents are also discrete.
-
- TODO: have this inherit from DiscreteFactor implementing explicit factor methods
"""
- def __init__(self, variable, variable_card,
- parents=[], parents_card=[], values=[]):
+ def __init__(self, variable, variable_card, parents=[], parents_card=[],
+ values=[], state_names=None):
"""
- Args:
- variable: int or string
- variable_card: int
- parents: optional, list of int and/or strings
- parents_card: optional, list of int
- values: optional, 2d list or array
+ Args
+ variable: int or string
+ variable_card: int
+ parents: list,
+ (optional) list of int and/or strings
+ parents_card: list,
+ (optional) list of int
+ values: 2-d list or array (optional)
+ state_names: dictionary (optional),
+ mapping variables to their states, of format {label_name: ['state1', 'state2']}
"""
+ super().__init__(variables=[variable] + parents,
+ cardinality=[variable_card] + parents_card,
+ values=values,
+ state_names=state_names)
self.variable = variable
- self.parents = parents
- self.variables = [variable] + parents
- self.cardinality = [variable_card] + parents_card
- self._values = np.array(values)
-
- @property
- def values(self):
- return self._values
+ self.parents = list(parents)
def get_values(self):
"""
@@ -38,8 +39,4 @@ class TabularCPD:
return self.values.reshape(self.cardinality[0], np.prod(self.cardinality[1:]))
def copy(self):
- return self.__class__(self.variable,
- self.cardinality[0],
- self.parents,
- self.cardinality[1:],
- self._values)
+ return copy.deepcopy(self)
diff --git a/beliefs/factors/discrete_factor.py b/beliefs/factors/discrete_factor.py
new file mode 100644
index 0000000..708f00c
--- /dev/null
+++ b/beliefs/factors/discrete_factor.py
@@ -0,0 +1,126 @@
+import copy
+import numpy as np
+
+
+class DiscreteFactor:
+
+ def __init__(self, variables, cardinality, values=None, state_names=None):
+ """
+ Args
+ variables: list,
+ variables in the scope of the factor
+ cardinality: list,
+ cardinalities of each variable, where len(cardinality)=len(variables)
+ values: list,
+ row vector of values of variables with ordering such that right-most variables
+ defined in `variables` cycle through their values the fastest
+ state_names: dictionary,
+ mapping variables to their states, of format {label_name: ['state1', 'state2']}
+ """
+ self.variables = list(variables)
+ self.cardinality = list(cardinality)
+ if values is None:
+ self._values = None
+ else:
+ self._values = np.array(values).reshape(self.cardinality)
+ self.state_names = state_names
+
+ def __mul__(self, other):
+ return self.product(other)
+
+ def copy(self):
+ """Return a copy of the factor"""
+ return self.__class__(self.variables,
+ self.cardinality,
+ self._values,
+ copy.deepcopy(self.state_names))
+
+ @property
+ def values(self):
+ return self._values
+
+ def update_values(self, new_values):
+ """We make this available because _values is allowed to be None on init"""
+ self._values = np.array(new_values).reshape(self.cardinality)
+
+ def get_value_for_state_vector(self, dict_of_states):
+ """
+ Return the value for a dictionary of variable states.
+
+ Args
+ dict_of_states: dictionary,
+ of format {label_name1: 'state1', label_name2: 'True'}
+ Returns
+ probability, a float, the factor value for a specific combination of variable states
+ """
+ assert sorted(dict_of_states.keys()) == sorted(self.variables), \
+ "The keys for the dictionary of states must match the variables in factor scope."
+ state_coordinates = []
+ for var in self.variables:
+ var_state = dict_of_states[var]
+ idx_in_var_axis = self.state_names[var].index(var_state)
+ state_coordinates.append(idx_in_var_axis)
+ return self.values[tuple(state_coordinates)]
+
+ def add_new_variables_from_other_factor(self, other):
+ """Add new variables from `other` factor to the factor."""
+ extra_vars = set(other.variables) - set(self.variables)
+ # if all of these variables already exist there is nothing to do
+ if len(extra_vars) == 0:
+ return
+ # otherwise, extend the values array
+ slice_ = [slice(None)] * len(self.variables)
+ slice_.extend([np.newaxis] * len(extra_vars))
+ self._values = self._values[slice_]
+ self.variables.extend(extra_vars)
+
+ new_card_var = other.get_cardinality(extra_vars)
+ self.cardinality.extend([new_card_var[var] for var in extra_vars])
+
+ def get_cardinality(self, variables):
+ return {var: self.cardinality[self.variables.index(var)] for var in variables}
+
+ def product(self, other):
+ left = self.copy()
+
+ if isinstance(other, (int, float)):
+ return self.values * other
+ else:
+ assert isinstance(other, DiscreteFactor), \
+ "__mul__ is only defined between subclasses of DiscreteFactor"
+ right = other.copy()
+ left.add_new_variables_from_other_factor(right)
+ right.add_new_variables_from_other_factor(left)
+
+ # reorder variables in right factor to match order in left
+ source_axes = list(range(right.values.ndim))
+ destination_axes = [right.variables.index(var) for var in left.variables]
+ right.variables = [right.variables[idx] for idx in destination_axes]
+
+ # rearrange values in right factor to correspond to the reordered variables
+ right._values = np.moveaxis(right.values, source_axes, destination_axes)
+ left._values = left.values * right.values
+ return left
+
+ def marginalize(self, vars):
+ """
+ Args
+ vars: list,
+ variables over which to marginalize the factor
+ Returns
+ DiscreteFactor, whose scope is set(self.variables) - set(vars)
+ """
+ phi = copy.deepcopy(self)
+
+ var_indexes = []
+ for var in vars:
+ if var not in phi.variables:
+ raise ValueError('{} not in scope'.format(var))
+ else:
+ var_indexes.append(self.variables.index(var))
+
+ index_to_keep = sorted(set(range(len(self.variables))) - set(var_indexes))
+ phi.variables = [self.variables[index] for index in index_to_keep]
+ phi.cardinality = [self.cardinality[index] for index in index_to_keep]
+ phi._values = np.sum(phi.values, axis=tuple(var_indexes))
+ return phi
diff --git a/beliefs/inference/belief_propagation.py b/beliefs/inference/belief_propagation.py
index 7ec648d..e6e7b18 100644
--- a/beliefs/inference/belief_propagation.py
+++ b/beliefs/inference/belief_propagation.py
@@ -28,10 +28,10 @@ class ConflictingEvidenceError(Exception):
class BeliefPropagation:
def __init__(self, model, inplace=True):
"""
- Input:
- model: an instance of BeliefUpdateNodeModel
- inplace: bool
- modify in-place the nodes in the model during belief propagation
+ Args
+ model: an instance of BeliefUpdateNodeModel
+ inplace: bool,
+ modify in-place the nodes in the model during belief propagation
"""
if not isinstance(model, BeliefUpdateNodeModel):
raise TypeError("Model must be an instance of BeliefUpdateNodeModel")
@@ -43,21 +43,20 @@ class BeliefPropagation:
def _belief_propagation(self, nodes_to_update, evidence):
"""
Implementation of Pearl's belief propagation algorithm for polytrees.
-
ref: "Fusion, Propagation, and Structuring in Belief Networks"
Artificial Intelligence 29 (1986) 241-288
- Input:
- nodes_to_update: list
- list of MsgPasser namedtuples.
- evidence: dict,
- a dict key, value pair as {var: state_of_var observed}
+ Args
+ nodes_to_update: list,
+ list of MsgPasser namedtuples.
+ evidence: dict,
+ a dict key, value pair as {var: state_of_var observed}
"""
if len(nodes_to_update) == 0:
return
node_to_update_label_id, msg_sender_label_id = nodes_to_update.pop()
- logging.info("Node: %s", node_to_update_label_id)
+ logging.debug("Node: %s", node_to_update_label_id)
node = self.model.nodes_dict[node_to_update_label_id]
@@ -65,18 +64,18 @@ class BeliefPropagation:
# outgoing msg from the node to update
parent_ids = set(node.parents) - set([msg_sender_label_id])
child_ids = set(node.children) - set([msg_sender_label_id])
- logging.info("parent_ids: %s", str(parent_ids))
- logging.info("child_ids: %s", str(child_ids))
+ logging.debug("parent_ids: %s", str(parent_ids))
+ logging.debug("child_ids: %s", str(child_ids))
if msg_sender_label_id is not None:
# update triggered by receiving a message, not pinning to evidence
assert len(node.parents) + len(node.children) - 1 == len(parent_ids) + len(child_ids)
if node_to_update_label_id not in evidence:
- node.compute_pi_agg()
- logging.info("belief propagation pi_agg: %s", np.array2string(node.pi_agg))
- node.compute_lambda_agg()
- logging.info("belief propagation lambda_agg: %s", np.array2string(node.lambda_agg))
+ node.compute_and_update_pi_agg()
+ logging.debug("belief propagation pi_agg: %s", np.array2string(node.pi_agg.values))
+ node.compute_and_update_lambda_agg()
+ logging.debug("belief propagation lambda_agg: %s", np.array2string(node.lambda_agg.values))
for parent_id in parent_ids:
try:
@@ -97,47 +96,46 @@ class BeliefPropagation:
new_value=new_pi_msg)
nodes_to_update.add(MsgPassers(msg_receiver=child_id,
msg_sender=node_to_update_label_id))
-
self._belief_propagation(nodes_to_update, evidence)
def initialize_model(self):
"""
- Apply boundary conditions:
+ 1. Apply boundary conditions:
- Set pi_agg equal to prior probabilities for root nodes.
- Set lambda_agg equal to vector of ones for leaf nodes.
- - Set lambda_agg, lambda_received_msgs to vectors of ones (same effect as
- actually passing lambda messages up from leaf nodes to root nodes).
- - Calculate pi_agg and pi_received_msgs for all nodes without evidence.
- (Without evidence, belief equals pi_agg.)
+ 2. Set lambda_agg, lambda_received_msgs to vectors of ones (same effect as
+ actually passing lambda messages up from leaf nodes to root nodes).
+ 3. Calculate pi_agg and pi_received_msgs for all nodes without evidence.
+ (Without evidence, belief equals pi_agg.)
"""
self.model.set_boundary_conditions()
for node in self.model.nodes_dict.values():
ones_vector = np.ones([node.cardinality])
+ node.update_lambda_agg(ones_vector)
- node.lambda_agg = ones_vector
for child in node.lambda_received_msgs.keys():
node.update_lambda_msg_from_child(child=child,
new_value=ones_vector)
- logging.info("Finished initializing Lambda(x) and lambda_received_msgs per node.")
+ logging.debug("Finished initializing Lambda(x) and lambda_received_msgs per node.")
- logging.info("Start downward sweep from nodes. Sending Pi messages only.")
+ logging.debug("Start downward sweep from nodes. Sending Pi messages only.")
topdown_order = self.model.get_topologically_sorted_nodes(reverse=False)
for node_id in topdown_order:
- logging.info('label in iteration through top-down order: %s', str(node_id))
+ logging.debug('label in iteration through top-down order: %s', str(node_id))
node_sending_msg = self.model.nodes_dict[node_id]
child_ids = node_sending_msg.children
- if node_sending_msg.pi_agg is None:
- node_sending_msg.compute_pi_agg()
+ if node_sending_msg.pi_agg.values is None:
+ node_sending_msg.compute_and_update_pi_agg()
for child_id in child_ids:
- logging.info("child: %s", str(child_id))
+ logging.debug("child: %s", str(child_id))
new_pi_msg = node_sending_msg.compute_pi_msg_to_child(child_k=child_id)
- logging.info("new_pi_msg: %s", np.array2string(new_pi_msg))
+ logging.debug("new_pi_msg: %s", np.array2string(new_pi_msg))
child_node = self.model.nodes_dict[child_id]
child_node.update_pi_msg_from_parent(parent=node_id,
@@ -145,42 +143,41 @@ class BeliefPropagation:
def _run_belief_propagation(self, evidence):
"""
- Input:
- evidence: dict
- a dict key, value pair as {var: state_of_var observed}
+ Sequentially perturb nodes with observed values, running belief propagation
+ after each perturbation.
+
+ Args
+ evidence: dict,
+ a dict key, value pair as {var: state_of_var observed}
"""
for evidence_id, observed_value in evidence.items():
- nodes_to_update = set()
-
if evidence_id not in self.model.nodes_dict.keys():
raise KeyError("Evidence supplied for non-existent label_id: {}"
.format(evidence_id))
if is_kronecker_delta(observed_value):
# specific evidence
- self.model.nodes_dict[evidence_id].lambda_agg = observed_value
+ self.model.nodes_dict[evidence_id].update_lambda_agg(observed_value)
else:
# virtual evidence
- self.model.nodes_dict[evidence_id].lambda_agg = \
- self.model.nodes_dict[evidence_id].lambda_agg * observed_value
-
+ self.model.nodes_dict[evidence_id].update_lambda_agg(
+ self.model.nodes_dict[evidence_id].lambda_agg.values * observed_value
+ )
nodes_to_update = [MsgPassers(msg_receiver=evidence_id, msg_sender=None)]
-
- self._belief_propagation(nodes_to_update=set(nodes_to_update),
- evidence=evidence)
+ self._belief_propagation(nodes_to_update=set(nodes_to_update), evidence=evidence)
def query(self, evidence={}):
"""
- Run belief propagation given evidence.
+ Run belief propagation given 0 or more pieces of evidence.
- Input:
- evidence: dict
- a dict key, value pair as {var: state_of_var observed},
- e.g. {'3': np.array([0,1])} if label '3' is True.
+ Args
+ evidence: dict,
+ a dict key, value pair as {var: state_of_var observed},
+ e.g. {'3': np.array([0,1])} if label '3' is True.
- Returns:
- beliefs: dict
- a dict key, value pair as {var: belief}
+ Returns
+ a dict key, value pair as {var: belief}, where belief is an np.array of the
+ marginal probability of each state of the variable given the evidence.
Example
-------
diff --git a/beliefs/models/base_models.py b/beliefs/models/base_models.py
index cb91566..71af0cb 100644
--- a/beliefs/models/base_models.py
+++ b/beliefs/models/base_models.py
@@ -9,9 +9,11 @@ class DirectedGraph(nx.DiGraph):
"""
def __init__(self, edges=None, node_labels=None):
"""
- Input:
- edges: an edge list, e.g. [(parent1, child1), (parent1, child2)]
- node_labels: a list of strings of node labels
+ Args
+ edges: list,
+ a list of edge tuples, e.g. [(parent1, child1), (parent1, child2)]
+ node_labels: list,
+ a list of strings or integers representing node label ids
"""
super().__init__()
if edges is not None:
@@ -20,18 +22,15 @@ class DirectedGraph(nx.DiGraph):
self.add_nodes_from(node_labels)
def get_leaves(self):
- """
- Returns a list of leaves of the graph.
- """
+ """Return a list of leaves of the graph"""
return [node for node, out_degree in self.out_degree() if out_degree == 0]
def get_roots(self):
- """
- Returns a list of roots of the graph.
- """
+ """Return a list of roots of the graph"""
return [node for node, in_degree in self.in_degree() if in_degree == 0]
def get_topologically_sorted_nodes(self, reverse=False):
+ """Return a list of nodes in topological sort order"""
if reverse:
return list(reversed(list(nx.topological_sort(self))))
else:
@@ -47,12 +46,12 @@ class BayesianModel(DirectedGraph):
"""
Base class for Bayesian model.
- Input:
- edges: (optional) list of edges,
+ Args
+ edges: (optional) list of edges,
tuples of form ('parent', 'child')
- variables: (optional) list of str or int
+ variables: (optional) list of str or int
labels for variables
- cpds: (optional) list of CPDs
+ cpds: (optional) list of CPDs
TabularCPD class or subclass
"""
super().__init__()
@@ -61,20 +60,17 @@ class BayesianModel(DirectedGraph):
self.cpds = cpds
def copy(self):
- """
- Returns a copy of the model.
- """
- copy_model = self.__class__(edges=list(self.edges()).copy(),
- variables=list(self.nodes()).copy(),
- cpds=[cpd.copy() for cpd in self.cpds])
- return copy_model
+ """Return a copy of the model"""
+ return self.__class__(edges=list(self.edges()).copy(),
+ variables=list(self.nodes()).copy(),
+ cpds=[cpd.copy() for cpd in self.cpds])
def get_variables_in_definite_state(self):
"""
- Returns a set of labels of all nodes in a definite state, i.e. with
- label values that are kronecker deltas.
+ Get labels of all nodes in a definite state, i.e. with label values
+ that are kronecker deltas.
- RETURNS
+ Returns
set of strings (labels)
"""
return {label for label, node in self.nodes_dict.items() if is_kronecker_delta(node.belief)}
@@ -84,14 +80,14 @@ class BayesianModel(DirectedGraph):
Returns a set of labels that are inferred to be in definite state, given
list of labels that were directly observed (e.g. YES/NOs, but not MAYBEs).
- INPUT
- observed: set of strings, directly observed labels
- RETURNS
- set of strings, labels inferred to be in a definite state
+ Args
+ observed: set,
+ set of strings, directly observed labels
+ Returns
+ set of strings, the labels inferred to be in a definite state
"""
-
- # Assert that beliefs of directly observed vars are kronecker deltas
for label in observed:
+ # beliefs of directly observed vars should be kronecker deltas
assert is_kronecker_delta(self.nodes_dict[label].belief), \
("Observed label has belief {} but should be kronecker delta"
.format(self.nodes_dict[label].belief))
@@ -101,28 +97,40 @@ class BayesianModel(DirectedGraph):
"Expected set of observed labels to be a subset of labels in definite state."
return vars_in_definite_state - observed
- def _get_ancestors_of(self, observed):
- """Return list of ancestors of observed labels"""
+ def _get_ancestors_of(self, labels):
+ """
+ Get set of ancestors of an iterable of labels.
+
+ Args
+ observed: iterable,
+ label ids for which ancestors should be retrieved
+
+ Returns
+ ancestors: set,
+ set of label ids of ancestors of the input labels
+ """
ancestors = set()
- for label in observed:
+ for label in labels:
ancestors.update(nx.ancestors(self, label))
return ancestors
def reachable_observed_variables(self, source, observed=set()):
"""
- Returns list of observed labels (labels with direct evidence to be in a definite
+ Get list of directly observed labels (labels with evidence in a definite
state) that are reachable from the source.
- INPUT
- source: string, label of node for which to evaluate reachable observed labels
- observed: set of strings, directly observed labels
- RETURNS
- reachable_observed_vars: set of strings, observed labels (variables with direct
- evidence) that are reachable from the source label.
+ Args
+ source: string,
+ label of node for which to evaluate reachable observed labels
+ observed: set,
+ set of strings, directly observed labels
+ Returns
+ reachable_observed_vars: set,
+ set of strings, observed labels (variables with direct evidence)
+ that are reachable from the source label
"""
- # ancestors of observed labels, including observed labels
ancestors_of_observed = self._get_ancestors_of(observed)
- ancestors_of_observed.update(observed)
+ ancestors_of_observed.update(observed) # include observed labels
visit_list = set()
visit_list.add((source, 'up'))
diff --git a/beliefs/models/belief_update_node_model.py b/beliefs/models/belief_update_node_model.py
index 1c3ba6e..ec329ca 100644
--- a/beliefs/models/belief_update_node_model.py
+++ b/beliefs/models/belief_update_node_model.py
@@ -7,6 +7,7 @@ from functools import reduce
import networkx as nx
from beliefs.models.base_models import BayesianModel
+from beliefs.factors.discrete_factor import DiscreteFactor
from beliefs.factors.bernoulli_or_cpd import BernoulliOrCPD
from beliefs.factors.bernoulli_and_cpd import BernoulliAndCPD
@@ -32,9 +33,9 @@ class BeliefUpdateNodeModel(BayesianModel):
"""
def __init__(self, nodes_dict):
"""
- Input:
- nodes_dict: dict
- a dict key, value pair as {label_id: instance_of_node_class_or_subclass}
+ Args
+ nodes_dict: dict
+ a dict key, value pair as {label_id: instance_of_node_class_or_subclass}
"""
super().__init__(edges=self._get_edges_from_nodes(nodes_dict.values()),
variables=list(nodes_dict.keys()),
@@ -44,12 +45,15 @@ class BeliefUpdateNodeModel(BayesianModel):
@classmethod
def init_from_edges(cls, edges, node_class):
- """Create nodes from the same node class.
+ """
+ Create model from edges where all nodes are a from the same node class.
- Input:
- edges: list of edge tuples of form ('parent', 'child')
- node_class: the Node class or subclass from which to
- create all the nodes from edges.
+ Args
+ edges: list,
+ list of edge tuples of form [('parent', 'child')]
+ node_class: Node class or subclass,
+ class from which to create all the nodes automatically from edges,
+ e.g. BernoulliAndNode or BernoulliOrNode
"""
nodes = set()
g = nx.DiGraph(edges)
@@ -67,10 +71,12 @@ class BeliefUpdateNodeModel(BayesianModel):
"""
Return list of all directed edges in nodes.
- Args:
- nodes: an iterable of objects of the Node class or subclass
- Returns:
- edges: list of edge tuples
+ Args
+ nodes: iterable,
+ iterable of objects of the Node class or subclass
+ Returns
+ edges: list,
+ list of edge tuples
"""
edges = set()
for node in nodes:
@@ -81,23 +87,28 @@ class BeliefUpdateNodeModel(BayesianModel):
def set_boundary_conditions(self):
"""
- 1. Root nodes: if x is a node with no parents, set Pi(x) = prior
- probability of x.
+ Set boundary conditions for nodes in the model.
+
+ 1. Root nodes: if x is a node with no parents, set Pi(x) = prior
+ probability of x.
- 2. Leaf nodes: if x is a node with no children, set Lambda(x)
- to an (unnormalized) unit vector, of length the cardinality of x.
+ 2. Leaf nodes: if x is a node with no children, set Lambda(x)
+ to an (unnormalized) unit vector, of length the cardinality of x.
"""
for root in self.get_roots():
- self.nodes_dict[root].pi_agg = self.nodes_dict[root].cpd.values
+ self.nodes_dict[root].update_pi_agg(self.nodes_dict[root].cpd.values)
for leaf in self.get_leaves():
- self.nodes_dict[leaf].lambda_agg = np.ones([self.nodes_dict[leaf].cardinality])
+ self.nodes_dict[leaf].update_lambda_agg(np.ones([self.nodes_dict[leaf].cardinality]))
@property
def all_nodes_are_fully_initialized(self):
"""
- Returns True if, for all nodes in the model, all lambda and pi
- messages and lambda_agg and pi_agg are not None, else False.
+ Check if all nodes in the model are initialized, i.e. lambda and pi messages and
+ lambda_agg and pi_agg are not None for every node.
+
+ Returns
+ bool, True if all nodes in the model are initialized, else False.
"""
for node in self.nodes_dict.values():
if not node.is_fully_initialized:
@@ -105,62 +116,53 @@ class BeliefUpdateNodeModel(BayesianModel):
return True
def copy(self):
- """
- Returns a copy of the model.
- """
+ """Return a copy of the model."""
copy_nodes = copy.deepcopy(self.nodes_dict)
copy_model = self.__class__(nodes_dict=copy_nodes)
return copy_model
class Node:
- """A node in a DAG with methods to compute the belief (marginal probability
- of the node given evidence) and compute pi/lambda messages to/from its neighbors
+ """
+ A node in a DAG with methods to compute the belief (marginal probability of
+ the node given evidence) and compute pi/lambda messages to/from its neighbors
to update its belief.
- Implemented from Pearl's belief propagation algorithm.
+ Implemented from Pearl's belief propagation algorithm for polytrees.
"""
- def __init__(self,
- label_id,
- children,
- parents,
- cardinality,
- cpd):
+ def __init__(self, children, cpd):
"""
Args
- label_id: str
- children: set of strings
- parents: set of strings
- cardinality: int, cardinality of the random variable the node represents
- cpd: an instance of a conditional probability distribution,
- e.g. BernoulliOrCPD or TabularCPD
- """
- self.label_id = label_id
+ children: list,
+ list of strings
+ cpd: an instance of TabularCPD or one of its subclasses,
+ e.g. BernoulliOrCPD or BernoulliAndCPD
+ """
+ self.label_id = cpd.variable
self.children = children
- self.parents = parents
- self.cardinality = cardinality
+ self.parents = cpd.parents
+ self.cardinality = cpd.cardinality[0]
self.cpd = cpd
- self.pi_agg = None # np.array dimensions [1, cardinality]
- self.lambda_agg = None # np.array dimensions [1, cardinality]
+ self.pi_agg = self._init_factors_for_variables([self.label_id])[self.label_id]
+ self.lambda_agg = self._init_factors_for_variables([self.label_id])[self.label_id]
- self.pi_received_msgs = self._init_received_msgs(parents)
- self.lambda_received_msgs = self._init_received_msgs(children)
+ self.pi_received_msgs = self._init_factors_for_variables(self.parents)
+ self.lambda_received_msgs = \
+ {child: self._init_factors_for_variables([self.label_id])[self.label_id]
+ for child in children}
- @classmethod
- def from_cpd_class(cls,
- label_id,
- children,
- parents,
- cardinality,
- cpd_class):
- cpd = cpd_class(label_id, parents)
- return cls(label_id, children, parents, cardinality, cpd)
@property
def belief(self):
- if self.pi_agg.any() and self.lambda_agg.any():
- belief = np.multiply(self.pi_agg, self.lambda_agg)
+ """
+ Calculate the marginal probability of the variable from its aggregate values.
+
+ Returns
+ belief, an np.array of ndim 1 and shape (self.cardinality,)
+ """
+ if any(self.pi_agg.values) and any(self.lambda_agg.values):
+ belief = (self.lambda_agg * self.pi_agg).values
return self._normalize(belief)
else:
return None
@@ -168,23 +170,48 @@ class Node:
def _normalize(self, value):
return value/value.sum()
- @staticmethod
- def _init_received_msgs(keys):
- return {k: None for k in keys}
+ def _init_factors_for_variables(self, variables):
+ """
+ Args
+ variables: list,
+ list of ints/strings, e.g. the single node variable or list
+ of parent ids of the node
+ Returns
+ factors: dict,
+ where the dict has key, value pair as {variable_id: instance of a DiscreteFactor},
+ where DiscreteFactor.values is an np.array of ndim 1 and
+ shape (cardinality of variable_id,)
+ """
+ variables = list(variables)
+ factors = {}
+
+ for var in variables:
+ if self.cpd.state_names is not None:
+ state_names = {var: self.cpd.state_names[var]}
+ else:
+ state_names = None
+
+ cardinality = self.cpd.cardinality[self.cpd.variables.index(var)]
+ factors[var] = DiscreteFactor(variables=[var],
+ cardinality=[cardinality],
+ values=None,
+ state_names=state_names)
+ return factors
def _return_msgs_received_for_msg_type(self, message_type):
"""
- Input:
- message_type: MessageType enum
-
- Returns:
- msg_values: list of message values (each an np.array)
+ Args
+ message_type: MessageType enum
+ Returns
+ msg_values: list,
+ list of DiscreteFactors with property `values` containing
+ the values of the messages (np.arrays)
"""
if message_type == MessageType.LAMBDA:
- msg_values = [msg for msg in self.lambda_received_msgs.values()]
+ msgs = [msg for msg in self.lambda_received_msgs.values()]
elif message_type == MessageType.PI:
- msg_values = [msg for msg in self.pi_received_msgs.values()]
- return msg_values
+ msgs = [msg for msg in self.pi_received_msgs.values()]
+ return msgs
def validate_and_return_msgs_received_for_msg_type(self, message_type):
"""
@@ -192,35 +219,58 @@ class Node:
Raise error if all messages have not been received. Called
before calculating lambda_agg (pi_agg).
- Input:
- message_type: MessageType enum
-
- Returns:
- msg_values: list of message values (each an np.array)
+ Args
+ message_type: MessageType enum
+ Returns
+ msgs: list,
+ list of DiscreteFactors with property `values` containing
+ the values of the messages (np.arrays)
"""
- msg_values = self._return_msgs_received_for_msg_type(message_type)
+ msgs = self._return_msgs_received_for_msg_type(message_type)
- if any(msg is None for msg in msg_values):
+ if any(msg.values is None for msg in msgs):
raise ValueError(
"Missing value for {msg_type} msg from child: can't compute {msg_type}_agg."
.format(msg_type=message_type.value)
)
else:
- return msg_values
-
- def compute_pi_agg(self):
- # TODO: implement explict factor product operation
- raise NotImplementedError
+ return msgs
- def compute_lambda_agg(self):
- if len(self.children) == 0:
- return self.lambda_agg
+ def compute_and_update_pi_agg(self):
+ """
+ Compute and update pi_agg, the prior probability, given the current state
+ of messages received from parents.
+ """
+ if len(self.parents) == 0:
+ self.update_pi_agg(self.cpd.values)
else:
- lambda_msg_values = self.validate_and_return_msgs_received_for_msg_type(MessageType.LAMBDA)
- self.lambda_agg = reduce(np.multiply, lambda_msg_values)
- return self.lambda_agg
+ factors_to_multiply = [self.cpd]
+ pi_msgs = self.validate_and_return_msgs_received_for_msg_type(MessageType.PI)
+ factors_to_multiply.extend(pi_msgs)
+
+ factor_product = reduce(lambda phi1, phi2: phi1*phi2, factors_to_multiply)
+ self.update_pi_agg(factor_product.marginalize(self.parents).values)
+ pi_msgs = self.validate_and_return_msgs_received_for_msg_type(MessageType.PI)
+
+ def compute_and_update_lambda_agg(self):
+ """
+ Compute and update lambda_agg, the likelihood, given the current state
+ of messages received from children.
+ """
+ if len(self.children) != 0:
+ lambda_msg_values = [
+ msg.values for msg in
+ self.validate_and_return_msgs_received_for_msg_type(MessageType.LAMBDA)
+ ]
+ self.update_lambda_agg(reduce(np.multiply, lambda_msg_values))
+
+ def update_pi_agg(self, new_value):
+ self.pi_agg.update_values(new_value)
- def _update_received_msg_by_key(self, received_msg_dict, key, new_value):
+ def update_lambda_agg(self, new_value):
+ self.lambda_agg.update_values(new_value)
+
+ def _update_received_msg_by_key(self, received_msg_dict, key, new_value, message_type):
if key not in received_msg_dict.keys():
raise ValueError("Label id '{}' to update message isn't in allowed set of keys: {}"
.format(key, received_msg_dict.keys()))
@@ -229,23 +279,39 @@ class Node:
raise TypeError("Expected a new value of type numpy.ndarray, but got type {}"
.format(type(new_value)))
- if new_value.shape != (self.cardinality,):
+ if message_type == MessageType.LAMBDA:
+ expected_shape = (self.cardinality,)
+ elif message_type == MessageType.PI:
+ expected_shape = (self.cpd.cardinality[self.cpd.variables.index(key)],)
+
+ if new_value.shape != expected_shape:
raise ValueError("Expected new value to be of dimensions ({},) but got {} instead"
- .format(self.cardinality, new_value.shape))
- received_msg_dict[key] = new_value
+ .format(expected_shape, new_value.shape))
+ received_msg_dict[key].update_values(new_value)
def update_pi_msg_from_parent(self, parent, new_value):
self._update_received_msg_by_key(received_msg_dict=self.pi_received_msgs,
key=parent,
- new_value=new_value)
+ new_value=new_value,
+ message_type=MessageType.PI)
def update_lambda_msg_from_child(self, child, new_value):
self._update_received_msg_by_key(received_msg_dict=self.lambda_received_msgs,
key=child,
- new_value=new_value)
+ new_value=new_value,
+ message_type=MessageType.LAMBDA)
def compute_pi_msg_to_child(self, child_k):
- lambda_msg_from_child = self.lambda_received_msgs[child_k]
+ """
+ Compute pi_msg to child.
+
+ Args
+ child_k: string or int,
+ the label_id of the child receiving the pi_msg
+ Returns
+ np.array of ndim 1 and shape (self.cardinality,)
+ """
+ lambda_msg_from_child = self.lambda_received_msgs[child_k].values
if lambda_msg_from_child is not None:
with np.errstate(divide='ignore', invalid='ignore'):
# 0/0 := 0
@@ -255,8 +321,26 @@ class Node:
raise ValueError("Can't compute pi message to child_{} without having received a lambda message from that child.")
def compute_lambda_msg_to_parent(self, parent_k):
- # TODO: implement explict factor product operation
- raise NotImplementedError
+ """
+ Compute lambda_msg to parent.
+
+ Args
+ parent_k: string or int,
+ the label_id of the parent receiving the lambda_msg
+ Returns
+ np.array of ndim 1 and shape (cardinality of parent_k,)
+ """
+ if np.array_equal(self.lambda_agg.values, np.ones([self.cardinality])):
+ return np.ones([self.cardinality])
+ else:
+ factors_to_multiply = [self.cpd]
+ pi_msgs_excl_k = [msg for par_id, msg in self.pi_received_msgs.items()
+ if par_id != parent_k]
+ factors_to_multiply.extend(pi_msgs_excl_k)
+ factor_product = reduce(lambda phi1, phi2: phi1*phi2, factors_to_multiply)
+ new_factor = factor_product.marginalize(list(set(self.parents) - set([parent_k])))
+ lambda_msg_to_k = (self.lambda_agg * new_factor).marginalize([self.lambda_agg.variables[0]])
+ return self._normalize(lambda_msg_to_k.values)
@property
def is_fully_initialized(self):
@@ -272,46 +356,60 @@ class Node:
if any(msg is None for msg in pi_msgs):
return False
- if (self.pi_agg is None) or (self.lambda_agg is None):
+ if (self.pi_agg.values is None) or (self.lambda_agg.values is None):
return False
return True
class BernoulliOrNode(Node):
- def __init__(self,
- label_id,
- children,
- parents):
- super().__init__(label_id=label_id,
- children=children,
- parents=parents,
- cardinality=2,
- cpd=BernoulliOrCPD(label_id, parents))
-
- def compute_pi_agg(self):
+ """
+ A node in a DAG associated with a Bernoulli random variable with state_names ['False', 'True']
+ and conditional probability distribution described by 'Or' logic.
+ """
+ def __init__(self, label_id, children, parents):
+ super().__init__(children=children, cpd=BernoulliOrCPD(label_id, parents))
+
+ def compute_and_update_pi_agg(self):
+ """
+ Compute and update pi_agg, the prior probability, given the current state
+ of messages received from parents. Sidestep explicit factor product and
+ marginalization.
+ """
if len(self.parents) == 0:
- self.pi_agg = self.cpd.values
+ self.update_pi_agg(self.cpd.values)
else:
- pi_msg_values = self.validate_and_return_msgs_received_for_msg_type(MessageType.PI)
- parents_p0 = [p[0] for p in pi_msg_values]
- # Parents are Bernoulli variables with pi_msg_values (surrogate prior probabilities)
- # of p = [P(False), P(True)]
+ pi_msgs = self.validate_and_return_msgs_received_for_msg_type(MessageType.PI)
+ parents_p0 = [p.get_value_for_state_vector({p.variables[0]: 'False'})
+ for p in pi_msgs]
p_0 = reduce(lambda x, y: x*y, parents_p0)
p_1 = 1 - p_0
- self.pi_agg = np.array([p_0, p_1])
- return self.pi_agg
+ self.update_pi_agg(np.array([p_0, p_1]))
def compute_lambda_msg_to_parent(self, parent_k):
- if np.array_equal(self.lambda_agg, np.ones([self.cardinality])):
+ """
+ Compute lambda_msg to parent. Sidestep explicit factor product and
+ marginalization.
+
+ Args
+ parent_k: string or int,
+ the label_id of the parent receiving the lambda_msg
+ Returns
+ np.array of ndim 1 and shape (cardinality of parent_k,)
+ """
+ if np.array_equal(self.lambda_agg.values, np.ones([self.cardinality])):
return np.ones([self.cardinality])
else:
# TODO: cleanup this validation
_ = self.validate_and_return_msgs_received_for_msg_type(MessageType.PI)
- p0_excluding_k = [p[0] for par_id, p in self.pi_received_msgs.items() if par_id != parent_k]
+ p0_excluding_k = [p.get_value_for_state_vector({p.variables[0]: 'False'})
+ for par_id, p in self.pi_received_msgs.items() if par_id != parent_k]
p0_product = reduce(lambda x, y: x*y, p0_excluding_k, 1)
- lambda_0 = self.lambda_agg[1] + (self.lambda_agg[0] - self.lambda_agg[1])*p0_product
- lambda_1 = self.lambda_agg[1]
+
+ lambda_agg_0 = self.lambda_agg.get_value_for_state_vector({self.label_id: 'False'})
+ lambda_agg_1 = self.lambda_agg.get_value_for_state_vector({self.label_id: 'True'})
+ lambda_0 = lambda_agg_1 + (lambda_agg_0 - lambda_agg_1)*p0_product
+ lambda_1 = lambda_agg_1
lambda_msg = np.array([lambda_0, lambda_1])
if not any(lambda_msg):
raise InvalidLambdaMsgToParent
@@ -319,39 +417,54 @@ class BernoulliOrNode(Node):
class BernoulliAndNode(Node):
- def __init__(self,
- label_id,
- children,
- parents):
- super().__init__(label_id=label_id,
- children=children,
- parents=parents,
- cardinality=2,
- cpd=BernoulliAndCPD(label_id, parents))
-
- def compute_pi_agg(self):
+ """
+ A node in a DAG associated with a Bernoulli random variable with state_names ['False', 'True']
+ and conditional probability distribution described by 'And' logic.
+ """
+ def __init__(self, label_id, children, parents):
+ super().__init__(children=children, cpd=BernoulliAndCPD(label_id, parents))
+
+ def compute_and_update_pi_agg(self):
+ """
+ Compute and update pi_agg, the prior probability, given the current state
+ of messages received from parents. Sidestep explicit factor product and
+ marginalization.
+ """
if len(self.parents) == 0:
- self.pi_agg = self.cpd.values
+ self.update_pi_agg(self.cpd.values)
else:
- pi_msg_values = self.validate_and_return_msgs_received_for_msg_type(MessageType.PI)
- parents_p1 = [p[1] for p in pi_msg_values]
- # Parents are Bernoulli variables with pi_msg_values (surrogate prior probabilities)
- # of p = [P(False), P(True)]
+ pi_msgs = self.validate_and_return_msgs_received_for_msg_type(MessageType.PI)
+ parents_p1 = [p.get_value_for_state_vector({p.variables[0]: 'True'})
+ for p in pi_msgs]
p_1 = reduce(lambda x, y: x*y, parents_p1)
p_0 = 1 - p_1
- self.pi_agg = np.array([p_0, p_1])
- return self.pi_agg
+ self.update_pi_agg(np.array([p_0, p_1]))
def compute_lambda_msg_to_parent(self, parent_k):
- if np.array_equal(self.lambda_agg, np.ones([self.cardinality])):
+ """
+ Compute lambda_msg to parent. Sidestep explicit factor product and
+ marginalization.
+
+ Args
+ parent_k: string or int,
+ the label_id of the parent receiving the lambda_msg
+ Returns
+ np.array of ndim 1 and shape (cardinality of parent_k,)
+ """
+ if np.array_equal(self.lambda_agg.values, np.ones([self.cardinality])):
return np.ones([self.cardinality])
else:
# TODO: cleanup this validation
_ = self.validate_and_return_msgs_received_for_msg_type(MessageType.PI)
- p1_excluding_k = [p[1] for par_id, p in self.pi_received_msgs.items() if par_id != parent_k]
+ p1_excluding_k = [p.get_value_for_state_vector({p.variables[0]: 'True'})
+ for par_id, p in self.pi_received_msgs.items() if par_id != parent_k]
p1_product = reduce(lambda x, y: x*y, p1_excluding_k, 1)
- lambda_0 = self.lambda_agg[0]
- lambda_1 = self.lambda_agg[0] + (self.lambda_agg[1] - self.lambda_agg[0])*p1_product
+
+ lambda_agg_0 = self.lambda_agg.get_value_for_state_vector({self.label_id: 'False'})
+ lambda_agg_1 = self.lambda_agg.get_value_for_state_vector({self.label_id: 'True'})
+
+ lambda_0 = lambda_agg_0
+ lambda_1 = lambda_agg_0 + (lambda_agg_1 - lambda_agg_0)*p1_product
lambda_msg = np.array([lambda_0, lambda_1])
if not any(lambda_msg):
raise InvalidLambdaMsgToParent
diff --git a/beliefs/utils/math_helper.py b/beliefs/utils/math_helper.py
index a25ea68..12325e1 100644
--- a/beliefs/utils/math_helper.py
+++ b/beliefs/utils/math_helper.py
@@ -1,10 +1,16 @@
-"""Random math utils."""
+"""Math utils"""
def is_kronecker_delta(vector):
- """Returns True if vector is a kronecker delta vector, False otherwise.
- Specific evidence ('YES' or 'NO') is a kronecker delta vector, whereas
- virtual evidence ('MAYBE') is not.
+ """
+ Check if vector is a kronecker delta.
+
+ Args:
+ vector: iterable of numbers
+ Returns:
+ bool, True if vector is a kronecker delta vector, False otherwise.
+ In belief propagation, specific evidence (variable is directly observed)
+ is a kronecker delta vector, but virtual evidence is not.
"""
count = 0
for x in vector:
diff --git a/beliefs/utils/random_variables.py b/beliefs/utils/random_variables.py
index 1a0b0f7..cad07aa 100644
--- a/beliefs/utils/random_variables.py
+++ b/beliefs/utils/random_variables.py
@@ -1,3 +1,4 @@
+"""Utilities for working with models and random variables."""
def get_reachable_observed_variables_for_inferred_variables(model, observed=set()):
@@ -6,12 +7,16 @@ def get_reachable_observed_variables_for_inferred_variables(model, observed=set(
("reachable observed variables") that influenced the beliefs of variables inferred
to be in a definite state.
- INPUT
- model: instance of BayesianModel class or subclass
- observed: set of labels (strings) corresponding to vars pinned to definite
- state during inference.
- RETURNS
- dict, of form key - source label (a string), value - a list of strings
+ Args
+ model: instance of BayesianModel class or subclass
+ observed: set,
+ set of labels (strings) corresponding to variables pinned to a definite
+ state during inference.
+ Returns
+ dict,
+ key, value pairs {source_label_id: reachable_observed_vars}, where
+ source_label_id is an int or string, and reachable_observed_vars is a list
+ of label_ids
"""
if not observed:
return {}
diff --git a/conda-build/meta.yaml b/conda-build/meta.yaml
index b7d065b..2d2d8eb 100644
--- a/conda-build/meta.yaml
+++ b/conda-build/meta.yaml
@@ -23,7 +23,7 @@ requirements:
- pyyaml
- pytest
- numpy
- - networkx >=1.11
+ - networkx >=2.0
anaconda_upload: True
diff --git a/examples/compare_pgmpy_belief_propagation.ipynb b/examples/compare_pgmpy_belief_propagation.ipynb
new file mode 100644
index 0000000..c886aea
--- /dev/null
+++ b/examples/compare_pgmpy_belief_propagation.ipynb
@@ -0,0 +1,990 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Run and compare belief propagation in pgmpy for a Bayesian Network\n",
+ "The purpose of this tutorial is to compare the results of inference from implementations of belief propagation by the `pgmpy` and `beliefs` libraries.\n",
+ "\n",
+ "We consider 4 different networks:\n",
+ " 1. Simple Model - 14 nodes, each with Bernoulli OR CPDs\n",
+ " 2. Custom CPD model - 4 nodes, where 2 of the nodes have custom CPDs (not OR or AND)\n",
+ " 3. Mixed AND and OR CPD model - 6 nodes, 5 with Bernoulli OR CPDs and 1 with Bernoulli AND CPD \n",
+ " 4. Many parents model* - 18 parent nodes sharing a single child node with a Bernoulli OR CPD\n",
+ "\n",
+ "For each network, we initialize a Bayesian model from a list of directed edges (tuples), visualize the network, then run inference using `pgmpy`. All models are polytrees -- only one path exists between any two nodes in the graph.\n",
+ "\n",
+ "*Note: We don't actually run inference with `pgmpy` on the \"Many parents model\", where one child node has 18 parents. The algorithm never converges because of the amount of memory (exponential in the number of parents of a node) required to work with explicit tabular conditional probability distributions (CPDs). In contrast, `beliefs` implements shortcuts (see [BernoulliAndNode and BernoulliOrNode)](https://github.com/drivergroup/beliefs/blob/generic_discrete_factor/beliefs/models/belief_update_node_model.py) for computing the marginal probabilities of nodes with deterministic CPDs like ANDs and ORs.\n",
+ "\n",
+ "Finally, note that `pgmpy` does not provide the ability to provide virtual evidence, i.e. to modify the likelihood of an unobserved node in the graph based on evidence that does not correspond to any nodes in the graph (see the method [`_run_belief_propagation`](https://github.com/drivergroup/beliefs/blob/13053909cb47464a800f708c0954f7def8a0deb4/beliefs/inference/belief_propagation.py)), so comparisons with `beliefs` involving inference with virtual evidence are not available.\n",
+ "\n",
+ "\n",
+ "### Requirements\n",
+ "Note that `pgmpy` is pinned to `networkx` v1.11, whereas `beliefs` is written for v2.0, which is incompatible with `pgmpy`. We therefore can't use `pgmpy` and `beliefs` in the same environment. Requirements for running this notebook are:\n",
+ "- pgmpy\n",
+ "- numpy\n",
+ "- networkx==1.11\n",
+ "- matplotlib"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "import networkx as nx\n",
+ "import warnings\n",
+ "warnings.filterwarnings(\"ignore\") # draw_networkx triggers Matplotlib deprecated warnings\n",
+ "\n",
+ "from pgmpy.models import BayesianModel\n",
+ "from pgmpy.factors.discrete import TabularCPD\n",
+ "from pgmpy.inference import BeliefPropagation\n",
+ "\n",
+ "import matplotlib as mpl\n",
+ "%matplotlib inline\n",
+ "mpl.rcParams['figure.figsize'] = (8, 8)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# I. Simple model\n",
+ "- 14 nodes, each corresponding to a Bernoulli random variable (a variable that only has two possible states: e.g. False/True or 0/1).\n",
+ "- Each node is a Bernoulli random variable, with an 'OR' conditional probability distribution, i.e. if at least one of the node's parents is True, then the node is True, otherwise False. Nodes without parents default to a flat prior probability.\n",
+ "- The same model is defined as `simple_model` in [test_belief_propagation.py](https://github.com/drivergroup/beliefs/blob/master/tests/test_belief_propagation.py)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1. Initialize Bayesian Model and visualize network"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Nodes in the graph: ['14', '10', '11', '1', '4', '2', '6', '13', '7', '5', '12', '9', 'x', '8', '3']\n"
+ ]
+ }
+ ],
+ "source": [
+ "simple_edges = [('1', '3'), ('2', '3'), ('3', '5'), ('4', '5'), ('5', '10'), ('5', '9'), ('6', '8'), ('7', '8'), \n",
+ " ('8', '9'), ('9', '11'), ('9', 'x'), ('14', 'x'), ('x', '12'), ('x', '13')]\n",
+ "simple_model = BayesianModel(simple_edges)\n",
+ "print(\"Nodes in the graph: \", simple_model.nodes())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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\nc+6c6wKXlFJ/KKWige+A5HMHegPzlVJhAEqpUMuGaXuSnB1L5cqVeeedd5gwYYLJz23xfbh8+TIN\nGzYkJCSEkJAQWrVqZdXziYw7evQot2/ftupiItmpMlhy9evXJzAw0Kzk2r59e3bu3EnBggWNti9f\nvpy33nqLyMhIa4WZLZiTnJ8HkpZ0+fvJtqSqAdU0TTukadpRTdNeN9WQpmkfapoWpGla0O3btzMX\nsY1IcnY8Y8eOZcOGDSYLJFj7+/Dtt99Sv359unfvzrZt2yhZsqTVziUyz9/fn0GDBlltMYrY2Fh+\n++23bFuCtWjRopQrV46TJ0+atX/Tpk3Zu3dvirrkmzdvxtvbO1ML1OQU5iRnUyNQkj8UyA1UBZoC\n3YBlmqalGOqslFqilPJUSnlauki8pUlydjzFixdnxIgR+Pn5pfjMWt+Hhw8f8vbbbzN58mR+/fVX\nhgwZIoO+7NT169fZuXMnPj4+VjvHmTNnKFu2LIUKFbLaOazNnOfOSXl4eHDw4EHKlStntH3v3r24\nublx/vx5S4eYLZiTnP8GyiZ5Xwb4x8Q+W5RSMUqpK8B5DMk6W1JKyVQqBzVo0CB+++039u/fb7Td\nGms5Hz16FHd3d5555hmOHz9OrVq1LNq+sKxFixbRvXt3q14XAgICsm2XdgJznzsnVa1aNQ4dOkSN\nGkbDmbhy5Qo1a9YkICDAkiFmC+Yk50CgqqZpFTVNywt0BX5Mts9moBmApmmuGLq5/7BkoLYUHh5O\nrly5cHZ21jsUYWPOzs58/vnn+Pr6Eh8fn7jdknfOcXFxfP7557Rr144ZM2awePFiXFxcLNK2sI7I\nyEiWLFlitelTCbLzSO0EqRUjSU+ZMmXYv38/9erVM9oeHR2Nl5cXO3bssFSI2UK6yVkpFQsMBHYB\nvwPrlVJnNE2bqGnaG0922wXc1TTtLLAHGK6UumutoK1NurQdW9euXYmPj2fdunXA0+Uikw9cyYzr\n16/TsmVLdu3aRVBQUIqyhsI+fffdd3h4eFh9SltOSM5VqlQhIiIiQ0uyJihevDi//PJLijWh4+Li\naNOmTbrFgnISs+Y5K6W2K6WqKaUqK6UmP9n2qVLqxyf/rpRSHyulaiilXlFKfWfNoK1NkrNjc3Jy\nYubMmYwePZqoqCiLLRe5efNmPDw8aN68Obt376Zs2bLpHyR0p5Rizpw5VlmzOanw8HDOnz+f7R9v\n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\nDRs2ZO/evZQvX95ibYeFhZks6WmuadOmMW3aNO7evWuxmETWhISEcPnyZTp27KhbDI7apf3nn3/S\noEEDnn32WXx8fChVqpTeIdk3c2+xLf3StVs7qSwUTT906JAqW7as8vX1VdHR0ers2bOqb9++Kn/+\n/Gl2eb/44otq4cKF6tGjRzr8gXOG8PBwNXDgQFW+fHl14MABq5zjyJEjqm7dullqo2/fvmro0KEW\nikhklY+Pj5o8ebKuMXTo0EGtXbtW1xhsbf/+/ap06dJq9uzZatmyZapHjx56h6QLMtCtLck5QWio\nUtOnK/XOO0q1bWv45/Tphu1puHPnjmrdurWqX7+++uuvv5RSSt29e1dNnTpVlSlTJs0kXbRoUeXn\n56euXr1qiz9hjnH69Gn18ssvq7feekvdu3fPaufZvn27atWqVZbauHnzpipevLi6dOmShaISmRUa\nGqqKFCmibt++rWscZcqUcajvw5IlS1TJkiXVrl27lFJKnTt3TlWoUEHnqPQhydnG4uLi1PTp01XJ\nkiXV1q1bE7dHR0er7777TtWvXz/NJJ0rVy7VpUsXdeTIER3/FPYvPj5eLViwQLm6uqqvvvoqUwO1\nMiK95SLN9dlnn6m33nrLAhGJrJg0aZJ6//33dY3hn3/+UcWKFbP6d9ceREdHq4EDB6oXXnhBnT9/\nPnF7fHy8Kl68uLp+/bqO0elDkrNODh48aNTNndTRo0dVt27dVO7cudNM1HXr1lVr1qxJcbyju337\ntmrXrp3y8PBQ586ds8k5582bp/r165fldh4/fqyef/55+fGlo+joaPXcc8+pkJAQXePYsmVLlntj\nsoM7d+6o5s2bK29vb5PrVbdt21Zt2LBBh8j0lZHk7LgDwqygQYMGnDhxgrNnz9KkSROuXr2a+Fm9\nevVYs2YNV65cYdSoUakONAoICKB79+5UrFiRKVOmyGAiYPfu3bi5uVGlShUOHz7MCy+8YJPzZmaO\nsykuLi5MnDgRX19fwy9iYXMbN26katWq1Kplquaf7ThC8ZEzZ85Qt25dateuzdatW02uV52RFaoc\nlrlZ3NKvnHjnnCBhzm3ybu6kHj9+rBYvXqyqV6+e5p20s7Oz+vDDD9Xp06dt/KfQX/T/2TvvsCiu\nLoy/Q1FaDFIVQUDFiqDUgIgFC6KiEiv2xBLsJnY0RkE0iA0DlhhEY0nssYEKFhBpgkpsWEBsUSyo\n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\n448r7Hc4HKxZs4bnnnuuWq28ZcuWVehmbt++vds0nb4KDQ1l/vz5fPjhh0RHR1c4drmbe8GCBT53\nc4eFhfH888+TkpLil65ykPFmYX+SnIXP4uLi+Oc//0lwcDDdu3cnJyfH6pCqZMeW87Zt20hMTOTY\nsWMV9rdo0YKMjAwefvjhapWfn5/PihUrKux78sknUVWt9+ul/v37c/DgQbcVsJxOJzNnzuSBBx7w\nuZt7+PDhlJaWut217gutNfv375eWs7A1Sc6iWhwOB2lpaTzyyCN07dqV7du3Wx3SVdkpOWutSU1N\nZcCAAW6Pp3Xr1u3KuKjXTp+GhQth1CgYOJAf7rmHCRcvElN2OCYmhhEjRlT/C/AgNjaWXbt2eVy8\nYtu2bcTHx5OZmel1uUFBQaSmpvL0009z6dKlasV46tQptNaVLgoihC2YfSDa35tMQlL37NixQzdr\n1kwvXry4yoUXrGKX5SILCwv1mDFjPE4skpyc7NtiHPv3G0uGhoUZW7npWvNBF4D+APSy5GT/f0Ee\nbNmyRUdHR7t9fcHBwXrBggU+TVrywAMP6MWLF1crrg8++MDjXOJC1DRkhjBhlZycHB0fH69HjBhR\no1Mv+sp1IYeffvop4DH8+OOPOjEx0eNMW0uWLPHtHxuTC52UgHY6HMb5AZCbm6u7devm8Z+QAQMG\n6LNnz3pV3ldffaVjYmL0uXPnfI5pxowZes6cOT5fL4SvvEnO0q0t/CouLo7MzEyCgoJsNw6ttba8\nWzs7O5uEhAT27dtXYX9UVBRbt27lj3/8o/djwa+8AikpUFBgpOCrCAaCCguN8195xcvovRcbG8vu\n3buZNm2a27EPP/yQDh06sHfvXtPl3Xbbbfzud7/j+eef9zkmuVNb1Apms7i/N2k5122lpaX6hRde\n0M2aNdPbt2+3OhyttftykQ6HI6Cf/9577+mwsDC3FuQtt9yiv/nmG98K3b9f6/Bw/RLoTqAbgH6k\nXEv5M9B9QDcGHQN6KOgfLx8PD7+yIlkgpKen68aNG3vs5l64cKHpbu6ffvpJR0dH6+PHj3sdg9Pp\n1FFRUV4tFCKEvyAtZ2E1pRSTJ09m1apVjBgxghdeeMEYR7GQa6vZ9bGfmlJaWsqsWbMYOXIkRUVF\nFY7169ePrKws32eqmjcPCgu5HngGGOdy+ALwByAH+B64Bhh7+WBhoXF9gAwcOJCDBw/StWvXCvud\n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\n",
+ "text/plain": [
+ "<matplotlib.figure.Figure at 0x7f6f543439e8>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "nx.draw_networkx(simple_model, pos=nx.spring_layout(simple_model, k=0.12, iterations=20))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 2. Define and associate CPDs for each node in the Bayesian Model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Define a helper function for generating the values of an OR conditional probability distribution (CPD) for a variable given the number of parents."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def get_tabular_OR_cpd_values(n_parents):\n",
+ " if n_parents == 0:\n",
+ " values = [[0.5], [0.5]]\n",
+ " else:\n",
+ " values = (np.array([1.,] + [0.]*(2**n_parents-1) + [0.,] + [1.]*(2**n_parents-1))\n",
+ " .reshape(2, 2**n_parents)\n",
+ " .tolist())\n",
+ " return values"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Example OR CPD values for Bernoulli variable with 2 parents: [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 1.0, 1.0]]\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(\"Example OR CPD values for Bernoulli variable with 2 parents: \", get_tabular_OR_cpd_values(2))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "simple_model_cpds = {}\n",
+ "for node in simple_model.nodes():\n",
+ " parents = simple_model.predecessors(node)\n",
+ " n_parents = len(parents)\n",
+ " cpd_values = get_tabular_OR_cpd_values(n_parents)\n",
+ " simple_model_cpds[node] = TabularCPD(variable=node,\n",
+ " variable_card=2,\n",
+ " values=cpd_values,\n",
+ " evidence=parents,\n",
+ " evidence_card=[2]*n_parents)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPD for variable x, with parents '9' and '14'\n",
+ "╒═════╤══════╤══════╤══════╤══════╕\n",
+ "│ 9 │ 9_0 │ 9_0 │ 9_1 │ 9_1 │\n",
+ "├─────┼──────┼──────┼──────┼──────┤\n",
+ "│ 14 │ 14_0 │ 14_1 │ 14_0 │ 14_1 │\n",
+ "├─────┼──────┼──────┼──────┼──────┤\n",
+ "│ x_0 │ 1.0 │ 0.0 │ 0.0 │ 0.0 │\n",
+ "├─────┼──────┼──────┼──────┼──────┤\n",
+ "│ x_1 │ 0.0 │ 1.0 │ 1.0 │ 1.0 │\n",
+ "╘═════╧══════╧══════╧══════╧══════╛\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(\"CPD for variable x, with parents '9' and '14'\")\n",
+ "print(simple_model_cpds['x'])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CPD for variable '7', with no parents\n",
+ "╒═════╤═════╕\n",
+ "│ 7_0 │ 0.5 │\n",
+ "├─────┼─────┤\n",
+ "│ 7_1 │ 0.5 │\n",
+ "╘═════╧═════╛\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(\"CPD for variable '7', with no parents\")\n",
+ "print(simple_model_cpds['7'])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Associate the CPDs we just defined per node with the Bayesian model\n",
+ "simple_model.add_cpds(*simple_model_cpds.values())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Is the model valid?\n",
+ "simple_model.check_model()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 3. Run inference using pgmpy's implementation of the belief propagation algorithm"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# instantiate the inference model\n",
+ "infer_simple_model = BeliefPropagation(simple_model)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Define four cases for which we want to run inference. Each case corresponds to a set of observations of variables, i.e. a set of evidence.\n",
+ "- 'test' is the name of the corresponding beliefs tests in [test_belief_propagation.py](https://github.com/drivergroup/beliefs/blob/master/tests/test_belief_propagation.py)\n",
+ "- 'evidence' is the set of variables and their observed values for that case\n",
+ "- 'expected' is the expected outcome of the inference, which matches the expected outcomes in test_belief_propagation.py"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cases = [{'test': 'test_no_evidence_simple_model', \n",
+ " 'evidence': {},\n",
+ " 'expected': {'x': 0.984375, '14': 0.5, '7': 0.5, '2': 0.5, '3': 0.75, '13': 0.984375,\n",
+ " '6': 0.5, '4': 0.5, '8': 0.75, '10': 0.875, '1': 0.5, '9': 0.96875,\n",
+ " '12': 0.984375, '5': 0.875, '11': 0.96875}},\n",
+ " {'test': 'test_positive_evidence_node_13',\n",
+ " 'evidence': {'13': 1},\n",
+ " 'expected': {'6': 0.50793650793650791, '3': 0.76190476190476186, \n",
+ " '9': 0.98412698412698407, '8': 0.76190476190476186, \n",
+ " 'x': 1.0, '4': 0.50793650793650791, '11': 0.98412698412698407,\n",
+ " '1': 0.50793650793650791, '5': 0.88888888888888884, \n",
+ " '2': 0.50793650793650791, '12': 1.0, '14': 0.50793650793650791, \n",
+ " '13': 1, '10': 0.88888888888888884, '7': 0.50793650793650791}},\n",
+ " {'test': 'test_positive_evidence_node_5',\n",
+ " 'evidence': {'5': 1},\n",
+ " 'expected': {'1': 0.5714285714285714, '5': 1, '3': 0.8571428571428571, '10': 1.0, \n",
+ " '8': 0.75, '2': 0.5714285714285714, '4': 0.5714285714285714, '6': 0.5, \n",
+ " '7': 0.5, '14': 0.5, '12': 1.0, '13': 1.0, '11': 1.0, '9': 1.0, \n",
+ " 'x': 1.0}},\n",
+ " {'test': 'test_positive_evidence_node_5_negative_evidence_node_14',\n",
+ " 'evidence': {'5': 1, '14': 0},\n",
+ " 'expected': {'6': 0.5, '7': 0.5, '9': 1.0, '3': 0.8571428571428571, \n",
+ " '1': 0.57142857142857151, '12': 1.0, 'x': 1.0, '11': 1.0, '14': 0.0, \n",
+ " '2': 0.57142857142857151, '4': 0.5714285714285714, '5': 1.0, '10': 1.0, \n",
+ " '13': 1.0, '8': 0.75}}]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Define helper functions for extracting and comparing inference results."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def get_probability_of_True(query):\n",
+ " \"\"\"Return the marginal probability of a Bernoulli variable being True\n",
+ " \n",
+ " Args\n",
+ " query: a result returned by inference, \n",
+ " e.g. query = belief_propagation.query(variables, evidence)\n",
+ " Returns\n",
+ " infer: dict,\n",
+ " key, value pairs of {var: P(var is True)}\n",
+ " \"\"\"\n",
+ " infer = {}\n",
+ " for variable, factor in query.items():\n",
+ " infer[variable] = factor.values[1]\n",
+ " return infer"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def compare_expected_to_observed(expected, observed):\n",
+ " \"\"\"Compare inference results between expected and observed, where both are dictionaries\n",
+ " of key, value pairs of {var: P(var is True)}\n",
+ " \"\"\"\n",
+ " assert set(expected.keys()) == set(observed.keys()), set(observed.keys())-set(expected.keys())\n",
+ " for node in expected:\n",
+ " assert np.allclose(expected[node], observed[node], rtol=1e-05, atol=1e-10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Run inference for each test case, then compare to expected results"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Test case: test_no_evidence_simple_model\n",
+ "Marginal probability of True: {'14': 0.5, '10': 0.87500000000000011, '11': 0.96875000000000011, '1': 0.5, '4': 0.50000000000000011, '2': 0.50000000000000011, '6': 0.5, '13': 0.984375, '9': 0.96875, '5': 0.875, '12': 0.984375, '7': 0.5, 'x': 0.984375, '8': 0.75, '3': 0.75000000000000011}\n",
+ "\n",
+ "Test case: test_positive_evidence_node_13\n",
+ "Marginal probability of True: {'14': 0.50793650793650791, '10': 0.88888888888888895, '11': 0.98412698412698418, '1': 0.50793650793650791, '4': 0.50793650793650802, '2': 0.50793650793650791, '6': 0.50793650793650791, '7': 0.50793650793650791, '5': 0.88888888888888895, '12': 1.0, '9': 0.98412698412698418, 'x': 1.0, '8': 0.76190476190476186, '3': 0.76190476190476197}\n",
+ "\n",
+ "Test case: test_positive_evidence_node_5\n",
+ "Marginal probability of True: {'14': 0.5, '10': 1.0, '11': 1.0, '1': 0.5714285714285714, '4': 0.5714285714285714, '2': 0.5714285714285714, '6': 0.5, '13': 1.0, '9': 1.0, '12': 1.0, '7': 0.5, 'x': 1.0, '8': 0.74999999999999989, '3': 0.85714285714285721}\n",
+ "\n",
+ "Test case: test_positive_evidence_node_5_negative_evidence_node_14\n",
+ "Marginal probability of True: {'10': 1.0, '11': 1.0, '1': 0.5714285714285714, '4': 0.5714285714285714, '2': 0.5714285714285714, '6': 0.5, '13': 1.0, '9': 1.0, '12': 1.0, '7': 0.5, 'x': 1.0, '8': 0.74999999999999989, '3': 0.85714285714285721}\n"
+ ]
+ }
+ ],
+ "source": [
+ "for test_case in cases:\n",
+ " print(\"\\nTest case: \", test_case['test'])\n",
+ " \n",
+ " # pgmpy doesn't allow you to include evidence vars in the set of query variables:\n",
+ " evidence_vars = test_case['evidence'].keys()\n",
+ " variables = set(simple_model.nodes()) - set(evidence_vars)\n",
+ " [test_case['expected'].pop(var) for var in evidence_vars]\n",
+ " \n",
+ " query = infer_simple_model.query(variables=variables, evidence=test_case['evidence'])\n",
+ " results = get_probability_of_True(query)\n",
+ " print(\"Marginal probability of True: \", results)\n",
+ " \n",
+ " compare_expected_to_observed(test_case['expected'], results)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Special case: conflicting evidence\n",
+ "Notice how in the case of conflicting evidence, inference with pgmpy returns `nan`s, whereas beliefs throws a `ConflictingEvidenceError`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Test case: test_conflicting_evidence\n",
+ "Results: {'14': nan, '10': nan, '11': nan, '1': nan, '4': nan, '2': nan, '6': nan, '13': nan, '9': nan, '12': nan, '7': nan, '8': nan, '3': nan}\n"
+ ]
+ }
+ ],
+ "source": [
+ "query = infer_simple_model.query(variables=set(simple_model.nodes()) - {'x', '5'}, evidence={'x': 0, '5': 1})\n",
+ "results = get_probability_of_True(query)\n",
+ "\n",
+ "print(\"Test case: test_conflicting_evidence\")\n",
+ "print(\"Results: \", results)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# II. Custom CPD model\n",
+ "- 4 nodes\n",
+ "- Y-shaped model, with parents 'u' and 'v' as Bernoulli variables, 'x' a node with cardinality 3 and custom CPD, 'y' a node with cardinality 2 and custom CPD\n",
+ "- The same model is defined as `custom_cpd_model` in [test_belief_propagation.py](https://github.com/drivergroup/beliefs/blob/master/tests/test_belief_propagation.py)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1. Initialize Bayesian Model and visualize network"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Nodes in the graph: ['y', 'v', 'x', 'u']\n"
+ ]
+ }
+ ],
+ "source": [
+ "custom_cpd_model = BayesianModel([('u', 'x'), ('v', 'x'), ('x', 'y')])\n",
+ "print(\"Nodes in the graph: \", custom_cpd_model.nodes())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAecAAAHVCAYAAADLvzPyAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4xLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvAOZPmwAAIABJREFUeJzs3XmczfXix/HXZwZjLIloG1uJ4mbL\niBahnfuzDNkZpBkzGEuNoigUSihLhiHLZMsy45aUpOVKlBGNpasQ2Yok5Y5tZr6/P0auwzCDM/M5\ny/v5eHjcOZ/zPd/zNlfzns/3fD/fr3EcBxEREfEcAbYDiIiIiCuVs4iIiIdROYuIiHgYlbOIiIiH\nUTmLiIh4GJWziIiIh1E5i4iIeBiVs4iIiIdROYuIiHiYfLbeuGTJkk758uVtvb2IiEieWr9+/W+O\n45TKybbWyrl8+fIkJyfbensREZE8ZYzZndNtdVhbRETEw6icRUREPIzKWURExMOonEVERDyMyllE\nRMTDqJxFREQ8jMpZRETEw6icRUREPIzKWURExMOonEVERDyMyllERMTDqJxFREQ8jMpZRETEw6ic\nRUREPIzKWURExMOonEVERDyMyllERMTDqJxFREQ8jMpZRETEw6icRUREPIzKWURExMOonEVERDyM\nyllERMTDqJxFREQ8jMpZRETEw6icRUREPIzKWURExMPksx1ARES8zMGDMHMmpKTA0aNQrBhUqwZd\nu0KpUrbT+QSVs4iI5My6dTByJHz4YebjEyf+91xiIrz0EjRqBAMHQu3adjL6CB3WFhGR7MXFQYMG\nsGRJZimfW8wAx49nji1ZkrldXJyNlD5DM2cREbm0uDiIjYXU1Oy3dZzM7WJjMx9HR+duNh+lmbOI\niFzcunU5L+Zz/V3Qycm5k8vHZVvOxpjpxpiDxpjNF3neGGPGG2O2G2NSjDF3uT+miIhYMXJk5iHr\nK3H8eObr5bLlZOY8E3j8Es83Aiqe+RMJ6IMGERFfcPBg5slfjuMybIDt5zzuAgzK6vWOA8uWwaFD\nuRbRV2Vbzo7j/Bv4/RKbNAMSnExrgWuNMTe5K6CIiFgyc+bV78MY9+zHz7jjM+cQYM85j/eeGRMR\nEW+WknLhWdmX6/hx2LTJPXn8iDvK2WQx5mQxhjEm0hiTbIxJPqTDHCIinu3oUffs58gR9+zHj7ij\n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\nTpZOnjxJZGSky1ilSpUYOHCgpUQi4i4qZ5GLeOGFFyhZsiT9+vWzHSVLo0aN4vvvv3cZi4+Pp2DB\ngpYSiYi7qJxFLiIgIICEhARWrlzJzJkzbcdx8cMPP1xw28snn3yS+vXrW0okIu6kE8JELqFYsWIk\nJSVRv3597rzzTkJDQ21HwnEcunfvzsmTJ8+OlSpVitdff91iKhFxJ82cRbJRuXJlJk+eTMuWLTl0\n6JDtOMycOZPPP//cZezNN9+kRIkSdgKJiNupnEVyoEWLFnTo0IG2bduSlpZmLcfBgweJjY11GXv0\n0Udp166dpUQikhtUziI59PLLL5M/f34GDBhgLcPTTz/N77//fvZxcHAwcXFxGGOsZRIR91M5i+RQ\nYGAgc+fOJTExkfnz5+f5+69YsYI5c+a4jA0ZMoRbb701z7OISO5SOYtchhIlSpCUlERMTAwpKSl5\n9r6pqalERUW5jFWrVs1jl3mJyNVROYtcpurVqzNu3DhatGjBkSNH8uQ9hw0bxs6dO88+NsYwdepU\n8ufPnyfvLyJ5S+UscgXat29PkyZN6NChA+np6bn6XikpKYwePdplrFevXtx99925+r4iYo/KWeQK\njRo1itTUVIYMGZJr75Genk5ERITLLwClS5e+4AIkIuJbVM4iVyh//vy8++67zJo1i3/961+58h5x\ncXF88803LmMTJ06kaNGiufJ+IuIZVM4iV+GGG25g0aJFREREsG3bNrfue+/evTz//PMuY2FhYTRr\n1syt7yMinkflLHKV7r77bkaMGEFYWBh//fWX2/YbExPjsr+iRYsyYcIEt+1fRDyXylnEDZ566inq\n1atHly5dcBznqveXlJTEkiVLXMZGjhxJSEjIVe9bRDyfylnETcaPH8/+/ft59dVXr2o/f/75JzEx\nMS5jdevWvWCds4j4Lt2VSsRNgoKCWLRoEbVr1+auu+7iscceu6L9vPDCC+zbt+/s43z58hEfH09g\nYKC7ooqIh9PMWcSNQkJCePfddwkPD3e5aEhOrV27lrfeestlrH///lStWtVdEUXEC6icRdysXr16\nDBo0iBYtWpCamprj150+fZrIyEiXz6wrVKjA4MGDcyOmiHgwlbNILujVqxfVqlUjIiIixyeIjR07\nlk2bNrmMTZ48meDg4NyIKCIeTJ85i+QCYwxTpkzhvvvuY9y4cfTt2/d/Tx48CDNnQkoKHD0KxYrx\n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\n",
+ "text/plain": [
+ "<matplotlib.figure.Figure at 0x7f6f202d0908>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "nx.draw_networkx(custom_cpd_model, pos=nx.spring_layout(custom_cpd_model, k=0.10, iterations=10))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 2. Define and associate CPDs for each node in the Bayesian Model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "custom_cpd_x = TabularCPD(variable='x',\n",
+ " variable_card=3,\n",
+ " values=[[0.2, 0, 0.3, 0.1],\n",
+ " [0.4, 1, 0.7, 0.2],\n",
+ " [0.4, 0, 0, 0.7]],\n",
+ " evidence=['u', 'v'],\n",
+ " evidence_card=[2, 2])\n",
+ "\n",
+ "custom_cpd_y = TabularCPD(variable='y',\n",
+ " variable_card=2,\n",
+ " evidence=['x'],\n",
+ " evidence_card=[3],\n",
+ " values=[[0.3, 0.1, 0],\n",
+ " [0.7, 0.9, 1]])\n",
+ "\n",
+ "cpd_u = TabularCPD(variable='u', variable_card=2, values=[[0.5], [0.5]])\n",
+ "cpd_v = TabularCPD(variable='v', variable_card=2, values=[[0.5], [0.5]])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "custom_cpd_model.add_cpds(custom_cpd_x, custom_cpd_y, cpd_u, cpd_v)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "custom_cpd_model.check_model()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 3. Run inference using pgmpy's implementation of the belief propagation algorithm"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# instantiate the inference model\n",
+ "infer_custom_cpd_model = BeliefPropagation(custom_cpd_model)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Two cases for which we want to run inference."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "cases = [{'test': 'test_no_evidence_custom_cpd_model', \n",
+ " 'evidence': {},\n",
+ " 'expected': {'x': [0.15, 0.575, 0.275],\n",
+ " 'v': [0.5, 0.5],\n",
+ " 'u': [0.5, 0.5],\n",
+ " 'y': [0.1025, 0.8975]}},\n",
+ " {'test': 'test_evidence_custom_cpd_model',\n",
+ " 'evidence': {'x': 1},\n",
+ " 'expected': {'x': [0., 1., 0.],\n",
+ " 'v': [0.47826087, 0.52173913],\n",
+ " 'u': [0.60869565, 0.39130435],\n",
+ " 'y': [0.1, 0.9]}}]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Test case: test_no_evidence_custom_cpd_model\n",
+ "Marginal probability of True: {'y': [0.10250000000000001, 0.8975000000000001], 'v': [0.5, 0.5], 'x': [0.15, 0.5750000000000001, 0.275], 'u': [0.5, 0.5]}\n",
+ "\n",
+ "Test case: test_evidence_custom_cpd_model\n",
+ "Marginal probability of True: {'y': [0.1, 0.9], 'v': [0.47826086956521735, 0.5217391304347826], 'u': [0.6086956521739131, 0.391304347826087]}\n"
+ ]
+ }
+ ],
+ "source": [
+ "for test_case in cases:\n",
+ " print(\"\\nTest case: \", test_case['test'])\n",
+ " \n",
+ " # pgmpy doesn't allow you to include evidence vars in the set of query variables:\n",
+ " evidence_vars = test_case['evidence'].keys()\n",
+ " variables = set(custom_cpd_model.nodes()) - set(evidence_vars)\n",
+ " [test_case['expected'].pop(var, None) for var in evidence_vars]\n",
+ " \n",
+ " query = infer_custom_cpd_model.query(variables=variables, evidence=test_case['evidence'])\n",
+ " results = {var: factor.values.tolist() for var, factor in query.items()}\n",
+ " print(\"Marginal probability of True: \", results)\n",
+ " \n",
+ " compare_expected_to_observed(test_case['expected'], results)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# III. Mixed AND and OR CPD model\n",
+ "- 6 nodes total\n",
+ "- All nodes are Bernoulli with OR CPDs, except 'z' has an AND CPD\n",
+ "- The same model is defined as `mixed_cpd_model` in [test_belief_propagation.py](https://github.com/drivergroup/beliefs/blob/master/tests/test_belief_propagation.py)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1. Initialize Bayesian Model and visualize network"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Nodes in the graph: ['v', 'z', 'y', 'w', 'x', 'u']\n"
+ ]
+ }
+ ],
+ "source": [
+ "mixed_cpd_model = BayesianModel([('u', 'x'), ('v', 'x'), ('x', 'y'), ('x', 'z'), ('w', 'z')])\n",
+ "print(\"Nodes in the graph: \", mixed_cpd_model.nodes())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ "<matplotlib.figure.Figure at 0x7f6f202ba438>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "nx.draw_networkx(mixed_cpd_model, pos=nx.spring_layout(mixed_cpd_model, k=0.10, iterations=10))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 2. Define and associate CPDs for each node in the Bayesian Model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "mixed_cpd_model_cpds = {}\n",
+ "\n",
+ "# First define CPDs for OR nodes\n",
+ "for node in {'y', 'u', 'v', 'w', 'x'}:\n",
+ " parents = mixed_cpd_model.predecessors(node)\n",
+ " n_parents = len(parents)\n",
+ " cpd_values = get_tabular_OR_cpd_values(n_parents)\n",
+ " mixed_cpd_model_cpds[node] = TabularCPD(variable=node,\n",
+ " variable_card=2,\n",
+ " values=cpd_values,\n",
+ " evidence=parents,\n",
+ " evidence_card=[2]*n_parents)\n",
+ " values = (np.array([1.,] + [0.]*(2**n_parents-1) + [0.,] + [1.]*(2**n_parents-1))\n",
+ " .reshape(2, 2**n_parents)\n",
+ " .tolist())\n",
+ "\n",
+ "# Define CPD for the single AND node\n",
+ "mixed_cpd_model_cpds['z'] = TabularCPD(variable='z',\n",
+ " variable_card=2,\n",
+ " values=[[1,1,1,0], [0,0,0,1]],\n",
+ " evidence=['w', 'x'],\n",
+ " evidence_card=[2, 2])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "mixed_cpd_model.add_cpds(*mixed_cpd_model_cpds.values())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 29,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "mixed_cpd_model.check_model()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 3. Run inference using pgmpy's implementation of the belief propagation algorithm"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "# instantiate the inference model\n",
+ "infer_mixed_cpd_model = BeliefPropagation(mixed_cpd_model)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "cases = [{'test': 'test_no_evidence_mixed_cpd_model', \n",
+ " 'evidence': {},\n",
+ " 'expected': {'x': 1-0.5**2, 'z': 0.5*(1-0.5**2), 'v': 0.5, 'u': 0.5, 'y': 0.75, 'w': 0.5}},\n",
+ " {'test': 'test_x_false_w_true_mixed_cpd_model',\n",
+ " 'evidence': {'x': 0, 'w': 1},\n",
+ " 'expected': {'u': 0, 'v': 0, 'y': 0, 'z': 0}},\n",
+ " {'test': 'test_x_true_w_true_mixed_cpd_model',\n",
+ " 'evidence': {'x': 1, 'w': 1},\n",
+ " 'expected': {'y': 1, 'z': 1, 'u': 2/3, 'v': 2/3}}]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Test case: test_no_evidence_mixed_cpd_model\n",
+ "Marginal probability of True: {'w': 0.5, 'z': 0.375, 'y': 0.75, 'x': 0.75, 'v': 0.5, 'u': 0.5}\n",
+ "\n",
+ "Test case: test_x_false_w_true_mixed_cpd_model\n",
+ "Marginal probability of True: {'y': 0.0, 'v': 0.0, 'z': 0.0, 'u': 0.0}\n",
+ "\n",
+ "Test case: test_x_true_w_true_mixed_cpd_model\n",
+ "Marginal probability of True: {'y': 1.0, 'v': 0.66666666666666663, 'z': 1.0, 'u': 0.66666666666666663}\n"
+ ]
+ }
+ ],
+ "source": [
+ "for test_case in cases:\n",
+ " print(\"\\nTest case: \", test_case['test'])\n",
+ " \n",
+ " # pgmpy doesn't allow you to include evidence vars in the set of query variables:\n",
+ " evidence_vars = test_case['evidence'].keys()\n",
+ " variables = set(mixed_cpd_model.nodes()) - set(evidence_vars)\n",
+ " [test_case['expected'].pop(var, None) for var in evidence_vars]\n",
+ " \n",
+ " query = infer_mixed_cpd_model.query(variables=variables, evidence=test_case['evidence'])\n",
+ " results = get_probability_of_True(query)\n",
+ "\n",
+ " print(\"Marginal probability of True: \", results)\n",
+ " \n",
+ " compare_expected_to_observed(test_case['expected'], results)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# IV. Many parents model\n",
+ "- 19 nodes: 18 parents sharing a single child, node '62.'\n",
+ "- All nodes are Bernoulli, and the child has an OR CPD\n",
+ "- The same model is defined as `many_parents_model` in [test_belief_propagation.py](https://github.com/drivergroup/beliefs/blob/master/tests/test_belief_propagation.py)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1. Initialize Bayesian Model and visualize network"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Nodes in the graph: ['100', '86', '106', '122', '94', '62', '104', '110', '96', '116', '112', '114', '108', '98', '102', '64', '80', '118', '70']\n"
+ ]
+ }
+ ],
+ "source": [
+ "many_parents_edges = [('96', '62'), ('80', '62'), ('98', '62'), ('100', '62'), ('86', '62'), \n",
+ " ('102', '62'), ('104', '62'), ('64', '62'), ('106', '62'), ('108', '62'), \n",
+ " ('110', '62'), ('112', '62'), ('114', '62'), ('116', '62'), ('118', '62'),\n",
+ " ('122', '62'), ('70', '62'), ('94', '62')]\n",
+ "many_parents_model = BayesianModel(many_parents_edges)\n",
+ "print(\"Nodes in the graph: \", many_parents_model.nodes())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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\nJydaW1uzbdu2nDx5Mvfu3ZunGzF86jx69Ig//vgjra2t2adPH164cEFnH3/99ZfK1o4Zj969e2e7\nBjk7IiIi1N6jfv366fkOMpmMQ4YMUbHRNEcrk8nYoEEDJdsvvvhCYx+kUilLliypZK9pd6zBgwcr\n2S1fvjzb9+jt7c2NGzeq7Wfz5s25YsWKbH18aghxFggMyLNnz7hnzx5OnjyZbdq0oZWVFZ2cnNi1\na1fOmTOHR44c+WQL++cV796947x581iuXDl+/vnn3Ldvn04RjcTERM6ZM0dle0bFUbx4cc6ePZuJ\niYk57uPz589ZvXp1Fd/Vq1dPDwUnJSWxWbNmKjaaxPHw4cMqtsePH9fYh1atWinZqhNTkuzRo4eS\n3ebNm7N8by9evKClpaXakfHevXtZtWpVMSWkBiHOAkEeIpPJePfuXW7atInff/89mzRpwuLFi9Pd\n3Z39+/fnkiVLeObMmVx90QvUk5SUxA0bNrBWrVqsVq0aV69erdPn/OTJE/bp00fjKLpixYpZZjln\nx5s3b9TW165YsWJ6MZGoqCg6OzsrXS9SpIha0ZXJZGzRooWSbdOmTTU+mEyYMEHJduTIkWrt2rRp\no2SnrlY4Ka/b7eXlxW7durFnz54q11NSUlilShX++eefWn5CnxZCnAWCfCYlJYWXL1/mb7/9xsGD\nB7NWrVo0MzNj3bp16e/vzzVr1vD69etihy49IZPJeOjQIbZt25YODg6cPXs23759m35dKpVmWZzm\nxIkT9PDw0CjS7du31xgSzo4PHz6oCCoAlitXjrdv3yZJXrlyRWUUb2Njo3ZDj5MnT6r42r9/v9p7\n79q1S8muYcOGau0yP0CEh4ertfvtt9/SbYyNjTl8+HCV6y1atBB5GRoQ4iwQFEDi4uL477//8pdf\nfmHv3r1ZqVIlWlhYsHnz5hwzZgy3bdvGiIgI8cWWS65cucIBAwbQysqKI0eO5IMHD7h3715+9tln\nDAkJ0bhkLjU1lcuWLaO1tbVagTYxMeG4ceNyVOM9Pj5ebTKanZ0dL126RJLcuXOn2hC4uqpm7du3\nV7Lz9PRU+3fz9OlTJbuiRYuqXZb22WefKdndvHlT7fvIvHZ82rRp6ddiY2Pp6OjIc+fO6fz5fCoI\ncRYICglv3rzhwYMHOXPmTHbq1IllypShnZ0dO3TowOnTp3Pfvn0676srkPPkyROOGzeONjY2Ssub\n7OzsOH36dI2f6+vXr+nv76+xypijoyNDQ0N1fohKTk5mr169VPyVKlWKJ0+eJEnOmDFD5Xrnzp1V\nRv0XLlxQsdu5c6fa+2Ze533+/HkVG3t7eyWbp0+fqtg8fPhQ5Z53795Nvz59+nT27t1bp88k17x8\nSc6dS/r5kT4+8p9z55IFdFc7Ic4CQSFFJpPx8ePH3LFjB8ePH8/PP/+cJUuWpKurK7/88kvOnz+f\n//zzj1iiogNHjx5VK7LFihWjv7+/xnD1pUuX2LRpU42h7qZNm6aPerUlNTVVJTMakG/RqKiIljk5\nC1BfQKRbt25KNtWqVVM7TeLr66tkt2zZMhWbzLtmqdt6c+bMmUo2Getvv3jxgtbW1nmyrzZJ8uxZ\n0teXLFZMfgD/f5iZyc/5+srtChBCnAWCjwipVMpbt25x3bp1HD58OOvXr09zc3PWqFGDX331FX/9\n9VdeuHDhk97EPiuCg4NZpEgRjSIrkUjo6+ubPnrNiEwmY2hoqMrWjYrDyMiI/v7+OlUZk8lkHD16\ntIovU1NT7tq1i7Gxsaxdu7bK9S1btij5uXHjBiUSiZKNumzszNtbZt53OSEhQSV8nzkqIJPJVELf\nGUXe39+fP/zwg9afQa5Ytow0NyclEmVRznxIJHI7NQ8j+YUQZ4HgIycpKYnnzp3jsmXLOHDgQFar\nVo3m5uZs2LAhR44cyQ0bNvC///4TO3Sl8fjxY44ZM0Zl3W/mo2HDhty+fbvKCDQmJobjx49X2UxC\ncVhbW3PZsmVaJ/jJZDKVkSggT7Jav349IyMjWbp0aaVrZmZmKuu6M+/bXLFiRZWHtMyRg6pVqypd\nf/HihdJ1W1tblf5m3uXKxMQk/YHk9u3btLW11VsZ1CxRCHNWopz5KEACLcRZIPgE+fDhA48fP86g\noCD26NGDzs7OtLS0ZOvWrTlhwgTu2rVLb9syFlbev3/PBQsW0MnJKUuRrlixIpcuXaoyfXDnzh2V\nZKyMR61atfjPP/9o3Z+FCxeq9RMcHMyTJ0+qPAyUK1dOqejNvXv3VKICmYt/vH//XmmELZFIlNbh\n3759W6l9pUqVVPo5fPhwJZuMpUO7dOnCoKAgrd9zjjl7VndhzijQBSBRTYizQCAgSb58+ZJ//vkn\np02bRm9vb9ra2tLR0ZGdO3fmzz//zL///ltpydGnQnJyMkNDQ9WGjzOPiCdPnqyyecOff/6ZZZWx\n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\n",
+ "text/plain": [
+ "<matplotlib.figure.Figure at 0x7f6f20280f28>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "nx.draw_networkx(many_parents_model, pos=nx.spring_layout(many_parents_model, k=0.10, iterations=10))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 2. Define and associate CPDs for each node in the Bayesian Model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "many_parents_model_cpds = {}\n",
+ "\n",
+ "for node in many_parents_model.nodes():\n",
+ " parents = many_parents_model.predecessors(node)\n",
+ " n_parents = len(parents)\n",
+ " cpd_values = get_tabular_OR_cpd_values(n_parents)\n",
+ " many_parents_model_cpds[node] = TabularCPD(variable=node,\n",
+ " variable_card=2,\n",
+ " values=cpd_values,\n",
+ " evidence=parents,\n",
+ " evidence_card=[2]*n_parents)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Associate the CPDs we just defined per node with the Bayesian model\n",
+ "many_parents_model.add_cpds(*many_parents_model_cpds.values())\n",
+ "many_parents_model.check_model()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 3. Run inference using pgmpy's implementation of the belief propagation algorithm\n",
+ "\n",
+ "We don't run inference for this model because pgmpy's implementation of belief propagation does not converge. The code is provided below (if you run it, be prepared to have to force-quit or wait for it to time out). \n",
+ "```\n",
+ "# instantiate the inference model\n",
+ "infer_many_parents_model = BeliefPropagation(many_parents_model)\n",
+ "\n",
+ "query = infer_many_parents_model.query(variables=many_parents_model.nodes(), evidence={})\n",
+ "```"
+ ]
+ }
+ ],
+ "metadata": {
+ "anaconda-cloud": {},
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.5.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/tests/test_belief_propagation.py b/tests/test_belief_propagation.py
index 7a77311..1b8c0ac 100644
--- a/tests/test_belief_propagation.py
+++ b/tests/test_belief_propagation.py
@@ -3,10 +3,12 @@ import pytest
from pytest import approx
from beliefs.inference.belief_propagation import BeliefPropagation, ConflictingEvidenceError
+from beliefs.factors.cpd import TabularCPD
from beliefs.models.belief_update_node_model import (
BeliefUpdateNodeModel,
BernoulliOrNode,
- BernoulliAndNode
+ BernoulliAndNode,
+ Node
)
@@ -89,6 +91,41 @@ def mixed_cpd_model(edges_five_nodes):
'w': w_node})
+@pytest.fixture(scope='function')
+def custom_cpd_model():
+ """
+ Y-shaped model, with parents ,'u' and 'v' as Or-nodes, 'x' a node with
+ cardinality 3 and custom CPD, 'y' a node with cardinality 2 and custom CPD.
+ """
+ custom_cpd_x = TabularCPD(variable='x',
+ variable_card=3,
+ parents=['u', 'v'],
+ parents_card=[2, 2],
+ values=[[0.2, 0, 0.3, 0.1],
+ [0.4, 1, 0.7, 0.2],
+ [0.4, 0, 0, 0.7]],
+ state_names={'x': ['lo', 'med', 'hi'],
+ 'u': ['False', 'True'],
+ 'v': ['False', 'True']})
+ custom_cpd_y = TabularCPD(variable='y',
+ variable_card=2,
+ parents=['x'],
+ parents_card=[3],
+ values=[[0.3, 0.1, 0],
+ [0.7, 0.9, 1]],
+ state_names={'x': ['lo', 'med', 'hi'],
+ 'y': ['False', 'True']})
+
+ u_node = BernoulliOrNode(label_id='u', children=['x'], parents=[])
+ v_node = BernoulliOrNode(label_id='v', children=['x'], parents=[])
+ x_node = Node(children=['y'], cpd=custom_cpd_x)
+ y_node = Node(children=[], cpd=custom_cpd_y)
+ return BeliefUpdateNodeModel(nodes_dict={'u': u_node,
+ 'v': v_node,
+ 'x': x_node,
+ 'y': y_node})
+
+
def get_label_mapped_to_positive_belief(query_result):
"""Return a dictionary mapping each label_id to the probability of
the label being True."""
@@ -355,3 +392,28 @@ def test_conflicting_evidence_and_model(many_parents_and_model):
with pytest.raises(ConflictingEvidenceError) as err:
query_result = infer.query(evidence={'62': np.array([0, 1]), '112': np.array([1, 0])})
assert "Can't run belief propagation with conflicting evidence" in str(err)
+
+
+#==============================================================================================
+# Model with two custom cpds
+
+
+def test_no_evidence_custom_cpd_model(custom_cpd_model):
+ expected = {'x': np.array([0.15, 0.575, 0.275]),
+ 'v': np.array([0.5, 0.5]),
+ 'u': np.array([0.5, 0.5]),
+ 'y': np.array([0.1025, 0.8975])}
+ infer = BeliefPropagation(custom_cpd_model)
+ query_result = infer.query(evidence={})
+ compare_dictionaries(expected, query_result)
+
+
+def test_evidence_custom_cpd_model(custom_cpd_model):
+ """Custom node is observed to be in 'med' state."""
+ expected = {'x': np.array([0., 1., 0.]),
+ 'u': np.array([0.60869565, 0.39130435]),
+ 'v': np.array([0.47826087, 0.52173913]),
+ 'y': np.array([0.1, 0.9])}
+ infer = BeliefPropagation(custom_cpd_model)
+ query_result = infer.query(evidence={'x': np.array([0, 1, 0])})
+ compare_dictionaries(expected, query_result)