Skip to content

Dynamic Bayesian Network in Python

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
Tags:

3 thoughts on “Dynamic Bayesian Network in Python”

  1. 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!

  2. 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.

    1. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *