I am implementing a dynamic bayesian network (DBN) for an umbrella problem with pgmpy and pyAgrum in this tutorial. A DBN is a bayesian network with nodes that can represent different time periods. A DBN can be used to make predictions about the future based on observations (evidence) from the past.
A DBN is a bayesian network that represents a temporal probability model, each time slice can have any number of state variables and evidence variables. Every hidden markov model (HMM) can be represented as a DBN and every DBN can be translated into an HMM. A DBN is smaller in size compared to a HMM and inference is faster in a DBN compared to a HMM.
A dynamic bayesian network consists of nodes, edges and conditional probability distributions for edges. Every edge in a DBN represent a time period and the network can include multiple time periods unlike markov models that only allow markov processes. DBN:s are common in robotics and data mining applications. Dynamic bayesian networks is also called 2 time-slice bayesian networks (2TBN).
Problem and libraries
I am using a simple problem that is modeled with two different libraries. The problem definition is: A security guard is stationed in an underground installation and tries to guess if it is raining based on observing if the director comes to work with an umbrella or not. I am using the following libraries in this tutorial: cairosvg, pyAgrum, matplotlib, pgmpy and numpy
. I had some problems when installing pgmpy
as it requires torch
, the installation of torch failed. I installed torch to Python 3.7 with: pip install https://download.pytorch.org/whl/cpu/torch-1.1.0-cp37-cp37m-win_amd64.whl
.
PGMPY implementation
The following code uses the DBN model in the pgmpy library to make predictions in the umbrella world. Output from a run is shown below the code. This library has the following inference methods: backward_inference, forward_inference and query
.
# Import libraries
import pgmpy.models
import pgmpy.inference
import numpy as np
# The main entry point for this module
def main():
# Create a dynamic bayesian network
model = pgmpy.models.DynamicBayesianNetwork()
# Add nodes
model.add_nodes_from(['Weather', 'Umbrella'])
# Print nodes
print('--- Nodes ---')
print(model.nodes())
# Add edges
model.add_edges_from([(('Umbrella',0), ('Weather',0)),
(('Weather',0), ('Weather',1)),
(('Umbrella',0), ('Umbrella',1))])
# Print edges
print('--- Edges ---')
print(model.edges())
print()
# Print parents
print('--- Parents ---')
print('Umbrella 0: {0}'.format(model.get_parents(('Umbrella', 0))))
print('Weather 0: {0}'.format(model.get_parents(('Weather', 0))))
print('Weather 1: {0}'.format(model.get_parents(('Weather', 1))))
print('Umbrella 1: {0}'.format(model.get_parents(('Umbrella', 1))))
print()
# Add probabilities
weather_cpd = pgmpy.factors.discrete.TabularCPD(('Weather', 0), 2, [[0.1, 0.8],
[0.9, 0.2]],
evidence=[('Umbrella', 0)],
evidence_card=[2])
umbrella_cpd = pgmpy.factors.discrete.TabularCPD(('Umbrella', 1), 2, [[0.5, 0.5],
[0.5, 0.5]],
evidence=[('Umbrella', 0)],
evidence_card=[2])
transition_cpd = pgmpy.factors.discrete.TabularCPD(('Weather', 1), 2, [[0.25, 0.9, 0.1, 0.25],
[0.75, 0.1, 0.9, 0.75]],
evidence=[('Weather', 0), ('Umbrella', 1)],
evidence_card=[2, 2])
# Add conditional probability distributions (cpd:s)
model.add_cpds(weather_cpd, umbrella_cpd, transition_cpd)
# This method will automatically re-adjust the cpds and the edges added to the bayesian network.
model.initialize_initial_state()
# Check if the model is valid, throw an exception otherwise
model.check_model()
# Print probability distributions
print('Probability distribution, P(Weather(0) | Umbrella(0)')
print(weather_cpd)
print()
print('Probability distribution, P(Umbrella(1) | Umbrella(0)')
print(umbrella_cpd)
print()
print('Probability distribution, P(Weather(1) | Umbrella(1), Weather(0)')
print(transition_cpd)
print()
# Make inference
map = {0: 'Sunny', 1: 'Rainy' }
dbn_inf = pgmpy.inference.DBNInference(model)
result = dbn_inf.forward_inference([('Weather', 1)], {('Umbrella', 1):0, ('Weather', 0):0})
arr = result[('Weather', 1)].values
print()
print('Prediction (Umbrella(1) : Yes, Weather(0): Sunny): {0} ({1} %)'.format(map[np.argmax(arr)], np.max(arr) * 100))
print()
result = dbn_inf.forward_inference([('Weather', 1)], {('Umbrella', 1):0, ('Weather', 0):1})
arr = result[('Weather', 1)].values
print()
print('Prediction (Umbrella(1) : Yes, Weather(0): Rainy): {0} ({1} %)'.format(map[np.argmax(arr)], np.max(arr) * 100))
print()
result = dbn_inf.forward_inference([('Weather', 1)], {('Umbrella', 1):1, ('Weather', 0):0})
arr = result[('Weather', 1)].values
print()
print('Prediction (Umbrella(1) : No, Weather(0): Sunny): {0} ({1} %)'.format(map[np.argmax(arr)], np.max(arr) * 100))
print()
result = dbn_inf.forward_inference([('Weather', 1)], {('Umbrella', 1):1, ('Weather', 0):1})
arr = result[('Weather', 1)].values
print()
print('Prediction (Umbrella(1) : No, Weather(0): Rainy): {0} ({1} %)'.format(map[np.argmax(arr)], np.max(arr) * 100))
print()
# Tell python to run main method
if __name__ == "__main__": main()
--- Nodes ---
[('Weather', 0), ('Umbrella', 0)]
--- Edges ---
[(('Weather', 0), ('Weather', 1)), (('Umbrella', 0), ('Weather', 0)), (('Umbrella', 0), ('Umbrella', 1)), (('Umbrella', 1), ('Weather', 1))]
--- Parents ---
Umbrella 0: []
Weather 0: [('Umbrella', 0)]
Weather 1: [('Umbrella', 1), ('Weather', 0)]
Umbrella 1: [('Umbrella', 0)]
Probability distribution, P(Weather(0) | Umbrella(0)
+-------------------+--------------------+--------------------+
| ('Umbrella', 0) | ('Umbrella', 0)(0) | ('Umbrella', 0)(1) |
+-------------------+--------------------+--------------------+
| ('Weather', 0)(0) | 0.1 | 0.8 |
+-------------------+--------------------+--------------------+
| ('Weather', 0)(1) | 0.9 | 0.2 |
+-------------------+--------------------+--------------------+
Probability distribution, P(Umbrella(1) | Umbrella(0)
+--------------------+--------------------+--------------------+
| ('Umbrella', 0) | ('Umbrella', 0)(0) | ('Umbrella', 0)(1) |
+--------------------+--------------------+--------------------+
| ('Umbrella', 1)(0) | 0.5 | 0.5 |
+--------------------+--------------------+--------------------+
| ('Umbrella', 1)(1) | 0.5 | 0.5 |
+--------------------+--------------------+--------------------+
Probability distribution, P(Weather(1) | Umbrella(1), Weather(0)
+-------------------+--------------------+--------------------+--------------------+--------------------+
| ('Weather', 0) | ('Weather', 0)(0) | ('Weather', 0)(0) | ('Weather', 0)(1) | ('Weather', 0)(1) |
+-------------------+--------------------+--------------------+--------------------+--------------------+
| ('Umbrella', 1) | ('Umbrella', 1)(0) | ('Umbrella', 1)(1) | ('Umbrella', 1)(0) | ('Umbrella', 1)(1) |
+-------------------+--------------------+--------------------+--------------------+--------------------+
| ('Weather', 1)(0) | 0.25 | 0.9 | 0.1 | 0.25 |
+-------------------+--------------------+--------------------+--------------------+--------------------+
| ('Weather', 1)(1) | 0.75 | 0.1 | 0.9 | 0.75 |
+-------------------+--------------------+--------------------+--------------------+--------------------+
WARNING:root:Replacing existing CPD for ('Weather', 1)
WARNING:root:Replacing existing CPD for ('Umbrella', 1)
Eliminating: ('Umbrella', 0): 100%|█████████████| 1/1 [00:00<00:00, 977.24it/s]
Prediction (Umbrella(1) : Yes, Weather(0): Sunny): Rainy (75.0 %)
Eliminating: ('Umbrella', 0): 100%|████████████| 1/1 [00:00<00:00, 1001.51it/s]
Prediction (Umbrella(1) : Yes, Weather(0): Rainy): Rainy (90.0 %)
Eliminating: ('Umbrella', 0): 100%|████████████| 1/1 [00:00<00:00, 1002.70it/s]
Prediction (Umbrella(1) : No, Weather(0): Sunny): Sunny (90.0 %)
Eliminating: ('Umbrella', 0): 100%|████████████| 1/1 [00:00<00:00, 1001.27it/s]
Prediction (Umbrella(1) : No, Weather(0): Rainy): Rainy (75.0 %)
PyAgrum implementation
The following code uses the DBN model in the pyAgrum library to make predictions in the umbrella world. Output from a run is shown below the code. This library has the following inference models: LazyPropagation, ShaferShenoyInference, VariableElimination and LoopyBeliefPropagation
.
# Import libraries
import cairosvg
import pyAgrum as gum
import pyAgrum.lib.notebook as gnb
import pyAgrum.lib.dynamicBN as gdyn
import matplotlib.pyplot as plt
# The main entry point for this module
def main():
# Create a dynamic bayesian network
model = gum.BayesNet()
# Add umbrella nodes
umbrella0 = gum.LabelizedVariable('Umbrella(0)','Umbrella day 0',2)
umbrella0.changeLabel(0,'Yes')
umbrella0.changeLabel(1,'No')
u0 = model.add(umbrella0)
umbrella1 = gum.LabelizedVariable('Umbrella(1)','Umbrella day 1',2)
umbrella1.changeLabel(0,'Yes')
umbrella1.changeLabel(1,'No')
u1 = model.add(umbrella1)
# Add weather nodes
weather0 = gum.LabelizedVariable('Weather(0)','Weather day 0',2)
weather0.changeLabel(0,'Sunny')
weather0.changeLabel(1,'Rainy')
w0 = model.add(weather0)
weather1 = gum.LabelizedVariable('Weather(1)','Weather day 1',2)
weather1.changeLabel(0,'Sunny')
weather1.changeLabel(1,'Rainy')
w1 = model.add(weather1)
# Add connections between nodes (tail, head)
model.addArc(u0, w0)
model.addArc(w0, w1)
model.addArc(u1, w1)
# Visualize bayesian network
svg = gnb.getBN(model)
cairosvg.svg2png(bytestring=svg,write_to='plots\\dnb_chart.png')
# Get time slices and save as image
svg = gdyn.getTimeSlices(model)
cairosvg.svg2png(bytestring=svg,write_to='plots\\dnb_time_slices.png')
# Add CPT:s (Conditional probability tables)
model.cpt(model.idFromName('Weather(0)'))[{'Umbrella(0)':'Yes'}]=[0.1, 0.8]
model.cpt(model.idFromName('Weather(0)'))[{'Umbrella(0)':'No'}]=[0.9, 0.2]
model.cpt(model.idFromName('Weather(1)'))[{'Umbrella(1)':'Yes'}]=[[0.25, 0.75],
[0.1, 0.9]]
model.cpt(model.idFromName('Weather(1)'))[{'Umbrella(1)':'No'}]=[[0.9, 0.1],
[0.75, 0.25]]
# Create an inference model
ie = gum.LazyPropagation(model)
# Make inference and print the results
print('--- Umbrella(0): No ---')
ie.setEvidence({'Umbrella(0)':'No'})
ie.makeInference()
print(ie.posterior('Weather(0)'))
print()
print('--- Umbrella(0): Yes ---')
ie.setEvidence({'Umbrella(0)':'Yes'})
ie.makeInference()
print(ie.posterior('Weather(0)'))
print()
print('--- Weather(0): Sunny, Umbrella(1): Yes ---')
ie.setEvidence({'Weather(0)':'Sunny', 'Umbrella(1)':'Yes'})
ie.makeInference()
#gnb.getPosterior(model, {'Weather(0)':'Sunny', 'Umbrella(1)':'Yes'}, 'Weather(1)')
#plt.show()
print(ie.posterior('Weather(1)'))
print()
print('--- Weather(0): Sunny, Umbrella(1): No ---')
ie.setEvidence({'Weather(0)':'Sunny', 'Umbrella(1)':'No'})
ie.makeInference()
print(ie.posterior('Weather(1)'))
print()
print('--- Weather(0): Rainy, Umbrella(1): Yes ---')
ie.setEvidence({'Weather(0)':'Rainy', 'Umbrella(1)':'Yes'})
ie.makeInference()
print(ie.posterior('Weather(1)'))
print()
print('--- Weather(0): Rainy, Umbrella(1): No ---')
ie.setEvidence({'Weather(0)':'Rainy', 'Umbrella(1)':'No'})
ie.makeInference()
print(ie.posterior('Weather(1)'))
print()
# Tell python to run main method
if __name__ == "__main__": main()
--- Umbrella(0): No ---
<Weather(0):Sunny> :: 0.818182 /<Weather(0):Rainy> :: 0.181818
--- Umbrella(0): Yes ---
<Weather(0):Sunny> :: 0.111111 /<Weather(0):Rainy> :: 0.888889
--- Weather(0): Sunny, Umbrella(1): Yes ---
<Weather(1):Sunny> :: 0.25 /<Weather(1):Rainy> :: 0.75
--- Weather(0): Sunny, Umbrella(1): No ---
<Weather(1):Sunny> :: 0.9 /<Weather(1):Rainy> :: 0.1
--- Weather(0): Rainy, Umbrella(1): Yes ---
<Weather(1):Sunny> :: 0.1 /<Weather(1):Rainy> :: 0.9
--- Weather(0): Rainy, Umbrella(1): No ---
<Weather(1):Sunny> :: 0.75 /<Weather(1):Rainy> :: 0.25
Hi,
just happened to stumble across your codes and tried it out.
For the first implementation, I actually got this error “values must be of shape (2, 1). Got shape: (1, 2)”
Just wondering wt am I missing Thanks!
Hello,
I am thinking how do I learn the CPD of this dynamic bayesian network from data. Because I can not find any method in Pgmpy DynamicBayesianNetwork uses ‘fit’ like the normal bayesian network.
Hello,
If i get you right then: probably you can try this with bnlearn library in python.you will also get CPD from data. But the problem right now i am facing is if the number of variable is more then the output of the model as CPD emits confusing values.