From a60f0f98526ad3f594e829cf3c6c170e3809bb20 Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Thu, 18 Jan 2018 14:15:30 +0100 Subject: [PATCH 01/12] max_sum algorithm added. Some updates to existing code --- examples/decision_example.py | 191 +++++++++++++++++++++++++++++++++++ primo2/inference/decision.py | 85 +++++++++++++++- primo2/inference/factor.py | 32 +++++- primo2/networks.py | 73 +++++++++++-- primo2/tests/Factor_test.py | 36 +------ primo2/tests/Node_test.py | 68 ------------- primo2/tests/tests.py | 2 +- 7 files changed, 372 insertions(+), 115 deletions(-) create mode 100644 examples/decision_example.py diff --git a/examples/decision_example.py b/examples/decision_example.py new file mode 100644 index 0000000..026dd93 --- /dev/null +++ b/examples/decision_example.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Thu Dec 7 17:59:32 2017 + +@author: jpoeppel +""" + +from primo2.networks import DecisionNetwork +from primo2.networks import DynamicBayesianNetwork +from primo2.nodes import DiscreteNode, DecisionNode, UtilityNode + +from primo2.inference.decision import VariableElimination + +""" +PHD example 7.3 from Bayesian Reasoning and Machine Learning - Barber +""" +net = DecisionNetwork() + +education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E + +income = DiscreteNode("income", values=["low", "average", "high"]) #I +nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P + +costs = UtilityNode("costs") #UC +gains = UtilityNode("gains") #UB + + + +#Add nodes to network. They can be treated the same +net.add_node(education) +net.add_node(income) +net.add_node(nobel) + +net.add_node(costs) +net.add_node(gains) + + +#Add edges. Edges can either be acutal dependencies or information links. +#The type is figured out by the nodes themsevles +net.add_edge(education, costs) +net.add_edge(education, nobel) +net.add_edge(education, income) + +net.add_edge(nobel, income) +net.add_edge(income, gains) + +#Define CPTs: (Needs to be done AFTER the structure is defined as that) +#determines the table structure for the different nodes + +income.set_probability("low", 0.1, parentValues={"education":"do Phd", "nobel":"no prize"}) +income.set_probability("low", 0.2, parentValues={"education":"no Phd", "nobel":"no prize"}) +income.set_probability("low", 0.01, parentValues={"education":"do Phd", "nobel":"prize"}) +income.set_probability("low", 0.01, parentValues={"education":"no Phd", "nobel":"prize"}) + +income.set_probability("average", 0.5, parentValues={"education":"do Phd", "nobel":"no prize"}) +income.set_probability("average", 0.6, parentValues={"education":"no Phd", "nobel":"no prize"}) +income.set_probability("average", 0.04, parentValues={"education":"do Phd", "nobel":"prize"}) +income.set_probability("average", 0.04, parentValues={"education":"no Phd", "nobel":"prize"}) + +income.set_probability("high", 0.4, parentValues={"education":"do Phd", "nobel":"no prize"}) +income.set_probability("high", 0.2, parentValues={"education":"no Phd", "nobel":"no prize"}) +income.set_probability("high", 0.95, parentValues={"education":"do Phd", "nobel":"prize"}) +income.set_probability("high", 0.95, parentValues={"education":"no Phd", "nobel":"prize"}) + + +nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) +nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) + +nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) +nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) + + +#Define utilities + +costs.set_utility(-50000, parentValues={"education":"do Phd"}) +costs.set_utility(0, parentValues={"education":"no Phd"}) + +gains.set_utility(100000, parentValues={"income":"low"}) +gains.set_utility(200000, parentValues={"income":"average"}) +gains.set_utility(500000, parentValues={"income":"high"}) + +net.set_PartialOrdering([education, [income,nobel]]) +ve = VariableElimination(net) + +# print("Expected Utility for doing a Phd: {}".format(ve.expected_utility(decisions={"education":"do Phd"}))) +# print("Expected Utility for not doing a Phd: {}".format(ve.expected_utility(decisions={"education":"no Phd"}))) +# print("Optimal deciosn: ", ve.get_optimal_decisions(["education"])) +print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education"))) + + + + + +""" +PHD + Startup example 7.4 from Bayesian Reasoning and Machine Learning - Barber +""" + +net = DecisionNetwork() + + +education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E +startup = DecisionNode("startup", decisions=["start up", "no start up"]) # S + +income = DiscreteNode("income", values=["low", "average", "high"]) #I +nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P + +costsEducation = UtilityNode("costsE") #UC +costsStartUp = UtilityNode("costsS") #US +gains = UtilityNode("gains") #UB + + + +#Add nodes to network. They can be treated the same +net.add_node(education) +net.add_node(startup) +net.add_node(income) +net.add_node(nobel) + +net.add_node(costsEducation) +net.add_node(costsStartUp) +net.add_node(gains) + + +#Add edges. Edges can either be acutal dependencies or information links. +#The type is figured out by the nodes themsevles +net.add_edge(education, costsEducation) +net.add_edge(education, nobel) + +net.add_edge(startup, income) +net.add_edge(startup, costsStartUp) + +net.add_edge(nobel, income) +net.add_edge(income, gains) + +#Define CPTs: (Needs to be done AFTER the structure is defined as that) +#determines the table structure for the different nodes + +income.set_probability("low", 0.1, parentValues={"startup":"start up", "nobel":"no prize"}) +income.set_probability("low", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) +income.set_probability("low", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) +income.set_probability("low", 0.05, parentValues={"startup":"no start up", "nobel":"prize"}) + +income.set_probability("average", 0.5, parentValues={"startup":"start up", "nobel":"no prize"}) +income.set_probability("average", 0.6, parentValues={"startup":"no start up", "nobel":"no prize"}) +income.set_probability("average", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) +income.set_probability("average", 0.15, parentValues={"startup":"no start up", "nobel":"prize"}) + +income.set_probability("high", 0.4, parentValues={"startup":"start up", "nobel":"no prize"}) +income.set_probability("high", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) +income.set_probability("high", 0.99, parentValues={"startup":"start up", "nobel":"prize"}) +income.set_probability("high", 0.8, parentValues={"startup":"no start up", "nobel":"prize"}) + + +nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) +nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) + +nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) +nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) + + +#Define utilities + +costsEducation.set_utility(-50000, parentValues={"education":"do Phd"}) +costsEducation.set_utility(0, parentValues={"education":"no Phd"}) + +costsStartUp.set_utility(-200000, parentValues={"startup":"start up"}) +costsStartUp.set_utility(0, parentValues={"startup":"no start up"}) + +gains.set_utility(100000, parentValues={"income":"low"}) +gains.set_utility(200000, parentValues={"income":"average"}) +gains.set_utility(500000, parentValues={"income":"high"}) + +net.set_PartialOrdering([education, nobel, startup, income]) +ve = VariableElimination(net) + +# print("Expected Utility for doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "start up"}))) +# print("Expected Utility for doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "no start up"}))) +# print("Expected Utility for not doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "start up"}))) +# print("Expected Utility for not doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "no start up"}))) +print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education"))) +print("Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup"))) +# print("Optimal deciosn: ", ve.get_optimal_decisions(["startup", "education"])) + +net2 = net.copy() +d_net = DynamicBayesianNetwork(b0=net) + + + +ve = VariableElimination(d_net._b0) +print("Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup"))) diff --git a/primo2/inference/decision.py b/primo2/inference/decision.py index 5a5d5fb..85d5728 100644 --- a/primo2/inference/decision.py +++ b/primo2/inference/decision.py @@ -12,6 +12,8 @@ from ..nodes import UtilityNode from .factor import Factor +import primo2.nodes +import numpy as np class VariableElimination(object): @@ -133,8 +135,7 @@ def expected_utility(self, decisions=None): eliminations = [node for node in self.net.node_lookup.values() if not isinstance(node, UtilityNode)] return self.generalized_VE(factors, eliminations)[1].potentials - - + def _optimize_locally(self, decisionNodeName): """ Helper function to choose the optimal decision for the given @@ -214,6 +215,82 @@ def get_optimal_decisions(self, decisionOrder, fixedDecisions=None): solution[decisionNode] = localDecision return solution + + + def inner_product(self,factors, utilities): + """ + Helper Function to multiply all factors in the given list. + This function is guided by the Algorithm 7.3.2 at page 112 in "Bayesian Reasoning and Machine Learning" from David Barber. + Its the inner_product before the summing/maxing out + - - \ No newline at end of file + Parameters + ---------- + factors: list + A list of factors to be multiplied with + + utilities: list + A list of utilities to be multiplied with the factors + + + + Returns + ------- + factor + The resulting factor + """ + prob_product = factors[0] + utility_factors = [Factor.from_utility_node(i) for i in utilities] + utility_sum = utility_factors[0] + + for i,v in enumerate(factors): + if i != len(factors)-1: + prob_product = prob_product*factors[i+1] + + for i,v in enumerate(utility_factors): + if i != len(utility_factors)-1: + utility_sum = utility_sum+utility_factors[i+1] + + result = prob_product*utility_sum + + return result + + def max_sum(self,decisionNode): + """ + Max Sum Algorithm taken from the Algorithm 7.3.2 at page 112 in + "Bayesian Reasoning and Machine Learning" from David Barber. + + + Parameters + ---------- + decisionNode: String + The optimal decision + + Returns + ------- + List + A list containing the optimal variable and corresponding optimal utility value of the resulting max sum algorithm + """ + + partialOrder = self.net.get_PartialOrdering() + reverseOrder = partialOrder[::-1] + randomVariables = self.net.get_all_nodes() + utilities = self.net.get_all_utility_nodes() + + factors = [] + for node in randomVariables: + factors.append(Factor.from_node(node)) + + current = self.inner_product(factors,utilities) + for i in reverseOrder: + if isinstance(i,list) or isinstance(i,primo2.nodes.DiscreteNode): + current = current.marginalize(i) + + elif i != decisionNode: + current = current.maximize(i) + + return [current.values.values()[0][np.argmax(current.potentials)],max(current.potentials)] + + + + diff --git a/primo2/inference/factor.py b/primo2/inference/factor.py index e58c8e3..f274f73 100644 --- a/primo2/inference/factor.py +++ b/primo2/inference/factor.py @@ -508,7 +508,6 @@ def marginalize(self, variables): Factor A new factor where the given variables has been summed out. """ - if not isinstance(variables, (list,set)): variables = [variables] @@ -519,6 +518,37 @@ def marginalize(self, variables): res.variableOrder.remove(v) return res + + def maximize(self, variables): + """ + Function to create a new factor with the given variable maxed out + of this factor. + + Parameter + --------- + variables: String, RandomNode, [String,], [RandomNode,], set(String,) or set(RandomNode) + Either a single variable or a list of variables that are to + be removed. + Variables can either be addressed by their names or by the + nodes themselves. + + Returns + ------ + Factor + A new factor where the given variables has been summed out. + """ + + if not isinstance(variables, (list,set)): + variables = [variables] + + res = self.copy() + for v in variables: + res.potentials = np.amax(res.potentials, axis=res.variableOrder.index(v)) + del res.values[v] + res.variableOrder.remove(v) + + return res + def get_potential(self, variables=None): """ diff --git a/primo2/networks.py b/primo2/networks.py index 686b9a8..b9e391d 100644 --- a/primo2/networks.py +++ b/primo2/networks.py @@ -23,6 +23,7 @@ from . import exceptions from . import nodes +import primo2 class BayesianNetwork(object): @@ -207,9 +208,10 @@ def __init__(self, b0=None, two_tbn=None, transitions=None): See add_transition for more information. """ super(DynamicBayesianNetwork, self).__init__() - self._b0 = BayesianNetwork() if b0 is None else b0 - self._two_tbn = BayesianNetwork() if two_tbn is None else two_tbn + self._b0 = DecisionNetwork() if b0 is None else b0 + self._two_tbn = TwoTDN() if two_tbn is None else two_tbn self._transitions = [] + self._time = 0 if transitions is not None: self.add_transitions(transitions) @@ -303,6 +305,7 @@ def add_transitions(self, transitions): for transition in transitions: self.add_transition(transition[0], transition[1]) + @property def transitions(self): """Get the transition model. @@ -314,14 +317,72 @@ def transitions(self): See add_transition for more information. """ return self._transitions + + class DecisionNetwork(object): def __init__(self): - self.node_lookup ={} + self.node_lookup = {} + self.partialOrdering = [] + self.random_nodes = [] + self.decision_nodes = [] + self.utility_nodes = [] + def add_node(self, node): - self.node_lookup[node.name] = node + if isinstance(node, primo2.nodes.RandomNode): + if node.name in self.node_lookup.keys(): + raise Exception("Node name already exists in Bayesnet: "+node.name) + if isinstance(node, primo2.nodes.DiscreteNode): + self.random_nodes.append(node) + elif isinstance(node, primo2.nodes.UtilityNode): + self.utility_nodes.append(node) + elif isinstance(node, primo2.nodes.DecisionNode): + self.decision_nodes.append(node) + else: + raise Exception("Tried to add a node which the Bayesian Decision Network can not work with") + self.node_lookup[node.name]=node + else: + raise Exception("Can only add 'Node' and its subclasses as nodes into the BayesianNetwork") - def add_edge(self, from_name, to_name): - self.node_lookup[to_name].add_parent(self.node_lookup[from_name]) \ No newline at end of file + def add_edge(self, node_from, node_to): + + self.node_lookup[node_to].add_parent(self.node_lookup[node_from]) + + def get_PartialOrdering(self): + return self.partialOrdering + + def set_PartialOrdering(self,partialOrder): + self.partialOrdering = partialOrder + + def get_all_nodes(self): + '''Returns all RandomNodes''' + return self.random_nodes + + def get_all_decision_nodes(self): + return self.decision_nodes + + def get_all_utility_nodes(self): + return self.utility_nodes + +class Two_TDN(DecisionNetwork): + def __init__(self, decisionnet=None): + super(Two_TDN, self).__init__() + + self.init_nodes = self.node_lookup + + + def set_conditional_distribution(): + + + + + + + + + + + + diff --git a/primo2/tests/Factor_test.py b/primo2/tests/Factor_test.py index a41b30d..4e41fdc 100644 --- a/primo2/tests/Factor_test.py +++ b/primo2/tests/Factor_test.py @@ -23,7 +23,7 @@ import unittest import numpy as np from primo2.inference.factor import Factor -from primo2.nodes import DiscreteNode, DecisionNode, UtilityNode +from primo2.nodes import DiscreteNode class FactorTest(unittest.TestCase): @@ -41,12 +41,6 @@ def setUp(self): cpt3 = np.array([0.8,0.2]) self.n3.set_cpd(cpt3) - self.un = UtilityNode("gains") - - self.un.add_parent(self.n3) - - self.un.set_utilities(np.array([100,10])) - def test_create_from_discrete_node_error(self): with self.assertRaises(TypeError) as cm: f = Factor.from_node("Node1") @@ -68,13 +62,6 @@ def test_create_from_discrete_node_with_parents(self): for v in self.n2.parents[p].values: self.assertTrue(v in f.values[p]) - - def test_create_from_utility_node(self): - f = Factor.from_utility_node(self.un) - - self.assertEqual(f.variableOrder, ["Node3"]) - np.testing.assert_array_equal(f.potentials, np.array([100,10])) - def test_create_from_samples(self): from collections import OrderedDict samples = [{"A":"True", "B":"True"}, {"A":"True", "B":"False"}, @@ -327,26 +314,5 @@ def test_normalize(self): self.assertEqual(np.sum(f1.potentials), 1.0) - def test_joint_factor_discrete_node(self): - (pF, uF) = Factor.joint_factor(self.n2) - - np.testing.assert_array_almost_equal(pF.potentials, self.n2.cpd) - np.testing.assert_array_almost_equal(uF.potentials, np.zeros(pF.potentials.shape)) - - def test_joint_factor_decision_node(self): - decisionNode = DecisionNode("dNode", decisions=["yes", "no"]) - (pF, uF) = Factor.joint_factor(decisionNode) - - np.testing.assert_array_almost_equal(pF.potentials, np.zeros(2)) - np.testing.assert_array_almost_equal(uF.potentials, np.zeros(pF.potentials.shape)) - - def test_joint_factor_utility_node(self): - utilityNode = UtilityNode("uNode") - utilityNode.set_utilities = np.array([10,100]) - (pF, uF) = Factor.joint_factor(utilityNode) - - np.testing.assert_array_almost_equal(pF.potentials, np.ones(uF.potentials.shape)) - np.testing.assert_array_almost_equal(uF.potentials, utilityNode.cpd) - if __name__ == "__main__": unittest.main() \ No newline at end of file diff --git a/primo2/tests/Node_test.py b/primo2/tests/Node_test.py index 64eb94b..735db21 100644 --- a/primo2/tests/Node_test.py +++ b/primo2/tests/Node_test.py @@ -403,74 +403,6 @@ def test_get_markov_prob(self): class ContinousNode(unittest.TestCase): pass -class UtilityNode(unittest.TestCase): - - def test_get_utility_error(self): - n = nodes.UtilityNode("Node1") - n2 = nodes.DiscreteNode("Node2", ["Value3", "Value4", "Value5"]) - n.add_parent(n2) - with self.assertRaises(ValueError) as cm: - n.get_utility({"Node2": ["Value6"]}) - self.assertEqual(str(cm.exception), "There is no utility for parent {}, value {} in node {}.".format("Node2", "['Value6']", "Node1")) - - - def test_get_utility_multiple_parents(self): - wet = nodes.UtilityNode("wet_grass") - sprink = nodes.DiscreteNode("sprinkler") - rain = nodes.DiscreteNode("rain") - wet.add_parent(sprink) - wet.add_parent(rain) - utilities = np.array([[10,20],[100,5]]) - wet.set_utilities(utilities) - np.testing.assert_array_almost_equal(wet.get_utility({"sprinkler":"True", "rain":"False"}),utilities[0,1]) - - def test_get_utility_single_parent_value(self): - n = nodes.UtilityNode("Node1") - n2 = nodes.DiscreteNode("Node2", ["Value3", "Value4", "Value5"]) - n.add_parent(n2) - utilities = np.array([5,10,35]) - n.set_utilities(utilities) - # Get only the specified probability - self.assertEqual(n.get_utility({"Node2": "Value4"}),10) - - def test_get_utility_single_parent_value_error(self): - n = nodes.UtilityNode("Node1") - n2 = nodes.DiscreteNode("Node2", ["Value3", "Value4", "Value5"]) - n.add_parent(n2) - utilities = np.array([5,10,35]) - n.set_utilities(utilities) - with self.assertRaises(ValueError) as cm: - n.get_utility({"Node2": "Value6"}) - self.assertEqual(str(cm.exception), "There is no utility for parent {}, value {} in node {}.".format("Node2", "Value6", "Node1")) - - def test_set_utilities_wrong_dimension(self): - wet = nodes.UtilityNode("wet_grass") - sprink = nodes.DiscreteNode("sprinkler") - rain = nodes.DiscreteNode("rain") - wet.add_parent(sprink) - wet.add_parent(rain) - utilities = np.array([[10,20],[100]]) - with self.assertRaises(ValueError) as cm: - wet.set_utilities(utilities) - self.assertEqual(str(cm.exception), "The dimensions of the given " \ - "utility table do not match the dependency structure of the node.") - - def test_set_utility(self): - n = nodes.UtilityNode("Node1") - n2 = nodes.DiscreteNode("Node2", ["Value3", "Value4", "Value5"]) - n.add_parent(n2) - n.set_utility(100, {"Node2":"Value3"}) - self.assertEqual(n.get_utility({"Node2": "Value3"}),100) - - - def test_set_utility_unknown_parentValue(self): - n = nodes.UtilityNode("Node1") - n2 = nodes.DiscreteNode("Node2", ["Value3", "Value4", "Value5"]) - n.add_parent(n2) - with self.assertRaises(ValueError) as cm: - n.set_utility(100, {"Node2":"Value6"}) - self.assertEqual(str(cm.exception), "Parent {} does not have values {}.".format("Node2", "Value6")) - """TODO for Decision Networks!!!""" class DecisionNode(unittest.TestCase): diff --git a/primo2/tests/tests.py b/primo2/tests/tests.py index 3108a1e..431e6b5 100644 --- a/primo2/tests/tests.py +++ b/primo2/tests/tests.py @@ -69,7 +69,7 @@ #print (f_sp * f_wgEm).marginalize("sprinkler").potentials print "f_wgEm variableorder: ", f_wgEm.variableOrder -print "f_wgEm variables: ", f_wgEm.variables +# print "f_wgEm variables: ", f_wgEm.variables print "f_wgEm: ", f_wgEm.potentials #print "f_sp: ", f_sp.potentials print "sp*wg: ", (f_sp * f_wgEm).potentials From f11ba7dceb85db6a1eda62db6578020d786dbf57 Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Thu, 18 Jan 2018 14:16:55 +0100 Subject: [PATCH 02/12] Some code commented --- examples/decision_example.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/decision_example.py b/examples/decision_example.py index 026dd93..6b83913 100644 --- a/examples/decision_example.py +++ b/examples/decision_example.py @@ -182,10 +182,10 @@ print("Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup"))) # print("Optimal deciosn: ", ve.get_optimal_decisions(["startup", "education"])) -net2 = net.copy() -d_net = DynamicBayesianNetwork(b0=net) +# net2 = net.copy() +# d_net = DynamicBayesianNetwork(b0=net) -ve = VariableElimination(d_net._b0) -print("Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup"))) +# ve = VariableElimination(d_net._b0) +# print("Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup"))) From ec13a18618852d0365168d510648683c6e775762 Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Tue, 3 Apr 2018 18:09:01 +0200 Subject: [PATCH 03/12] Several new features added --- examples/decisionExample.py | 178 -------------- examples/decision_example.py | 438 +++++++++++++++++++++++++--------- primo2/inference/decision.py | 31 +-- primo2/networks.py | 442 ++++++++++++++++++++++++----------- primo2/nodes.py | 25 +- 5 files changed, 666 insertions(+), 448 deletions(-) delete mode 100644 examples/decisionExample.py diff --git a/examples/decisionExample.py b/examples/decisionExample.py deleted file mode 100644 index a86c34e..0000000 --- a/examples/decisionExample.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Thu Dec 7 17:59:32 2017 - -@author: jpoeppel -""" - -from primo2.networks import DecisionNetwork -from primo2.nodes import DiscreteNode, DecisionNode, UtilityNode - -from primo2.inference.decision import VariableElimination - -""" -PHD example 7.3 from Bayesian Reasoning and Machine Learning - Barber -""" -net = DecisionNetwork() - -education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E - -income = DiscreteNode("income", values=["low", "average", "high"]) #I -nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P - -costs = UtilityNode("costs") #UC -gains = UtilityNode("gains") #UB - - -#Add nodes to network. They can be treated the same -net.add_node(education) -net.add_node(income) -net.add_node(nobel) - -net.add_node(costs) -net.add_node(gains) - - -#Add edges. Edges can either be acutal dependencies or information links. -#The type is figured out by the nodes themsevles -net.add_edge(education, costs) -net.add_edge(education, nobel) -net.add_edge(education, income) - -net.add_edge(nobel, income) -net.add_edge(income, gains) - -#Define CPTs: (Needs to be done AFTER the structure is defined as that) -#determines the table structure for the different nodes - -income.set_probability("low", 0.1, parentValues={"education":"do Phd", "nobel":"no prize"}) -income.set_probability("low", 0.2, parentValues={"education":"no Phd", "nobel":"no prize"}) -income.set_probability("low", 0.01, parentValues={"education":"do Phd", "nobel":"prize"}) -income.set_probability("low", 0.01, parentValues={"education":"no Phd", "nobel":"prize"}) - -income.set_probability("average", 0.5, parentValues={"education":"do Phd", "nobel":"no prize"}) -income.set_probability("average", 0.6, parentValues={"education":"no Phd", "nobel":"no prize"}) -income.set_probability("average", 0.04, parentValues={"education":"do Phd", "nobel":"prize"}) -income.set_probability("average", 0.04, parentValues={"education":"no Phd", "nobel":"prize"}) - -income.set_probability("high", 0.4, parentValues={"education":"do Phd", "nobel":"no prize"}) -income.set_probability("high", 0.2, parentValues={"education":"no Phd", "nobel":"no prize"}) -income.set_probability("high", 0.95, parentValues={"education":"do Phd", "nobel":"prize"}) -income.set_probability("high", 0.95, parentValues={"education":"no Phd", "nobel":"prize"}) - - -nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) -nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) - -nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) -nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) - - -#Define utilities - -costs.set_utility(-50000, parentValues={"education":"do Phd"}) -costs.set_utility(0, parentValues={"education":"no Phd"}) - -gains.set_utility(100000, parentValues={"income":"low"}) -gains.set_utility(200000, parentValues={"income":"average"}) -gains.set_utility(500000, parentValues={"income":"high"}) - - -ve = VariableElimination(net) - -print("Expected Utility for doing a Phd: {}".format(ve.expected_utility(decisions={"education":"do Phd"}))) -print("Expected Utility for not doing a Phd: {}".format(ve.expected_utility(decisions={"education":"no Phd"}))) - -print("Optimal deciosn: ", ve.get_optimal_decisions(["education"])) - - - - -""" -PHD + Startup example 7.4 from Bayesian Reasoning and Machine Learning - Barber -""" - -net = DecisionNetwork() - -education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E -startup = DecisionNode("startup", decisions=["start up", "no start up"]) # S - -income = DiscreteNode("income", values=["low", "average", "high"]) #I -nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P - -costsEducation = UtilityNode("costsE") #UC -costsStartUp = UtilityNode("costsS") #US -gains = UtilityNode("gains") #UB - - - -#Add nodes to network. They can be treated the same -net.add_node(education) -net.add_node(startup) -net.add_node(income) -net.add_node(nobel) - -net.add_node(costsEducation) -net.add_node(costsStartUp) -net.add_node(gains) - - -#Add edges. Edges can either be acutal dependencies or information links. -#The type is figured out by the nodes themsevles -net.add_edge(education, costsEducation) -net.add_edge(education, nobel) - -net.add_edge(startup, income) -net.add_edge(startup, costsStartUp) - -net.add_edge(nobel, income) -net.add_edge(income, gains) - -#Define CPTs: (Needs to be done AFTER the structure is defined as that) -#determines the table structure for the different nodes - -income.set_probability("low", 0.1, parentValues={"startup":"start up", "nobel":"no prize"}) -income.set_probability("low", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) -income.set_probability("low", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) -income.set_probability("low", 0.05, parentValues={"startup":"no start up", "nobel":"prize"}) - -income.set_probability("average", 0.5, parentValues={"startup":"start up", "nobel":"no prize"}) -income.set_probability("average", 0.6, parentValues={"startup":"no start up", "nobel":"no prize"}) -income.set_probability("average", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) -income.set_probability("average", 0.15, parentValues={"startup":"no start up", "nobel":"prize"}) - -income.set_probability("high", 0.4, parentValues={"startup":"start up", "nobel":"no prize"}) -income.set_probability("high", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) -income.set_probability("high", 0.99, parentValues={"startup":"start up", "nobel":"prize"}) -income.set_probability("high", 0.8, parentValues={"startup":"no start up", "nobel":"prize"}) - - -nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) -nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) - -nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) -nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) - - -#Define utilities - -costsEducation.set_utility(-50000, parentValues={"education":"do Phd"}) -costsEducation.set_utility(0, parentValues={"education":"no Phd"}) - -costsStartUp.set_utility(-200000, parentValues={"startup":"start up"}) -costsStartUp.set_utility(0, parentValues={"startup":"no start up"}) - -gains.set_utility(100000, parentValues={"income":"low"}) -gains.set_utility(200000, parentValues={"income":"average"}) -gains.set_utility(500000, parentValues={"income":"high"}) - -ve = VariableElimination(net) - -print("Expected Utility for doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "start up"}))) -print("Expected Utility for doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "no start up"}))) -print("Expected Utility for not doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "start up"}))) -print("Expected Utility for not doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "no start up"}))) - - -print("Optimal deciosn: ", ve.get_optimal_decisions(["startup", "education"])) \ No newline at end of file diff --git a/examples/decision_example.py b/examples/decision_example.py index 6b83913..08bdb29 100644 --- a/examples/decision_example.py +++ b/examples/decision_example.py @@ -6,8 +6,7 @@ @author: jpoeppel """ -from primo2.networks import DecisionNetwork -from primo2.networks import DynamicBayesianNetwork +from primo2.networks import DecisionNetwork, DynamicDecisionNetwork, d0_net, Two_TDN from primo2.nodes import DiscreteNode, DecisionNode, UtilityNode from primo2.inference.decision import VariableElimination @@ -15,177 +14,386 @@ """ PHD example 7.3 from Bayesian Reasoning and Machine Learning - Barber """ -net = DecisionNetwork() +# d0 = d0_net() +# +# education_0 = DecisionNode("education_0", decisions=["do Phd", "no Phd"]) #E +# +# income_0 = DiscreteNode("income_0", values=["low", "average", "high"]) #I +# nobel_0 = DiscreteNode("nobel_0", values=["prize", "no prize"]) #P +# +# costs_0 = UtilityNode("costs_0") #UC +# gains_0 = UtilityNode("gains_0") #UB +# +# d0.add_nodes([education_0,income_0,nobel_0,costs_0,gains_0]) +# +# d0.set_zero_timeslice([income_0,nobel_0]) +# +# #Add edges. Edges can either be acutal dependencies or information links. +# #The type is figured out by the nodes themsevles +# d0.add_edge(education_0, costs_0) +# d0.add_edge(education_0, nobel_0) +# d0.add_edge(education_0, income_0) +# +# d0.add_edge(nobel_0, income_0) +# d0.add_edge(income_0, gains_0) +# +# #Define CPTs: (Needs to be done AFTER the structure is defined as that) +# #determines the table structure for the different nodes +# +# income_0.set_probability("low", 0.1, parentValues={"education_0":"do Phd", "nobel_0":"no prize"}) +# income_0.set_probability("low", 0.2, parentValues={"education_0":"no Phd", "nobel_0":"no prize"}) +# income_0.set_probability("low", 0.01, parentValues={"education_0":"do Phd", "nobel_0":"prize"}) +# income_0.set_probability("low", 0.01, parentValues={"education_0":"no Phd", "nobel_0":"prize"}) +# +# income_0.set_probability("average", 0.5, parentValues={"education_0":"do Phd", "nobel_0":"no prize"}) +# income_0.set_probability("average", 0.6, parentValues={"education_0":"no Phd", "nobel_0":"no prize"}) +# income_0.set_probability("average", 0.04, parentValues={"education_0":"do Phd", "nobel_0":"prize"}) +# income_0.set_probability("average", 0.04, parentValues={"education_0":"no Phd", "nobel_0":"prize"}) +# +# income_0.set_probability("high", 0.4, parentValues={"education_0":"do Phd", "nobel_0":"no prize"}) +# income_0.set_probability("high", 0.2, parentValues={"education_0":"no Phd", "nobel_0":"no prize"}) +# income_0.set_probability("high", 0.95, parentValues={"education_0":"do Phd", "nobel_0":"prize"}) +# income_0.set_probability("high", 0.95, parentValues={"education_0":"no Phd", "nobel_0":"prize"}) +# +# +# nobel_0.set_probability("prize", 0.0000001, parentValues={"education_0":"no Phd"}) +# nobel_0.set_probability("prize", 0.001, parentValues={"education_0":"do Phd"}) +# +# nobel_0.set_probability("no prize", 0.9999999, parentValues={"education_0":"no Phd"}) +# nobel_0.set_probability("no prize", 0.999, parentValues={"education_0":"do Phd"}) +# +# +# #Define utilities +# +# costs_0.set_utility(-50000, parentValues={"education_0":"do Phd"}) +# costs_0.set_utility(0, parentValues={"education_0":"no Phd"}) +# +# gains_0.set_utility(100000, parentValues={"income_0":"low"}) +# gains_0.set_utility(200000, parentValues={"income_0":"average"}) +# gains_0.set_utility(500000, parentValues={"income_0":"high"}) +# +# d0.set_partial_ordering(["education_0", ["income_0", "nobel_0"]]) +# +# # print(d0.get_all_nodes()) +# +# two_tdn = Two_TDN() +# +# education_t = DecisionNode("education_t", decisions=["do Phd", "no Phd"]) +# +# income_t = DiscreteNode("income_t", values=["low", "average", "high"]) +# income_t_plus_1 = DiscreteNode("income_t_plus_1", values=["low", "average", "high"]) +# +# nobel_t = DiscreteNode("nobel_t", values=["prize", "no prize"]) +# nobel_t_plus_1 = DiscreteNode("nobel_t_plus_1", values=["prize", "no prize"]) +# +# costs_t = UtilityNode("costs_t") +# +# gains_t = UtilityNode("gains_t") +# +# +# +# #Add nodes to network. They can be treated the same +# two_tdn.add_nodes([education_t, income_t, income_t_plus_1, nobel_t, nobel_t_plus_1, costs_t, gains_t]) +# two_tdn.set_t_timeslice([income_t, nobel_t]) +# two_tdn.set_t_plus_one_timeslice([income_t_plus_1, nobel_t_plus_1]) +# +# two_tdn.add_inter_edge(income_t, income_t_plus_1) +# two_tdn.add_inter_edge(nobel_t, nobel_t_plus_1) +# +# two_tdn.add_intra_edge(education_t, costs_t) +# two_tdn.add_intra_edge(education_t, nobel_t) +# two_tdn.add_intra_edge(education_t, income_t) +# +# two_tdn.add_intra_edge(income_t, gains_t) +# +# DDN = DynamicDecisionNetwork(d0, two_tdn) +# +# new_net = DDN.unroll(2) + + +# print "checking parents \n" +# for i in new_net.get_all_node_names(): +# print "Parent of" +# print i +# for j in new_net.node_lookup[i].parents: +# print "is:" +# print j + +# print(new_net.get_partial_ordering()) +# for i in new_net.get_all_nodes(): +# print i.cpd +# education_.set_decision("do phd", 0.5, parentValues={"education":"do phd"}) +# education_.set_decision("do phd", 0.5, parentValues={"education":"no phd"}) +# education_.set_decision("no phd", 0.5, parentValues={"education":"do phd"}) +# education_.set_decision("no phd", 0.5, parentValues={"education":"no phd"}) + +# income_.set_probability("low", 0.33, parentValues={"income":"low"}) +# income_.set_probability("low", 0.33, parentValues={"income":"average"}) +# income_.set_probability("low", 0.33, parentValues={"income":"high"}) +# income_.set_probability("average", 0.33, parentValues={"income":"low"}) +# income_.set_probability("average", 0.33, parentValues={"income":"average"}) +# income_.set_probability("average", 0.33, parentValues={"income":"high"}) +# income_.set_probability("high", 0.33, parentValues={"income":"low"}) +# income_.set_probability("high", 0.33, parentValues={"income":"average"}) +# income_.set_probability("high", 0.33, parentValues={"income":"high"}) + +# nobel_.set_probability("prize", 0.5, parentValues={"nobel":"prize"}) +# nobel_.set_probability("prize", 0.5, parentValues={"nobel":"no prize"}) +# nobel_.set_probability("no prize", 0.5, parentValues={"nobel":"prize"}) +# nobel_.set_probability("no prize", 0.5, parentValues={"nobel":"no prize"}) + +# costs_.set_utility(0, parentValues={"costs":"do Phd"}) +# costs_.set_utility(0, parentValues={"education":"no Phd"}) + + +# ve = VariableElimination(new_net) + +# print("Expected Utility for doing a Phd: {}".format(ve.expected_utility(decisions={"education_0":"do Phd"}))) +# print("Expected Utility for not doing a Phd: {}".format(ve.expected_utility(decisions={"education_0":"no Phd"}))) +# print("Optimal decision: ", ve.get_optimal_decisions(["education_0"])) +# print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education_0"))) -education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E -income = DiscreteNode("income", values=["low", "average", "high"]) #I -nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P -costs = UtilityNode("costs") #UC -gains = UtilityNode("gains") #UB +""" +PHD + Startup example 7.4 from Bayesian Reasoning and Machine Learning - Barber +""" +# net = DecisionNetwork() -#Add nodes to network. They can be treated the same -net.add_node(education) -net.add_node(income) -net.add_node(nobel) -net.add_node(costs) -net.add_node(gains) +# education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E +# startup = DecisionNode("startup", decisions=["start up", "no start up"]) # S +# income = DiscreteNode("income", values=["low", "average", "high"]) #I +# nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P -#Add edges. Edges can either be acutal dependencies or information links. -#The type is figured out by the nodes themsevles -net.add_edge(education, costs) -net.add_edge(education, nobel) -net.add_edge(education, income) +# costsEducation = UtilityNode("costsE") #UC +# costsStartUp = UtilityNode("costsS") #US +# gains = UtilityNode("gains") #UB -net.add_edge(nobel, income) -net.add_edge(income, gains) -#Define CPTs: (Needs to be done AFTER the structure is defined as that) -#determines the table structure for the different nodes -income.set_probability("low", 0.1, parentValues={"education":"do Phd", "nobel":"no prize"}) -income.set_probability("low", 0.2, parentValues={"education":"no Phd", "nobel":"no prize"}) -income.set_probability("low", 0.01, parentValues={"education":"do Phd", "nobel":"prize"}) -income.set_probability("low", 0.01, parentValues={"education":"no Phd", "nobel":"prize"}) +# #Add nodes to network. They can be treated the same +# net.add_node(education) +# net.add_node(startup) +# net.add_node(income) +# net.add_node(nobel) -income.set_probability("average", 0.5, parentValues={"education":"do Phd", "nobel":"no prize"}) -income.set_probability("average", 0.6, parentValues={"education":"no Phd", "nobel":"no prize"}) -income.set_probability("average", 0.04, parentValues={"education":"do Phd", "nobel":"prize"}) -income.set_probability("average", 0.04, parentValues={"education":"no Phd", "nobel":"prize"}) +# net.add_node(costsEducation) +# net.add_node(costsStartUp) +# net.add_node(gains) -income.set_probability("high", 0.4, parentValues={"education":"do Phd", "nobel":"no prize"}) -income.set_probability("high", 0.2, parentValues={"education":"no Phd", "nobel":"no prize"}) -income.set_probability("high", 0.95, parentValues={"education":"do Phd", "nobel":"prize"}) -income.set_probability("high", 0.95, parentValues={"education":"no Phd", "nobel":"prize"}) +# #Add edges. Edges can either be acutal dependencies or information links. +# #The type is figured out by the nodes themsevles +# net.add_edge(education, costsEducation) +# net.add_edge(education, nobel) -nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) -nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) +# net.add_edge(startup, income) +# net.add_edge(startup, costsStartUp) -nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) -nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) +# net.add_edge(nobel, income) +# net.add_edge(income, gains) +# #Define CPTs: (Needs to be done AFTER the structure is defined as that) +# #determines the table structure for the different nodes -#Define utilities +# income.set_probability("low", 0.1, parentValues={"startup":"start up", "nobel":"no prize"}) +# income.set_probability("low", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) +# income.set_probability("low", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) +# income.set_probability("low", 0.05, parentValues={"startup":"no start up", "nobel":"prize"}) -costs.set_utility(-50000, parentValues={"education":"do Phd"}) -costs.set_utility(0, parentValues={"education":"no Phd"}) +# income.set_probability("average", 0.5, parentValues={"startup":"start up", "nobel":"no prize"}) +# income.set_probability("average", 0.6, parentValues={"startup":"no start up", "nobel":"no prize"}) +# income.set_probability("average", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) +# income.set_probability("average", 0.15, parentValues={"startup":"no start up", "nobel":"prize"}) -gains.set_utility(100000, parentValues={"income":"low"}) -gains.set_utility(200000, parentValues={"income":"average"}) -gains.set_utility(500000, parentValues={"income":"high"}) +# income.set_probability("high", 0.4, parentValues={"startup":"start up", "nobel":"no prize"}) +# income.set_probability("high", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) +# income.set_probability("high", 0.99, parentValues={"startup":"start up", "nobel":"prize"}) +# income.set_probability("high", 0.8, parentValues={"startup":"no start up", "nobel":"prize"}) -net.set_PartialOrdering([education, [income,nobel]]) -ve = VariableElimination(net) -# print("Expected Utility for doing a Phd: {}".format(ve.expected_utility(decisions={"education":"do Phd"}))) -# print("Expected Utility for not doing a Phd: {}".format(ve.expected_utility(decisions={"education":"no Phd"}))) -# print("Optimal deciosn: ", ve.get_optimal_decisions(["education"])) -print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education"))) +# nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) +# nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) +# nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) +# nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) +# #Define utilities +# costsEducation.set_utility(-50000, parentValues={"education":"do Phd"}) +# costsEducation.set_utility(0, parentValues={"education":"no Phd"}) -""" -PHD + Startup example 7.4 from Bayesian Reasoning and Machine Learning - Barber -""" +# costsStartUp.set_utility(-200000, parentValues={"startup":"start up"}) +# costsStartUp.set_utility(0, parentValues={"startup":"no start up"}) -net = DecisionNetwork() +# gains.set_utility(100000, parentValues={"income":"low"}) +# gains.set_utility(200000, parentValues={"income":"average"}) +# gains.set_utility(500000, parentValues={"income":"high"}) +# net.set_PartialOrdering([education, nobel, startup, income]) +# ve = VariableElimination(net) -education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E -startup = DecisionNode("startup", decisions=["start up", "no start up"]) # S +# print("Expected Utility for doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "start up"}))) +# print("Expected Utility for doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "no start up"}))) +# print("Expected Utility for not doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "start up"}))) +# print("Expected Utility for not doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "no start up"}))) +# print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education"))) +# print("Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup"))) +# print("Optimal deciosn: ", ve.get_optimal_decisions(["startup", "education"])) -income = DiscreteNode("income", values=["low", "average", "high"]) #I -nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P +# net2 = net.copy() -costsEducation = UtilityNode("costsE") #UC -costsStartUp = UtilityNode("costsS") #US -gains = UtilityNode("gains") #UB -#Add nodes to network. They can be treated the same -net.add_node(education) -net.add_node(startup) -net.add_node(income) -net.add_node(nobel) -net.add_node(costsEducation) -net.add_node(costsStartUp) -net.add_node(gains) +#Define CPTs: (Needs to be done AFTER the structure is defined as that) +#determines the table structure for the different nodes -#Add edges. Edges can either be acutal dependencies or information links. -#The type is figured out by the nodes themsevles -net.add_edge(education, costsEducation) -net.add_edge(education, nobel) -net.add_edge(startup, income) -net.add_edge(startup, costsStartUp) +# net.set_PartialOrdering([education, [income,nobel]]) +# print(net.get_all_nodes()) -net.add_edge(nobel, income) -net.add_edge(income, gains) -#Define CPTs: (Needs to be done AFTER the structure is defined as that) -#determines the table structure for the different nodes +# d_net = DynamicBayesianNetwork(b0=net, two_tdn=net2) +# print d_net.b0.get_all_node_names() +# print d_net.two_tdn.get_all_node_names() +# d_net.unroll(3) +# d_net = DynamicBayesianNetwork(net) -income.set_probability("low", 0.1, parentValues={"startup":"start up", "nobel":"no prize"}) -income.set_probability("low", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) -income.set_probability("low", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) -income.set_probability("low", 0.05, parentValues={"startup":"no start up", "nobel":"prize"}) +# print "What?" +# print d_net.b0.get_all_nodes() -income.set_probability("average", 0.5, parentValues={"startup":"start up", "nobel":"no prize"}) -income.set_probability("average", 0.6, parentValues={"startup":"no start up", "nobel":"no prize"}) -income.set_probability("average", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) -income.set_probability("average", 0.15, parentValues={"startup":"no start up", "nobel":"prize"}) +# education_ = DecisionNode("education_", decisions=["do Phd", "no Phd"]) #E -income.set_probability("high", 0.4, parentValues={"startup":"start up", "nobel":"no prize"}) -income.set_probability("high", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) -income.set_probability("high", 0.99, parentValues={"startup":"start up", "nobel":"prize"}) -income.set_probability("high", 0.8, parentValues={"startup":"no start up", "nobel":"prize"}) +# income_ = DiscreteNode("income_", values=["low", "average", "high"]) #I +# nobel_ = DiscreteNode("nobel_", values=["prize", "no prize"]) #P +# costs_ = UtilityNode("costs_") #UC +# gains_ = UtilityNode("gains_") #UB -nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) -nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) -nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) -nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) +# d_net.two_tbn.add_node(education_) +# d_net.two_tbn.add_node(income_) +# d_net.two_tbn.add_node(nobel_) -#Define utilities +# d_net.two_tbn.add_node(costs_) +# d_net.two_tbn.add_node(gains_) -costsEducation.set_utility(-50000, parentValues={"education":"do Phd"}) -costsEducation.set_utility(0, parentValues={"education":"no Phd"}) +# income_.set_probability("low", 0.11, parentValues={"income":"low"}) +# income_.set_probability("low", 0.21, parentValues={"income":"low"}) +# income_.set_probability("low", 0.011, parentValues={"income":"low"}) +# income_.set_probability("low", 0.011, parentValues={"income":"low"}) -costsStartUp.set_utility(-200000, parentValues={"startup":"start up"}) -costsStartUp.set_utility(0, parentValues={"startup":"no start up"}) +# income_.set_probability("average", 0.51, parentValues={"income":"average"}) +# income_.set_probability("average", 0.61, parentValues={"income":"average"}) +# income_.set_probability("average", 0.041, parentValues={"income":"average"}) +# income_.set_probability("average", 0.041, parentValues={"income":"average"}) -gains.set_utility(100000, parentValues={"income":"low"}) -gains.set_utility(200000, parentValues={"income":"average"}) -gains.set_utility(500000, parentValues={"income":"high"}) +# income_.set_probability("high", 0.41, parentValues={"income":"high"}) +# income_.set_probability("high", 0.21, parentValues={"income":"high"}) +# income_.set_probability("high", 0.951, parentValues={"income":"high"}) +# income_.set_probability("high", 0.951, parentValues={"income":"high"}) -net.set_PartialOrdering([education, nobel, startup, income]) -ve = VariableElimination(net) -# print("Expected Utility for doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "start up"}))) -# print("Expected Utility for doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "no start up"}))) -# print("Expected Utility for not doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "start up"}))) -# print("Expected Utility for not doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "no start up"}))) -print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education"))) -print("Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup"))) -# print("Optimal deciosn: ", ve.get_optimal_decisions(["startup", "education"])) +# nobel_.set_probability("prize", 0.00000011, parentValues={"nobel":"prize"}) +# nobel_.set_probability("prize", 0.0011, parentValues={"nobel":"prize"}) + +# nobel_.set_probability("no prize", 0.99999991, parentValues={"nobel":"no prize"}) +# nobel_.set_probability("no prize", 0.9991, parentValues={"nobel":"no prize"}) + + +# #Define utilities + +# costs_.set_utility(-50001, parentValues={"costs"}) +# costs_.set_utility(1, parentValues={"costs"}) + +# gains_.set_utility(100001, parentValues={"gains"}) +# gains_.set_utility(200001, parentValues={"gains"}) +# gains_.set_utility(500001, parentValues={"gains"}) -# net2 = net.copy() -# d_net = DynamicBayesianNetwork(b0=net) +# ve = VariableElimination(net) + +# print("Expected Utility for doing a Phd: {}".format(ve.expected_utility(decisions={"education":"do Phd"}))) +# print("Expected Utility for not doing a Phd: {}".format(ve.expected_utility(decisions={"education":"no Phd"}))) +# print("Optimal deciosn: ", ve.get_optimal_decisions(["education"])) +#ccprint("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education"))) + # ve = VariableElimination(d_net._b0) # print("Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup"))) + + +""" +Fever Example 4.3.5 from Bayesian Artificial Intelligence - Korb Nicholson +""" + +d0 = d0_net() + +flu_0 = DiscreteNode("flu_0", values=["True", "False"]) +fever_0 = DiscreteNode("fever_0", values=["True", "False"]) +therm_0 = DiscreteNode("therm_0", values=["True", "False"]) +take_aspirin_0 = DecisionNode("take_aspirin_0", decisions=["Yes", "No"]) +fever_later_0 = DiscreteNode("fever_later_0", values=["True", "False"]) +reaction_0 = DiscreteNode("reaction_0", values=["Yes", "No"]) +utility_0 = UtilityNode("utility_0") + +d0.add_nodes([flu_0, fever_0, therm_0, take_aspirin_0, fever_later_0, reaction_0, utility_0]) + +d0.add_edge(flu_0, fever_0) +d0.add_edge(fever_0, therm_0) +d0.add_edge(fever_0, fever_later_0) +d0.add_edge(fever_later_0, utility_0) +d0.add_edge(take_aspirin_0, reaction_0) +d0.add_edge(take_aspirin_0, fever_later_0) +d0.add_edge(reaction_0, utility_0) + +flu_0.set_probability("True", 0.05) +flu_0.set_probability("False", 0.95) + +fever_0.set_probability("True", 0.95, parentValues={"flu_0": "True"}) +fever_0.set_probability("True", 0.02, parentValues={"flu_0": "False"}) +fever_0.set_probability("False", 0.05, parentValues={"flu_0": "True"}) +fever_0.set_probability("False", 0.98, parentValues={"flu_0": "False"}) + +therm_0.set_probability("True", 0.90, parentValues={"fever_0": "True"}) +therm_0.set_probability("True", 0.05, parentValues={"fever_0": "False"}) +therm_0.set_probability("False", 0.10, parentValues={"fever_0": "True"}) +therm_0.set_probability("False", 0.95, parentValues={"fever_0": "False"}) + +fever_later_0.set_probability("True", 0.05, parentValues={"fever_0": "True", "take_aspirin_0": "Yes"}) +fever_later_0.set_probability("True", 0.90, parentValues={"fever_0": "True", "take_aspirin_0": "No"}) +fever_later_0.set_probability("True", 0.01, parentValues={"fever_0": "False", "take_aspirin_0": "Yes"}) +fever_later_0.set_probability("True", 0.02, parentValues={"fever_0": "False", "take_aspirin_0": "No"}) +fever_later_0.set_probability("False", 0.95, parentValues={"fever_0": "True", "take_aspirin_0": "Yes"}) +fever_later_0.set_probability("False", 0.10, parentValues={"fever_0": "True", "take_aspirin_0": "No"}) +fever_later_0.set_probability("False", 0.99, parentValues={"fever_0": "False", "take_aspirin_0": "Yes"}) +fever_later_0.set_probability("False", 0.98, parentValues={"fever_0": "False", "take_aspirin_0": "No"}) + +reaction_0.set_probability("Yes", 0.05, parentValues={"take_aspirin_0": "Yes"}) +reaction_0.set_probability("Yes", 0.00, parentValues={"take_aspirin_0": "No"}) +reaction_0.set_probability("No", 0.95, parentValues={"take_aspirin_0": "Yes"}) +reaction_0.set_probability("No", 1.0, parentValues={"take_aspirin_0": "No"}) + +utility_0.set_utility(-50, {"fever_later_0": "True", "reaction_0": "Yes"}) +utility_0.set_utility(-10, {"fever_later_0": "True", "reaction_0": "No"}) +utility_0.set_utility(-30, {"fever_later_0": "False", "reaction_0": "Yes"}) +utility_0.set_utility(50, {"fever_later_0": "False", "reaction_0": "No"}) + +d0.set_partial_ordering([["flu_0", "fever_0", "therm_0"], "take_aspirin_0", ["fever_later_0", "reaction_0"]]) + +ve = VariableElimination(d0) + +print("Expected Utility for Taking Aspirin: {}".format(ve.expected_utility(decisions={"take_aspirin_0": "Yes"}))) +print("Expected Utility for not Taking Aspirin: {}".format(ve.expected_utility(decisions={"take_aspirin_0": "No"}))) +print("Optimal decision: ", ve.get_optimal_decisions(["take_aspirin_0"])) +print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("take_aspirin_0"))) \ No newline at end of file diff --git a/primo2/inference/decision.py b/primo2/inference/decision.py index 85d5728..41c2b26 100644 --- a/primo2/inference/decision.py +++ b/primo2/inference/decision.py @@ -12,7 +12,7 @@ from ..nodes import UtilityNode from .factor import Factor -import primo2.nodes +from primo2.nodes import DiscreteNode, DecisionNode, UtilityNode import numpy as np class VariableElimination(object): @@ -217,11 +217,12 @@ def get_optimal_decisions(self, decisionOrder, fixedDecisions=None): return solution - def inner_product(self,factors, utilities): + def inner_product(self, factors, utilities): """ Helper Function to multiply all factors in the given list. - This function is guided by the Algorithm 7.3.2 at page 112 in "Bayesian Reasoning and Machine Learning" from David Barber. - Its the inner_product before the summing/maxing out + This function is guided by the Algorithm 7.3.2 at page 112 in "Bayesian Reasoning and Machine Learning" + from David Barber. + Here the inner_product is calculated before the summing/maxing out using factors Parameters @@ -243,11 +244,11 @@ def inner_product(self,factors, utilities): utility_factors = [Factor.from_utility_node(i) for i in utilities] utility_sum = utility_factors[0] - for i,v in enumerate(factors): + for i, v in enumerate(factors): if i != len(factors)-1: prob_product = prob_product*factors[i+1] - for i,v in enumerate(utility_factors): + for i, v in enumerate(utility_factors): if i != len(utility_factors)-1: utility_sum = utility_sum+utility_factors[i+1] @@ -272,24 +273,28 @@ def max_sum(self,decisionNode): A list containing the optimal variable and corresponding optimal utility value of the resulting max sum algorithm """ - partialOrder = self.net.get_PartialOrdering() + partialOrder = self.net.get_partial_ordering() reverseOrder = partialOrder[::-1] - randomVariables = self.net.get_all_nodes() - utilities = self.net.get_all_utility_nodes() + randomVariables = self.net.get_random_nodes() + utilities = self.net.get_utility_nodes() factors = [] for node in randomVariables: factors.append(Factor.from_node(node)) - current = self.inner_product(factors,utilities) + current = self.inner_product(factors, utilities) for i in reverseOrder: - if isinstance(i,list) or isinstance(i,primo2.nodes.DiscreteNode): + if isinstance(i, list): + if all(isinstance(self.net.node_lookup[val], DiscreteNode) for val in i): + current = current.marginalize(i) + else: + raise Exception("Marginalizing failed: Not all elements in the list are Discrete Nodes") + elif isinstance(self.net.node_lookup[i], DiscreteNode): current = current.marginalize(i) - elif i != decisionNode: current = current.maximize(i) - return [current.values.values()[0][np.argmax(current.potentials)],max(current.potentials)] + return [current.values.values()[0][np.argmax(current.potentials)], max(current.potentials)] diff --git a/primo2/networks.py b/primo2/networks.py index b9e391d..6cd4d65 100644 --- a/primo2/networks.py +++ b/primo2/networks.py @@ -23,7 +23,7 @@ from . import exceptions from . import nodes -import primo2 +from primo2.nodes import RandomNode, DiscreteNode, DecisionNode, UtilityNode class BayesianNetwork(object): @@ -35,7 +35,7 @@ def __init__(self): self.meta = [] # Used to be compatible with XMLBIF, stores properties def add_node(self, node): - if isinstance(node, nodes.RandomNode): + if isinstance(node, RandomNode): if node.name in self.node_lookup: raise ValueError("The network already contains a node " \ "called '{}'.".format(node.name)) @@ -43,7 +43,7 @@ def add_node(self, node): self.graph.add_node(node) else: raise TypeError("Only subclasses of RandomNode are valid nodes.") - + def remove_node(self, node): if node in self.graph: # Go over all children of this node @@ -51,7 +51,7 @@ def remove_node(self, node): child.remove_parent(self.node_lookup[node]) self.graph.remove_node(node) del self.node_lookup[node] - + def remove_edge(self, from_name, to_name): if from_name in self.graph and to_name in self.graph: self.node_lookup[to_name].remove_parent(self.node_lookup[from_name]) @@ -72,7 +72,7 @@ def get_node(self, node_name): except KeyError: raise Exception("There is no node with name {} in the " \ "BayesianNetwork".format(node_name)) - + def change_node_values(self, node, new_values): """ Updates the values of the given node. This will invalidate the node @@ -93,7 +93,7 @@ def change_node_values(self, node, new_values): else: raise Exception("There is no node with name {} in " \ "the network.".format(node)) - + def change_node_name(self, old_name, new_name): """ Renames the given node to the new name. @@ -108,27 +108,27 @@ def change_node_name(self, old_name, new_name): child.parents[new_name] = n idx = child.parentOrder.index(old_name) child.parentOrder[idx] = new_name - + # Fix nx graph self.graph.remove_node(n) - + n.name = new_name del self.node_lookup[old_name] self.node_lookup[new_name] = n - + self.graph.add_node(n) for child in children: self.graph.add_edge(n, self.node_lookup[child]) for parent in parents: self.graph.add_edge(self.node_lookup[parent], n) - + else: raise Exception("There is no node with name {} in the " \ "network.".format(old_name)) def get_all_nodes(self): return self.graph.nodes() - + def get_all_node_names(self): return self.node_lookup.keys() @@ -140,7 +140,7 @@ def get_nodes(self, node_names=None): for node_name in node_names: nodes.append(self.get_node(node_name)) return nodes - + def get_children(self, node_name): """ Returns a list of all the children of the given node. @@ -157,17 +157,17 @@ def get_children(self, node_name): node as parent. """ return self.graph.succ[node_name] - + def get_sample(self, evidence): sample = {} for e in evidence: sample[e] = evidence[e] # Make sure evidence contains RandomNodes and not only names for n in nx.topological_sort(self.graph): - + if n not in evidence: sample[n.name] = n.sample_value(sample, self.get_children(n), forward=True) - + return sample def clear(self): @@ -183,83 +183,224 @@ def number_of_nodes(self): def __len__(self): """Return the number of nodes in the graph.""" return len(self.graph) - -class DynamicBayesianNetwork(object): - """Class representing a the structure of a dynamic Bayesian network. - This temporal relationship is modelled as a 2-time-slice Bayesian - network (2-TBN; Koller & Friedman, 2009, §6.2.2). The Bayesian network - B_0 represents the initial distribution. The Bayesian network B_{->}, - a 2-TBN, represents the process. +class DynamicDecisionNetwork(object): + """Class representing a the structure of a dynamic Decision network. + This temporal relationship is modelled as a 2-time-slice Decision + network. The Decision network + d0 represents the initial distribution. The Decision network D_{->}, + a 2-TDN, represents the process. """ - def __init__(self, b0=None, two_tbn=None, transitions=None): + def __init__(self, d0=None, two_tdn=None): """Create a dynamic Bayesian network. - Parameters ---------- - b0 : BayesianNetwork + d0 : d0_net The network representing the initial distribution. - two_tbn : BayesianNetwork + two_tdn : Two_TDN The two-time-slice network representing the process. - transitions : [(node, node_p),] - A list of pairs, each of which represents one transition. - See add_transition for more information. """ - super(DynamicBayesianNetwork, self).__init__() - self._b0 = DecisionNetwork() if b0 is None else b0 - self._two_tbn = TwoTDN() if two_tbn is None else two_tbn - self._transitions = [] - self._time = 0 - if transitions is not None: - self.add_transitions(transitions) - - @property - def b0(self): - """Get the Bayesian network B_0. - + super(DynamicDecisionNetwork, self).__init__() + + self._d0 = d0_net() if d0 is None else d0 + self._two_tdn = Two_TDN() if two_tdn is None else two_tdn + + def unroll(self, length): + + """Unrolling the network over specified length. Returns ------- - BayesianNetwork - The network representing the initial distribution. + DecisionNetwork + An unrolled DDN as a DecisionNetwork """ - return self._b0 - @b0.setter - def b0(self, bn): - """Set the Bayesian network B_0. + unrolled_net = DecisionNetwork() + + zero_slice = self._d0.timeslice[0] + t_plus_1_slice = self._two_tdn.timeslice['t_plus_1'] + if length > 0: + for i in range(0, length): + if i == 0: + unrolled_net.add_nodes(zero_slice + self._d0.decision_nodes + self._d0.utility_nodes) + elif i == 1: + """copy nodes from the two_tdn Network into unrolled_net""" + unrolled_net.copy_nodes_indexed(t_plus_1_slice, i) + for j in self._two_tdn._inter_edges: + parent_node_name = j[0].name.split("_", 1)[0] + "_" + str(i - 1) + child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) + """Connect copied nodes via inter edges like described in the two tdn""" + unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], + unrolled_net.node_lookup[child_node_name]) + + else: + unrolled_net.copy_nodes_indexed(t_plus_1_slice, i) + unrolled_net.copy_nodes_indexed(self._two_tdn.decision_nodes, i - 1) + unrolled_net.copy_nodes_indexed(self._two_tdn.utility_nodes, i - 1) + + for j in self._two_tdn._inter_edges: + parent_node_name = j[0].name.split("_", 1)[0] + "_" + str(i - 1) + child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) + unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], + unrolled_net.node_lookup[child_node_name]) + + for j in self._two_tdn._intra_edges: + parent_node_name = j[0].name.split("_", 1)[0] + "_" + str(i - 1) + child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i - 1) + unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], + unrolled_net.node_lookup[child_node_name]) + + unrolled_net.set_partial_ordering_u(self._d0.get_partial_ordering(), length) + else: + raise Exception("Can't unroll over length: 0") + return unrolled_net + + +class d0_net(object): + + def __init__(self): + self.timeslice = {} + self.utility_nodes = [] + self.decision_nodes = [] + self.random_nodes = [] + self.node_lookup = {} + self.partialOrdering = [] + + def set_zero_timeslice(self, nodes): + self.timeslice[0] = nodes + + def add_nodes(self, nodes): + for i in nodes: + if isinstance(i, RandomNode): + if i.name in self.node_lookup.keys(): + pass + if isinstance(i, DiscreteNode): + self.random_nodes.append(i) + elif isinstance(i, UtilityNode): + self.utility_nodes.append(i) + elif isinstance(i, DecisionNode): + self.decision_nodes.append(i) + else: + raise Exception("Tried to add a node which the D0 Network can not work with") + self.node_lookup[i.name] = i + else: + raise Exception("Can only add 'Node' and its subclasses as nodes into the D0 Network") + + def add_edge(self, node_from, node_to): + + node_to.add_parent(node_from) + + def get_partial_ordering(self): + return self.partialOrdering + + def set_partial_ordering(self, partial_order): + self.partialOrdering = partial_order + + def get_random_nodes(self): + return self.random_nodes + + def get_decision_nodes(self): + return self.decision_nodes + + def get_utility_nodes(self): + return self.utility_nodes + + def get_all_nodes(self): + return self.random_nodes + self.decision_nodes + self.utility_nodes + + def get_all_node_names(self): + return self.node_lookup.keys() + + +class Two_TDN(object): + def __init__(self, inter_edges=None, intra_edges=None): + + self.timeslice = {} + self.utility_nodes = [] + self.decision_nodes = [] + self.random_nodes = [] + self.node_lookup = {} + self._inter_edges = [] + self._intra_edges = [] + if inter_edges is not None: + self.add_inter_edges(inter_edges) + if intra_edges is not None: + self.add_intra_edges(intra_edges) + + def add_nodes(self, nodes): + for i in nodes: + if isinstance(i, RandomNode): + if i.name in self.node_lookup.keys(): + pass + if isinstance(i, DiscreteNode): + self.random_nodes.append(i) + elif isinstance(i, UtilityNode): + self.utility_nodes.append(i) + elif isinstance(i, DecisionNode): + self.decision_nodes.append(i) + else: + raise Exception("Tried to add a node which the D0 Network can not work with") + self.node_lookup[i.name] = i + else: + raise Exception("Can only add 'Node' and its subclasses as nodes into the D0 Network") + + def get_random_nodes(self): + return self.random_nodes + + def get_decision_nodes(self): + return self.decision_nodes + + def get_utility_nodes(self): + return self.utility_nodes + + def get_all_nodes(self): + return self.random_nodes + self.decision_nodes + self.utility_nodes + + def get_all_node_names(self): + return self.node_lookup.keys() + + def add_inter_edge(self, node, node_p): + """Add a transition connecting nodes when unrolling the network. + + The transition is a directed edge from node X_i (`node`) to node X_i' + (`node_p`). Parameters ---------- - bn : BayesianNetwork - The network representing the initial distribution. - """ - self._b0 = bn + node : RandomNode, String + The node X_i in the next time-slice. - @property - def two_tbn(self): - """Get the 2-TBN B_{->}. + node_p: RandomNode, String + The corresponding node X_i' in the current time-slice. - Returns - ------- - BayesianNetwork - The two-time-slice network representing the process. + Raises + ------ + primo2.exceptions.StructureError + If a node specified in the transition model cannot be found in the + corresponding networks. """ - return self._two_tbn + if node_p not in self.get_all_nodes(): + raise exceptions.StructureError( + 'Node "{}" is not found in D_{->} network "{}".') + if node not in self.get_all_nodes(): + raise exceptions.StructureError( + 'Node "{}" is not found in D_{{->}} network "{}".') + self._inter_edges.append((node, node_p)) - @two_tbn.setter - def two_tbn(self, bn): - """Set the 2-TBN B_{->}. + def add_inter_edges(self, inter_edges): + """Add multiple transitions connecting nodes when unrolling the network. Parameters ---------- - bn : BayesianNetwork - The two-time-slice network representing the process. + inter_edges : [(node, node_p),] + A list of pairs, each of which represents one transition. + See add_transition for more information. """ - self._two_tbn = bn + for inter_edges in inter_edges: + self.add_inter_edge(inter_edges[0], inter_edges[1]) - def add_transition(self, node, node_p): + def add_intra_edge(self, node, node_p): """Add a transition connecting nodes when unrolling the network. The transition is a directed edge from node X_i (`node`) to node X_i' @@ -269,7 +410,7 @@ def add_transition(self, node, node_p): ---------- node : RandomNode, String The node X_i in the next time-slice. - + node_p: RandomNode, String The corresponding node X_i' in the current time-slice. @@ -279,49 +420,35 @@ def add_transition(self, node, node_p): If a node specified in the transition model cannot be found in the corresponding networks. """ - if node_p not in self._b0.get_all_nodes(): - raise exceptions.StructureError( - 'Node "{}" is not found in B_0 network "{}".'.format( - node_p, self._b0.name)) - if node_p not in self._two_tbn.get_all_nodes(): + if node_p not in self.get_all_nodes(): raise exceptions.StructureError( - 'Node "{}" is not found in B_{->} network "{}".'.format( - node_p, self._two_tbn.name)) - if node not in self._two_tbn.get_all_nodes(): + 'Node "{}" is not found in D_{->} network "{}".') + if node not in self.get_all_nodes(): raise exceptions.StructureError( - 'Node "{}" is not found in B_{{->}} network "{}".'.format( - node, self._two_tbn.name)) - self._transitions.append((node, node_p)) + 'Node "{}" is not found in D_{{->}} network "{}".') + self._intra_edges.append((node, node_p)) - def add_transitions(self, transitions): + def add_intra_edges(self, intra_edges): """Add multiple transitions connecting nodes when unrolling the network. Parameters ---------- - transitions : [(node, node_p),] - A list of pairs, each of which represents one transition. + intra_edges : [(node, node_p),] + A list of pairs, each of which represents one transition. See add_transition for more information. - """ - for transition in transitions: - self.add_transition(transition[0], transition[1]) + """ + for intra_edges in intra_edges: + self.add_intra_edge(intra_edges[0], intra_edges[1]) + def set_t_timeslice(self, initial_nodes): + self.timeslice['t'] = initial_nodes - @property - def transitions(self): - """Get the transition model. + def set_t_plus_one_timeslice(self, next_nodes): + self.timeslice['t_plus_1'] = next_nodes - Returns - ------- - [(node, node_p),] - A list of pairs, each of which represents one transition. - See add_transition for more information. - """ - return self._transitions - - class DecisionNetwork(object): - + def __init__(self): self.node_lookup = {} self.partialOrdering = [] @@ -329,60 +456,109 @@ def __init__(self): self.decision_nodes = [] self.utility_nodes = [] - def add_node(self, node): - if isinstance(node, primo2.nodes.RandomNode): + if isinstance(node, RandomNode): if node.name in self.node_lookup.keys(): - raise Exception("Node name already exists in Bayesnet: "+node.name) - if isinstance(node, primo2.nodes.DiscreteNode): + pass + if isinstance(node, DiscreteNode): self.random_nodes.append(node) - elif isinstance(node, primo2.nodes.UtilityNode): + elif isinstance(node, UtilityNode): self.utility_nodes.append(node) - elif isinstance(node, primo2.nodes.DecisionNode): + elif isinstance(node, DecisionNode): self.decision_nodes.append(node) else: raise Exception("Tried to add a node which the Bayesian Decision Network can not work with") - self.node_lookup[node.name]=node + self.node_lookup[node.name] = node else: raise Exception("Can only add 'Node' and its subclasses as nodes into the BayesianNetwork") - + + def add_nodes(self, nodes): + for i in nodes: + if isinstance(i, RandomNode): + if i.name in self.node_lookup.keys(): + pass + if isinstance(i, DiscreteNode): + self.random_nodes.append(i) + elif isinstance(i, UtilityNode): + self.utility_nodes.append(i) + elif isinstance(i, DecisionNode): + self.decision_nodes.append(i) + else: + raise Exception("Tried to add a node which the Bayesian Decision Network can not work with") + self.node_lookup[i.name] = i + else: + raise Exception("Can only add 'Node' and its subclasses as nodes into the BayesianNetwork") + + def copy_nodes_indexed(self, nodes_to_copy, index): + for i in nodes_to_copy: + if i.name.split("_", 1)[0] + "_" + str(index) not in self.get_all_node_names(): + if isinstance(i, RandomNode): + if isinstance(i, DiscreteNode): + new_node = DiscreteNode(i.name.split("_", 1)[0] + "_" + str(index), i.values) + self.random_nodes.append(new_node) + self.node_lookup[new_node.name] = new_node + elif isinstance(i, UtilityNode): + new_node = UtilityNode(i.name.split("_", 1)[0] + "_" + str(index)) + self.utility_nodes.append(new_node) + self.node_lookup[new_node.name] = new_node + + elif isinstance(i, DecisionNode): + new_node = DiscreteNode(i.name.split("_", 1)[0] + "_" + str(index), i.values) + self.decision_nodes.append(new_node) + self.node_lookup[new_node.name] = new_node + else: + raise Exception("Tried to copy a node which the Bayesian Decision Network can not work with") + + else: + raise Exception("Can only add 'Node' and its subclasses as nodes into the BayesianNetwork") + else: + raise Exception("A copy of this node already exists") + def add_edge(self, node_from, node_to): - + self.node_lookup[node_to].add_parent(self.node_lookup[node_from]) - - def get_PartialOrdering(self): - return self.partialOrdering - def set_PartialOrdering(self,partialOrder): - self.partialOrdering = partialOrder + def get_partial_ordering(self): + return self.partialOrdering - def get_all_nodes(self): + def set_partial_ordering(self, partial_order): + self.partialOrdering = partial_order + + def extend_partial_ordering(self, extension): + self.partialOrdering.append(extension) + + def set_partial_ordering_u(self, init_order, length): + + for i in range(0, length): + for j in init_order: + print(type(j)) + if isinstance(j, list): + if isinstance(self.node_lookup[j[0]], DiscreteNode): + temp = [value.split("_", 1)[0] + "_" + str(i) for value in j] + self.partialOrdering.append(temp) + elif i < length - 1 or i == 0: + temp = [value.split("_", 1)[0] + "_" + str(i) for value in j] + self.partialOrdering.append(temp) + elif isinstance(j, str): + print j + print(self.node_lookup) + if isinstance(self.node_lookup[j], DiscreteNode): + self.partialOrdering.append(j.split("_", 1)[0] + "_" + str(i)) + elif i < length - 1 or i == 0: + self.partialOrdering.append(j.split("_", 1)[0] + "_" + str(i)) + + def get_random_nodes(self): '''Returns all RandomNodes''' return self.random_nodes - def get_all_decision_nodes(self): + def get_decision_nodes(self): return self.decision_nodes - def get_all_utility_nodes(self): + def get_utility_nodes(self): return self.utility_nodes -class Two_TDN(DecisionNetwork): - def __init__(self, decisionnet=None): - super(Two_TDN, self).__init__() - - self.init_nodes = self.node_lookup - - - def set_conditional_distribution(): - - - - - - - - - - - + def get_all_nodes(self): + return self.random_nodes + self.decision_nodes + self.utility_nodes + def get_all_node_names(self): + return self.node_lookup.keys() diff --git a/primo2/nodes.py b/primo2/nodes.py index 40d05a9..0dc39a4 100644 --- a/primo2/nodes.py +++ b/primo2/nodes.py @@ -88,6 +88,9 @@ def __init__(self, nodename, values=("True", "False")): self.parentOrder = [] self.valid = False self._update_dimensions() + + def rename(self, new_name): + self.name = new_name def add_parent(self, parentNode): """ @@ -106,7 +109,7 @@ def add_parent(self, parentNode): self.parents[parentNode.name] = parentNode self.parentOrder.append(parentNode.name) self._update_dimensions() - + def set_values(self, new_values): """ Allows to change/set the values of this variable. This will @@ -421,7 +424,10 @@ def __init__(self, nodeName): self.parentOrder = [] self.utilities = self.cpd # Use different name for cpd self.valid = False - + + def rename(self, new_name): + self.name = new_name + def add_parent(self, parentNode): """ Adds the given node as a parent/cause node of this node. @@ -540,6 +546,8 @@ def __init__(self, nodeName, decisions=["Yes", "No"]): self.deterministic = True self._update_dimensions() + def rename(self, new_name): + self.name = new_name def add_parent(self, parentNode): """ @@ -597,11 +605,10 @@ def fully_mixed(self): if __name__ == "__main__": - d1 = DiscreteNode("a") - d1.set_cpd(np.array([0.3,0.7])) - d2 = DiscreteNode("b", []) - d2.add_parent(d1) - d2.set_cpd(np.array([100,10])) - - print(d2.cpd) + + costs = UtilityNode("costs") + costs_ = UtilityNode("costs_") + costs_.add_parent(costs) + + print(costs_.utilities) \ No newline at end of file From d6a52057b869b6dc94b02eb62ae1c91b5ea1a5e5 Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Mon, 9 Apr 2018 20:11:57 +0200 Subject: [PATCH 04/12] Old DBN Network added (commented). L2Tor example added (Still buggy) --- examples/decision_example.py | 274 ++++++++++++++++++++++++++--------- primo2/inference/decision.py | 103 +++++++------ primo2/networks.py | 228 +++++++++++++++++++++++++++-- primo2/nodes.py | 20 +++ 4 files changed, 488 insertions(+), 137 deletions(-) diff --git a/examples/decision_example.py b/examples/decision_example.py index 08bdb29..9c7e94d 100644 --- a/examples/decision_example.py +++ b/examples/decision_example.py @@ -154,8 +154,6 @@ # print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education_0"))) - - """ PHD + Startup example 7.4 from Bayesian Reasoning and Machine Learning - Barber """ @@ -174,7 +172,6 @@ # gains = UtilityNode("gains") #UB - # #Add nodes to network. They can be treated the same # net.add_node(education) # net.add_node(startup) @@ -249,12 +246,8 @@ # net2 = net.copy() - - - -#Define CPTs: (Needs to be done AFTER the structure is defined as that) -#determines the table structure for the different nodes - +# Define CPTs: (Needs to be done AFTER the structure is defined as that) +# determines the table structure for the different nodes # net.set_PartialOrdering([education, [income,nobel]]) @@ -279,7 +272,6 @@ # gains_ = UtilityNode("gains_") #UB - # d_net.two_tbn.add_node(education_) # d_net.two_tbn.add_node(income_) # d_net.two_tbn.add_node(nobel_) @@ -320,80 +312,218 @@ # gains_.set_utility(500001, parentValues={"gains"}) - - # ve = VariableElimination(net) # print("Expected Utility for doing a Phd: {}".format(ve.expected_utility(decisions={"education":"do Phd"}))) # print("Expected Utility for not doing a Phd: {}".format(ve.expected_utility(decisions={"education":"no Phd"}))) # print("Optimal deciosn: ", ve.get_optimal_decisions(["education"])) -#ccprint("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education"))) +# ccprint("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education"))) # ve = VariableElimination(d_net._b0) # print("Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup"))) -""" -Fever Example 4.3.5 from Bayesian Artificial Intelligence - Korb Nicholson -""" +# """ +# Fever Example 4.3.5 from Bayesian Artificial Intelligence - Korb Nicholson +# """ +# +# d0 = d0_net() +# +# flu_0 = DiscreteNode("flu_0", values=["True", "False"]) +# fever_0 = DiscreteNode("fever_0", values=["True", "False"]) +# therm_0 = DiscreteNode("therm_0", values=["True", "False"]) +# take_aspirin_0 = DecisionNode("take_aspirin_0", decisions=["Yes", "No"]) +# fever_later_0 = DiscreteNode("fever_later_0", values=["True", "False"]) +# reaction_0 = DiscreteNode("reaction_0", values=["Yes", "No"]) +# utility_0 = UtilityNode("utility_0") +# +# d0.add_nodes([flu_0, fever_0, therm_0, take_aspirin_0, fever_later_0, reaction_0, utility_0]) +# +# d0.add_edge(flu_0, fever_0) +# d0.add_edge(fever_0, therm_0) +# d0.add_edge(fever_0, fever_later_0) +# d0.add_edge(fever_later_0, utility_0) +# d0.add_edge(take_aspirin_0, reaction_0) +# d0.add_edge(take_aspirin_0, fever_later_0) +# d0.add_edge(reaction_0, utility_0) +# +# flu_0.set_probability("True", 0.05) +# flu_0.set_probability("False", 0.95) +# +# fever_0.set_probability("True", 0.95, parentValues={"flu_0": "True"}) +# fever_0.set_probability("True", 0.02, parentValues={"flu_0": "False"}) +# fever_0.set_probability("False", 0.05, parentValues={"flu_0": "True"}) +# fever_0.set_probability("False", 0.98, parentValues={"flu_0": "False"}) +# +# therm_0.set_probability("True", 0.90, parentValues={"fever_0": "True"}) +# therm_0.set_probability("True", 0.05, parentValues={"fever_0": "False"}) +# therm_0.set_probability("False", 0.10, parentValues={"fever_0": "True"}) +# therm_0.set_probability("False", 0.95, parentValues={"fever_0": "False"}) +# +# fever_later_0.set_probability("True", 0.05, parentValues={"fever_0": "True", "take_aspirin_0": "Yes"}) +# fever_later_0.set_probability("True", 0.90, parentValues={"fever_0": "True", "take_aspirin_0": "No"}) +# fever_later_0.set_probability("True", 0.01, parentValues={"fever_0": "False", "take_aspirin_0": "Yes"}) +# fever_later_0.set_probability("True", 0.02, parentValues={"fever_0": "False", "take_aspirin_0": "No"}) +# fever_later_0.set_probability("False", 0.95, parentValues={"fever_0": "True", "take_aspirin_0": "Yes"}) +# fever_later_0.set_probability("False", 0.10, parentValues={"fever_0": "True", "take_aspirin_0": "No"}) +# fever_later_0.set_probability("False", 0.99, parentValues={"fever_0": "False", "take_aspirin_0": "Yes"}) +# fever_later_0.set_probability("False", 0.98, parentValues={"fever_0": "False", "take_aspirin_0": "No"}) +# +# reaction_0.set_probability("Yes", 0.05, parentValues={"take_aspirin_0": "Yes"}) +# reaction_0.set_probability("Yes", 0.00, parentValues={"take_aspirin_0": "No"}) +# reaction_0.set_probability("No", 0.95, parentValues={"take_aspirin_0": "Yes"}) +# reaction_0.set_probability("No", 1.0, parentValues={"take_aspirin_0": "No"}) +# +# utility_0.set_utility(-50, {"fever_later_0": "True", "reaction_0": "Yes"}) +# utility_0.set_utility(-10, {"fever_later_0": "True", "reaction_0": "No"}) +# utility_0.set_utility(-30, {"fever_later_0": "False", "reaction_0": "Yes"}) +# utility_0.set_utility(50, {"fever_later_0": "False", "reaction_0": "No"}) +# +# d0.set_partial_ordering([["flu_0", "fever_0", "therm_0"], "take_aspirin_0", ["fever_later_0", "reaction_0"]]) +# +# ve = VariableElimination(d0) +# +# print("Expected Utility for Taking Aspirin: {}".format(ve.expected_utility(decisions={"take_aspirin_0": "Yes"}))) +# print("Expected Utility for not Taking Aspirin: {}".format(ve.expected_utility(decisions={"take_aspirin_0": "No"}))) +# print("Optimal decision: ", ve.get_optimal_decisions(["take_aspirin_0"])) +# print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("take_aspirin_0"))) + + +# L2Tor example d0 = d0_net() -flu_0 = DiscreteNode("flu_0", values=["True", "False"]) -fever_0 = DiscreteNode("fever_0", values=["True", "False"]) -therm_0 = DiscreteNode("therm_0", values=["True", "False"]) -take_aspirin_0 = DecisionNode("take_aspirin_0", decisions=["Yes", "No"]) -fever_later_0 = DiscreteNode("fever_later_0", values=["True", "False"]) -reaction_0 = DiscreteNode("reaction_0", values=["Yes", "No"]) -utility_0 = UtilityNode("utility_0") - -d0.add_nodes([flu_0, fever_0, therm_0, take_aspirin_0, fever_later_0, reaction_0, utility_0]) - -d0.add_edge(flu_0, fever_0) -d0.add_edge(fever_0, therm_0) -d0.add_edge(fever_0, fever_later_0) -d0.add_edge(fever_later_0, utility_0) -d0.add_edge(take_aspirin_0, reaction_0) -d0.add_edge(take_aspirin_0, fever_later_0) -d0.add_edge(reaction_0, utility_0) - -flu_0.set_probability("True", 0.05) -flu_0.set_probability("False", 0.95) - -fever_0.set_probability("True", 0.95, parentValues={"flu_0": "True"}) -fever_0.set_probability("True", 0.02, parentValues={"flu_0": "False"}) -fever_0.set_probability("False", 0.05, parentValues={"flu_0": "True"}) -fever_0.set_probability("False", 0.98, parentValues={"flu_0": "False"}) - -therm_0.set_probability("True", 0.90, parentValues={"fever_0": "True"}) -therm_0.set_probability("True", 0.05, parentValues={"fever_0": "False"}) -therm_0.set_probability("False", 0.10, parentValues={"fever_0": "True"}) -therm_0.set_probability("False", 0.95, parentValues={"fever_0": "False"}) - -fever_later_0.set_probability("True", 0.05, parentValues={"fever_0": "True", "take_aspirin_0": "Yes"}) -fever_later_0.set_probability("True", 0.90, parentValues={"fever_0": "True", "take_aspirin_0": "No"}) -fever_later_0.set_probability("True", 0.01, parentValues={"fever_0": "False", "take_aspirin_0": "Yes"}) -fever_later_0.set_probability("True", 0.02, parentValues={"fever_0": "False", "take_aspirin_0": "No"}) -fever_later_0.set_probability("False", 0.95, parentValues={"fever_0": "True", "take_aspirin_0": "Yes"}) -fever_later_0.set_probability("False", 0.10, parentValues={"fever_0": "True", "take_aspirin_0": "No"}) -fever_later_0.set_probability("False", 0.99, parentValues={"fever_0": "False", "take_aspirin_0": "Yes"}) -fever_later_0.set_probability("False", 0.98, parentValues={"fever_0": "False", "take_aspirin_0": "No"}) - -reaction_0.set_probability("Yes", 0.05, parentValues={"take_aspirin_0": "Yes"}) -reaction_0.set_probability("Yes", 0.00, parentValues={"take_aspirin_0": "No"}) -reaction_0.set_probability("No", 0.95, parentValues={"take_aspirin_0": "Yes"}) -reaction_0.set_probability("No", 1.0, parentValues={"take_aspirin_0": "No"}) - -utility_0.set_utility(-50, {"fever_later_0": "True", "reaction_0": "Yes"}) -utility_0.set_utility(-10, {"fever_later_0": "True", "reaction_0": "No"}) -utility_0.set_utility(-30, {"fever_later_0": "False", "reaction_0": "Yes"}) -utility_0.set_utility(50, {"fever_later_0": "False", "reaction_0": "No"}) - -d0.set_partial_ordering([["flu_0", "fever_0", "therm_0"], "take_aspirin_0", ["fever_later_0", "reaction_0"]]) +skill_0 = DiscreteNode("skill_0", values=[0, 1, 2, 3, 4, 5]) +action_0 = DecisionNode("action_0", decisions=["1", "2", "3", "4"]) +observation_0 = DiscreteNode("observation_0", values=["+O", "-O"]) -ve = VariableElimination(d0) +d0.add_nodes([skill_0, action_0, observation_0]) +d0.add_edge(skill_0, observation_0) +d0.add_edge(skill_0, action_0) +d0.add_edge(action_0, observation_0) + +d0.set_zero_timeslice([skill_0, observation_0, action_0]) + +d0.set_partial_ordering(["skill_0", "action_0", "observation_0"]) + +skill_0.set_cpd([1.0 / 6, 1.0 / 6, 1.0 / 6, 1.0 / 6, 1.0 / 6, 1.0 / 6]) + +action_0.set_cpd([[0.40, 0.30, 0.20, 0.15, 0.10, 0.05], + [0.30, 0.35, 0.40, 0.25, 0.25, 0.25], + [0.25, 0.25, 0.25, 0.40, 0.35, 0.30], + [0.05, 0.10, 0.15, 0.20, 0.30, 0.40]]) + +observation_0.set_cpd([[[0.50, 0.3, 0.25, 0.15], + [0.55, 0.40, 0.30, 0.20], + [0.65, 0.55, 0.40, 0.30], + [0.75, 0.65, 0.50, 0.40], + [0.85, 0.75, 0.60, 0.50], + [0.95, 0.85, 0.70, 0.60]], + [[0.50, 0.67, 0.75, 0.85], + [0.45, 0.60, 0.70, 0.80], + [0.35, 0.45, 0.60, 0.70], + [0.25, 0.35, 0.50, 0.60], + [0.15, 0.25, 0.40, 0.50], + [0.05, 0.15, 0.30, 0.40]]]) + +# print observation_0.get_probability("-O", {"skill_0": 4, "action_0": "1"}) + +# print observation_0.cpd +# +# print skill_0.cpd +# +# print action_0.cpd -print("Expected Utility for Taking Aspirin: {}".format(ve.expected_utility(decisions={"take_aspirin_0": "Yes"}))) -print("Expected Utility for not Taking Aspirin: {}".format(ve.expected_utility(decisions={"take_aspirin_0": "No"}))) -print("Optimal decision: ", ve.get_optimal_decisions(["take_aspirin_0"])) -print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("take_aspirin_0"))) \ No newline at end of file +two_tdn = Two_TDN() + +skill_t = DiscreteNode("skill_t", values=[0, 1, 2, 3, 4, 5]) + +action_t = DecisionNode("action_t", decisions=["1", "2", "3", "4"]) +observation_t = DiscreteNode("observation_t", values=["+O", "-O"]) + +skill_t_plus_one = DiscreteNode("skill_t_plus_one", values=[0, 1, 2, 3, 4, 5]) + +action_t_plus_one = DecisionNode("action_t_plus_one", decisions=["1", "2", "3", "4"]) +observation_t_plus_one = DiscreteNode("observation_t_plus_one", values=["+O", "-O"]) + +two_tdn.add_nodes([skill_t, action_t, observation_t, skill_t_plus_one, action_t_plus_one, observation_t_plus_one]) +two_tdn.set_t_timeslice([skill_t, action_t, observation_t]) +two_tdn.set_t_plus_one_timeslice([skill_t_plus_one, action_t_plus_one, observation_t_plus_one]) + +two_tdn.add_intra_edge(skill_t, action_t) +two_tdn.add_intra_edge(skill_t, observation_t) +two_tdn.add_intra_edge(action_t, observation_t) + +two_tdn.add_inter_edge(skill_t, skill_t_plus_one) +two_tdn.add_inter_edge(action_t, skill_t_plus_one) +two_tdn.add_inter_edge(observation_t, skill_t_plus_one) + +DDN = DynamicDecisionNetwork(d0, two_tdn) + +new_net = DDN.unroll(2) + +# print new_net.node_lookup["observation_1"].cpd + +ve = VariableElimination(d0) +# print("Optimal decision: ", ve.get_optimal_decisions(["action_0"])) +# print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("action_0"))) + +# self._t_s = {'+O': {"1": {0: {0: 0.60, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.25, 1: 0.70, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 2: {0: 0.15, 1: 0.20, 2: 0.80, 3: 0.00, 4: 0.00, 5: 0.00}, +# 3: {0: 0.00, 1: 0.10, 2: 0.15, 3: 0.85, 4: 0.00, 5: 0.00}, +# 4: {0: 0.00, 1: 0.00, 2: 0.05, 3: 0.10, 4: 0.90, 5: 0.00}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.05, 4: 0.10, 5: 1.00} +# }, +# "2": {0: {0: 0.55, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.35, 1: 0.60, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 2: {0: 0.10, 1: 0.30, 2: 0.70, 3: 0.00, 4: 0.00, 5: 0.00}, +# 3: {0: 0.00, 1: 0.10, 2: 0.20, 3: 0.70, 4: 0.00, 5: 0.00}, +# 4: {0: 0.00, 1: 0.00, 2: 0.10, 3: 0.25, 4: 0.75, 5: 0.00}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.05, 4: 0.25, 5: 1.00} +# }, +# "3": {0: {0: 0.40, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.40, 1: 0.40, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 2: {0: 0.20, 1: 0.40, 2: 0.40, 3: 0.00, 4: 0.00, 5: 0.00}, +# 3: {0: 0.00, 1: 0.20, 2: 0.40, 3: 0.50, 4: 0.00, 5: 0.00}, +# 4: {0: 0.00, 1: 0.00, 2: 0.20, 3: 0.40, 4: 0.60, 5: 0.00}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.10, 4: 0.40, 5: 1.00} +# }, +# "4": {0: {0: 0.30, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.45, 1: 0.30, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 2: {0: 0.20, 1: 0.45, 2: 0.30, 3: 0.00, 4: 0.00, 5: 0.00}, +# 3: {0: 0.05, 1: 0.20, 2: 0.45, 3: 0.30, 4: 0.00, 5: 0.00}, +# 4: {0: 0.00, 1: 0.05, 2: 0.20, 3: 0.45, 4: 0.40, 5: 0.00}, +# 5: {0: 0.00, 1: 0.00, 2: 0.05, 3: 0.25, 4: 0.60, 5: 1.00} +# } +# }, +# '-O': {"1": {0: {0: 1.00, 1: 0.10, 2: 0.05, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.00, 1: 0.90, 2: 0.10, 3: 0.15, 4: 0.00, 5: 0.00}, +# 2: {0: 0.00, 1: 0.00, 2: 0.85, 3: 0.25, 4: 0.20, 5: 0.00}, +# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.60, 4: 0.30, 5: 0.25}, +# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.50, 5: 0.35}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.40} +# }, +# "2": {0: {0: 1.00, 1: 0.25, 2: 0.05, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.00, 1: 0.75, 2: 0.25, 3: 0.10, 4: 0.00, 5: 0.00}, +# 2: {0: 0.00, 1: 0.00, 2: 0.70, 3: 0.25, 4: 0.10, 5: 0.00}, +# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.65, 4: 0.30, 5: 0.10}, +# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.60, 5: 0.35}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.55} +# }, +# "3": {0: {0: 1.00, 1: 0.20, 2: 0.20, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.00, 1: 0.80, 2: 0.40, 3: 0.20, 4: 0.00, 5: 0.00}, +# 2: {0: 0.00, 1: 0.00, 2: 0.40, 3: 0.40, 4: 0.20, 5: 0.00}, +# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.40, 4: 0.40, 5: 0.20}, +# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.40, 5: 0.40}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.40} +# }, +# "4": {0: {0: 1.00, 1: 0.40, 2: 0.15, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.00, 1: 0.60, 2: 0.35, 3: 0.15, 4: 0.00, 5: 0.00}, +# 2: {0: 0.00, 1: 0.00, 2: 0.50, 3: 0.35, 4: 0.15, 5: 0.00}, +# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.50, 4: 0.35, 5: 0.15}, +# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.50, 5: 0.35}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.50} +# } +# } +# } diff --git a/primo2/inference/decision.py b/primo2/inference/decision.py index 41c2b26..7bf22d6 100644 --- a/primo2/inference/decision.py +++ b/primo2/inference/decision.py @@ -6,7 +6,7 @@ @author: jpoeppel """ -from __future__ import division +from __future__ import division import functools @@ -15,18 +15,18 @@ from primo2.nodes import DiscreteNode, DecisionNode, UtilityNode import numpy as np + class VariableElimination(object): - + def __init__(self, decisionNetwork): self.net = decisionNetwork - + def get_decision(self, decisionNode, otherDecisions=None): """ """ pass - def _combine_factors(self, factors): """ Helper function to combine multiple joint factors. @@ -42,11 +42,12 @@ def _combine_factors(self, factors): A joint factor representing the combination of all the given factors. """ + def _combine_two_factors(f1, f2): - return (f1[0]*f2[0], f1[1]+f2[1]) - + return (f1[0] * f2[0], f1[1] + f2[1]) + return functools.reduce(_combine_two_factors, factors) - + def _marginalize_joint_factor(self, factor, variable): """ Helper function to marginalize a variable from a joint factor, @@ -66,7 +67,7 @@ def _marginalize_joint_factor(self, factor, variable): A joint factor with the given variable marginalized out. """ tmp = factor[0].marginalize(variable) - return (tmp, (factor[0]*factor[1]).marginalize(variable)/tmp) + return (tmp, (factor[0] * factor[1]).marginalize(variable) / tmp) def generalized_VE(self, joint_factors, elimination_variables): """ @@ -89,20 +90,20 @@ def generalized_VE(self, joint_factors, elimination_variables): utility factor over all the variables from the initial joint_factors which have not been eliminated. """ - + working_factors = set(joint_factors) for var in elimination_variables: relevant_factors = set([f for f in working_factors if var in f[0].variableOrder]) tmp_factor = self._combine_factors(relevant_factors) marginalized_factor = self._marginalize_joint_factor(tmp_factor, var) - + working_factors = (working_factors - relevant_factors) working_factors.add(marginalized_factor) - + res_factor = self._combine_factors(working_factors) - + return res_factor - + def expected_utility(self, decisions=None): """ Computes the expected utility fo the decision network, given the @@ -122,20 +123,20 @@ def expected_utility(self, decisions=None): """ if decisions is None: decisions = {} - #Set given decisions + # Set given decisions for decision in decisions: decisionNode = self.net.node_lookup[decision] -# decisionNode.clear() + # decisionNode.clear() decisionNode.set_decision(decisions[decision]) - - #Create joint factors + + # Create joint factors factors = set([]) for node in self.net.node_lookup.values(): factors.add(Factor.joint_factor(node)) - + eliminations = [node for node in self.net.node_lookup.values() if not isinstance(node, UtilityNode)] return self.generalized_VE(factors, eliminations)[1].potentials - + def _optimize_locally(self, decisionNodeName): """ Helper function to choose the optimal decision for the given @@ -151,19 +152,18 @@ def _optimize_locally(self, decisionNodeName): string The optimal decision for this node """ - nodes = [node for node in self.net.node_lookup.keys() - if node != decisionNodeName and - not isinstance(self.net.node_lookup[node], UtilityNode) and - node not in self.net.node_lookup[decisionNodeName].parents] + nodes = [node for node in self.net.node_lookup.keys() + if node != decisionNodeName and + not isinstance(self.net.node_lookup[node], UtilityNode) and + node not in self.net.node_lookup[decisionNodeName].parents] decisionNode = self.net.node_lookup[decisionNodeName] - #Create joint factors + # Create joint factors factors = set([]) for node in self.net.node_lookup.values(): factors.add(Factor.joint_factor(node)) - + reducedFactor = self.generalized_VE(factors, nodes) - - + argmax = None maxVal = None for decision in decisionNode.values: @@ -171,10 +171,9 @@ def _optimize_locally(self, decisionNodeName): if maxVal is None or eu > maxVal: maxVal = eu argmax = decision - + return argmax - - + def get_optimal_decisions(self, decisionOrder, fixedDecisions=None): """ Iteratively computes the optimal decisions for all given decision @@ -201,23 +200,22 @@ def get_optimal_decisions(self, decisionOrder, fixedDecisions=None): """ if fixedDecisions is None: fixedDecisions = {} - - #Initialize random fully mixed strategy + + # Initialize random fully mixed strategy for decisionNode in decisionOrder: if not decisionNode in fixedDecisions: self.net.node_lookup[decisionNode].fully_mixed() else: self.net.node_lookup[decisionNode].set_decision(fixedDecisions[decisionNode]) - + solution = {} for decisionNode in decisionOrder: localDecision = self._optimize_locally(decisionNode) solution[decisionNode] = localDecision - + return solution - - def inner_product(self, factors, utilities): + def inner_product(self, factors, utilities=None): """ Helper Function to multiply all factors in the given list. This function is guided by the Algorithm 7.3.2 at page 112 in "Bayesian Reasoning and Machine Learning" @@ -240,23 +238,28 @@ def inner_product(self, factors, utilities): factor The resulting factor """ + prob_product = factors[0] - utility_factors = [Factor.from_utility_node(i) for i in utilities] - utility_sum = utility_factors[0] for i, v in enumerate(factors): - if i != len(factors)-1: - prob_product = prob_product*factors[i+1] + if i != len(factors) - 1: + prob_product = prob_product * factors[i + 1] + + if utilities: + utility_factors = [Factor.from_utility_node(i) for i in utilities] + utility_sum = utility_factors[0] - for i, v in enumerate(utility_factors): - if i != len(utility_factors)-1: - utility_sum = utility_sum+utility_factors[i+1] + for i, v in enumerate(utility_factors): + if i != len(utility_factors) - 1: + utility_sum = utility_sum + utility_factors[i + 1] - result = prob_product*utility_sum + return prob_product * utility_sum - return result + else: - def max_sum(self,decisionNode): + return prob_product + + def max_sum(self, decisionNode): """ Max Sum Algorithm taken from the Algorithm 7.3.2 at page 112 in "Bayesian Reasoning and Machine Learning" from David Barber. @@ -277,7 +280,7 @@ def max_sum(self,decisionNode): reverseOrder = partialOrder[::-1] randomVariables = self.net.get_random_nodes() utilities = self.net.get_utility_nodes() - + factors = [] for node in randomVariables: factors.append(Factor.from_node(node)) @@ -293,9 +296,5 @@ def max_sum(self,decisionNode): current = current.marginalize(i) elif i != decisionNode: current = current.maximize(i) - - return [current.values.values()[0][np.argmax(current.potentials)], max(current.potentials)] - - - + return [current.values.values()[0][np.argmax(current.potentials)], max(current.potentials)] diff --git a/primo2/networks.py b/primo2/networks.py index 6cd4d65..e28b352 100644 --- a/primo2/networks.py +++ b/primo2/networks.py @@ -185,6 +185,184 @@ def __len__(self): return len(self.graph) +# class DynamicBayesianNetwork(object): +# """Class representing a the structure of a dynamic Bayesian network. +# +# This temporal relationship is modelled as a 2-time-slice Bayesian +# network (2-TBN; Koller & Friedman, 2009, §6.2.2). The Bayesian network +# B_0 represents the initial distribution. The Bayesian network B_{->}, +# a 2-TBN, represents the process. +# """ +# +# def __init__(self, b0=None, two_tbn=None, transitions=None): +# """Create a dynamic Bayesian network. +# +# Parameters +# ---------- +# b0 : BayesianNetwork +# The network representing the initial distribution. +# two_tbn : BayesianNetwork +# The two-time-slice network representing the process. +# transitions : [(node, node_p),] +# A list of pairs, each of which represents one transition. +# See add_transition for more information. +# """ +# super(DynamicBayesianNetwork, self).__init__() +# self._b0 = BayesianNetwork() if b0 is None else b0 +# self._two_tbn = BayesianNetwork() if two_tbn is None else two_tbn +# self._transitions = [] +# if transitions is not None: +# self.add_transitions(transitions) +# +# @property +# def b0(self): +# """Get the Bayesian network B_0. +# +# Returns +# ------- +# BayesianNetwork +# The network representing the initial distribution. +# """ +# return self._b0 +# +# @b0.setter +# def b0(self, bn): +# """Set the Bayesian network B_0. +# +# Parameters +# ---------- +# bn : BayesianNetwork +# The network representing the initial distribution. +# """ +# self._b0 = bn +# +# @property +# def two_tbn(self): +# """Get the 2-TBN B_{->}. +# +# Returns +# ------- +# BayesianNetwork +# The two-time-slice network representing the process. +# """ +# return self._two_tbn +# +# @two_tbn.setter +# def two_tbn(self, bn): +# """Set the 2-TBN B_{->}. +# +# Parameters +# ---------- +# bn : BayesianNetwork +# The two-time-slice network representing the process. +# """ +# self._two_tbn = bn +# +# def add_transition(self, node, node_p): +# """Add a transition connecting nodes when unrolling the network. +# +# The transition is a directed edge from node X_i (`node`) to node X_i' +# (`node_p`). +# +# Parameters +# ---------- +# node : RandomNode, String +# The node X_i in the next time-slice. +# +# node_p: RandomNode, String +# The corresponding node X_i' in the current time-slice. +# +# Raises +# ------ +# primo2.exceptions.StructureError +# If a node specified in the transition model cannot be found in the +# corresponding networks. +# """ +# if node_p not in self._b0.get_all_nodes(): +# raise exceptions.StructureError( +# 'Node "{}" is not found in B_0 network "{}".'.format( +# node_p, self._b0.name)) +# if node_p not in self._two_tbn.get_all_nodes(): +# raise exceptions.StructureError( +# 'Node "{}" is not found in B_{->} network "{}".'.format( +# node_p, self._two_tbn.name)) +# if node not in self._two_tbn.get_all_nodes(): +# raise exceptions.StructureError( +# 'Node "{}" is not found in B_{{->}} network "{}".'.format( +# node, self._two_tbn.name)) +# self._transitions.append((node, node_p)) +# +# def add_transitions(self, transitions): +# """Add multiple transitions connecting nodes when unrolling the network. +# +# Parameters +# ---------- +# transitions : [(node, node_p),] +# A list of pairs, each of which represents one transition. +# See add_transition for more information. +# """ +# for transition in transitions: +# self.add_transition(transition[0], transition[1]) +# +# @property +# def transitions(self): +# """Get the transition model. +# +# Returns +# ------- +# [(node, node_p),] +# A list of pairs, each of which represents one transition. +# See add_transition for more information. +# """ +# return self._transitions +# +# +# class DecisionNetwork(object): +# +# def __init__(self): +# self.node_lookup = {} +# self.partialOrdering = [] +# self.random_nodes = [] +# self.decision_nodes = [] +# self.utility_nodes = [] +# +# def add_node(self, node): +# if isinstance(node, primo2.nodes.RandomNode): +# if node.name in self.node_lookup.keys(): +# raise Exception("Node name already exists in Bayesnet: " + node.name) +# if isinstance(node, primo2.nodes.DiscreteNode): +# self.random_nodes.append(node) +# elif isinstance(node, primo2.nodes.UtilityNode): +# self.utility_nodes.append(node) +# elif isinstance(node, primo2.nodes.DecisionNode): +# self.decision_nodes.append(node) +# else: +# raise Exception("Tried to add a node which the Bayesian Decision Network can not work with") +# self.node_lookup[node.name] = node +# else: +# raise Exception("Can only add 'Node' and its subclasses as nodes into the BayesianNetwork") +# +# def add_edge(self, node_from, node_to): +# +# self.node_lookup[node_to].add_parent(self.node_lookup[node_from]) +# +# def get_PartialOrdering(self): +# return self.partialOrdering +# +# def set_PartialOrdering(self, partialOrder): +# self.partialOrdering = partialOrder +# +# def get_all_nodes(self): +# '''Returns all RandomNodes''' +# return self.random_nodes +# +# def get_all_decision_nodes(self): +# return self.decision_nodes +# +# def get_all_utility_nodes(self): +# return self.utility_nodes + + class DynamicDecisionNetwork(object): """Class representing a the structure of a dynamic Decision network. This temporal relationship is modelled as a 2-time-slice Decision @@ -233,10 +411,27 @@ def unroll(self, length): """Connect copied nodes via inter edges like described in the two tdn""" unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) + for j in self._two_tdn._intra_edges: + parent_node_name = j[0].name.split("_", 1)[0] + "_" + str(i) + child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) + """Connect copied nodes via inter edges like described in the two tdn""" + unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], + unrolled_net.node_lookup[child_node_name]) + print "child" + print child_node_name + print unrolled_net.node_lookup[child_node_name].parents + # print self._d0.node_lookup[j[1].name.split("_", 1)[0] + "_" + str(0)].cpd + unrolled_net.node_lookup[child_node_name].set_cpd( + self._d0.node_lookup[j[1].name.split("_", 1)[0] + + "_" + str(0)].cpd) + print "copied" else: unrolled_net.copy_nodes_indexed(t_plus_1_slice, i) - unrolled_net.copy_nodes_indexed(self._two_tdn.decision_nodes, i - 1) + # in case we dont want the decisions to be copied for every new timeslice but only for every + # two. Write this and leave the decisions node out of the timeslices in both d0 and two_tbn network: + # unrolled_net.copy_nodes_indexed(self._two_tdn.decision_nodes, i - 1) + unrolled_net.copy_nodes_indexed(self._two_tdn.utility_nodes, i - 1) for j in self._two_tdn._inter_edges: @@ -246,10 +441,13 @@ def unroll(self, length): unrolled_net.node_lookup[child_node_name]) for j in self._two_tdn._intra_edges: - parent_node_name = j[0].name.split("_", 1)[0] + "_" + str(i - 1) - child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i - 1) + parent_node_name = j[0].name.split("_", 1)[0] + "_" + str(i) + child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) + unrolled_net.node_lookup[child_node_name].set_cpd( + self._d0.node_lookup[j[1].name.split("_", 1)[0] + + "_" + str(0)].cpd) unrolled_net.set_partial_ordering_u(self._d0.get_partial_ordering(), length) else: @@ -531,21 +729,25 @@ def set_partial_ordering_u(self, init_order, length): for i in range(0, length): for j in init_order: - print(type(j)) + # print(type(j)) if isinstance(j, list): - if isinstance(self.node_lookup[j[0]], DiscreteNode): - temp = [value.split("_", 1)[0] + "_" + str(i) for value in j] - self.partialOrdering.append(temp) - elif i < length - 1 or i == 0: + if isinstance(self.node_lookup[j[0]], DiscreteNode) or \ + isinstance(self.node_lookup[j[0]], DecisionNode): temp = [value.split("_", 1)[0] + "_" + str(i) for value in j] self.partialOrdering.append(temp) + # Write this if decisions are over 2 timeslices + # elif i < length - 1 or i == 0 or decisions: + # temp = [value.split("_", 1)[0] + "_" + str(i) for value in j] + # self.partialOrdering.append(temp) elif isinstance(j, str): - print j - print(self.node_lookup) - if isinstance(self.node_lookup[j], DiscreteNode): - self.partialOrdering.append(j.split("_", 1)[0] + "_" + str(i)) - elif i < length - 1 or i == 0: + # print j + # print(self.node_lookup) + if isinstance(self.node_lookup[j], DiscreteNode) or \ + isinstance(self.node_lookup[j], DecisionNode): self.partialOrdering.append(j.split("_", 1)[0] + "_" + str(i)) + # Write this if decisions are over 2 timeslices + # elif i < length - 1 or i == 0: + # self.partialOrdering.append(j.split("_", 1)[0] + "_" + str(i)) def get_random_nodes(self): '''Returns all RandomNodes''' diff --git a/primo2/nodes.py b/primo2/nodes.py index 0dc39a4..0fc3bf0 100644 --- a/primo2/nodes.py +++ b/primo2/nodes.py @@ -545,6 +545,7 @@ def __init__(self, nodeName, decisions=["Yes", "No"]): self.parents = {} self.deterministic = True self._update_dimensions() + self.valid = False def rename(self, new_name): self.name = new_name @@ -602,6 +603,25 @@ def set_decision(self, decision): def fully_mixed(self): self.cpd = np.ones(self.cpd.shape) self.cpd /= len(self.values) + + def set_cpd(self, cpd): + """ + Allows to set the conditional probability density(table) of this + node directly. Will check that the dimensions of the given cpd + is comform to the current dependency structure but will not perform + any tests on the actual values. + + Parameters + ---------- + cpd : np.array + Table containing the conditional probabilities. Each variable + is represented by a dimension in the size of the number of its + outcomes. + """ + if np.shape(self.cpd) != np.shape(cpd): + raise ValueError("The dimensions of the given cpd do not match the dependency structure of the node.") + self.cpd = np.copy(cpd) + self.valid = True if __name__ == "__main__": From c5e05eaa6387490594ded46edb31119d1348c20b Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Wed, 11 Apr 2018 10:49:01 +0200 Subject: [PATCH 05/12] L2tor example fixed. Unrolling is functioning with this example. Next: testing max sum and adding transition probability --- examples/decision_example.py | 5 +++-- primo2/networks.py | 22 ++++++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/examples/decision_example.py b/examples/decision_example.py index 9c7e94d..ba81a62 100644 --- a/examples/decision_example.py +++ b/examples/decision_example.py @@ -460,9 +460,10 @@ DDN = DynamicDecisionNetwork(d0, two_tdn) -new_net = DDN.unroll(2) +new_net = DDN.unroll(5) -# print new_net.node_lookup["observation_1"].cpd +print new_net.node_lookup["observation_3"].cpd +print new_net.get_partial_ordering() ve = VariableElimination(d0) # print("Optimal decision: ", ve.get_optimal_decisions(["action_0"])) diff --git a/primo2/networks.py b/primo2/networks.py index e28b352..3ef5e59 100644 --- a/primo2/networks.py +++ b/primo2/networks.py @@ -414,17 +414,27 @@ def unroll(self, length): for j in self._two_tdn._intra_edges: parent_node_name = j[0].name.split("_", 1)[0] + "_" + str(i) child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) - """Connect copied nodes via inter edges like described in the two tdn""" + """Connect copied nodes via intra edges like described in the two tdn""" unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) - print "child" + print "Set CPD for:" print child_node_name + print "Which has the initial CPD:" + print unrolled_net.node_lookup[child_node_name].cpd + print "And his parents are:" print unrolled_net.node_lookup[child_node_name].parents - # print self._d0.node_lookup[j[1].name.split("_", 1)[0] + "_" + str(0)].cpd + print "Setting CPD to the following: " + print self._d0.node_lookup[j[1].name.split("_", 1)[0] + "_" + str(0)].cpd + # unrolled_net.node_lookup[child_node_name].set_cpd( + # self._d0.node_lookup[j[1].name.split("_", 1)[0] + # + "_" + str(0)].cpd) + print "copied" + for j in self._two_tdn._intra_edges: + child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) + unrolled_net.node_lookup[child_node_name].set_cpd( self._d0.node_lookup[j[1].name.split("_", 1)[0] + "_" + str(0)].cpd) - print "copied" else: unrolled_net.copy_nodes_indexed(t_plus_1_slice, i) @@ -445,6 +455,10 @@ def unroll(self, length): child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) + + for j in self._two_tdn._intra_edges: + child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) + unrolled_net.node_lookup[child_node_name].set_cpd( self._d0.node_lookup[j[1].name.split("_", 1)[0] + "_" + str(0)].cpd) From c1411b2460918d0a0e4133159fd34cf7e7189d43 Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Fri, 13 Apr 2018 12:00:02 +0200 Subject: [PATCH 06/12] Some code fixing: More readability + comments and slight bugfixing --- examples/decision_example.py | 170 +++++++++++++++++++++-------------- primo2/inference/decision.py | 1 - primo2/networks.py | 119 +++++++++++++----------- primo2/nodes.py | 148 +++++++++++++++--------------- 4 files changed, 248 insertions(+), 190 deletions(-) diff --git a/examples/decision_example.py b/examples/decision_example.py index ba81a62..325432d 100644 --- a/examples/decision_example.py +++ b/examples/decision_example.py @@ -426,14 +426,6 @@ [0.15, 0.25, 0.40, 0.50], [0.05, 0.15, 0.30, 0.40]]]) -# print observation_0.get_probability("-O", {"skill_0": 4, "action_0": "1"}) - -# print observation_0.cpd -# -# print skill_0.cpd -# -# print action_0.cpd - two_tdn = Two_TDN() skill_t = DiscreteNode("skill_t", values=[0, 1, 2, 3, 4, 5]) @@ -446,6 +438,43 @@ action_t_plus_one = DecisionNode("action_t_plus_one", decisions=["1", "2", "3", "4"]) observation_t_plus_one = DiscreteNode("observation_t_plus_one", values=["+O", "-O"]) +cpd = [[[[0.60, 1.00], [0.55, 1.00], [0.40, 1.00], [0.30, 1.00]], + [[0.25, 0], [0.35, 0], [0.40, 0], [0.45, 0]], + [[0.15, 0], [0.10, 0], [0.20, 0], [0.20, 0]], + [[0, 0], [0, 0], [0, 0], [0.05, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0]]], + [[[0, 0.10], [0, 0.25], [0, 0.20], [0, 0.40]], + [[0.70, 0.90], [0.60, 0.75], [0.40, 0.80], [0.30, 0.60]], + [[0.20, 0], [0.30, 0], [0.40, 0], [0.45, 0]], + [[0.10, 0], [0.10, 0], [0.20, 0], [0.20, 0]], + [[0, 0], [0, 0], [0, 0], [0.05, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0]]], + [[[0, 0.05], [0, 0.05], [0, 0.20], [0, 0.15]], + [[0, 0.10], [0, 0.25], [0, 0.40], [0, 0.35]], + [[0, 0.25], [0, 0.25], [0, 0.40], [0, 0.35]], + [[0.85, 0.60], [0.70, 0.65], [0.50, 0.40], [0.30, 0.50]], + [[0.10, 0], [0.25, 0], [0.40, 0], [0.45, 0]], + [[0.05, 0], [0.05, 0], [0.10, 0], [0.25, 0]]], + [[[0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0.15], [0, 0.10], [0, 0.20], [0, 0.15]], + [[0, 0.25], [0, 0.25], [0, 0.40], [0, 0.35]], + [[0.85, 0.60], [0.70, 0.65], [0.50, 0.40], [0.30, 0.35]], + [[0.10, 0], [0.25, 0], [0.40, 0], [0.45, 0.]], + [[0.05, 0], [0.05, 0], [0.10, 0], [0.25, 0]]], + [[[0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0.25], [0, 0.10], [0, 0.20], [0, 0.15]], + [[0, 0.35], [0, 0.35], [0, 0.40], [0, 0.35]], + [[1.0, 0.40], [1.0, 0.55], [1.0, 0.40], [1.0, 0.50]]], + [[[0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0.15], [0, 0.10], [0, 0.20], [0, 0.15]], + [[0, 0.25], [0, 0.25], [0, 0.40], [0, 0.35]], + [[0.85, 0.60], [0.70, 0.65], [0.50, 0.40], [0.30, 0.35]], + [[0, 0], [0, 0], [0, 0], [0, 0]], + [[0, 0], [0, 0], [0, 0], [0, 0]]]] + two_tdn.add_nodes([skill_t, action_t, observation_t, skill_t_plus_one, action_t_plus_one, observation_t_plus_one]) two_tdn.set_t_timeslice([skill_t, action_t, observation_t]) two_tdn.set_t_plus_one_timeslice([skill_t_plus_one, action_t_plus_one, observation_t_plus_one]) @@ -458,73 +487,78 @@ two_tdn.add_inter_edge(action_t, skill_t_plus_one) two_tdn.add_inter_edge(observation_t, skill_t_plus_one) +two_tdn.add_transition(skill_t_plus_one, cpd) + + +# two_tdn.set_transition_probability(skill_t_plus_one, ) + DDN = DynamicDecisionNetwork(d0, two_tdn) -new_net = DDN.unroll(5) +new_net = DDN.unroll(2) -print new_net.node_lookup["observation_3"].cpd +print new_net.node_lookup["observation_1"].cpd print new_net.get_partial_ordering() -ve = VariableElimination(d0) +# ve = VariableElimination(new_net) # print("Optimal decision: ", ve.get_optimal_decisions(["action_0"])) # print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("action_0"))) -# self._t_s = {'+O': {"1": {0: {0: 0.60, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.25, 1: 0.70, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 2: {0: 0.15, 1: 0.20, 2: 0.80, 3: 0.00, 4: 0.00, 5: 0.00}, -# 3: {0: 0.00, 1: 0.10, 2: 0.15, 3: 0.85, 4: 0.00, 5: 0.00}, -# 4: {0: 0.00, 1: 0.00, 2: 0.05, 3: 0.10, 4: 0.90, 5: 0.00}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.05, 4: 0.10, 5: 1.00} -# }, -# "2": {0: {0: 0.55, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.35, 1: 0.60, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 2: {0: 0.10, 1: 0.30, 2: 0.70, 3: 0.00, 4: 0.00, 5: 0.00}, -# 3: {0: 0.00, 1: 0.10, 2: 0.20, 3: 0.70, 4: 0.00, 5: 0.00}, -# 4: {0: 0.00, 1: 0.00, 2: 0.10, 3: 0.25, 4: 0.75, 5: 0.00}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.05, 4: 0.25, 5: 1.00} -# }, -# "3": {0: {0: 0.40, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.40, 1: 0.40, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 2: {0: 0.20, 1: 0.40, 2: 0.40, 3: 0.00, 4: 0.00, 5: 0.00}, -# 3: {0: 0.00, 1: 0.20, 2: 0.40, 3: 0.50, 4: 0.00, 5: 0.00}, -# 4: {0: 0.00, 1: 0.00, 2: 0.20, 3: 0.40, 4: 0.60, 5: 0.00}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.10, 4: 0.40, 5: 1.00} -# }, -# "4": {0: {0: 0.30, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.45, 1: 0.30, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 2: {0: 0.20, 1: 0.45, 2: 0.30, 3: 0.00, 4: 0.00, 5: 0.00}, -# 3: {0: 0.05, 1: 0.20, 2: 0.45, 3: 0.30, 4: 0.00, 5: 0.00}, -# 4: {0: 0.00, 1: 0.05, 2: 0.20, 3: 0.45, 4: 0.40, 5: 0.00}, -# 5: {0: 0.00, 1: 0.00, 2: 0.05, 3: 0.25, 4: 0.60, 5: 1.00} -# } +# new = {'+O': {"1": {0: {0: 0.60, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.25, 1: 0.70, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 2: {0: 0.15, 1: 0.20, 2: 0.80, 3: 0.00, 4: 0.00, 5: 0.00}, +# 3: {0: 0.00, 1: 0.10, 2: 0.15, 3: 0.85, 4: 0.00, 5: 0.00}, +# 4: {0: 0.00, 1: 0.00, 2: 0.05, 3: 0.10, 4: 0.90, 5: 0.00}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.05, 4: 0.10, 5: 1.00} +# }, +# "2": {0: {0: 0.55, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.35, 1: 0.60, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 2: {0: 0.10, 1: 0.30, 2: 0.70, 3: 0.00, 4: 0.00, 5: 0.00}, +# 3: {0: 0.00, 1: 0.10, 2: 0.20, 3: 0.70, 4: 0.00, 5: 0.00}, +# 4: {0: 0.00, 1: 0.00, 2: 0.10, 3: 0.25, 4: 0.75, 5: 0.00}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.05, 4: 0.25, 5: 1.00} +# }, +# "3": {0: {0: 0.40, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.40, 1: 0.40, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 2: {0: 0.20, 1: 0.40, 2: 0.40, 3: 0.00, 4: 0.00, 5: 0.00}, +# 3: {0: 0.00, 1: 0.20, 2: 0.40, 3: 0.50, 4: 0.00, 5: 0.00}, +# 4: {0: 0.00, 1: 0.00, 2: 0.20, 3: 0.40, 4: 0.60, 5: 0.00}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.10, 4: 0.40, 5: 1.00} +# }, +# "4": {0: {0: 0.30, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.45, 1: 0.30, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, +# 2: {0: 0.20, 1: 0.45, 2: 0.30, 3: 0.00, 4: 0.00, 5: 0.00}, +# 3: {0: 0.05, 1: 0.20, 2: 0.45, 3: 0.30, 4: 0.00, 5: 0.00}, +# 4: {0: 0.00, 1: 0.05, 2: 0.20, 3: 0.45, 4: 0.40, 5: 0.00}, +# 5: {0: 0.00, 1: 0.00, 2: 0.05, 3: 0.25, 4: 0.60, 5: 1.00} +# } +# }, +# '-O': {"1": {0: {0: 1.00, 1: 0.10, 2: 0.05, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.00, 1: 0.90, 2: 0.10, 3: 0.15, 4: 0.00, 5: 0.00}, +# 2: {0: 0.00, 1: 0.00, 2: 0.85, 3: 0.25, 4: 0.20, 5: 0.00}, +# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.60, 4: 0.30, 5: 0.25}, +# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.50, 5: 0.35}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.40} +# }, +# "2": {0: {0: 1.00, 1: 0.25, 2: 0.05, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.00, 1: 0.75, 2: 0.25, 3: 0.10, 4: 0.00, 5: 0.00}, +# 2: {0: 0.00, 1: 0.00, 2: 0.70, 3: 0.25, 4: 0.10, 5: 0.00}, +# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.65, 4: 0.30, 5: 0.10}, +# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.60, 5: 0.35}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.55} +# }, +# "3": {0: {0: 1.00, 1: 0.20, 2: 0.20, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.00, 1: 0.80, 2: 0.40, 3: 0.20, 4: 0.00, 5: 0.00}, +# 2: {0: 0.00, 1: 0.00, 2: 0.40, 3: 0.40, 4: 0.20, 5: 0.00}, +# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.40, 4: 0.40, 5: 0.20}, +# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.40, 5: 0.40}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.40} # }, -# '-O': {"1": {0: {0: 1.00, 1: 0.10, 2: 0.05, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.00, 1: 0.90, 2: 0.10, 3: 0.15, 4: 0.00, 5: 0.00}, -# 2: {0: 0.00, 1: 0.00, 2: 0.85, 3: 0.25, 4: 0.20, 5: 0.00}, -# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.60, 4: 0.30, 5: 0.25}, -# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.50, 5: 0.35}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.40} -# }, -# "2": {0: {0: 1.00, 1: 0.25, 2: 0.05, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.00, 1: 0.75, 2: 0.25, 3: 0.10, 4: 0.00, 5: 0.00}, -# 2: {0: 0.00, 1: 0.00, 2: 0.70, 3: 0.25, 4: 0.10, 5: 0.00}, -# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.65, 4: 0.30, 5: 0.10}, -# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.60, 5: 0.35}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.55} -# }, -# "3": {0: {0: 1.00, 1: 0.20, 2: 0.20, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.00, 1: 0.80, 2: 0.40, 3: 0.20, 4: 0.00, 5: 0.00}, -# 2: {0: 0.00, 1: 0.00, 2: 0.40, 3: 0.40, 4: 0.20, 5: 0.00}, -# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.40, 4: 0.40, 5: 0.20}, -# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.40, 5: 0.40}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.40} -# }, -# "4": {0: {0: 1.00, 1: 0.40, 2: 0.15, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.00, 1: 0.60, 2: 0.35, 3: 0.15, 4: 0.00, 5: 0.00}, -# 2: {0: 0.00, 1: 0.00, 2: 0.50, 3: 0.35, 4: 0.15, 5: 0.00}, -# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.50, 4: 0.35, 5: 0.15}, -# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.50, 5: 0.35}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.50} -# } +# "4": {0: {0: 1.00, 1: 0.40, 2: 0.15, 3: 0.00, 4: 0.00, 5: 0.00}, +# 1: {0: 0.00, 1: 0.60, 2: 0.35, 3: 0.15, 4: 0.00, 5: 0.00}, +# 2: {0: 0.00, 1: 0.00, 2: 0.50, 3: 0.35, 4: 0.15, 5: 0.00}, +# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.50, 4: 0.35, 5: 0.15}, +# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.50, 5: 0.35}, +# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.50} # } -# } +# } +# } diff --git a/primo2/inference/decision.py b/primo2/inference/decision.py index 7bf22d6..1744bb1 100644 --- a/primo2/inference/decision.py +++ b/primo2/inference/decision.py @@ -296,5 +296,4 @@ def max_sum(self, decisionNode): current = current.marginalize(i) elif i != decisionNode: current = current.maximize(i) - return [current.values.values()[0][np.argmax(current.potentials)], max(current.potentials)] diff --git a/primo2/networks.py b/primo2/networks.py index 3ef5e59..57bb9b3 100644 --- a/primo2/networks.py +++ b/primo2/networks.py @@ -396,74 +396,69 @@ def unroll(self, length): unrolled_net = DecisionNetwork() - zero_slice = self._d0.timeslice[0] - t_plus_1_slice = self._two_tdn.timeslice['t_plus_1'] if length > 0: for i in range(0, length): if i == 0: - unrolled_net.add_nodes(zero_slice + self._d0.decision_nodes + self._d0.utility_nodes) + unrolled_net.add_nodes(self._d0.timeslice[0] + self._d0.utility_nodes) elif i == 1: """copy nodes from the two_tdn Network into unrolled_net""" - unrolled_net.copy_nodes_indexed(t_plus_1_slice, i) - for j in self._two_tdn._inter_edges: - parent_node_name = j[0].name.split("_", 1)[0] + "_" + str(i - 1) - child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) + unrolled_net.copy_nodes_indexed(self._two_tdn.timeslice['t_plus_1'], i) + for j in self._two_tdn.get_inter_edges(): + parent_node_name = j[0].get_indexed_node(i - 1) + child_node_name = j[1].get_indexed_node(i) """Connect copied nodes via inter edges like described in the two tdn""" unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) - for j in self._two_tdn._intra_edges: - parent_node_name = j[0].name.split("_", 1)[0] + "_" + str(i) - child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) + "Copy the CPD's describing the transition probability (defined by the two_tdn) into the new net" + for j in self._two_tdn.transitions.keys(): + node_name = j.get_indexed_node(i) + + unrolled_net.node_lookup[node_name].set_cpd(self._two_tdn.get_transition_probability(j)) + for j in self._two_tdn.get_intra_edges(): + parent_node_name = j[0].get_indexed_node(i) + child_node_name = j[1].get_indexed_node(i) """Connect copied nodes via intra edges like described in the two tdn""" unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) - print "Set CPD for:" - print child_node_name - print "Which has the initial CPD:" - print unrolled_net.node_lookup[child_node_name].cpd - print "And his parents are:" - print unrolled_net.node_lookup[child_node_name].parents - print "Setting CPD to the following: " - print self._d0.node_lookup[j[1].name.split("_", 1)[0] + "_" + str(0)].cpd - # unrolled_net.node_lookup[child_node_name].set_cpd( - # self._d0.node_lookup[j[1].name.split("_", 1)[0] - # + "_" + str(0)].cpd) - print "copied" - for j in self._two_tdn._intra_edges: - child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) + "Copy the CPD's of the intra connections to the next timeslice" + for j in self._two_tdn.get_intra_edges(): + child_node_name = j[1].get_indexed_node(i) unrolled_net.node_lookup[child_node_name].set_cpd( - self._d0.node_lookup[j[1].name.split("_", 1)[0] - + "_" + str(0)].cpd) + self._d0.node_lookup[j[1].get_indexed_node(0)].cpd) else: - unrolled_net.copy_nodes_indexed(t_plus_1_slice, i) + unrolled_net.copy_nodes_indexed(self._two_tdn.timeslice['t_plus_1'], i) # in case we dont want the decisions to be copied for every new timeslice but only for every # two. Write this and leave the decisions node out of the timeslices in both d0 and two_tbn network: # unrolled_net.copy_nodes_indexed(self._two_tdn.decision_nodes, i - 1) unrolled_net.copy_nodes_indexed(self._two_tdn.utility_nodes, i - 1) - for j in self._two_tdn._inter_edges: - parent_node_name = j[0].name.split("_", 1)[0] + "_" + str(i - 1) - child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) + for j in self._two_tdn.get_inter_edges(): + parent_node_name = j[0].get_indexed_node(i - 1) + child_node_name = j[1].get_indexed_node(i) unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) - for j in self._two_tdn._intra_edges: - parent_node_name = j[0].name.split("_", 1)[0] + "_" + str(i) - child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) + for j in self._two_tdn.transitions.keys(): + node_name = j.get_indexed_node(i) + + unrolled_net.node_lookup[node_name].set_cpd(self._two_tdn.get_transition_probability(j)) + + for j in self._two_tdn.get_intra_edges(): + parent_node_name = j[0].get_indexed_node(i) + child_node_name = j[1].get_indexed_node(i) unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) - for j in self._two_tdn._intra_edges: - child_node_name = j[1].name.split("_", 1)[0] + "_" + str(i) + for j in self._two_tdn.get_intra_edges(): + child_node_name = j[1].get_indexed_node(i) unrolled_net.node_lookup[child_node_name].set_cpd( - self._d0.node_lookup[j[1].name.split("_", 1)[0] - + "_" + str(0)].cpd) - - unrolled_net.set_partial_ordering_u(self._d0.get_partial_ordering(), length) + self._d0.node_lookup[j[1].get_indexed_node(0)].cpd) + "Since we are unrolling over a specified length, the partial ordering for the DN has to be extended" + unrolled_net.extend_partial_ordering(self._d0.get_partial_ordering(), length) else: raise Exception("Can't unroll over length: 0") return unrolled_net @@ -500,8 +495,7 @@ def add_nodes(self, nodes): raise Exception("Can only add 'Node' and its subclasses as nodes into the D0 Network") def add_edge(self, node_from, node_to): - - node_to.add_parent(node_from) + self.node_lookup[node_to].add_parent(self.node_lookup[node_from]) def get_partial_ordering(self): return self.partialOrdering @@ -535,6 +529,7 @@ def __init__(self, inter_edges=None, intra_edges=None): self.node_lookup = {} self._inter_edges = [] self._intra_edges = [] + self.transitions = {} if inter_edges is not None: self.add_inter_edges(inter_edges) if intra_edges is not None: @@ -652,12 +647,27 @@ def add_intra_edges(self, intra_edges): for intra_edges in intra_edges: self.add_intra_edge(intra_edges[0], intra_edges[1]) + def get_intra_edges(self): + return self._intra_edges + + def get_inter_edges(self): + return self._inter_edges + + def add_transition(self, node, cpd): + self.transitions[node] = cpd + + def get_transition_probability(self, node): + return self.transitions[node] + def set_t_timeslice(self, initial_nodes): self.timeslice['t'] = initial_nodes def set_t_plus_one_timeslice(self, next_nodes): self.timeslice['t_plus_1'] = next_nodes + def get_t_timeslice(self): + return self.timeslice['t'] + class DecisionNetwork(object): @@ -703,19 +713,19 @@ def add_nodes(self, nodes): def copy_nodes_indexed(self, nodes_to_copy, index): for i in nodes_to_copy: - if i.name.split("_", 1)[0] + "_" + str(index) not in self.get_all_node_names(): + if i.get_indexed_node(index) not in self.get_all_node_names(): if isinstance(i, RandomNode): if isinstance(i, DiscreteNode): - new_node = DiscreteNode(i.name.split("_", 1)[0] + "_" + str(index), i.values) + new_node = DiscreteNode(i.get_indexed_node(index), i.values) self.random_nodes.append(new_node) self.node_lookup[new_node.name] = new_node elif isinstance(i, UtilityNode): - new_node = UtilityNode(i.name.split("_", 1)[0] + "_" + str(index)) + new_node = UtilityNode(i.get_indexed_node(index)) self.utility_nodes.append(new_node) self.node_lookup[new_node.name] = new_node elif isinstance(i, DecisionNode): - new_node = DiscreteNode(i.name.split("_", 1)[0] + "_" + str(index), i.values) + new_node = DiscreteNode(i.get_indexed_node(index), i.values) self.decision_nodes.append(new_node) self.node_lookup[new_node.name] = new_node else: @@ -736,11 +746,12 @@ def get_partial_ordering(self): def set_partial_ordering(self, partial_order): self.partialOrdering = partial_order - def extend_partial_ordering(self, extension): - self.partialOrdering.append(extension) - - def set_partial_ordering_u(self, init_order, length): - + def extend_partial_ordering(self, init_order, length): + """ + "Extending Partial Order when unrolling network. The exact partial order is copied for the next timeslices" + :param init_order: The initial Partial Order which is described in the d0 network + :param length: The total length of the unrolled network + """ for i in range(0, length): for j in init_order: # print(type(j)) @@ -749,6 +760,9 @@ def set_partial_ordering_u(self, init_order, length): isinstance(self.node_lookup[j[0]], DecisionNode): temp = [value.split("_", 1)[0] + "_" + str(i) for value in j] self.partialOrdering.append(temp) + else: + raise ValueError("The type of node you wanted to extend the partial " + "Ordering with is not a DiscreteNode or a DecisionNode") # Write this if decisions are over 2 timeslices # elif i < length - 1 or i == 0 or decisions: # temp = [value.split("_", 1)[0] + "_" + str(i) for value in j] @@ -759,12 +773,15 @@ def set_partial_ordering_u(self, init_order, length): if isinstance(self.node_lookup[j], DiscreteNode) or \ isinstance(self.node_lookup[j], DecisionNode): self.partialOrdering.append(j.split("_", 1)[0] + "_" + str(i)) + + else: + raise ValueError("The type of node you wanted to extend the partial " + "Ordering with is not a DiscreteNode or a DecisionNode") # Write this if decisions are over 2 timeslices # elif i < length - 1 or i == 0: # self.partialOrdering.append(j.split("_", 1)[0] + "_" + str(i)) def get_random_nodes(self): - '''Returns all RandomNodes''' return self.random_nodes def get_decision_nodes(self): diff --git a/primo2/nodes.py b/primo2/nodes.py index 0fc3bf0..510ace6 100644 --- a/primo2/nodes.py +++ b/primo2/nodes.py @@ -30,16 +30,19 @@ class RandomNode(object): Only subclasses of this should actually be used in a network as this is underspecied for any inference algorithms. """ - + def __init__(self, nodename): self.name = nodename self.cpd = 1 self.meta = [] - + def set_cpd(self, cpd): raise NotImplementedError("Called unimplemented method.") - - + + # Renames a node to the scheme: "Node_index" and returns it + def get_indexed_node(self, index): + return self.name.split("_", 1)[0] + "_" + str(index) + def __eq__(self, other): """ Two random nodes are considered to be identical if they have the @@ -51,10 +54,10 @@ def __eq__(self, other): return other.name == self.name except AttributeError: return other == self.name - + def __ne__(self, other): return not self.__eq__(other) - + def __hash__(self): """ The hash of a random node is the same as the hash of its name. @@ -62,15 +65,16 @@ def __hash__(self): instantiation or their name. """ return hash(self.name) - + def __str__(self): return self.name def __repr__(self): - return self.name + return self.name + class DiscreteNode(RandomNode): - + def __init__(self, nodename, values=("True", "False")): """ Creates a new discrete node with the given name and outcomes. @@ -82,16 +86,14 @@ def __init__(self, nodename, values=("True", "False")): vales: [String,], optional List of outcome values this discrete node has. """ - super(DiscreteNode,self).__init__(nodename) - self.values = list(values) #Consider copying this if for some reason something other than strings are used in there - self.parents = dict() #Consider removing this + super(DiscreteNode, self).__init__(nodename) + self.values = list( + values) # Consider copying this if for some reason something other than strings are used in there + self.parents = dict() # Consider removing this self.parentOrder = [] self.valid = False self._update_dimensions() - def rename(self, new_name): - self.name = new_name - def add_parent(self, parentNode): """ Adds the given node as a parent/cause node of this node. Will invalidate the @@ -105,7 +107,7 @@ def add_parent(self, parentNode): parentNode: RandomNode The new parent/cause node """ - + self.parents[parentNode.name] = parentNode self.parentOrder.append(parentNode.name) self._update_dimensions() @@ -127,9 +129,8 @@ def set_values(self, new_values): List of the new value names. """ self.values = list(new_values) - self._update_dimensions() - - + self._update_dimensions() + def _update_dimensions(self): """ Private helper function to update the dimensions of the cpd. @@ -140,7 +141,7 @@ def _update_dimensions(self): dimensions.append(len(self.parents[parentName].values)) self.cpd = np.zeros(dimensions) self.valid = False - + def remove_parent(self, parentNode): """ Removes the given parent/cause node from this node. @@ -156,7 +157,7 @@ def remove_parent(self, parentNode): del self.parents[parentNode] self.parentOrder.remove(parentNode.name) self._update_dimensions() - + def set_cpd(self, cpd): """ Allows to set the conditional probability density(table) of this @@ -175,7 +176,7 @@ def set_cpd(self, cpd): raise ValueError("The dimensions of the given cpd do not match the dependency structure of the node.") self.cpd = np.copy(cpd) self.valid = True - + def set_probability(self, valueName, prob, parentValues=None): """ Function that allows to set parts of the cpt of this variable. @@ -205,10 +206,10 @@ def set_probability(self, valueName, prob, parentValues=None): index = [self.values.index(valueName)] if self.values else [] except ValueError: raise ValueError("This node as no value {}.".format(valueName)) - + if not parentValues: parentValues = {} - + for parentName in self.parentOrder: if parentName in parentValues: try: @@ -217,9 +218,9 @@ def set_probability(self, valueName, prob, parentValues=None): raise ValueError("Parent {} does not have values {}.".format(parentName, parentValues[parentName])) else: index.append(slice(len(self.parents[parentName].values))) - + self.cpd[tuple(index)] = prob - + def get_probability(self, value, parentValues=None): """ Function to return the probability(ies) for a given value of this @@ -251,31 +252,37 @@ def get_probability(self, value, parentValues=None): index = [[self.values.index(value)]] if self.values else [] except ValueError: raise ValueError("This node as no value {}.".format(value)) - + if not parentValues: parentValues = {} - + for parentName in self.parentOrder: if parentName in parentValues: if isinstance(parentValues[parentName], list): try: index.append([self.parents[parentName].values.index(v) for v in parentValues[parentName]]) except ValueError: - raise ValueError("There is no conditional probability for parent {}, values {} in node {}.".format(parentName, parentValues[parentName], self.name)) + raise ValueError( + "There is no conditional probability for parent {}, values {} in node {}.".format( + parentName, parentValues[parentName], self.name)) else: try: index.append([self.parents[parentName].values.index(parentValues[parentName])]) except ValueError: - raise ValueError("There is no conditional probability for parent {}, value {} in node {}.".format(parentName, parentValues[parentName], self.name)) + raise ValueError( + "There is no conditional probability for parent {}, value {} in node {}.".format(parentName, + parentValues[ + parentName], + self.name)) else: index.append(range(len(self.parents[parentName].values))) - + # use np.ix_ to construct the appropriate index array! index = np.ix_(*index) tmp = self.cpd[index] res = np.squeeze(tmp) - return res #np.squeeze(np.copy(self.cpd[np.ix_(*index])) - + return res # np.squeeze(np.copy(self.cpd[np.ix_(*index])) + def _get_single_probability(self, value, parentValues=None): """ Fast-path function to return the probability for a given value of this @@ -304,19 +311,24 @@ def _get_single_probability(self, value, parentValues=None): index = [self.values.index(value)] if self.values else [] except ValueError: raise ValueError("This node as no value {}.".format(value)) - + if not parentValues: parentValues = {} - + for parentName in self.parentOrder: try: index.append(self.parents[parentName].values.index(parentValues[parentName])) except KeyError: - raise KeyError("parentValues need to specify a value for parent {} of node: {}.".format(parentName, self.name)) + raise KeyError( + "parentValues need to specify a value for parent {} of node: {}.".format(parentName, self.name)) except ValueError: - raise ValueError("There is no conditional probability for parent {}, value {} in node {}.".format(parentName, parentValues[parentName], self.name)) + raise ValueError( + "There is no conditional probability for parent {}, value {} in node {}.".format(parentName, + parentValues[ + parentName], + self.name)) return self.cpd[tuple(index)] - + def get_markov_prob(self, outcome, children, state, forward=False): """ Computes the markov probability of the given outcome of this random variable, @@ -350,7 +362,7 @@ def get_markov_prob(self, outcome, children, state, forward=False): for child in children: prob *= child._get_single_probability(state[child.name], state) return prob - + def sample_value(self, currentState, children, forward=False): """ Returns a value drawn from the probability density given by this node @@ -377,21 +389,21 @@ def sample_value(self, currentState, children, forward=False): String Name of the value this DiscreteNode has most likely adopted. """ - #Compute probabilities of possible outcomes + # Compute probabilities of possible outcomes weights = [] for outcome in self.values: adaptedState = dict(currentState) adaptedState[self] = outcome weights.append(self.get_markov_prob(outcome, children, adaptedState, forward)) - - #Perform roulette-wheel-sampling: - rndVal = random.random()* sum(weights) + + # Perform roulette-wheel-sampling: + rndVal = random.random() * sum(weights) s = 0 for i in range(len(weights)): s += weights[i] if s >= rndVal: return self.values[i] - + def sample_local(self, currentValue): """ Returns a randomly chosen possible outcome of this node. @@ -417,17 +429,14 @@ class UtilityNode(RandomNode): 1. A DiscreteNode does not have values itself 2. The CPD does not represent probabilities but rather utilities! """ - + def __init__(self, nodeName): - super(UtilityNode,self).__init__(nodeName) + super(UtilityNode, self).__init__(nodeName) self.parents = {} self.parentOrder = [] - self.utilities = self.cpd # Use different name for cpd + self.utilities = self.cpd # Use different name for cpd self.valid = False - def rename(self, new_name): - self.name = new_name - def add_parent(self, parentNode): """ Adds the given node as a parent/cause node of this node. @@ -445,7 +454,7 @@ def add_parent(self, parentNode): self.parents[parentNode.name] = parentNode self.parentOrder.append(parentNode.name) self._update_dimensions() - + def _update_dimensions(self): """ Private helper function to update the dimensions of the cpd. @@ -456,7 +465,7 @@ def _update_dimensions(self): dimensions.append(len(self.parents[parentName].values)) self.cpd = np.zeros(dimensions) self.valid = False - + def get_utility(self, parentValues): """ Returns the utilities for a specific parent outcome. @@ -476,11 +485,15 @@ def get_utility(self, parentValues): try: index.append(self.parents[parentName].values.index(parentValues[parentName])) except KeyError: - raise KeyError("parentValues need to specify a value for parent {} of node: {}.".format(parentName, self.name)) + raise KeyError( + "parentValues need to specify a value for parent {} of node: {}.".format(parentName, self.name)) except ValueError: - raise ValueError("There is no utility for parent {}, value {} in node {}.".format(parentName, parentValues[parentName], self.name)) + raise ValueError("There is no utility for parent {}, value {} in node {}.".format(parentName, + parentValues[ + parentName], + self.name)) return self.cpd[tuple(index)] - + def set_utilities(self, utilities): """ Allows to set the utilities directly as a utility table. @@ -500,7 +513,7 @@ def set_utilities(self, utilities): "match the dependency structure of the node.") self.cpd = np.copy(utilities) self.valid = True - + def set_utility(self, utility, parentValues): """ Allows to specify the utility for the specified parent value @@ -524,9 +537,10 @@ def set_utility(self, utility, parentValues): raise ValueError("Parent {} does not have values {}.".format(parentName, parentValues[parentName])) else: index.append(slice(len(self.parents[parentName].values))) - + self.cpd[tuple(index)] = utility + class DecisionNode(RandomNode): """ A DecisionNode is a RandomNode where the cpd represents a decision-rule @@ -534,10 +548,9 @@ class DecisionNode(RandomNode): In most single-agent cases, this will be deterministic mapping to exactly 1 outcome. """ - - + def __init__(self, nodeName, decisions=["Yes", "No"]): - super(DecisionNode,self).__init__(nodeName) + super(DecisionNode, self).__init__(nodeName) self.name = nodeName self.values = list(decisions) self.state = None @@ -547,9 +560,6 @@ def __init__(self, nodeName, decisions=["Yes", "No"]): self._update_dimensions() self.valid = False - def rename(self, new_name): - self.name = new_name - def add_parent(self, parentNode): """ Adds the given node as a parent/cause node of this node. Will invalidate the @@ -563,7 +573,7 @@ def add_parent(self, parentNode): parentNode: RandomNode The new parent/cause node """ - + self.parents[parentNode.name] = parentNode self.parentOrder.append(parentNode.name) self._update_dimensions() @@ -578,7 +588,7 @@ def _update_dimensions(self): dimensions.append(len(self.parents[parentName].values)) self.cpd = np.zeros(dimensions) self.valid = False - + def set_decision(self, decision): """ Sets the state of this decisionNode to the given decision. @@ -597,9 +607,9 @@ def set_decision(self, decision): index = [self.values.index(decision)] if self.values else [] except ValueError: raise ValueError("This node as no value {}.".format(decision)) - + self.cpd[tuple(index)] = 1 - + def fully_mixed(self): self.cpd = np.ones(self.cpd.shape) self.cpd /= len(self.values) @@ -622,13 +632,11 @@ def set_cpd(self, cpd): raise ValueError("The dimensions of the given cpd do not match the dependency structure of the node.") self.cpd = np.copy(cpd) self.valid = True - -if __name__ == "__main__": +if __name__ == "__main__": costs = UtilityNode("costs") costs_ = UtilityNode("costs_") costs_.add_parent(costs) print(costs_.utilities) - \ No newline at end of file From d9fb85ebe8f7b07ccdfd2b0f8a8e1089ed29128e Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Fri, 13 Apr 2018 13:38:08 +0200 Subject: [PATCH 07/12] Fixed a bug in copy_nodes_indexed function: Copied Decision node was a discrete Node --- examples/decision_example.py | 4 +-- primo2/networks.py | 58 ++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/examples/decision_example.py b/examples/decision_example.py index 325432d..a4c2ba2 100644 --- a/examples/decision_example.py +++ b/examples/decision_example.py @@ -499,9 +499,9 @@ print new_net.node_lookup["observation_1"].cpd print new_net.get_partial_ordering() -# ve = VariableElimination(new_net) +ve = VariableElimination(new_net) # print("Optimal decision: ", ve.get_optimal_decisions(["action_0"])) -# print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("action_0"))) +print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("action_1"))) # new = {'+O': {"1": {0: {0: 0.60, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, # 1: {0: 0.25, 1: 0.70, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, diff --git a/primo2/networks.py b/primo2/networks.py index 57bb9b3..8bb1b25 100644 --- a/primo2/networks.py +++ b/primo2/networks.py @@ -401,33 +401,34 @@ def unroll(self, length): if i == 0: unrolled_net.add_nodes(self._d0.timeslice[0] + self._d0.utility_nodes) elif i == 1: + children = [] """copy nodes from the two_tdn Network into unrolled_net""" unrolled_net.copy_nodes_indexed(self._two_tdn.timeslice['t_plus_1'], i) - for j in self._two_tdn.get_inter_edges(): - parent_node_name = j[0].get_indexed_node(i - 1) - child_node_name = j[1].get_indexed_node(i) + for inter_pair in self._two_tdn.get_inter_edges(): + parent_node_name = inter_pair[0].get_indexed_node(i - 1) + child_node_name = inter_pair[1].get_indexed_node(i) """Connect copied nodes via inter edges like described in the two tdn""" unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) "Copy the CPD's describing the transition probability (defined by the two_tdn) into the new net" - for j in self._two_tdn.transitions.keys(): - node_name = j.get_indexed_node(i) + for current_transition in self._two_tdn.transitions.keys(): + node_name = current_transition.get_indexed_node(i) - unrolled_net.node_lookup[node_name].set_cpd(self._two_tdn.get_transition_probability(j)) - for j in self._two_tdn.get_intra_edges(): - parent_node_name = j[0].get_indexed_node(i) - child_node_name = j[1].get_indexed_node(i) + unrolled_net.node_lookup[node_name].set_cpd(self._two_tdn.get_transition_probability(current_transition)) + for intra_pair in self._two_tdn.get_intra_edges(): + parent_node_name = intra_pair[0].get_indexed_node(i) + child_node_name = intra_pair[1].get_indexed_node(i) """Connect copied nodes via intra edges like described in the two tdn""" + children.append(child_node_name) unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) "Copy the CPD's of the intra connections to the next timeslice" - for j in self._two_tdn.get_intra_edges(): - child_node_name = j[1].get_indexed_node(i) - - unrolled_net.node_lookup[child_node_name].set_cpd( - self._d0.node_lookup[j[1].get_indexed_node(0)].cpd) + for intra_child in children: + unrolled_net.node_lookup[intra_child].set_cpd( + self._d0.node_lookup[intra_child.split("_", 1)[0] + "_" + str(0)].cpd) else: + children = [] unrolled_net.copy_nodes_indexed(self._two_tdn.timeslice['t_plus_1'], i) # in case we dont want the decisions to be copied for every new timeslice but only for every # two. Write this and leave the decisions node out of the timeslices in both d0 and two_tbn network: @@ -435,28 +436,27 @@ def unroll(self, length): unrolled_net.copy_nodes_indexed(self._two_tdn.utility_nodes, i - 1) - for j in self._two_tdn.get_inter_edges(): - parent_node_name = j[0].get_indexed_node(i - 1) - child_node_name = j[1].get_indexed_node(i) + for inter_pair in self._two_tdn.get_inter_edges(): + parent_node_name = inter_pair[0].get_indexed_node(i - 1) + child_node_name = inter_pair[1].get_indexed_node(i) unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) - for j in self._two_tdn.transitions.keys(): - node_name = j.get_indexed_node(i) + for current_transition in self._two_tdn.transitions.keys(): + node_name = current_transition.get_indexed_node(i) - unrolled_net.node_lookup[node_name].set_cpd(self._two_tdn.get_transition_probability(j)) + unrolled_net.node_lookup[node_name].set_cpd(self._two_tdn.get_transition_probability(current_transition)) - for j in self._two_tdn.get_intra_edges(): - parent_node_name = j[0].get_indexed_node(i) - child_node_name = j[1].get_indexed_node(i) + for intra_pair in self._two_tdn.get_intra_edges(): + parent_node_name = intra_pair[0].get_indexed_node(i) + child_node_name = intra_pair[1].get_indexed_node(i) + children.append(child_node_name) unrolled_net.add_edge(unrolled_net.node_lookup[parent_node_name], unrolled_net.node_lookup[child_node_name]) - for j in self._two_tdn.get_intra_edges(): - child_node_name = j[1].get_indexed_node(i) - - unrolled_net.node_lookup[child_node_name].set_cpd( - self._d0.node_lookup[j[1].get_indexed_node(0)].cpd) + for intra_child in children: + unrolled_net.node_lookup[intra_child].set_cpd( + self._d0.node_lookup[intra_child.split("_", 1)[0] + "_" + str(0)].cpd) "Since we are unrolling over a specified length, the partial ordering for the DN has to be extended" unrolled_net.extend_partial_ordering(self._d0.get_partial_ordering(), length) else: @@ -725,7 +725,7 @@ def copy_nodes_indexed(self, nodes_to_copy, index): self.node_lookup[new_node.name] = new_node elif isinstance(i, DecisionNode): - new_node = DiscreteNode(i.get_indexed_node(index), i.values) + new_node = DecisionNode(i.get_indexed_node(index), i.values) self.decision_nodes.append(new_node) self.node_lookup[new_node.name] = new_node else: From 02eca65d0b3927ee8b73899009ad5e41ab00884e Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Fri, 13 Apr 2018 14:28:41 +0200 Subject: [PATCH 08/12] Cleaned the decision_example file. Sorted and added some new/old examples. For every example to get the optimal decision both algorithms are now used (classical and max_sum) --- examples/decision_example.py | 533 +++++++++++++---------------------- 1 file changed, 192 insertions(+), 341 deletions(-) diff --git a/examples/decision_example.py b/examples/decision_example.py index a4c2ba2..c52ca4a 100644 --- a/examples/decision_example.py +++ b/examples/decision_example.py @@ -11,385 +11,239 @@ from primo2.inference.decision import VariableElimination -""" -PHD example 7.3 from Bayesian Reasoning and Machine Learning - Barber -""" -# d0 = d0_net() -# -# education_0 = DecisionNode("education_0", decisions=["do Phd", "no Phd"]) #E -# -# income_0 = DiscreteNode("income_0", values=["low", "average", "high"]) #I -# nobel_0 = DiscreteNode("nobel_0", values=["prize", "no prize"]) #P -# -# costs_0 = UtilityNode("costs_0") #UC -# gains_0 = UtilityNode("gains_0") #UB -# -# d0.add_nodes([education_0,income_0,nobel_0,costs_0,gains_0]) -# -# d0.set_zero_timeslice([income_0,nobel_0]) -# -# #Add edges. Edges can either be acutal dependencies or information links. -# #The type is figured out by the nodes themsevles -# d0.add_edge(education_0, costs_0) -# d0.add_edge(education_0, nobel_0) -# d0.add_edge(education_0, income_0) -# -# d0.add_edge(nobel_0, income_0) -# d0.add_edge(income_0, gains_0) -# -# #Define CPTs: (Needs to be done AFTER the structure is defined as that) -# #determines the table structure for the different nodes -# -# income_0.set_probability("low", 0.1, parentValues={"education_0":"do Phd", "nobel_0":"no prize"}) -# income_0.set_probability("low", 0.2, parentValues={"education_0":"no Phd", "nobel_0":"no prize"}) -# income_0.set_probability("low", 0.01, parentValues={"education_0":"do Phd", "nobel_0":"prize"}) -# income_0.set_probability("low", 0.01, parentValues={"education_0":"no Phd", "nobel_0":"prize"}) -# -# income_0.set_probability("average", 0.5, parentValues={"education_0":"do Phd", "nobel_0":"no prize"}) -# income_0.set_probability("average", 0.6, parentValues={"education_0":"no Phd", "nobel_0":"no prize"}) -# income_0.set_probability("average", 0.04, parentValues={"education_0":"do Phd", "nobel_0":"prize"}) -# income_0.set_probability("average", 0.04, parentValues={"education_0":"no Phd", "nobel_0":"prize"}) -# -# income_0.set_probability("high", 0.4, parentValues={"education_0":"do Phd", "nobel_0":"no prize"}) -# income_0.set_probability("high", 0.2, parentValues={"education_0":"no Phd", "nobel_0":"no prize"}) -# income_0.set_probability("high", 0.95, parentValues={"education_0":"do Phd", "nobel_0":"prize"}) -# income_0.set_probability("high", 0.95, parentValues={"education_0":"no Phd", "nobel_0":"prize"}) -# -# -# nobel_0.set_probability("prize", 0.0000001, parentValues={"education_0":"no Phd"}) -# nobel_0.set_probability("prize", 0.001, parentValues={"education_0":"do Phd"}) -# -# nobel_0.set_probability("no prize", 0.9999999, parentValues={"education_0":"no Phd"}) -# nobel_0.set_probability("no prize", 0.999, parentValues={"education_0":"do Phd"}) -# -# -# #Define utilities -# -# costs_0.set_utility(-50000, parentValues={"education_0":"do Phd"}) -# costs_0.set_utility(0, parentValues={"education_0":"no Phd"}) -# -# gains_0.set_utility(100000, parentValues={"income_0":"low"}) -# gains_0.set_utility(200000, parentValues={"income_0":"average"}) -# gains_0.set_utility(500000, parentValues={"income_0":"high"}) -# -# d0.set_partial_ordering(["education_0", ["income_0", "nobel_0"]]) -# -# # print(d0.get_all_nodes()) -# -# two_tdn = Two_TDN() -# -# education_t = DecisionNode("education_t", decisions=["do Phd", "no Phd"]) -# -# income_t = DiscreteNode("income_t", values=["low", "average", "high"]) -# income_t_plus_1 = DiscreteNode("income_t_plus_1", values=["low", "average", "high"]) -# -# nobel_t = DiscreteNode("nobel_t", values=["prize", "no prize"]) -# nobel_t_plus_1 = DiscreteNode("nobel_t_plus_1", values=["prize", "no prize"]) -# -# costs_t = UtilityNode("costs_t") -# -# gains_t = UtilityNode("gains_t") -# -# -# -# #Add nodes to network. They can be treated the same -# two_tdn.add_nodes([education_t, income_t, income_t_plus_1, nobel_t, nobel_t_plus_1, costs_t, gains_t]) -# two_tdn.set_t_timeslice([income_t, nobel_t]) -# two_tdn.set_t_plus_one_timeslice([income_t_plus_1, nobel_t_plus_1]) -# -# two_tdn.add_inter_edge(income_t, income_t_plus_1) -# two_tdn.add_inter_edge(nobel_t, nobel_t_plus_1) -# -# two_tdn.add_intra_edge(education_t, costs_t) -# two_tdn.add_intra_edge(education_t, nobel_t) -# two_tdn.add_intra_edge(education_t, income_t) -# -# two_tdn.add_intra_edge(income_t, gains_t) -# -# DDN = DynamicDecisionNetwork(d0, two_tdn) -# -# new_net = DDN.unroll(2) - - -# print "checking parents \n" -# for i in new_net.get_all_node_names(): -# print "Parent of" -# print i -# for j in new_net.node_lookup[i].parents: -# print "is:" -# print j - -# print(new_net.get_partial_ordering()) -# for i in new_net.get_all_nodes(): -# print i.cpd -# education_.set_decision("do phd", 0.5, parentValues={"education":"do phd"}) -# education_.set_decision("do phd", 0.5, parentValues={"education":"no phd"}) -# education_.set_decision("no phd", 0.5, parentValues={"education":"do phd"}) -# education_.set_decision("no phd", 0.5, parentValues={"education":"no phd"}) - -# income_.set_probability("low", 0.33, parentValues={"income":"low"}) -# income_.set_probability("low", 0.33, parentValues={"income":"average"}) -# income_.set_probability("low", 0.33, parentValues={"income":"high"}) -# income_.set_probability("average", 0.33, parentValues={"income":"low"}) -# income_.set_probability("average", 0.33, parentValues={"income":"average"}) -# income_.set_probability("average", 0.33, parentValues={"income":"high"}) -# income_.set_probability("high", 0.33, parentValues={"income":"low"}) -# income_.set_probability("high", 0.33, parentValues={"income":"average"}) -# income_.set_probability("high", 0.33, parentValues={"income":"high"}) - -# nobel_.set_probability("prize", 0.5, parentValues={"nobel":"prize"}) -# nobel_.set_probability("prize", 0.5, parentValues={"nobel":"no prize"}) -# nobel_.set_probability("no prize", 0.5, parentValues={"nobel":"prize"}) -# nobel_.set_probability("no prize", 0.5, parentValues={"nobel":"no prize"}) - -# costs_.set_utility(0, parentValues={"costs":"do Phd"}) -# costs_.set_utility(0, parentValues={"education":"no Phd"}) - - -# ve = VariableElimination(new_net) - -# print("Expected Utility for doing a Phd: {}".format(ve.expected_utility(decisions={"education_0":"do Phd"}))) -# print("Expected Utility for not doing a Phd: {}".format(ve.expected_utility(decisions={"education_0":"no Phd"}))) -# print("Optimal decision: ", ve.get_optimal_decisions(["education_0"])) -# print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education_0"))) +print ("") +print ("PHD example 7.3 from Bayesian Reasoning and Machine Learning - Barber") +print ("") +net = DecisionNetwork() -""" -PHD + Startup example 7.4 from Bayesian Reasoning and Machine Learning - Barber -""" +education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E + +income = DiscreteNode("income", values=["low", "average", "high"]) #I +nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P + +costs = UtilityNode("costs") #UC +gains = UtilityNode("gains") #UB + + + +#Add nodes to network. They can be treated the same +net.add_node(education) +net.add_node(income) +net.add_node(nobel) + +net.add_node(costs) +net.add_node(gains) -# net = DecisionNetwork() +#Add edges. Edges can either be acutal dependencies or information links. +#The type is figured out by the nodes themsevles +net.add_edge(education, costs) +net.add_edge(education, nobel) +net.add_edge(education, income) -# education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E -# startup = DecisionNode("startup", decisions=["start up", "no start up"]) # S +net.add_edge(nobel, income) +net.add_edge(income, gains) -# income = DiscreteNode("income", values=["low", "average", "high"]) #I -# nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P +#Define CPTs: (Needs to be done AFTER the structure is defined as that) +#determines the table structure for the different nodes -# costsEducation = UtilityNode("costsE") #UC -# costsStartUp = UtilityNode("costsS") #US -# gains = UtilityNode("gains") #UB +income.set_probability("low", 0.1, parentValues={"education":"do Phd", "nobel":"no prize"}) +income.set_probability("low", 0.2, parentValues={"education":"no Phd", "nobel":"no prize"}) +income.set_probability("low", 0.01, parentValues={"education":"do Phd", "nobel":"prize"}) +income.set_probability("low", 0.01, parentValues={"education":"no Phd", "nobel":"prize"}) +income.set_probability("average", 0.5, parentValues={"education":"do Phd", "nobel":"no prize"}) +income.set_probability("average", 0.6, parentValues={"education":"no Phd", "nobel":"no prize"}) +income.set_probability("average", 0.04, parentValues={"education":"do Phd", "nobel":"prize"}) +income.set_probability("average", 0.04, parentValues={"education":"no Phd", "nobel":"prize"}) -# #Add nodes to network. They can be treated the same -# net.add_node(education) -# net.add_node(startup) -# net.add_node(income) -# net.add_node(nobel) +income.set_probability("high", 0.4, parentValues={"education":"do Phd", "nobel":"no prize"}) +income.set_probability("high", 0.2, parentValues={"education":"no Phd", "nobel":"no prize"}) +income.set_probability("high", 0.95, parentValues={"education":"do Phd", "nobel":"prize"}) +income.set_probability("high", 0.95, parentValues={"education":"no Phd", "nobel":"prize"}) -# net.add_node(costsEducation) -# net.add_node(costsStartUp) -# net.add_node(gains) +nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) +nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) -# #Add edges. Edges can either be acutal dependencies or information links. -# #The type is figured out by the nodes themsevles -# net.add_edge(education, costsEducation) -# net.add_edge(education, nobel) +nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) +nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) -# net.add_edge(startup, income) -# net.add_edge(startup, costsStartUp) -# net.add_edge(nobel, income) -# net.add_edge(income, gains) +#Define utilities -# #Define CPTs: (Needs to be done AFTER the structure is defined as that) -# #determines the table structure for the different nodes +costs.set_utility(-50000, parentValues={"education":"do Phd"}) +costs.set_utility(0, parentValues={"education":"no Phd"}) -# income.set_probability("low", 0.1, parentValues={"startup":"start up", "nobel":"no prize"}) -# income.set_probability("low", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) -# income.set_probability("low", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) -# income.set_probability("low", 0.05, parentValues={"startup":"no start up", "nobel":"prize"}) +gains.set_utility(100000, parentValues={"income":"low"}) +gains.set_utility(200000, parentValues={"income":"average"}) +gains.set_utility(500000, parentValues={"income":"high"}) -# income.set_probability("average", 0.5, parentValues={"startup":"start up", "nobel":"no prize"}) -# income.set_probability("average", 0.6, parentValues={"startup":"no start up", "nobel":"no prize"}) -# income.set_probability("average", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) -# income.set_probability("average", 0.15, parentValues={"startup":"no start up", "nobel":"prize"}) +net.set_partial_ordering([education, [income,nobel]]) +ve = VariableElimination(net) -# income.set_probability("high", 0.4, parentValues={"startup":"start up", "nobel":"no prize"}) -# income.set_probability("high", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) -# income.set_probability("high", 0.99, parentValues={"startup":"start up", "nobel":"prize"}) -# income.set_probability("high", 0.8, parentValues={"startup":"no start up", "nobel":"prize"}) +print("Expected Utility for doing a Phd: {}".format(ve.expected_utility(decisions={"education":"do Phd"}))) +print("Expected Utility for not doing a Phd: {}".format(ve.expected_utility(decisions={"education":"no Phd"}))) +print "Optimal decision using max_sum: ", ve.max_sum("education") +print "Get optimal decision using classic algorithm: ", ve.get_optimal_decisions(["education"]) +print ("") +print ("PHD example 7.4 from Bayesian Reasoning and Machine Learning - Barber") +print ("") -# nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) -# nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) +net = DecisionNetwork() -# nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) -# nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) +education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E +startup = DecisionNode("startup", decisions=["start up", "no start up"]) # S -# #Define utilities +income = DiscreteNode("income", values=["low", "average", "high"]) #I +nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P -# costsEducation.set_utility(-50000, parentValues={"education":"do Phd"}) -# costsEducation.set_utility(0, parentValues={"education":"no Phd"}) +costsEducation = UtilityNode("costsE") #UC +costsStartUp = UtilityNode("costsS") #US +gains = UtilityNode("gains") #UB -# costsStartUp.set_utility(-200000, parentValues={"startup":"start up"}) -# costsStartUp.set_utility(0, parentValues={"startup":"no start up"}) -# gains.set_utility(100000, parentValues={"income":"low"}) -# gains.set_utility(200000, parentValues={"income":"average"}) -# gains.set_utility(500000, parentValues={"income":"high"}) +#Add nodes to network. They can be treated the same +net.add_node(education) +net.add_node(startup) +net.add_node(income) +net.add_node(nobel) -# net.set_PartialOrdering([education, nobel, startup, income]) -# ve = VariableElimination(net) +net.add_node(costsEducation) +net.add_node(costsStartUp) +net.add_node(gains) -# print("Expected Utility for doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "start up"}))) -# print("Expected Utility for doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "no start up"}))) -# print("Expected Utility for not doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "start up"}))) -# print("Expected Utility for not doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "no start up"}))) -# print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education"))) -# print("Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup"))) -# print("Optimal deciosn: ", ve.get_optimal_decisions(["startup", "education"])) -# net2 = net.copy() +#Add edges. Edges can either be acutal dependencies or information links. +#The type is figured out by the nodes themsevles +net.add_edge(education, costsEducation) +net.add_edge(education, nobel) +net.add_edge(startup, income) +net.add_edge(startup, costsStartUp) -# Define CPTs: (Needs to be done AFTER the structure is defined as that) -# determines the table structure for the different nodes +net.add_edge(nobel, income) +net.add_edge(income, gains) +#Define CPTs: (Needs to be done AFTER the structure is defined as that) +#determines the table structure for the different nodes -# net.set_PartialOrdering([education, [income,nobel]]) -# print(net.get_all_nodes()) +income.set_probability("low", 0.1, parentValues={"startup":"start up", "nobel":"no prize"}) +income.set_probability("low", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) +income.set_probability("low", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) +income.set_probability("low", 0.05, parentValues={"startup":"no start up", "nobel":"prize"}) +income.set_probability("average", 0.5, parentValues={"startup":"start up", "nobel":"no prize"}) +income.set_probability("average", 0.6, parentValues={"startup":"no start up", "nobel":"no prize"}) +income.set_probability("average", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) +income.set_probability("average", 0.15, parentValues={"startup":"no start up", "nobel":"prize"}) -# d_net = DynamicBayesianNetwork(b0=net, two_tdn=net2) -# print d_net.b0.get_all_node_names() -# print d_net.two_tdn.get_all_node_names() -# d_net.unroll(3) -# d_net = DynamicBayesianNetwork(net) +income.set_probability("high", 0.4, parentValues={"startup":"start up", "nobel":"no prize"}) +income.set_probability("high", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) +income.set_probability("high", 0.99, parentValues={"startup":"start up", "nobel":"prize"}) +income.set_probability("high", 0.8, parentValues={"startup":"no start up", "nobel":"prize"}) -# print "What?" -# print d_net.b0.get_all_nodes() -# education_ = DecisionNode("education_", decisions=["do Phd", "no Phd"]) #E +nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) +nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) -# income_ = DiscreteNode("income_", values=["low", "average", "high"]) #I -# nobel_ = DiscreteNode("nobel_", values=["prize", "no prize"]) #P +nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) +nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) -# costs_ = UtilityNode("costs_") #UC -# gains_ = UtilityNode("gains_") #UB +#Define utilities -# d_net.two_tbn.add_node(education_) -# d_net.two_tbn.add_node(income_) -# d_net.two_tbn.add_node(nobel_) - -# d_net.two_tbn.add_node(costs_) -# d_net.two_tbn.add_node(gains_) - -# income_.set_probability("low", 0.11, parentValues={"income":"low"}) -# income_.set_probability("low", 0.21, parentValues={"income":"low"}) -# income_.set_probability("low", 0.011, parentValues={"income":"low"}) -# income_.set_probability("low", 0.011, parentValues={"income":"low"}) - -# income_.set_probability("average", 0.51, parentValues={"income":"average"}) -# income_.set_probability("average", 0.61, parentValues={"income":"average"}) -# income_.set_probability("average", 0.041, parentValues={"income":"average"}) -# income_.set_probability("average", 0.041, parentValues={"income":"average"}) - -# income_.set_probability("high", 0.41, parentValues={"income":"high"}) -# income_.set_probability("high", 0.21, parentValues={"income":"high"}) -# income_.set_probability("high", 0.951, parentValues={"income":"high"}) -# income_.set_probability("high", 0.951, parentValues={"income":"high"}) - - -# nobel_.set_probability("prize", 0.00000011, parentValues={"nobel":"prize"}) -# nobel_.set_probability("prize", 0.0011, parentValues={"nobel":"prize"}) - -# nobel_.set_probability("no prize", 0.99999991, parentValues={"nobel":"no prize"}) -# nobel_.set_probability("no prize", 0.9991, parentValues={"nobel":"no prize"}) - - -# #Define utilities - -# costs_.set_utility(-50001, parentValues={"costs"}) -# costs_.set_utility(1, parentValues={"costs"}) - -# gains_.set_utility(100001, parentValues={"gains"}) -# gains_.set_utility(200001, parentValues={"gains"}) -# gains_.set_utility(500001, parentValues={"gains"}) - - -# ve = VariableElimination(net) - -# print("Expected Utility for doing a Phd: {}".format(ve.expected_utility(decisions={"education":"do Phd"}))) -# print("Expected Utility for not doing a Phd: {}".format(ve.expected_utility(decisions={"education":"no Phd"}))) -# print("Optimal deciosn: ", ve.get_optimal_decisions(["education"])) -# ccprint("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education"))) - -# ve = VariableElimination(d_net._b0) -# print("Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup"))) - - -# """ -# Fever Example 4.3.5 from Bayesian Artificial Intelligence - Korb Nicholson -# """ -# -# d0 = d0_net() -# -# flu_0 = DiscreteNode("flu_0", values=["True", "False"]) -# fever_0 = DiscreteNode("fever_0", values=["True", "False"]) -# therm_0 = DiscreteNode("therm_0", values=["True", "False"]) -# take_aspirin_0 = DecisionNode("take_aspirin_0", decisions=["Yes", "No"]) -# fever_later_0 = DiscreteNode("fever_later_0", values=["True", "False"]) -# reaction_0 = DiscreteNode("reaction_0", values=["Yes", "No"]) -# utility_0 = UtilityNode("utility_0") -# -# d0.add_nodes([flu_0, fever_0, therm_0, take_aspirin_0, fever_later_0, reaction_0, utility_0]) -# -# d0.add_edge(flu_0, fever_0) -# d0.add_edge(fever_0, therm_0) -# d0.add_edge(fever_0, fever_later_0) -# d0.add_edge(fever_later_0, utility_0) -# d0.add_edge(take_aspirin_0, reaction_0) -# d0.add_edge(take_aspirin_0, fever_later_0) -# d0.add_edge(reaction_0, utility_0) -# -# flu_0.set_probability("True", 0.05) -# flu_0.set_probability("False", 0.95) -# -# fever_0.set_probability("True", 0.95, parentValues={"flu_0": "True"}) -# fever_0.set_probability("True", 0.02, parentValues={"flu_0": "False"}) -# fever_0.set_probability("False", 0.05, parentValues={"flu_0": "True"}) -# fever_0.set_probability("False", 0.98, parentValues={"flu_0": "False"}) -# -# therm_0.set_probability("True", 0.90, parentValues={"fever_0": "True"}) -# therm_0.set_probability("True", 0.05, parentValues={"fever_0": "False"}) -# therm_0.set_probability("False", 0.10, parentValues={"fever_0": "True"}) -# therm_0.set_probability("False", 0.95, parentValues={"fever_0": "False"}) -# -# fever_later_0.set_probability("True", 0.05, parentValues={"fever_0": "True", "take_aspirin_0": "Yes"}) -# fever_later_0.set_probability("True", 0.90, parentValues={"fever_0": "True", "take_aspirin_0": "No"}) -# fever_later_0.set_probability("True", 0.01, parentValues={"fever_0": "False", "take_aspirin_0": "Yes"}) -# fever_later_0.set_probability("True", 0.02, parentValues={"fever_0": "False", "take_aspirin_0": "No"}) -# fever_later_0.set_probability("False", 0.95, parentValues={"fever_0": "True", "take_aspirin_0": "Yes"}) -# fever_later_0.set_probability("False", 0.10, parentValues={"fever_0": "True", "take_aspirin_0": "No"}) -# fever_later_0.set_probability("False", 0.99, parentValues={"fever_0": "False", "take_aspirin_0": "Yes"}) -# fever_later_0.set_probability("False", 0.98, parentValues={"fever_0": "False", "take_aspirin_0": "No"}) -# -# reaction_0.set_probability("Yes", 0.05, parentValues={"take_aspirin_0": "Yes"}) -# reaction_0.set_probability("Yes", 0.00, parentValues={"take_aspirin_0": "No"}) -# reaction_0.set_probability("No", 0.95, parentValues={"take_aspirin_0": "Yes"}) -# reaction_0.set_probability("No", 1.0, parentValues={"take_aspirin_0": "No"}) -# -# utility_0.set_utility(-50, {"fever_later_0": "True", "reaction_0": "Yes"}) -# utility_0.set_utility(-10, {"fever_later_0": "True", "reaction_0": "No"}) -# utility_0.set_utility(-30, {"fever_later_0": "False", "reaction_0": "Yes"}) -# utility_0.set_utility(50, {"fever_later_0": "False", "reaction_0": "No"}) -# -# d0.set_partial_ordering([["flu_0", "fever_0", "therm_0"], "take_aspirin_0", ["fever_later_0", "reaction_0"]]) -# -# ve = VariableElimination(d0) -# -# print("Expected Utility for Taking Aspirin: {}".format(ve.expected_utility(decisions={"take_aspirin_0": "Yes"}))) -# print("Expected Utility for not Taking Aspirin: {}".format(ve.expected_utility(decisions={"take_aspirin_0": "No"}))) -# print("Optimal decision: ", ve.get_optimal_decisions(["take_aspirin_0"])) -# print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("take_aspirin_0"))) - - -# L2Tor example +costsEducation.set_utility(-50000, parentValues={"education":"do Phd"}) +costsEducation.set_utility(0, parentValues={"education":"no Phd"}) + +costsStartUp.set_utility(-200000, parentValues={"startup":"start up"}) +costsStartUp.set_utility(0, parentValues={"startup":"no start up"}) + +gains.set_utility(100000, parentValues={"income":"low"}) +gains.set_utility(200000, parentValues={"income":"average"}) +gains.set_utility(500000, parentValues={"income":"high"}) + +net.set_partial_ordering([education, nobel, startup, income]) +ve = VariableElimination(net) + +print("Expected Utility for doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "start up"}))) +print("Expected Utility for doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "no start up"}))) +print("Expected Utility for not doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "start up"}))) +print("Expected Utility for not doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "no start up"}))) +print "Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education")) +print "Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup")) + +print "" +print "Fever Example 4.3.5 from Bayesian Artificial Intelligence - Korb Nicholson" +print "" + +d0 = d0_net() + +flu_0 = DiscreteNode("flu_0", values=["True", "False"]) +fever_0 = DiscreteNode("fever_0", values=["True", "False"]) +therm_0 = DiscreteNode("therm_0", values=["True", "False"]) +take_aspirin_0 = DecisionNode("take_aspirin_0", decisions=["Yes", "No"]) +fever_later_0 = DiscreteNode("fever_later_0", values=["True", "False"]) +reaction_0 = DiscreteNode("reaction_0", values=["Yes", "No"]) +utility_0 = UtilityNode("utility_0") + +d0.add_nodes([flu_0, fever_0, therm_0, take_aspirin_0, fever_later_0, reaction_0, utility_0]) + +d0.add_edge(flu_0, fever_0) +d0.add_edge(fever_0, therm_0) +d0.add_edge(fever_0, fever_later_0) +d0.add_edge(fever_later_0, utility_0) +d0.add_edge(take_aspirin_0, reaction_0) +d0.add_edge(take_aspirin_0, fever_later_0) +d0.add_edge(reaction_0, utility_0) + +flu_0.set_probability("True", 0.05) +flu_0.set_probability("False", 0.95) + +fever_0.set_probability("True", 0.95, parentValues={"flu_0": "True"}) +fever_0.set_probability("True", 0.02, parentValues={"flu_0": "False"}) +fever_0.set_probability("False", 0.05, parentValues={"flu_0": "True"}) +fever_0.set_probability("False", 0.98, parentValues={"flu_0": "False"}) + +therm_0.set_probability("True", 0.90, parentValues={"fever_0": "True"}) +therm_0.set_probability("True", 0.05, parentValues={"fever_0": "False"}) +therm_0.set_probability("False", 0.10, parentValues={"fever_0": "True"}) +therm_0.set_probability("False", 0.95, parentValues={"fever_0": "False"}) + +fever_later_0.set_probability("True", 0.05, parentValues={"fever_0": "True", "take_aspirin_0": "Yes"}) +fever_later_0.set_probability("True", 0.90, parentValues={"fever_0": "True", "take_aspirin_0": "No"}) +fever_later_0.set_probability("True", 0.01, parentValues={"fever_0": "False", "take_aspirin_0": "Yes"}) +fever_later_0.set_probability("True", 0.02, parentValues={"fever_0": "False", "take_aspirin_0": "No"}) +fever_later_0.set_probability("False", 0.95, parentValues={"fever_0": "True", "take_aspirin_0": "Yes"}) +fever_later_0.set_probability("False", 0.10, parentValues={"fever_0": "True", "take_aspirin_0": "No"}) +fever_later_0.set_probability("False", 0.99, parentValues={"fever_0": "False", "take_aspirin_0": "Yes"}) +fever_later_0.set_probability("False", 0.98, parentValues={"fever_0": "False", "take_aspirin_0": "No"}) + +reaction_0.set_probability("Yes", 0.05, parentValues={"take_aspirin_0": "Yes"}) +reaction_0.set_probability("Yes", 0.00, parentValues={"take_aspirin_0": "No"}) +reaction_0.set_probability("No", 0.95, parentValues={"take_aspirin_0": "Yes"}) +reaction_0.set_probability("No", 1.0, parentValues={"take_aspirin_0": "No"}) + +utility_0.set_utility(-50, {"fever_later_0": "True", "reaction_0": "Yes"}) +utility_0.set_utility(-10, {"fever_later_0": "True", "reaction_0": "No"}) +utility_0.set_utility(-30, {"fever_later_0": "False", "reaction_0": "Yes"}) +utility_0.set_utility(50, {"fever_later_0": "False", "reaction_0": "No"}) + +d0.set_partial_ordering([["flu_0", "fever_0", "therm_0"], "take_aspirin_0", ["fever_later_0", "reaction_0"]]) + +ve = VariableElimination(d0) + +print("Expected Utility for Taking Aspirin: {}".format(ve.expected_utility(decisions={"take_aspirin_0": "Yes"}))) +print("Expected Utility for not Taking Aspirin: {}".format(ve.expected_utility(decisions={"take_aspirin_0": "No"}))) +print "Get optimal decision using classic algorithm: ", ve.get_optimal_decisions(["take_aspirin_0"]) +print "Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("take_aspirin_0")) + +print "" +print "L2Tor example" +print "" d0 = d0_net() @@ -496,9 +350,6 @@ new_net = DDN.unroll(2) -print new_net.node_lookup["observation_1"].cpd -print new_net.get_partial_ordering() - ve = VariableElimination(new_net) # print("Optimal decision: ", ve.get_optimal_decisions(["action_0"])) print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("action_1"))) From 736c2dcdd8605951a7827764d342bb5bc61ee7f7 Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Mon, 16 Apr 2018 11:04:39 +0200 Subject: [PATCH 09/12] Deleted some not used functions and Parameters --- examples/decision_example.py | 7 ------- primo2/networks.py | 23 +++++------------------ 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/examples/decision_example.py b/examples/decision_example.py index c52ca4a..e013f57 100644 --- a/examples/decision_example.py +++ b/examples/decision_example.py @@ -256,8 +256,6 @@ d0.add_edge(skill_0, action_0) d0.add_edge(action_0, observation_0) -d0.set_zero_timeslice([skill_0, observation_0, action_0]) - d0.set_partial_ordering(["skill_0", "action_0", "observation_0"]) skill_0.set_cpd([1.0 / 6, 1.0 / 6, 1.0 / 6, 1.0 / 6, 1.0 / 6, 1.0 / 6]) @@ -330,8 +328,6 @@ [[0, 0], [0, 0], [0, 0], [0, 0]]]] two_tdn.add_nodes([skill_t, action_t, observation_t, skill_t_plus_one, action_t_plus_one, observation_t_plus_one]) -two_tdn.set_t_timeslice([skill_t, action_t, observation_t]) -two_tdn.set_t_plus_one_timeslice([skill_t_plus_one, action_t_plus_one, observation_t_plus_one]) two_tdn.add_intra_edge(skill_t, action_t) two_tdn.add_intra_edge(skill_t, observation_t) @@ -343,9 +339,6 @@ two_tdn.add_transition(skill_t_plus_one, cpd) - -# two_tdn.set_transition_probability(skill_t_plus_one, ) - DDN = DynamicDecisionNetwork(d0, two_tdn) new_net = DDN.unroll(2) diff --git a/primo2/networks.py b/primo2/networks.py index 8bb1b25..9db3bc5 100644 --- a/primo2/networks.py +++ b/primo2/networks.py @@ -399,11 +399,11 @@ def unroll(self, length): if length > 0: for i in range(0, length): if i == 0: - unrolled_net.add_nodes(self._d0.timeslice[0] + self._d0.utility_nodes) + unrolled_net.add_nodes(self._d0.get_all_nodes()) elif i == 1: children = [] """copy nodes from the two_tdn Network into unrolled_net""" - unrolled_net.copy_nodes_indexed(self._two_tdn.timeslice['t_plus_1'], i) + unrolled_net.copy_nodes_indexed(self._two_tdn.get_future_nodes(), i) for inter_pair in self._two_tdn.get_inter_edges(): parent_node_name = inter_pair[0].get_indexed_node(i - 1) child_node_name = inter_pair[1].get_indexed_node(i) @@ -429,13 +429,11 @@ def unroll(self, length): else: children = [] - unrolled_net.copy_nodes_indexed(self._two_tdn.timeslice['t_plus_1'], i) + unrolled_net.copy_nodes_indexed(self._two_tdn.get_future_nodes(), i) # in case we dont want the decisions to be copied for every new timeslice but only for every # two. Write this and leave the decisions node out of the timeslices in both d0 and two_tbn network: # unrolled_net.copy_nodes_indexed(self._two_tdn.decision_nodes, i - 1) - unrolled_net.copy_nodes_indexed(self._two_tdn.utility_nodes, i - 1) - for inter_pair in self._two_tdn.get_inter_edges(): parent_node_name = inter_pair[0].get_indexed_node(i - 1) child_node_name = inter_pair[1].get_indexed_node(i) @@ -467,16 +465,12 @@ def unroll(self, length): class d0_net(object): def __init__(self): - self.timeslice = {} self.utility_nodes = [] self.decision_nodes = [] self.random_nodes = [] self.node_lookup = {} self.partialOrdering = [] - def set_zero_timeslice(self, nodes): - self.timeslice[0] = nodes - def add_nodes(self, nodes): for i in nodes: if isinstance(i, RandomNode): @@ -522,7 +516,6 @@ def get_all_node_names(self): class Two_TDN(object): def __init__(self, inter_edges=None, intra_edges=None): - self.timeslice = {} self.utility_nodes = [] self.decision_nodes = [] self.random_nodes = [] @@ -659,14 +652,8 @@ def add_transition(self, node, cpd): def get_transition_probability(self, node): return self.transitions[node] - def set_t_timeslice(self, initial_nodes): - self.timeslice['t'] = initial_nodes - - def set_t_plus_one_timeslice(self, next_nodes): - self.timeslice['t_plus_1'] = next_nodes - - def get_t_timeslice(self): - return self.timeslice['t'] + def get_future_nodes(self): + return [self.node_lookup[x] for x in self.node_lookup.keys() if "t_plus_one" in x] class DecisionNetwork(object): From e8815be9feea3d5de3090e3ae4bf9d75ec7fb4de Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Mon, 16 Apr 2018 18:13:19 +0200 Subject: [PATCH 10/12] Decision Node extended to now return the best current action using a gaussian filter and the current believe state --- examples/decision_example.py | 187 +++++++++++++++++++---------------- primo2/networks.py | 10 +- primo2/nodes.py | 25 +++++ 3 files changed, 132 insertions(+), 90 deletions(-) diff --git a/examples/decision_example.py b/examples/decision_example.py index e013f57..cbf393a 100644 --- a/examples/decision_example.py +++ b/examples/decision_example.py @@ -10,6 +10,11 @@ from primo2.nodes import DiscreteNode, DecisionNode, UtilityNode from primo2.inference.decision import VariableElimination +from math import exp, sqrt, pi +import matplotlib.pyplot as plt +import numpy as np +import matplotlib.mlab as mlab +import pandas as pd print ("") print ("PHD example 7.3 from Bayesian Reasoning and Machine Learning - Barber") @@ -17,17 +22,15 @@ net = DecisionNetwork() -education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E +education = DecisionNode("education", decisions=["do Phd", "no Phd"]) # E -income = DiscreteNode("income", values=["low", "average", "high"]) #I -nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P +income = DiscreteNode("income", values=["low", "average", "high"]) # I +nobel = DiscreteNode("nobel", values=["prize", "no prize"]) # P -costs = UtilityNode("costs") #UC -gains = UtilityNode("gains") #UB +costs = UtilityNode("costs") # UC +gains = UtilityNode("gains") # UB - - -#Add nodes to network. They can be treated the same +# Add nodes to network. They can be treated the same net.add_node(education) net.add_node(income) net.add_node(nobel) @@ -35,9 +38,8 @@ net.add_node(costs) net.add_node(gains) - -#Add edges. Edges can either be acutal dependencies or information links. -#The type is figured out by the nodes themsevles +# Add edges. Edges can either be acutal dependencies or information links. +# The type is figured out by the nodes themsevles net.add_edge(education, costs) net.add_edge(education, nobel) net.add_edge(education, income) @@ -45,46 +47,44 @@ net.add_edge(nobel, income) net.add_edge(income, gains) -#Define CPTs: (Needs to be done AFTER the structure is defined as that) -#determines the table structure for the different nodes - -income.set_probability("low", 0.1, parentValues={"education":"do Phd", "nobel":"no prize"}) -income.set_probability("low", 0.2, parentValues={"education":"no Phd", "nobel":"no prize"}) -income.set_probability("low", 0.01, parentValues={"education":"do Phd", "nobel":"prize"}) -income.set_probability("low", 0.01, parentValues={"education":"no Phd", "nobel":"prize"}) +# Define CPTs: (Needs to be done AFTER the structure is defined as that) +# determines the table structure for the different nodes -income.set_probability("average", 0.5, parentValues={"education":"do Phd", "nobel":"no prize"}) -income.set_probability("average", 0.6, parentValues={"education":"no Phd", "nobel":"no prize"}) -income.set_probability("average", 0.04, parentValues={"education":"do Phd", "nobel":"prize"}) -income.set_probability("average", 0.04, parentValues={"education":"no Phd", "nobel":"prize"}) +income.set_probability("low", 0.1, parentValues={"education": "do Phd", "nobel": "no prize"}) +income.set_probability("low", 0.2, parentValues={"education": "no Phd", "nobel": "no prize"}) +income.set_probability("low", 0.01, parentValues={"education": "do Phd", "nobel": "prize"}) +income.set_probability("low", 0.01, parentValues={"education": "no Phd", "nobel": "prize"}) -income.set_probability("high", 0.4, parentValues={"education":"do Phd", "nobel":"no prize"}) -income.set_probability("high", 0.2, parentValues={"education":"no Phd", "nobel":"no prize"}) -income.set_probability("high", 0.95, parentValues={"education":"do Phd", "nobel":"prize"}) -income.set_probability("high", 0.95, parentValues={"education":"no Phd", "nobel":"prize"}) +income.set_probability("average", 0.5, parentValues={"education": "do Phd", "nobel": "no prize"}) +income.set_probability("average", 0.6, parentValues={"education": "no Phd", "nobel": "no prize"}) +income.set_probability("average", 0.04, parentValues={"education": "do Phd", "nobel": "prize"}) +income.set_probability("average", 0.04, parentValues={"education": "no Phd", "nobel": "prize"}) +income.set_probability("high", 0.4, parentValues={"education": "do Phd", "nobel": "no prize"}) +income.set_probability("high", 0.2, parentValues={"education": "no Phd", "nobel": "no prize"}) +income.set_probability("high", 0.95, parentValues={"education": "do Phd", "nobel": "prize"}) +income.set_probability("high", 0.95, parentValues={"education": "no Phd", "nobel": "prize"}) -nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) -nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) +nobel.set_probability("prize", 0.0000001, parentValues={"education": "no Phd"}) +nobel.set_probability("prize", 0.001, parentValues={"education": "do Phd"}) -nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) -nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) +nobel.set_probability("no prize", 0.9999999, parentValues={"education": "no Phd"}) +nobel.set_probability("no prize", 0.999, parentValues={"education": "do Phd"}) +# Define utilities -#Define utilities +costs.set_utility(-50000, parentValues={"education": "do Phd"}) +costs.set_utility(0, parentValues={"education": "no Phd"}) -costs.set_utility(-50000, parentValues={"education":"do Phd"}) -costs.set_utility(0, parentValues={"education":"no Phd"}) +gains.set_utility(100000, parentValues={"income": "low"}) +gains.set_utility(200000, parentValues={"income": "average"}) +gains.set_utility(500000, parentValues={"income": "high"}) -gains.set_utility(100000, parentValues={"income":"low"}) -gains.set_utility(200000, parentValues={"income":"average"}) -gains.set_utility(500000, parentValues={"income":"high"}) - -net.set_partial_ordering([education, [income,nobel]]) +net.set_partial_ordering([education, [income, nobel]]) ve = VariableElimination(net) -print("Expected Utility for doing a Phd: {}".format(ve.expected_utility(decisions={"education":"do Phd"}))) -print("Expected Utility for not doing a Phd: {}".format(ve.expected_utility(decisions={"education":"no Phd"}))) +print("Expected Utility for doing a Phd: {}".format(ve.expected_utility(decisions={"education": "do Phd"}))) +print("Expected Utility for not doing a Phd: {}".format(ve.expected_utility(decisions={"education": "no Phd"}))) print "Optimal decision using max_sum: ", ve.max_sum("education") print "Get optimal decision using classic algorithm: ", ve.get_optimal_decisions(["education"]) @@ -94,19 +94,17 @@ net = DecisionNetwork() +education = DecisionNode("education", decisions=["do Phd", "no Phd"]) # E +startup = DecisionNode("startup", decisions=["start up", "no start up"]) # S -education = DecisionNode("education", decisions=["do Phd", "no Phd"]) #E -startup = DecisionNode("startup", decisions=["start up", "no start up"]) # S - -income = DiscreteNode("income", values=["low", "average", "high"]) #I -nobel = DiscreteNode("nobel", values=["prize", "no prize"]) #P - -costsEducation = UtilityNode("costsE") #UC -costsStartUp = UtilityNode("costsS") #US -gains = UtilityNode("gains") #UB +income = DiscreteNode("income", values=["low", "average", "high"]) # I +nobel = DiscreteNode("nobel", values=["prize", "no prize"]) # P +costsEducation = UtilityNode("costsE") # UC +costsStartUp = UtilityNode("costsS") # US +gains = UtilityNode("gains") # UB -#Add nodes to network. They can be treated the same +# Add nodes to network. They can be treated the same net.add_node(education) net.add_node(startup) net.add_node(income) @@ -116,9 +114,8 @@ net.add_node(costsStartUp) net.add_node(gains) - -#Add edges. Edges can either be acutal dependencies or information links. -#The type is figured out by the nodes themsevles +# Add edges. Edges can either be acutal dependencies or information links. +# The type is figured out by the nodes themsevles net.add_edge(education, costsEducation) net.add_edge(education, nobel) @@ -128,51 +125,53 @@ net.add_edge(nobel, income) net.add_edge(income, gains) -#Define CPTs: (Needs to be done AFTER the structure is defined as that) -#determines the table structure for the different nodes - -income.set_probability("low", 0.1, parentValues={"startup":"start up", "nobel":"no prize"}) -income.set_probability("low", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) -income.set_probability("low", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) -income.set_probability("low", 0.05, parentValues={"startup":"no start up", "nobel":"prize"}) - -income.set_probability("average", 0.5, parentValues={"startup":"start up", "nobel":"no prize"}) -income.set_probability("average", 0.6, parentValues={"startup":"no start up", "nobel":"no prize"}) -income.set_probability("average", 0.005, parentValues={"startup":"start up", "nobel":"prize"}) -income.set_probability("average", 0.15, parentValues={"startup":"no start up", "nobel":"prize"}) +# Define CPTs: (Needs to be done AFTER the structure is defined as that) +# determines the table structure for the different nodes -income.set_probability("high", 0.4, parentValues={"startup":"start up", "nobel":"no prize"}) -income.set_probability("high", 0.2, parentValues={"startup":"no start up", "nobel":"no prize"}) -income.set_probability("high", 0.99, parentValues={"startup":"start up", "nobel":"prize"}) -income.set_probability("high", 0.8, parentValues={"startup":"no start up", "nobel":"prize"}) +income.set_probability("low", 0.1, parentValues={"startup": "start up", "nobel": "no prize"}) +income.set_probability("low", 0.2, parentValues={"startup": "no start up", "nobel": "no prize"}) +income.set_probability("low", 0.005, parentValues={"startup": "start up", "nobel": "prize"}) +income.set_probability("low", 0.05, parentValues={"startup": "no start up", "nobel": "prize"}) +income.set_probability("average", 0.5, parentValues={"startup": "start up", "nobel": "no prize"}) +income.set_probability("average", 0.6, parentValues={"startup": "no start up", "nobel": "no prize"}) +income.set_probability("average", 0.005, parentValues={"startup": "start up", "nobel": "prize"}) +income.set_probability("average", 0.15, parentValues={"startup": "no start up", "nobel": "prize"}) -nobel.set_probability("prize", 0.0000001, parentValues={"education":"no Phd"}) -nobel.set_probability("prize", 0.001, parentValues={"education":"do Phd"}) +income.set_probability("high", 0.4, parentValues={"startup": "start up", "nobel": "no prize"}) +income.set_probability("high", 0.2, parentValues={"startup": "no start up", "nobel": "no prize"}) +income.set_probability("high", 0.99, parentValues={"startup": "start up", "nobel": "prize"}) +income.set_probability("high", 0.8, parentValues={"startup": "no start up", "nobel": "prize"}) -nobel.set_probability("no prize", 0.9999999, parentValues={"education":"no Phd"}) -nobel.set_probability("no prize", 0.999, parentValues={"education":"do Phd"}) +nobel.set_probability("prize", 0.0000001, parentValues={"education": "no Phd"}) +nobel.set_probability("prize", 0.001, parentValues={"education": "do Phd"}) +nobel.set_probability("no prize", 0.9999999, parentValues={"education": "no Phd"}) +nobel.set_probability("no prize", 0.999, parentValues={"education": "do Phd"}) -#Define utilities +# Define utilities -costsEducation.set_utility(-50000, parentValues={"education":"do Phd"}) -costsEducation.set_utility(0, parentValues={"education":"no Phd"}) +costsEducation.set_utility(-50000, parentValues={"education": "do Phd"}) +costsEducation.set_utility(0, parentValues={"education": "no Phd"}) -costsStartUp.set_utility(-200000, parentValues={"startup":"start up"}) -costsStartUp.set_utility(0, parentValues={"startup":"no start up"}) +costsStartUp.set_utility(-200000, parentValues={"startup": "start up"}) +costsStartUp.set_utility(0, parentValues={"startup": "no start up"}) -gains.set_utility(100000, parentValues={"income":"low"}) -gains.set_utility(200000, parentValues={"income":"average"}) -gains.set_utility(500000, parentValues={"income":"high"}) +gains.set_utility(100000, parentValues={"income": "low"}) +gains.set_utility(200000, parentValues={"income": "average"}) +gains.set_utility(500000, parentValues={"income": "high"}) net.set_partial_ordering([education, nobel, startup, income]) ve = VariableElimination(net) -print("Expected Utility for doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "start up"}))) -print("Expected Utility for doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"do Phd", "startup": "no start up"}))) -print("Expected Utility for not doing a Phd + startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "start up"}))) -print("Expected Utility for not doing a Phd + no startup: {}".format(ve.expected_utility(decisions={"education":"no Phd", "startup": "no start up"}))) +print("Expected Utility for doing a Phd + startup: {}".format( + ve.expected_utility(decisions={"education": "do Phd", "startup": "start up"}))) +print("Expected Utility for doing a Phd + no startup: {}".format( + ve.expected_utility(decisions={"education": "do Phd", "startup": "no start up"}))) +print("Expected Utility for not doing a Phd + startup: {}".format( + ve.expected_utility(decisions={"education": "no Phd", "startup": "start up"}))) +print("Expected Utility for not doing a Phd + no startup: {}".format( + ve.expected_utility(decisions={"education": "no Phd", "startup": "no start up"}))) print "Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("education")) print "Get optimal decision for startup using max_sum Algorithm: {}".format(ve.max_sum("startup")) @@ -230,7 +229,7 @@ utility_0.set_utility(-50, {"fever_later_0": "True", "reaction_0": "Yes"}) utility_0.set_utility(-10, {"fever_later_0": "True", "reaction_0": "No"}) utility_0.set_utility(-30, {"fever_later_0": "False", "reaction_0": "Yes"}) -utility_0.set_utility(50, {"fever_later_0": "False", "reaction_0": "No"}) +utility_0.set_utility(50, {"fever_later_0": "False", "reaction_0": "No"}) d0.set_partial_ordering([["flu_0", "fever_0", "therm_0"], "take_aspirin_0", ["fever_later_0", "reaction_0"]]) @@ -265,6 +264,11 @@ [0.25, 0.25, 0.25, 0.40, 0.35, 0.30], [0.05, 0.10, 0.15, 0.20, 0.30, 0.40]]) +action_0.set_action_norm({'1': {'loc': .15, 'scale': .05}, + '2': {'loc': .40, 'scale': .05}, + '3': {'loc': .625, 'scale': .05}, + '4': {'loc': .85, 'scale': .05}}) + observation_0.set_cpd([[[0.50, 0.3, 0.25, 0.15], [0.55, 0.40, 0.30, 0.20], [0.65, 0.55, 0.40, 0.30], @@ -345,7 +349,16 @@ ve = VariableElimination(new_net) # print("Optimal decision: ", ve.get_optimal_decisions(["action_0"])) -print("Get optimal decision for education using max_sum Algorithm: {}".format(ve.max_sum("action_1"))) +print("Get optimal decision for action_1 using max_sum Algorithm: {}".format(ve.max_sum("action_1"))) + + +# mu = [.15, .40, .625, .85] +# variance = [.05, .05, .05, .05] +# sigma = [sqrt(i) for i in variance] +# x = [np.linspace(mu[i] - 3 * sigma[i], mu[i] + 3 * sigma[i], 100) for i, _ in enumerate(mu)] +# for i, v in enumerate(x): +# plt.plot(v, mlab.normpdf(v, mu[i], sigma[i])) +# plt.show() # new = {'+O': {"1": {0: {0: 0.60, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, # 1: {0: 0.25, 1: 0.70, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, diff --git a/primo2/networks.py b/primo2/networks.py index 9db3bc5..da692db 100644 --- a/primo2/networks.py +++ b/primo2/networks.py @@ -24,6 +24,7 @@ from . import exceptions from . import nodes from primo2.nodes import RandomNode, DiscreteNode, DecisionNode, UtilityNode +from math import exp, pi, sqrt class BayesianNetwork(object): @@ -414,7 +415,8 @@ def unroll(self, length): for current_transition in self._two_tdn.transitions.keys(): node_name = current_transition.get_indexed_node(i) - unrolled_net.node_lookup[node_name].set_cpd(self._two_tdn.get_transition_probability(current_transition)) + unrolled_net.node_lookup[node_name].set_cpd( + self._two_tdn.get_transition_probability(current_transition)) for intra_pair in self._two_tdn.get_intra_edges(): parent_node_name = intra_pair[0].get_indexed_node(i) child_node_name = intra_pair[1].get_indexed_node(i) @@ -443,7 +445,8 @@ def unroll(self, length): for current_transition in self._two_tdn.transitions.keys(): node_name = current_transition.get_indexed_node(i) - unrolled_net.node_lookup[node_name].set_cpd(self._two_tdn.get_transition_probability(current_transition)) + unrolled_net.node_lookup[node_name].set_cpd( + self._two_tdn.get_transition_probability(current_transition)) for intra_pair in self._two_tdn.get_intra_edges(): parent_node_name = intra_pair[0].get_indexed_node(i) @@ -512,7 +515,6 @@ def get_all_nodes(self): def get_all_node_names(self): return self.node_lookup.keys() - class Two_TDN(object): def __init__(self, inter_edges=None, intra_edges=None): @@ -528,6 +530,8 @@ def __init__(self, inter_edges=None, intra_edges=None): if intra_edges is not None: self.add_intra_edges(intra_edges) + + def add_nodes(self, nodes): for i in nodes: if isinstance(i, RandomNode): diff --git a/primo2/nodes.py b/primo2/nodes.py index 510ace6..b6222df 100644 --- a/primo2/nodes.py +++ b/primo2/nodes.py @@ -22,6 +22,8 @@ import random import numpy as np +import pandas as pd +from math import sqrt, exp, pi class RandomNode(object): @@ -559,6 +561,7 @@ def __init__(self, nodeName, decisions=["Yes", "No"]): self.deterministic = True self._update_dimensions() self.valid = False + self.action_norm = {} def add_parent(self, parentNode): """ @@ -614,6 +617,9 @@ def fully_mixed(self): self.cpd = np.ones(self.cpd.shape) self.cpd /= len(self.values) + def set_action_norm(self, action_norm): + self.action_norm = pd.DataFrame(action_norm) + def set_cpd(self, cpd): """ Allows to set the conditional probability density(table) of this @@ -633,6 +639,25 @@ def set_cpd(self, cpd): self.cpd = np.copy(cpd) self.valid = True + def get_best_action(self, skill_believe): + """ + :param skill_believe: The current skill believe + :return: The reduced CPD with only the best chosen action + """ + def get_normal(x, mu, var): + return (1 / (sqrt(2 * pi * var))) * exp(-((x - mu) ** 2) / (2 * var)) + + best_value = 0 + best_action = None + + for i in self.action_norm: + current_value = get_normal(skill_believe, self.action_norm.loc[('loc', i)], self.action_norm.loc[('scale', i)]) + if current_value > best_value: + best_value = current_value + best_action = i + + return self.cpd[self.values.index(best_action)] + if __name__ == "__main__": costs = UtilityNode("costs") From 04674911ba6b7cecad3968013d66f2c82121376a Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Tue, 17 Apr 2018 14:28:50 +0200 Subject: [PATCH 11/12] Modified the max_sum Algorithm: Now also decision Tables are included in calculating the inner product, when a decision Rule is given for ALL Decision Nodes. Transition Matrix can now also be a dictionary (better readability). Added the possibility to set the decision Nodes to the best action based on a skill believe of the parent node: This feature still has to be updated --- examples/decision_example.py | 161 +++++++++++++---------------------- primo2/inference/decision.py | 47 +++++++--- primo2/networks.py | 35 ++++++-- primo2/nodes.py | 20 +++-- 4 files changed, 140 insertions(+), 123 deletions(-) diff --git a/examples/decision_example.py b/examples/decision_example.py index cbf393a..93a2e16 100644 --- a/examples/decision_example.py +++ b/examples/decision_example.py @@ -294,42 +294,65 @@ action_t_plus_one = DecisionNode("action_t_plus_one", decisions=["1", "2", "3", "4"]) observation_t_plus_one = DiscreteNode("observation_t_plus_one", values=["+O", "-O"]) -cpd = [[[[0.60, 1.00], [0.55, 1.00], [0.40, 1.00], [0.30, 1.00]], - [[0.25, 0], [0.35, 0], [0.40, 0], [0.45, 0]], - [[0.15, 0], [0.10, 0], [0.20, 0], [0.20, 0]], - [[0, 0], [0, 0], [0, 0], [0.05, 0]], - [[0, 0], [0, 0], [0, 0], [0, 0]], - [[0, 0], [0, 0], [0, 0], [0, 0]]], - [[[0, 0.10], [0, 0.25], [0, 0.20], [0, 0.40]], - [[0.70, 0.90], [0.60, 0.75], [0.40, 0.80], [0.30, 0.60]], - [[0.20, 0], [0.30, 0], [0.40, 0], [0.45, 0]], - [[0.10, 0], [0.10, 0], [0.20, 0], [0.20, 0]], - [[0, 0], [0, 0], [0, 0], [0.05, 0]], - [[0, 0], [0, 0], [0, 0], [0, 0]]], - [[[0, 0.05], [0, 0.05], [0, 0.20], [0, 0.15]], - [[0, 0.10], [0, 0.25], [0, 0.40], [0, 0.35]], - [[0, 0.25], [0, 0.25], [0, 0.40], [0, 0.35]], - [[0.85, 0.60], [0.70, 0.65], [0.50, 0.40], [0.30, 0.50]], - [[0.10, 0], [0.25, 0], [0.40, 0], [0.45, 0]], - [[0.05, 0], [0.05, 0], [0.10, 0], [0.25, 0]]], - [[[0, 0], [0, 0], [0, 0], [0, 0]], - [[0, 0.15], [0, 0.10], [0, 0.20], [0, 0.15]], - [[0, 0.25], [0, 0.25], [0, 0.40], [0, 0.35]], - [[0.85, 0.60], [0.70, 0.65], [0.50, 0.40], [0.30, 0.35]], - [[0.10, 0], [0.25, 0], [0.40, 0], [0.45, 0.]], - [[0.05, 0], [0.05, 0], [0.10, 0], [0.25, 0]]], - [[[0, 0], [0, 0], [0, 0], [0, 0]], - [[0, 0], [0, 0], [0, 0], [0, 0]], - [[0, 0], [0, 0], [0, 0], [0, 0]], - [[0, 0.25], [0, 0.10], [0, 0.20], [0, 0.15]], - [[0, 0.35], [0, 0.35], [0, 0.40], [0, 0.35]], - [[1.0, 0.40], [1.0, 0.55], [1.0, 0.40], [1.0, 0.50]]], - [[[0, 0], [0, 0], [0, 0], [0, 0]], - [[0, 0.15], [0, 0.10], [0, 0.20], [0, 0.15]], - [[0, 0.25], [0, 0.25], [0, 0.40], [0, 0.35]], - [[0.85, 0.60], [0.70, 0.65], [0.50, 0.40], [0.30, 0.35]], - [[0, 0], [0, 0], [0, 0], [0, 0]], - [[0, 0], [0, 0], [0, 0], [0, 0]]]] +transition_dict = {'+O': {"1": {0: {0: 0.60, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, + 1: {0: 0.25, 1: 0.70, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, + 2: {0: 0.15, 1: 0.20, 2: 0.80, 3: 0.00, 4: 0.00, 5: 0.00}, + 3: {0: 0.00, 1: 0.10, 2: 0.15, 3: 0.85, 4: 0.00, 5: 0.00}, + 4: {0: 0.00, 1: 0.00, 2: 0.05, 3: 0.10, 4: 0.90, 5: 0.00}, + 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.05, 4: 0.10, 5: 1.00} + }, + "2": {0: {0: 0.55, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, + 1: {0: 0.35, 1: 0.60, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, + 2: {0: 0.10, 1: 0.30, 2: 0.70, 3: 0.00, 4: 0.00, 5: 0.00}, + 3: {0: 0.00, 1: 0.10, 2: 0.20, 3: 0.70, 4: 0.00, 5: 0.00}, + 4: {0: 0.00, 1: 0.00, 2: 0.10, 3: 0.25, 4: 0.75, 5: 0.00}, + 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.05, 4: 0.25, 5: 1.00} + }, + "3": {0: {0: 0.40, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, + 1: {0: 0.40, 1: 0.40, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, + 2: {0: 0.20, 1: 0.40, 2: 0.40, 3: 0.00, 4: 0.00, 5: 0.00}, + 3: {0: 0.00, 1: 0.20, 2: 0.40, 3: 0.50, 4: 0.00, 5: 0.00}, + 4: {0: 0.00, 1: 0.00, 2: 0.20, 3: 0.40, 4: 0.60, 5: 0.00}, + 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.10, 4: 0.40, 5: 1.00} + }, + "4": {0: {0: 0.30, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, + 1: {0: 0.45, 1: 0.30, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, + 2: {0: 0.20, 1: 0.45, 2: 0.30, 3: 0.00, 4: 0.00, 5: 0.00}, + 3: {0: 0.05, 1: 0.20, 2: 0.45, 3: 0.30, 4: 0.00, 5: 0.00}, + 4: {0: 0.00, 1: 0.05, 2: 0.20, 3: 0.45, 4: 0.40, 5: 0.00}, + 5: {0: 0.00, 1: 0.00, 2: 0.05, 3: 0.25, 4: 0.60, 5: 1.00} + } + }, + '-O': {"1": {0: {0: 1.00, 1: 0.10, 2: 0.05, 3: 0.00, 4: 0.00, 5: 0.00}, + 1: {0: 0.00, 1: 0.90, 2: 0.10, 3: 0.15, 4: 0.00, 5: 0.00}, + 2: {0: 0.00, 1: 0.00, 2: 0.85, 3: 0.25, 4: 0.20, 5: 0.00}, + 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.60, 4: 0.30, 5: 0.25}, + 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.50, 5: 0.35}, + 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.40} + }, + "2": {0: {0: 1.00, 1: 0.25, 2: 0.05, 3: 0.00, 4: 0.00, 5: 0.00}, + 1: {0: 0.00, 1: 0.75, 2: 0.25, 3: 0.10, 4: 0.00, 5: 0.00}, + 2: {0: 0.00, 1: 0.00, 2: 0.70, 3: 0.25, 4: 0.10, 5: 0.00}, + 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.65, 4: 0.30, 5: 0.10}, + 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.60, 5: 0.35}, + 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.55} + }, + "3": {0: {0: 1.00, 1: 0.20, 2: 0.20, 3: 0.00, 4: 0.00, 5: 0.00}, + 1: {0: 0.00, 1: 0.80, 2: 0.40, 3: 0.20, 4: 0.00, 5: 0.00}, + 2: {0: 0.00, 1: 0.00, 2: 0.40, 3: 0.40, 4: 0.20, 5: 0.00}, + 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.40, 4: 0.40, 5: 0.20}, + 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.40, 5: 0.40}, + 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.40} + }, + "4": {0: {0: 1.00, 1: 0.40, 2: 0.15, 3: 0.00, 4: 0.00, 5: 0.00}, + 1: {0: 0.00, 1: 0.60, 2: 0.35, 3: 0.15, 4: 0.00, 5: 0.00}, + 2: {0: 0.00, 1: 0.00, 2: 0.50, 3: 0.35, 4: 0.15, 5: 0.00}, + 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.50, 4: 0.35, 5: 0.15}, + 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.50, 5: 0.35}, + 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.50} + } + } + } two_tdn.add_nodes([skill_t, action_t, observation_t, skill_t_plus_one, action_t_plus_one, observation_t_plus_one]) @@ -341,16 +364,14 @@ two_tdn.add_inter_edge(action_t, skill_t_plus_one) two_tdn.add_inter_edge(observation_t, skill_t_plus_one) -two_tdn.add_transition(skill_t_plus_one, cpd) +two_tdn.add_transition(skill_t_plus_one, transition_dict) DDN = DynamicDecisionNetwork(d0, two_tdn) - new_net = DDN.unroll(2) ve = VariableElimination(new_net) # print("Optimal decision: ", ve.get_optimal_decisions(["action_0"])) -print("Get optimal decision for action_1 using max_sum Algorithm: {}".format(ve.max_sum("action_1"))) - +print("Get optimal decision for action_0 using max_sum Algorithm: {}".format(ve.max_sum("action_0"))) # mu = [.15, .40, .625, .85] # variance = [.05, .05, .05, .05] @@ -359,63 +380,3 @@ # for i, v in enumerate(x): # plt.plot(v, mlab.normpdf(v, mu[i], sigma[i])) # plt.show() - -# new = {'+O': {"1": {0: {0: 0.60, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.25, 1: 0.70, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 2: {0: 0.15, 1: 0.20, 2: 0.80, 3: 0.00, 4: 0.00, 5: 0.00}, -# 3: {0: 0.00, 1: 0.10, 2: 0.15, 3: 0.85, 4: 0.00, 5: 0.00}, -# 4: {0: 0.00, 1: 0.00, 2: 0.05, 3: 0.10, 4: 0.90, 5: 0.00}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.05, 4: 0.10, 5: 1.00} -# }, -# "2": {0: {0: 0.55, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.35, 1: 0.60, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 2: {0: 0.10, 1: 0.30, 2: 0.70, 3: 0.00, 4: 0.00, 5: 0.00}, -# 3: {0: 0.00, 1: 0.10, 2: 0.20, 3: 0.70, 4: 0.00, 5: 0.00}, -# 4: {0: 0.00, 1: 0.00, 2: 0.10, 3: 0.25, 4: 0.75, 5: 0.00}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.05, 4: 0.25, 5: 1.00} -# }, -# "3": {0: {0: 0.40, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.40, 1: 0.40, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 2: {0: 0.20, 1: 0.40, 2: 0.40, 3: 0.00, 4: 0.00, 5: 0.00}, -# 3: {0: 0.00, 1: 0.20, 2: 0.40, 3: 0.50, 4: 0.00, 5: 0.00}, -# 4: {0: 0.00, 1: 0.00, 2: 0.20, 3: 0.40, 4: 0.60, 5: 0.00}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.10, 4: 0.40, 5: 1.00} -# }, -# "4": {0: {0: 0.30, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.45, 1: 0.30, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.00}, -# 2: {0: 0.20, 1: 0.45, 2: 0.30, 3: 0.00, 4: 0.00, 5: 0.00}, -# 3: {0: 0.05, 1: 0.20, 2: 0.45, 3: 0.30, 4: 0.00, 5: 0.00}, -# 4: {0: 0.00, 1: 0.05, 2: 0.20, 3: 0.45, 4: 0.40, 5: 0.00}, -# 5: {0: 0.00, 1: 0.00, 2: 0.05, 3: 0.25, 4: 0.60, 5: 1.00} -# } -# }, -# '-O': {"1": {0: {0: 1.00, 1: 0.10, 2: 0.05, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.00, 1: 0.90, 2: 0.10, 3: 0.15, 4: 0.00, 5: 0.00}, -# 2: {0: 0.00, 1: 0.00, 2: 0.85, 3: 0.25, 4: 0.20, 5: 0.00}, -# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.60, 4: 0.30, 5: 0.25}, -# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.50, 5: 0.35}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.40} -# }, -# "2": {0: {0: 1.00, 1: 0.25, 2: 0.05, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.00, 1: 0.75, 2: 0.25, 3: 0.10, 4: 0.00, 5: 0.00}, -# 2: {0: 0.00, 1: 0.00, 2: 0.70, 3: 0.25, 4: 0.10, 5: 0.00}, -# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.65, 4: 0.30, 5: 0.10}, -# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.60, 5: 0.35}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.55} -# }, -# "3": {0: {0: 1.00, 1: 0.20, 2: 0.20, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.00, 1: 0.80, 2: 0.40, 3: 0.20, 4: 0.00, 5: 0.00}, -# 2: {0: 0.00, 1: 0.00, 2: 0.40, 3: 0.40, 4: 0.20, 5: 0.00}, -# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.40, 4: 0.40, 5: 0.20}, -# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.40, 5: 0.40}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.40} -# }, -# "4": {0: {0: 1.00, 1: 0.40, 2: 0.15, 3: 0.00, 4: 0.00, 5: 0.00}, -# 1: {0: 0.00, 1: 0.60, 2: 0.35, 3: 0.15, 4: 0.00, 5: 0.00}, -# 2: {0: 0.00, 1: 0.00, 2: 0.50, 3: 0.35, 4: 0.15, 5: 0.00}, -# 3: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.50, 4: 0.35, 5: 0.15}, -# 4: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.50, 5: 0.35}, -# 5: {0: 0.00, 1: 0.00, 2: 0.00, 3: 0.00, 4: 0.00, 5: 0.50} -# } -# } -# } diff --git a/primo2/inference/decision.py b/primo2/inference/decision.py index 1744bb1..2f1a8b7 100644 --- a/primo2/inference/decision.py +++ b/primo2/inference/decision.py @@ -215,7 +215,7 @@ def get_optimal_decisions(self, decisionOrder, fixedDecisions=None): return solution - def inner_product(self, factors, utilities=None): + def inner_product(self, factors, decision_factors=None, utilities=None): """ Helper Function to multiply all factors in the given list. This function is guided by the Algorithm 7.3.2 at page 112 in "Bayesian Reasoning and Machine Learning" @@ -228,6 +228,10 @@ def inner_product(self, factors, utilities=None): factors: list A list of factors to be multiplied with + decision_factors: list + A list of decisions to be multiplied with the factors. Not none if the decisions + in the net have a decision rule (CPD) + utilities: list A list of utilities to be multiplied with the factors @@ -236,14 +240,22 @@ def inner_product(self, factors, utilities=None): Returns ------- factor - The resulting factor + The resulting (combined) factor """ - prob_product = factors[0] for i, v in enumerate(factors): if i != len(factors) - 1: prob_product = prob_product * factors[i + 1] + combined_factors = prob_product + + if decision_factors: + decision_product = decision_factors[0] + + for i, v in enumerate(decision_factors): + if i != len(decision_factors) - 1: + decision_product = decision_product * decision_factors[i + 1] + combined_factors = combined_factors * decision_product if utilities: utility_factors = [Factor.from_utility_node(i) for i in utilities] @@ -253,16 +265,16 @@ def inner_product(self, factors, utilities=None): if i != len(utility_factors) - 1: utility_sum = utility_sum + utility_factors[i + 1] - return prob_product * utility_sum - - else: + combined_factors = combined_factors * utility_sum - return prob_product + return combined_factors def max_sum(self, decisionNode): """ Max Sum Algorithm taken from the Algorithm 7.3.2 at page 112 in - "Bayesian Reasoning and Machine Learning" from David Barber. + "Bayesian Reasoning and Machine Learning" from David Barber. + + NEW: Now works with decision rules (That means the decisions have a CPD) Parameters @@ -280,18 +292,31 @@ def max_sum(self, decisionNode): reverseOrder = partialOrder[::-1] randomVariables = self.net.get_random_nodes() utilities = self.net.get_utility_nodes() + decisions = self.net.get_decision_nodes() factors = [] for node in randomVariables: factors.append(Factor.from_node(node)) - current = self.inner_product(factors, utilities) + decision_factors = [] + + 'If All decision Nodes have a decision Rule' + if all(val.check_decision_rule() for val in decisions): + for node in decisions: + decision_factors.append(Factor.from_node(node)) + else: + decision_factors = None + + current = self.inner_product(factors, decision_factors, utilities) for i in reverseOrder: if isinstance(i, list): - if all(isinstance(self.net.node_lookup[val], DiscreteNode) for val in i): + if all(isinstance(self.net.node_lookup[val], DiscreteNode) for val in i) \ + or (all(isinstance(self.net.node_lookup[val], DecisionNode) for val in i) + and all([val.check_decision_rule() for val in i])): current = current.marginalize(i) else: - raise Exception("Marginalizing failed: Not all elements in the list are Discrete Nodes") + raise Exception("Marginalizing failed: Not all elements in the list are Discrete Nodes or " + "Decision Nodes with a specified decision rule") elif isinstance(self.net.node_lookup[i], DiscreteNode): current = current.marginalize(i) elif i != decisionNode: diff --git a/primo2/networks.py b/primo2/networks.py index da692db..fdba39e 100644 --- a/primo2/networks.py +++ b/primo2/networks.py @@ -25,7 +25,8 @@ from . import nodes from primo2.nodes import RandomNode, DiscreteNode, DecisionNode, UtilityNode from math import exp, pi, sqrt - +import pandas as pd +import numpy as np class BayesianNetwork(object): @@ -515,6 +516,7 @@ def get_all_nodes(self): def get_all_node_names(self): return self.node_lookup.keys() + class Two_TDN(object): def __init__(self, inter_edges=None, intra_edges=None): @@ -530,8 +532,6 @@ def __init__(self, inter_edges=None, intra_edges=None): if intra_edges is not None: self.add_intra_edges(intra_edges) - - def add_nodes(self, nodes): for i in nodes: if isinstance(i, RandomNode): @@ -650,8 +650,33 @@ def get_intra_edges(self): def get_inter_edges(self): return self._inter_edges - def add_transition(self, node, cpd): - self.transitions[node] = cpd + def add_transition(self, node, transition_): + """ + Adds the transition probability from the child node of a two node pair connected through a persistent edge ( + connecting two timeslices) + :param node: The child node we want to assign the CPD to + :param transition_: The transition CPD + """ + if isinstance(transition_, dict): + cpd = [] + + def get_table(transition, answer, action): + return pd.DataFrame(transition[answer][action]) + + for i in transition_.keys(): + temp = [] + for j in transition_[i]: + temp.append(get_table(transition_, i, j).as_matrix()) + + cpd.append(temp) + + self.transitions[node] = np.array(cpd).T + + elif isinstance(transition_, list): + self.transitions[node] = np.array(transition_) + + else: + raise ValueError("The transition Matrix has to be either a dictionary or a list") def get_transition_probability(self, node): return self.transitions[node] diff --git a/primo2/nodes.py b/primo2/nodes.py index b6222df..54722f3 100644 --- a/primo2/nodes.py +++ b/primo2/nodes.py @@ -558,9 +558,9 @@ def __init__(self, nodeName, decisions=["Yes", "No"]): self.state = None self.parentOrder = [] self.parents = {} + self.decision_rule = False self.deterministic = True self._update_dimensions() - self.valid = False self.action_norm = {} def add_parent(self, parentNode): @@ -590,12 +590,12 @@ def _update_dimensions(self): for parentName in self.parentOrder: dimensions.append(len(self.parents[parentName].values)) self.cpd = np.zeros(dimensions) - self.valid = False + self.decision_rule = False def set_decision(self, decision): """ Sets the state of this decisionNode to the given decision. - This will equivalate a deterministic decision rule where only + This will equate a deterministic decision rule where only the given decision has a probability of 1. This invalidates any prior decision assignments. @@ -637,13 +637,15 @@ def set_cpd(self, cpd): if np.shape(self.cpd) != np.shape(cpd): raise ValueError("The dimensions of the given cpd do not match the dependency structure of the node.") self.cpd = np.copy(cpd) - self.valid = True + self.decision_rule = True - def get_best_action(self, skill_believe): + def set_best_action(self, skill_believe): """ + Invalidates the current CPD and sets the decisionNode to the action based on the previous skill_believe :param skill_believe: The current skill believe :return: The reduced CPD with only the best chosen action """ + def get_normal(x, mu, var): return (1 / (sqrt(2 * pi * var))) * exp(-((x - mu) ** 2) / (2 * var)) @@ -651,12 +653,16 @@ def get_normal(x, mu, var): best_action = None for i in self.action_norm: - current_value = get_normal(skill_believe, self.action_norm.loc[('loc', i)], self.action_norm.loc[('scale', i)]) + current_value = get_normal(skill_believe, self.action_norm.loc[('loc', i)], + self.action_norm.loc[('scale', i)]) if current_value > best_value: best_value = current_value best_action = i - return self.cpd[self.values.index(best_action)] + self.set_decision(best_action) + + def check_decision_rule(self): + return self.decision_rule if __name__ == "__main__": From c14007af672d4e04a5f952db094a588862e2eaa6 Mon Sep 17 00:00:00 2001 From: Hendrik Luecking Date: Sat, 12 May 2018 15:30:39 +0200 Subject: [PATCH 12/12] Added update Function of Thorstens Bayesian Knowledge Tracing Algorithm (For the next skill). Bugfixing: Deleted inter edges in the unrolled net after doing update. The Child of the inter edge Relationship had an exponentially growing CPD which lead to Memory Problems. Obviously we dont want the next node to have the inter_edges internalized in the unrolled net otherwise we cant make proper updates with the same Dimension we had in the beginning. The Unrolled function looks a bit ugly now i try to make it more compact in the future --- examples/decision_example.py | 34 ++++++++++++++-- primo2/inference/decision.py | 3 +- primo2/networks.py | 77 ++++++++++++++++++++++++++---------- primo2/nodes.py | 5 +++ 4 files changed, 95 insertions(+), 24 deletions(-) diff --git a/examples/decision_example.py b/examples/decision_example.py index 93a2e16..9fda6d7 100644 --- a/examples/decision_example.py +++ b/examples/decision_example.py @@ -364,14 +364,42 @@ two_tdn.add_inter_edge(action_t, skill_t_plus_one) two_tdn.add_inter_edge(observation_t, skill_t_plus_one) -two_tdn.add_transition(skill_t_plus_one, transition_dict) + +# This is just a helper function to translate the given dictionary to an array (for this specific example. +# Should not be applied to different examples. + +def convert_to_array(transition_): + if isinstance(transition_, dict): + cpd = [] + + def get_table(transition, answer, action): + return pd.DataFrame(transition[answer][action]) + + for i in transition_.keys(): + temp = [] + for j in transition_[i]: + temp.append(get_table(transition_, i, j).as_matrix()) + + cpd.append(temp) + + # return np.transpose(np.array(cpd).T, (0, 1, 3, 2)) + return np.array(cpd).T + + +print(convert_to_array(transition_dict).shape) +two_tdn.add_transition(skill_t_plus_one, convert_to_array(transition_dict)) DDN = DynamicDecisionNetwork(d0, two_tdn) new_net = DDN.unroll(2) -ve = VariableElimination(new_net) +for i in new_net.get_all_nodes(): + print "Current One:" + print i.name + print(i.cpd) + +# ve = VariableElimination(new_net) # print("Optimal decision: ", ve.get_optimal_decisions(["action_0"])) -print("Get optimal decision for action_0 using max_sum Algorithm: {}".format(ve.max_sum("action_0"))) +# print("Get optimal decision for action_0 using max_sum Algorithm: {}".format(ve.max_sum("action_0"))) # mu = [.15, .40, .625, .85] # variance = [.05, .05, .05, .05] diff --git a/primo2/inference/decision.py b/primo2/inference/decision.py index 2f1a8b7..c821c7f 100644 --- a/primo2/inference/decision.py +++ b/primo2/inference/decision.py @@ -215,7 +215,8 @@ def get_optimal_decisions(self, decisionOrder, fixedDecisions=None): return solution - def inner_product(self, factors, decision_factors=None, utilities=None): + @staticmethod + def inner_product(factors, decision_factors=None, utilities=None): """ Helper Function to multiply all factors in the given list. This function is guided by the Algorithm 7.3.2 at page 112 in "Bayesian Reasoning and Machine Learning" diff --git a/primo2/networks.py b/primo2/networks.py index fdba39e..3282f01 100644 --- a/primo2/networks.py +++ b/primo2/networks.py @@ -27,6 +27,8 @@ from math import exp, pi, sqrt import pandas as pd import numpy as np +from primo2.inference.factor import Factor + class BayesianNetwork(object): @@ -387,6 +389,35 @@ def __init__(self, d0=None, two_tdn=None): self._d0 = d0_net() if d0 is None else d0 self._two_tdn = Two_TDN() if two_tdn is None else two_tdn + @staticmethod + def update(node): + print ("In Update Method \n") + factor_parents_all_ = [] + factor_parents_persist = [] + node_factor = Factor.from_node(node) + print "Ordering of the parents \n" + for j in node.parents.values(): + factor_parents_all_.append(Factor.from_node(j)) + if node.get_unindexed_node() == j.get_unindexed_node(): + factor_parents_persist.append(Factor.from_node(node.parents[j])) + + prob_product = node_factor + print "Initial Shape" + print prob_product.potentials.shape + for i, _ in enumerate(factor_parents_all_): + print "We are multiplying with:", node.parents.keys()[i] + + if i != len(factor_parents_all_): + print "Shape after Multiplication" + prob_product = prob_product * factor_parents_all_[i] + print prob_product.potentials.shape + + print "Final shape" + print(prob_product.get_potential().shape) + prob_product = prob_product.marginalize(node.parentOrder) + print prob_product.potentials.shape + return prob_product.get_potential() + def unroll(self, length): """Unrolling the network over specified length. @@ -415,9 +446,18 @@ def unroll(self, length): "Copy the CPD's describing the transition probability (defined by the two_tdn) into the new net" for current_transition in self._two_tdn.transitions.keys(): node_name = current_transition.get_indexed_node(i) - unrolled_net.node_lookup[node_name].set_cpd( self._two_tdn.get_transition_probability(current_transition)) + + potential = self.update(unrolled_net.node_lookup[node_name]) + + for inter_pair in self._two_tdn.get_inter_edges(): + parent_node_name = inter_pair[0].get_indexed_node(i - 1) + child_node_name = inter_pair[1].get_indexed_node(i) + unrolled_net.remove_edge(unrolled_net.node_lookup[parent_node_name], + unrolled_net.node_lookup[child_node_name]) + unrolled_net.node_lookup[node_name].set_cpd(potential) + for intra_pair in self._two_tdn.get_intra_edges(): parent_node_name = intra_pair[0].get_indexed_node(i) child_node_name = intra_pair[1].get_indexed_node(i) @@ -449,6 +489,15 @@ def unroll(self, length): unrolled_net.node_lookup[node_name].set_cpd( self._two_tdn.get_transition_probability(current_transition)) + potential = self.update(unrolled_net.node_lookup[node_name]) + + for inter_pair in self._two_tdn.get_inter_edges(): + parent_node_name = inter_pair[0].get_indexed_node(i - 1) + child_node_name = inter_pair[1].get_indexed_node(i) + unrolled_net.remove_edge(unrolled_net.node_lookup[parent_node_name], + unrolled_net.node_lookup[child_node_name]) + unrolled_net.node_lookup[node_name].set_cpd(potential) + for intra_pair in self._two_tdn.get_intra_edges(): parent_node_name = intra_pair[0].get_indexed_node(i) child_node_name = intra_pair[1].get_indexed_node(i) @@ -495,6 +544,9 @@ def add_nodes(self, nodes): def add_edge(self, node_from, node_to): self.node_lookup[node_to].add_parent(self.node_lookup[node_from]) + def remove_edge(self, node_from, node_to): + self.node_lookup[node_to].remove_parent(self.node_lookup[node_from]) + def get_partial_ordering(self): return self.partialOrdering @@ -657,26 +709,8 @@ def add_transition(self, node, transition_): :param node: The child node we want to assign the CPD to :param transition_: The transition CPD """ - if isinstance(transition_, dict): - cpd = [] - - def get_table(transition, answer, action): - return pd.DataFrame(transition[answer][action]) - - for i in transition_.keys(): - temp = [] - for j in transition_[i]: - temp.append(get_table(transition_, i, j).as_matrix()) - cpd.append(temp) - - self.transitions[node] = np.array(cpd).T - - elif isinstance(transition_, list): - self.transitions[node] = np.array(transition_) - - else: - raise ValueError("The transition Matrix has to be either a dictionary or a list") + self.transitions[node] = np.array(transition_) def get_transition_probability(self, node): return self.transitions[node] @@ -756,6 +790,9 @@ def add_edge(self, node_from, node_to): self.node_lookup[node_to].add_parent(self.node_lookup[node_from]) + def remove_edge(self, node_from, node_to): + self.node_lookup[node_to].remove_parent(self.node_lookup[node_from]) + def get_partial_ordering(self): return self.partialOrdering diff --git a/primo2/nodes.py b/primo2/nodes.py index 54722f3..4ac725f 100644 --- a/primo2/nodes.py +++ b/primo2/nodes.py @@ -41,6 +41,11 @@ def __init__(self, nodename): def set_cpd(self, cpd): raise NotImplementedError("Called unimplemented method.") + def get_unindexed_node(self): + return self.name.split("_", 1)[0] + + def get_index(self): + return self.name.split("_", 1)[1] # Renames a node to the scheme: "Node_index" and returns it def get_indexed_node(self, index): return self.name.split("_", 1)[0] + "_" + str(index)