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.

  1. # Import libraries
  2. import pgmpy.models
  3. import pgmpy.inference
  4. import numpy as np
  5. # The main entry point for this module
  6. def main():
  7. # Create a dynamic bayesian network
  8. model = pgmpy.models.DynamicBayesianNetwork()
  9. # Add nodes
  10. model.add_nodes_from(['Weather', 'Umbrella'])
  11. # Print nodes
  12. print('--- Nodes ---')
  13. print(model.nodes())
  14. # Add edges
  15. model.add_edges_from([(('Umbrella',0), ('Weather',0)),
  16. (('Weather',0), ('Weather',1)),
  17. (('Umbrella',0), ('Umbrella',1))])
  18. # Print edges
  19. print('--- Edges ---')
  20. print(model.edges())
  21. print()
  22. # Print parents
  23. print('--- Parents ---')
  24. print('Umbrella 0: {0}'.format(model.get_parents(('Umbrella', 0))))
  25. print('Weather 0: {0}'.format(model.get_parents(('Weather', 0))))
  26. print('Weather 1: {0}'.format(model.get_parents(('Weather', 1))))
  27. print('Umbrella 1: {0}'.format(model.get_parents(('Umbrella', 1))))
  28. print()
  29. # Add probabilities
  30. weather_cpd = pgmpy.factors.discrete.TabularCPD(('Weather', 0), 2, [[0.1, 0.8],
  31. [0.9, 0.2]],
  32. evidence=[('Umbrella', 0)],
  33. evidence_card=[2])
  34. umbrella_cpd = pgmpy.factors.discrete.TabularCPD(('Umbrella', 1), 2, [[0.5, 0.5],
  35. [0.5, 0.5]],
  36. evidence=[('Umbrella', 0)],
  37. evidence_card=[2])
  38. transition_cpd = pgmpy.factors.discrete.TabularCPD(('Weather', 1), 2, [[0.25, 0.9, 0.1, 0.25],
  39. [0.75, 0.1, 0.9, 0.75]],
  40. evidence=[('Weather', 0), ('Umbrella', 1)],
  41. evidence_card=[2, 2])
  42. # Add conditional probability distributions (cpd:s)
  43. model.add_cpds(weather_cpd, umbrella_cpd, transition_cpd)
  44. # This method will automatically re-adjust the cpds and the edges added to the bayesian network.
  45. model.initialize_initial_state()
  46. # Check if the model is valid, throw an exception otherwise
  47. model.check_model()
  48. # Print probability distributions
  49. print('Probability distribution, P(Weather(0) | Umbrella(0)')
  50. print(weather_cpd)
  51. print()
  52. print('Probability distribution, P(Umbrella(1) | Umbrella(0)')
  53. print(umbrella_cpd)
  54. print()
  55. print('Probability distribution, P(Weather(1) | Umbrella(1), Weather(0)')
  56. print(transition_cpd)
  57. print()
  58. # Make inference
  59. map = {0: 'Sunny', 1: 'Rainy' }
  60. dbn_inf = pgmpy.inference.DBNInference(model)
  61. result = dbn_inf.forward_inference([('Weather', 1)], {('Umbrella', 1):0, ('Weather', 0):0})
  62. arr = result[('Weather', 1)].values
  63. print()
  64. print('Prediction (Umbrella(1) : Yes, Weather(0): Sunny): {0} ({1} %)'.format(map[np.argmax(arr)], np.max(arr) * 100))
  65. print()
  66. result = dbn_inf.forward_inference([('Weather', 1)], {('Umbrella', 1):0, ('Weather', 0):1})
  67. arr = result[('Weather', 1)].values
  68. print()
  69. print('Prediction (Umbrella(1) : Yes, Weather(0): Rainy): {0} ({1} %)'.format(map[np.argmax(arr)], np.max(arr) * 100))
  70. print()
  71. result = dbn_inf.forward_inference([('Weather', 1)], {('Umbrella', 1):1, ('Weather', 0):0})
  72. arr = result[('Weather', 1)].values
  73. print()
  74. print('Prediction (Umbrella(1) : No, Weather(0): Sunny): {0} ({1} %)'.format(map[np.argmax(arr)], np.max(arr) * 100))
  75. print()
  76. result = dbn_inf.forward_inference([('Weather', 1)], {('Umbrella', 1):1, ('Weather', 0):1})
  77. arr = result[('Weather', 1)].values
  78. print()
  79. print('Prediction (Umbrella(1) : No, Weather(0): Rainy): {0} ({1} %)'.format(map[np.argmax(arr)], np.max(arr) * 100))
  80. print()
  81. # Tell python to run main method
  82. if __name__ == "__main__": main()
  1. --- Nodes ---
  2. [('Weather', 0), ('Umbrella', 0)]
  3. --- Edges ---
  4. [(('Weather', 0), ('Weather', 1)), (('Umbrella', 0), ('Weather', 0)), (('Umbrella', 0), ('Umbrella', 1)), (('Umbrella', 1), ('Weather', 1))]
  5. --- Parents ---
  6. Umbrella 0: []
  7. Weather 0: [('Umbrella', 0)]
  8. Weather 1: [('Umbrella', 1), ('Weather', 0)]
  9. Umbrella 1: [('Umbrella', 0)]
  10. Probability distribution, P(Weather(0) | Umbrella(0)
  11. +-------------------+--------------------+--------------------+
  12. | ('Umbrella', 0) | ('Umbrella', 0)(0) | ('Umbrella', 0)(1) |
  13. +-------------------+--------------------+--------------------+
  14. | ('Weather', 0)(0) | 0.1 | 0.8 |
  15. +-------------------+--------------------+--------------------+
  16. | ('Weather', 0)(1) | 0.9 | 0.2 |
  17. +-------------------+--------------------+--------------------+
  18. Probability distribution, P(Umbrella(1) | Umbrella(0)
  19. +--------------------+--------------------+--------------------+
  20. | ('Umbrella', 0) | ('Umbrella', 0)(0) | ('Umbrella', 0)(1) |
  21. +--------------------+--------------------+--------------------+
  22. | ('Umbrella', 1)(0) | 0.5 | 0.5 |
  23. +--------------------+--------------------+--------------------+
  24. | ('Umbrella', 1)(1) | 0.5 | 0.5 |
  25. +--------------------+--------------------+--------------------+
  26. Probability distribution, P(Weather(1) | Umbrella(1), Weather(0)
  27. +-------------------+--------------------+--------------------+--------------------+--------------------+
  28. | ('Weather', 0) | ('Weather', 0)(0) | ('Weather', 0)(0) | ('Weather', 0)(1) | ('Weather', 0)(1) |
  29. +-------------------+--------------------+--------------------+--------------------+--------------------+
  30. | ('Umbrella', 1) | ('Umbrella', 1)(0) | ('Umbrella', 1)(1) | ('Umbrella', 1)(0) | ('Umbrella', 1)(1) |
  31. +-------------------+--------------------+--------------------+--------------------+--------------------+
  32. | ('Weather', 1)(0) | 0.25 | 0.9 | 0.1 | 0.25 |
  33. +-------------------+--------------------+--------------------+--------------------+--------------------+
  34. | ('Weather', 1)(1) | 0.75 | 0.1 | 0.9 | 0.75 |
  35. +-------------------+--------------------+--------------------+--------------------+--------------------+
  36. WARNING:root:Replacing existing CPD for ('Weather', 1)
  37. WARNING:root:Replacing existing CPD for ('Umbrella', 1)
  38. Eliminating: ('Umbrella', 0): 100%|█████████████| 1/1 [00:00<00:00, 977.24it/s]
  39. Prediction (Umbrella(1) : Yes, Weather(0): Sunny): Rainy (75.0 %)
  40. Eliminating: ('Umbrella', 0): 100%|████████████| 1/1 [00:00<00:00, 1001.51it/s]
  41. Prediction (Umbrella(1) : Yes, Weather(0): Rainy): Rainy (90.0 %)
  42. Eliminating: ('Umbrella', 0): 100%|████████████| 1/1 [00:00<00:00, 1002.70it/s]
  43. Prediction (Umbrella(1) : No, Weather(0): Sunny): Sunny (90.0 %)
  44. Eliminating: ('Umbrella', 0): 100%|████████████| 1/1 [00:00<00:00, 1001.27it/s]
  45. 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.

  1. # Import libraries
  2. import cairosvg
  3. import pyAgrum as gum
  4. import pyAgrum.lib.notebook as gnb
  5. import pyAgrum.lib.dynamicBN as gdyn
  6. import matplotlib.pyplot as plt
  7. # The main entry point for this module
  8. def main():
  9. # Create a dynamic bayesian network
  10. model = gum.BayesNet()
  11. # Add umbrella nodes
  12. umbrella0 = gum.LabelizedVariable('Umbrella(0)','Umbrella day 0',2)
  13. umbrella0.changeLabel(0,'Yes')
  14. umbrella0.changeLabel(1,'No')
  15. u0 = model.add(umbrella0)
  16. umbrella1 = gum.LabelizedVariable('Umbrella(1)','Umbrella day 1',2)
  17. umbrella1.changeLabel(0,'Yes')
  18. umbrella1.changeLabel(1,'No')
  19. u1 = model.add(umbrella1)
  20. # Add weather nodes
  21. weather0 = gum.LabelizedVariable('Weather(0)','Weather day 0',2)
  22. weather0.changeLabel(0,'Sunny')
  23. weather0.changeLabel(1,'Rainy')
  24. w0 = model.add(weather0)
  25. weather1 = gum.LabelizedVariable('Weather(1)','Weather day 1',2)
  26. weather1.changeLabel(0,'Sunny')
  27. weather1.changeLabel(1,'Rainy')
  28. w1 = model.add(weather1)
  29. # Add connections between nodes (tail, head)
  30. model.addArc(u0, w0)
  31. model.addArc(w0, w1)
  32. model.addArc(u1, w1)
  33. # Visualize bayesian network
  34. svg = gnb.getBN(model)
  35. cairosvg.svg2png(bytestring=svg,write_to='plots\\dnb_chart.png')
  36. # Get time slices and save as image
  37. svg = gdyn.getTimeSlices(model)
  38. cairosvg.svg2png(bytestring=svg,write_to='plots\\dnb_time_slices.png')
  39. # Add CPT:s (Conditional probability tables)
  40. model.cpt(model.idFromName('Weather(0)'))[{'Umbrella(0)':'Yes'}]=[0.1, 0.8]
  41. model.cpt(model.idFromName('Weather(0)'))[{'Umbrella(0)':'No'}]=[0.9, 0.2]
  42. model.cpt(model.idFromName('Weather(1)'))[{'Umbrella(1)':'Yes'}]=[[0.25, 0.75],
  43. [0.1, 0.9]]
  44. model.cpt(model.idFromName('Weather(1)'))[{'Umbrella(1)':'No'}]=[[0.9, 0.1],
  45. [0.75, 0.25]]
  46. # Create an inference model
  47. ie = gum.LazyPropagation(model)
  48. # Make inference and print the results
  49. print('--- Umbrella(0): No ---')
  50. ie.setEvidence({'Umbrella(0)':'No'})
  51. ie.makeInference()
  52. print(ie.posterior('Weather(0)'))
  53. print()
  54. print('--- Umbrella(0): Yes ---')
  55. ie.setEvidence({'Umbrella(0)':'Yes'})
  56. ie.makeInference()
  57. print(ie.posterior('Weather(0)'))
  58. print()
  59. print('--- Weather(0): Sunny, Umbrella(1): Yes ---')
  60. ie.setEvidence({'Weather(0)':'Sunny', 'Umbrella(1)':'Yes'})
  61. ie.makeInference()
  62. #gnb.getPosterior(model, {'Weather(0)':'Sunny', 'Umbrella(1)':'Yes'}, 'Weather(1)')
  63. #plt.show()
  64. print(ie.posterior('Weather(1)'))
  65. print()
  66. print('--- Weather(0): Sunny, Umbrella(1): No ---')
  67. ie.setEvidence({'Weather(0)':'Sunny', 'Umbrella(1)':'No'})
  68. ie.makeInference()
  69. print(ie.posterior('Weather(1)'))
  70. print()
  71. print('--- Weather(0): Rainy, Umbrella(1): Yes ---')
  72. ie.setEvidence({'Weather(0)':'Rainy', 'Umbrella(1)':'Yes'})
  73. ie.makeInference()
  74. print(ie.posterior('Weather(1)'))
  75. print()
  76. print('--- Weather(0): Rainy, Umbrella(1): No ---')
  77. ie.setEvidence({'Weather(0)':'Rainy', 'Umbrella(1)':'No'})
  78. ie.makeInference()
  79. print(ie.posterior('Weather(1)'))
  80. print()
  81. # Tell python to run main method
  82. if __name__ == "__main__": main()
  1. --- Umbrella(0): No ---
  2. <Weather(0):Sunny> :: 0.818182 /<Weather(0):Rainy> :: 0.181818
  3. --- Umbrella(0): Yes ---
  4. <Weather(0):Sunny> :: 0.111111 /<Weather(0):Rainy> :: 0.888889
  5. --- Weather(0): Sunny, Umbrella(1): Yes ---
  6. <Weather(1):Sunny> :: 0.25 /<Weather(1):Rainy> :: 0.75
  7. --- Weather(0): Sunny, Umbrella(1): No ---
  8. <Weather(1):Sunny> :: 0.9 /<Weather(1):Rainy> :: 0.1
  9. --- Weather(0): Rainy, Umbrella(1): Yes ---
  10. <Weather(1):Sunny> :: 0.1 /<Weather(1):Rainy> :: 0.9
  11. --- Weather(0): Rainy, Umbrella(1): No ---
  12. <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 *