aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCathy Yeh <cathy@driver.xyz>2017-11-20 17:05:37 -0800
committerCathy Yeh <cathy@driver.xyz>2017-11-21 13:18:34 -0800
commitd166e36eaf5803af035e444628c67701322b0eb6 (patch)
tree3e715d2ab34ce447222ccfa11bcde31065faae26
parent71e384a741e52f94882b14062a3dc10e5f391533 (diff)
downloadbeliefs-d166e36eaf5803af035e444628c67701322b0eb6.tar.gz
beliefs-d166e36eaf5803af035e444628c67701322b0eb6.tar.bz2
beliefs-d166e36eaf5803af035e444628c67701322b0eb6.zip
refactor msg passing methods to BeliefUpdateNodeModel from BayesianModel
-rw-r--r--beliefs/factors/BernoulliOrCPD.py13
-rw-r--r--beliefs/factors/CPD.py15
-rw-r--r--beliefs/inference/belief_propagation.py7
-rw-r--r--beliefs/models/BayesianModel.py76
-rw-r--r--beliefs/models/BernoulliOrModel.py17
-rw-r--r--beliefs/models/beliefupdate/BeliefUpdateNodeModel.py91
-rw-r--r--beliefs/models/beliefupdate/BernoulliOrNode.py (renamed from beliefs/types/BernoulliOrNode.py)2
-rw-r--r--beliefs/models/beliefupdate/Node.py (renamed from beliefs/types/Node.py)4
-rw-r--r--beliefs/types/__init__.py0
-rw-r--r--beliefs/utils/edges_helper.py2
-rw-r--r--tests/test_belief_propagation.py13
11 files changed, 142 insertions, 98 deletions
diff --git a/beliefs/factors/BernoulliOrCPD.py b/beliefs/factors/BernoulliOrCPD.py
index e4fcbf1..2c6a31e 100644
--- a/beliefs/factors/BernoulliOrCPD.py
+++ b/beliefs/factors/BernoulliOrCPD.py
@@ -10,17 +10,22 @@ class BernoulliOrCPD(TabularCPD):
If at least one of the variable's parents is True, then the variable
is True, and False otherwise.
"""
- def __init__(self, variable, parents=set()):
+ def __init__(self, variable, parents=[]):
+ """
+ Args:
+ variable: int or string
+ parents: optional, list of int and/or strings
+ """
super().__init__(variable=variable,
variable_card=2,
parents=parents,
parents_card=[2]*len(parents),
- values=None)
- self._values = None
+ values=[])
+ self._values = []
@property
def values(self):
- if self._values is None:
+ if not any(self._values):
self._values = self._build_kwise_values_array(len(self.variables))
self._values = self._values.reshape(self.cardinality)
return self._values
diff --git a/beliefs/factors/CPD.py b/beliefs/factors/CPD.py
index 8de47b3..a286aaa 100644
--- a/beliefs/factors/CPD.py
+++ b/beliefs/factors/CPD.py
@@ -6,7 +6,7 @@ class TabularCPD:
Defines the conditional probability table for a discrete variable
whose parents are also discrete.
- TODO: have this inherit from DiscreteFactor
+ TODO: have this inherit from DiscreteFactor implementing explicit factor methods
"""
def __init__(self, variable, variable_card,
parents=[], parents_card=[], values=[]):
@@ -22,9 +22,11 @@ class TabularCPD:
self.parents = parents
self.variables = [variable] + parents
self.cardinality = [variable_card] + parents_card
+ self._values = np.array(values)
- if values:
- self.values = np.array(values)
+ @property
+ def values(self):
+ return self._values
def get_values(self):
"""
@@ -34,3 +36,10 @@ class TabularCPD:
return self.values.reshape(1, np.prod(self.cardinality))
else:
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)
diff --git a/beliefs/inference/belief_propagation.py b/beliefs/inference/belief_propagation.py
index 37aa437..02f5595 100644
--- a/beliefs/inference/belief_propagation.py
+++ b/beliefs/inference/belief_propagation.py
@@ -1,7 +1,8 @@
import numpy as np
from collections import namedtuple
-from beliefs.types.Node import InvalidLambdaMsgToParent
+from beliefs.models.beliefupdate.Node import InvalidLambdaMsgToParent
+from beliefs.models.beliefupdate.BeliefUpdateNodeModel import BeliefUpdateNodeModel
from beliefs.utils.math_helper import is_kronecker_delta
@@ -22,10 +23,12 @@ class BeliefPropagation:
def __init__(self, model, inplace=True):
"""
Input:
- model: an instance of BayesianModel class or subclass
+ 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")
if inplace is False:
self.model = model.copy()
else:
diff --git a/beliefs/models/BayesianModel.py b/beliefs/models/BayesianModel.py
index 6257a57..b57f968 100644
--- a/beliefs/models/BayesianModel.py
+++ b/beliefs/models/BayesianModel.py
@@ -1,9 +1,7 @@
import copy
-import numpy as np
import networkx as nx
from beliefs.models.DirectedGraph import DirectedGraph
-from beliefs.utils.edges_helper import EdgesHelper
from beliefs.utils.math_helper import is_kronecker_delta
@@ -12,74 +10,30 @@ class BayesianModel(DirectedGraph):
Bayesian model stores nodes and edges described by conditional probability
distributions.
"""
- def __init__(self, edges, nodes_dict=None):
+ def __init__(self, edges=[], variables=[], cpds=[]):
"""
- Input:
- edges: list of edge tuples of form ('parent', 'child')
- nodes: (optional) dict
- a dict key, value pair as {label_id: instance_of_node_class_or_subclass}
- """
- if nodes_dict is not None:
- super().__init__(edges, nodes_dict.keys())
- else:
- super().__init__(edges)
- self.nodes_dict = nodes_dict
-
- @classmethod
- def from_node_class(cls, edges, node_class):
- """Automatically create all nodes from the same node class
+ Base class for Bayesian model.
Input:
- edges: list of edge tuples of form ('parent', 'child')
- node_class: (optional) the Node class or subclass from which to
- create all the nodes from edges.
- """
- nodes = cls.create_nodes(edges, node_class)
- return cls.__init__(edges=edges, nodes=nodes)
-
- @staticmethod
- def create_nodes(edges, node_class):
- """Returns list of Node instances created from edges using
- the default node_class"""
- edges_helper = EdgesHelper(edges)
- nodes = edges_helper.create_nodes_from_edges(node_class=node_class)
- label_to_node = dict()
- for node in nodes:
- label_to_node[node.label_id] = node
- return label_to_node
-
- def set_boundary_conditions(self):
- """
- 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.
- """
- for root in self.get_roots():
- self.nodes_dict[root].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])
-
- @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.
+ edges: (optional) list of edges,
+ tuples of form ('parent', 'child')
+ variables: (optional) list of str or int
+ labels for variables
+ cpds: (optional) list of CPDs
+ TabularCPD class or subclass
"""
- for node in self.nodes_dict.values():
- if not node.is_fully_initialized:
- return False
- return True
+ super().__init__()
+ super().add_edges_from(edges)
+ super().add_nodes_from(variables)
+ self.cpds = cpds
def copy(self):
"""
Returns a copy of the model.
"""
- copy_edges = list(self.edges()).copy()
- copy_nodes = copy.deepcopy(self.nodes_dict)
- copy_model = self.__class__(edges=copy_edges, nodes=copy_nodes)
+ 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
def get_variables_in_definite_state(self):
diff --git a/beliefs/models/BernoulliOrModel.py b/beliefs/models/BernoulliOrModel.py
deleted file mode 100644
index bf2b44c..0000000
--- a/beliefs/models/BernoulliOrModel.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from beliefs.models.BayesianModel import BayesianModel
-from beliefs.types.BernoulliOrNode import BernoulliOrNode
-
-
-class BernoulliOrModel(BayesianModel):
- """
- BernoulliOrModel stores node instances of BernoulliOrNodes (Bernoulli
- variables associated with an OR conditional probability distribution).
- """
- def __init__(self, edges, nodes=None):
- """
- Input:
- edges: an edge list, e.g. [(parent1, child1), (parent1, child2)]
- """
- if nodes is None:
- nodes = self.create_nodes(edges, node_class=BernoulliOrNode)
- super().__init__(edges, nodes_dict=nodes)
diff --git a/beliefs/models/beliefupdate/BeliefUpdateNodeModel.py b/beliefs/models/beliefupdate/BeliefUpdateNodeModel.py
new file mode 100644
index 0000000..d74eaa7
--- /dev/null
+++ b/beliefs/models/beliefupdate/BeliefUpdateNodeModel.py
@@ -0,0 +1,91 @@
+import copy
+import numpy as np
+
+from beliefs.models.BayesianModel import BayesianModel
+from beliefs.utils.edges_helper import EdgesHelper
+
+
+class BeliefUpdateNodeModel(BayesianModel):
+ """
+ A Bayesian model storing nodes (e.g. Node or BernoulliOrNode) implementing properties
+ and methods for Pearl's belief update algorithm.
+
+ ref: "Fusion, Propagation, and Structuring in Belief Networks"
+ Artificial Intelligence 29 (1986) 241-288
+
+ """
+ def __init__(self, nodes_dict):
+ """
+ Input:
+ 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()),
+ cpds=[node.cpd for node in nodes_dict.values()])
+
+ self.nodes_dict = nodes_dict
+
+ @classmethod
+ def from_edges(cls, edges, node_class):
+ """Create nodes 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.
+ """
+ edges_helper = EdgesHelper(edges)
+ nodes = edges_helper.create_nodes_from_edges(node_class)
+ nodes_dict = {node.label_id: node for node in nodes}
+ return cls(nodes_dict)
+
+ @staticmethod
+ def _get_edges_from_nodes(nodes):
+ """
+ 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
+ """
+ edges = set()
+ for node in nodes:
+ if node.parents:
+ edge_tuples = zip(node.parents, [node.label_id]*len(node.parents))
+ edges.update(edge_tuples)
+ return list(edges)
+
+ def set_boundary_conditions(self):
+ """
+ 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.
+ """
+ for root in self.get_roots():
+ self.nodes_dict[root].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])
+
+ @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.
+ """
+ for node in self.nodes_dict.values():
+ if not node.is_fully_initialized:
+ return False
+ return True
+
+ def copy(self):
+ """
+ Returns a copy of the model.
+ """
+ copy_nodes = copy.deepcopy(self.nodes_dict)
+ copy_model = self.__class__(nodes_dict=copy_nodes)
+ return copy_model
diff --git a/beliefs/types/BernoulliOrNode.py b/beliefs/models/beliefupdate/BernoulliOrNode.py
index ce497b9..3386275 100644
--- a/beliefs/types/BernoulliOrNode.py
+++ b/beliefs/models/beliefupdate/BernoulliOrNode.py
@@ -1,7 +1,7 @@
import numpy as np
from functools import reduce
-from beliefs.types.Node import (
+from beliefs.models.beliefupdate.Node import (
Node,
MessageType,
InvalidLambdaMsgToParent
diff --git a/beliefs/types/Node.py b/beliefs/models/beliefupdate/Node.py
index a496acf..daa2f14 100644
--- a/beliefs/types/Node.py
+++ b/beliefs/models/beliefupdate/Node.py
@@ -27,9 +27,9 @@ class Node:
cardinality,
cpd):
"""
- Input:
+ Args
label_id: str
- children: 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,
diff --git a/beliefs/types/__init__.py b/beliefs/types/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/beliefs/types/__init__.py
+++ /dev/null
diff --git a/beliefs/utils/edges_helper.py b/beliefs/utils/edges_helper.py
index c959a3b..130686c 100644
--- a/beliefs/utils/edges_helper.py
+++ b/beliefs/utils/edges_helper.py
@@ -1,6 +1,6 @@
from collections import defaultdict
-from beliefs.types.Node import Node
+from beliefs.models.beliefupdate.Node import Node
from beliefs.factors.BernoulliOrCPD import BernoulliOrCPD
diff --git a/tests/test_belief_propagation.py b/tests/test_belief_propagation.py
index 24ee94b..264ddae 100644
--- a/tests/test_belief_propagation.py
+++ b/tests/test_belief_propagation.py
@@ -3,8 +3,8 @@ import pytest
from pytest import approx
from beliefs.inference.belief_propagation import BeliefPropagation, ConflictingEvidenceError
-from beliefs.models.BernoulliOrModel import BernoulliOrModel
-from beliefs.types.BernoulliOrNode import BernoulliOrNode
+from beliefs.models.beliefupdate.BeliefUpdateNodeModel import BeliefUpdateNodeModel
+from beliefs.models.beliefupdate.BernoulliOrNode import BernoulliOrNode
@pytest.fixture(scope='module')
@@ -37,24 +37,23 @@ def many_parents_edges():
@pytest.fixture(scope='function')
def four_node_model(edges_four_nodes):
- return BernoulliOrModel(edges_four_nodes)
+ return BeliefUpdateNodeModel.from_edges(edges_four_nodes, BernoulliOrNode)
@pytest.fixture(scope='function')
def simple_model(simple_edges):
- return BernoulliOrModel(simple_edges)
+ return BeliefUpdateNodeModel.from_edges(simple_edges, BernoulliOrNode)
@pytest.fixture(scope='function')
def many_parents_model(many_parents_edges):
- return BernoulliOrModel(many_parents_edges)
+ return BeliefUpdateNodeModel.from_edges(many_parents_edges, BernoulliOrNode)
@pytest.fixture(scope='function')
def one_node_model():
a_node = BernoulliOrNode(label_id='x', children=[], parents=[])
- # a_node = BernoulliOrNode(label_id='x', children=set(), parents=set())
- return BernoulliOrModel(edges=None, nodes={'x': a_node})
+ return BeliefUpdateNodeModel(nodes_dict={'x': a_node})
def get_label_mapped_to_positive_belief(query_result):