Relevance Reasoning with pyAgrum

Creative Commons License

aGrUM

interactive online version

Relevance reasoning is the analysis of the influence of evidence on a Bayesian network.

In this notebook we will explain what is relevance reasoning and how to do it using pyAgrum.

In [1]:
import pyAgrum as gum
import pyAgrum.lib.notebook as gnb

import time
import os
%matplotlib inline
from pylab import *
import matplotlib.pyplot as plt

Multiple inference

In the well known ‘alarm’ BN, how to analyze the influence on ‘VENTALV’ of a soft evidence on ‘MINVOLSET’ ?

In [2]:
bn=gum.loadBN("res/alarm.dsl")
gnb.showBN(bn,size="6")
../_images/notebooks_42-Inference_relevanceReasoning_5_0.svg

We propose to draw the plot of the posterior of ‘VENTALV’ for the evidence :

\[\forall x \in [0,1], e_{MINVOLSET}=[0,x,0.5]\]

To do so, we perform a large number of inference and plot the posteriors.

In [3]:
K=1000
r=range(0,K)
xs=[x/K for x in r]

def getPlot(xs,ys,K,duration):
    p=plot(xs,ys)
    legend(p,[bn.variableFromName('VENTALV').label(i)
              for i in range(bn.variableFromName('VENTALV').domainSize())],loc=7);
    title('VENTALV ({} inferences in {:5.3} s)'.format(K,duration));
    ylabel('posterior Probability');
    xlabel('Evidence on MINVOLSET : [0,x,0.5]');

First try : classical lazy propagation

In [4]:
tf=time.time()
ys=[]
for x in r:
    ie=gum.LazyPropagation(bn)
    ie.setNumberOfThreads(1) # to be fair, we avoid multithreaded inference
    ie.addEvidence('MINVOLSET',[0,x/K,0.5])
    ie.makeInference()
    ys.append(ie.posterior('VENTALV').tolist())
delta1=time.time()-tf
getPlot(xs,ys,K,delta1)
../_images/notebooks_42-Inference_relevanceReasoning_9_0.svg

The title of the figure above gives the time for those 1000 inference.

Second try : classical variable elimination

One can note that we just need one posterior. This is a case where VariableElimination should give better results.

In [5]:
tf=time.time()
ys=[]
for x in r:
    ie=gum.VariableElimination(bn)
    ie.addEvidence('MINVOLSET',[0,x/K,0.5])
    ie.makeInference()
    ys.append(ie.posterior('VENTALV').tolist())
delta2=time.time()-tf
getPlot(xs,ys,K,delta2)
../_images/notebooks_42-Inference_relevanceReasoning_11_0.svg

pyAgrum give us a function gum.getPosterior to do this same job more easily.

In [6]:
tf=time.time()
ys=[gum.getPosterior(bn,{'MINVOLSET':[0,x/K,0.5]},'VENTALV').tolist()
        for x in r]
getPlot(xs,ys,K,time.time()-tf)
../_images/notebooks_42-Inference_relevanceReasoning_13_0.svg

Last try : optimized Lazy propagation with relevance reasoning and incremental inference

Optimized inference in aGrUM can use the targets and the evidence to optimize the computations. This is called relevance reasonning.

Moreover, if the values of the evidence change but not the structure of the query (same nodes as target, same nodes as hard evidence, same nodes as soft evidence), inference in aGrUM may re-use some of the computations from a query to another. This is called incremental inference.

In [7]:
tf=time.time()
ie=gum.LazyPropagation(bn)
ie.setNumberOfThreads(1) # to be fair, we avoid multithreaded inference
ie.addEvidence('MINVOLSET',[1,1,1])
ie.addTarget('VENTALV')
ys=[]
for x in r:
    ie.chgEvidence('MINVOLSET',[0,x/K,0.5])
    ie.makeInference()
    ys.append(ie.posterior('VENTALV').tolist())
delta3=time.time()-tf
getPlot(xs,ys,K,delta3)
../_images/notebooks_42-Inference_relevanceReasoning_15_0.svg
In [8]:
print("Mean duration of a lazy propagation            : {:5.3f}ms".format(1000*delta1/K))
print("Mean duration of a variable elimination        : {:5.3f}ms".format(1000*delta2/K))
print("Mean duration of an optimized lazy propagation : {:5.3f}ms".format(1000*delta3/K))
Mean duration of a lazy propagation            : 4.921ms
Mean duration of a variable elimination        : 0.565ms
Mean duration of an optimized lazy propagation : 0.457ms

How it works

In [9]:
bn=gum.fastBN("Y->X->T1;Z2->X;Z1->X;Z1->T1;Z1->Z3->T2")
ie=gum.LazyPropagation(bn)

gnb.flow.row(bn,bn.cpt("X"),gnb.getJunctionTree(bn),gnb.getJunctionTreeMap(bn,size="3!"),
             captions=["BN","potential","Junction Tree","The map"])
G Y Y X X Y->X Z3 Z3 T2 T2 Z3->T2 Z1 Z1 Z1->Z3 T1 T1 Z1->T1 Z1->X X->T1 Z2 Z2 Z2->X
BN
X
Z1
Z2
Y
0
1
0
0
0
0.81420.1858
1
0.36030.6397
1
0
0.15950.8405
1
0.50730.4927
1
0
0
0.42520.5748
1
0.12100.8790
1
0
0.98420.0158
1
0.14950.8505

potential
G (0) 0-1-3-4 Y X Z2 Z1 (0) 0-1-3-4^(5) 4-5 Z1 (0) 0-1-3-4--(0) 0-1-3-4^(5) 4-5 (0) 0-1-3-4^(2) 1-2-4 X Z1 (0) 0-1-3-4--(0) 0-1-3-4^(2) 1-2-4 (1) 5-6 Z3 T2 (1) 5-6^(5) 4-5 Z3 (1) 5-6--(1) 5-6^(5) 4-5 (2) 1-2-4 X T1 Z1 (5) 4-5 Z1 Z3 (1) 5-6^(5) 4-5--(5) 4-5 (0) 0-1-3-4^(5) 4-5--(5) 4-5 (0) 0-1-3-4^(2) 1-2-4--(2) 1-2-4
Junction Tree
G 0 0~5 0--0~5 0~2 0--0~2 1 1~5 1--1~5 2 5 1~5--5 0~5--5 0~2--2
The map

aGrUM/pyAgrum uses as much as possible techniques of relevance reasonning to reduce the complexity of the inference.

In [10]:
ie.setEvidence({"X":0})
gnb.sideBySide(ie,gnb.getDot(ie.joinTree().toDotWithNames(bn)),ie.joinTree().map(),
               captions=["","Join tree optimized for hard evidence on X","the map"])
G Y Y X X Y->X Z3 Z3 T2 T2 Z3->T2 Z1 Z1 Z1->Z3 T1 T1 Z1->T1 Z1->X X->T1 Z2 Z2 Z2->X
Lazy Propagation on this BN
  • hard evidence
    X
  • target(s)
    all

Evidence and targets

G (0) 0-3-4 Y Z2 Z1 (0) 0-3-4^(4) 4-5 Z1 (0) 0-3-4--(0) 0-3-4^(4) 4-5 (1) 5-6 Z3 T2 (1) 5-6^(4) 4-5 Z3 (1) 5-6--(1) 5-6^(4) 4-5 (2) 2-4 T1 Z1 (2) 2-4^(4) 4-5 Z1 (2) 2-4--(2) 2-4^(4) 4-5 (4) 4-5 Z1 Z3 (2) 2-4^(4) 4-5--(4) 4-5 (1) 5-6^(4) 4-5--(4) 4-5 (0) 0-3-4^(4) 4-5--(4) 4-5
Join tree optimized for hard evidence on X
G 0 0~4 0--0~4 1 1~4 1--1~4 2 2~4 2--2~4 4 2~4--4 1~4--4 0~4--4
the map
In [11]:
ie.updateEvidence({"X":[0.1,0.9]})
gnb.sideBySide(ie,gnb.getDot(ie.joinTree().toDotWithNames(bn)),ie.joinTree().map(),
               captions=["","Join tree optimized for soft evidence on X","the map"])
G Y Y X X Y->X Z3 Z3 T2 T2 Z3->T2 Z1 Z1 Z1->Z3 T1 T1 Z1->T1 Z1->X X->T1 Z2 Z2 Z2->X
Lazy Propagation on this BN
  • soft evidence
    X
  • target(s)
    all

Evidence and targets

G (0) 0-1-3-4 Y X Z2 Z1 (0) 0-1-3-4^(5) 4-5 Z1 (0) 0-1-3-4--(0) 0-1-3-4^(5) 4-5 (0) 0-1-3-4^(2) 1-2-4 X Z1 (0) 0-1-3-4--(0) 0-1-3-4^(2) 1-2-4 (1) 5-6 Z3 T2 (1) 5-6^(5) 4-5 Z3 (1) 5-6--(1) 5-6^(5) 4-5 (2) 1-2-4 X T1 Z1 (5) 4-5 Z1 Z3 (1) 5-6^(5) 4-5--(5) 4-5 (0) 0-1-3-4^(5) 4-5--(5) 4-5 (0) 0-1-3-4^(2) 1-2-4--(2) 1-2-4
Join tree optimized for soft evidence on X
G 0 0~5 0--0~5 0~2 0--0~2 1 1~5 1--1~5 2 5 1~5--5 0~5--5 0~2--2
the map
In [12]:
ie.updateEvidence({"Y":0,"X":0,3:[0.1,0.9],"Z1":[0.4,0.6]})
gnb.sideBySide(ie,gnb.getDot(ie.joinTree().toDotWithNames(bn)),ie.joinTree().map(),
               captions=["","Join tree optimized for hard evidence on X and Y, soft on Z2 and Z1","the map"])
G Y Y X X Y->X Z3 Z3 T2 T2 Z3->T2 Z1 Z1 Z1->Z3 T1 T1 Z1->T1 Z1->X X->T1 Z2 Z2 Z2->X
Lazy Propagation on this BN
  • hard evidence
    Y, X
  • soft evidence
    Z2, Z1
  • target(s)
    all

Evidence and targets

G (0) 3-4 Z2 Z1 (0) 3-4^(4) 4 Z1 (0) 3-4--(0) 3-4^(4) 4 (1) 5-6 Z3 T2 (1) 5-6^(3) 4-5 Z3 (1) 5-6--(1) 5-6^(3) 4-5 (2) 2-4 T1 Z1 (2) 2-4^(4) 4 Z1 (2) 2-4--(2) 2-4^(4) 4 (3) 4-5 Z1 Z3 (3) 4-5^(4) 4 Z1 (3) 4-5--(3) 4-5^(4) 4 (4) 4 Z1 (1) 5-6^(3) 4-5--(3) 4-5 (0) 3-4^(4) 4--(4) 4 (2) 2-4^(4) 4--(4) 4 (3) 4-5^(4) 4--(4) 4
Join tree optimized for hard evidence on X and Y, soft on Z2 and Z1
G 0 0~4 0--0~4 1 1~3 1--1~3 2 2~4 2--2~4 3 3~4 3--3~4 4 1~3--3 0~4--4 2~4--4 3~4--4
the map
In [13]:
ie.setEvidence({"X":0})
ie.setTargets({"T1","Z1"})
gnb.sideBySide(ie,gnb.getDot(ie.joinTree().toDotWithNames(bn)),ie.joinTree().map(),
               captions=["","Join tree optimized for hard evidence on X and targets T1,Z1","the map"])
G Y Y X X Y->X Z3 Z3 T2 T2 Z3->T2 Z1 Z1 Z1->Z3 T1 T1 Z1->T1 Z1->X X->T1 Z2 Z2 Z2->X
Lazy Propagation on this BN
  • hard evidence
    X
  • target(s)
    T1, Z1

Evidence and targets

G (0) 0-3-4 Y Z2 Z1 (0) 0-3-4^(2) 2-4 Z1 (0) 0-3-4--(0) 0-3-4^(2) 2-4 (2) 2-4 T1 Z1 (0) 0-3-4^(2) 2-4--(2) 2-4
Join tree optimized for hard evidence on X and targets T1,Z1
G 0 0~2 0--0~2 2 0~2--2
the map
In [14]:
ie.updateEvidence({"Y":0,"X":0,3:[0.1,0.9],"Z1":[0.4,0.6]})
ie.addJointTarget({"Z2","Z1","T1"})

gnb.sideBySide(ie,
               gnb.getDot(ie.joinTree().toDotWithNames(bn)),ie.joinTree().map(),
               captions=["","Join tree optimized for hard evidence on X and targets T1,Z1","the map"])
G Y Y X X Y->X Z3 Z3 T2 T2 Z3->T2 Z1 Z1 Z1->Z3 T1 T1 Z1->T1 Z1->X X->T1 Z2 Z2 Z2->X
Lazy Propagation on this BN
  • hard evidence
    Y, X
  • soft evidence
    Z2, Z1
  • target(s)
    T1, Z1
  • Joint target(s)
    [T1, Z2, Z1]

Evidence and targets

G (0) 2-3-4 T1 Z2 Z1
Join tree optimized for hard evidence on X and targets T1,Z1
G 0
the map
In [15]:
ie.makeInference()
ie.jointPosterior({"Z2","Z1","T1"})
Out[15]:
Z1
Z2
T1
0
1
0
0
0.02180.0111
1
0.01250.0356
1
0
0.03420.2052
1
0.01960.6599
In [16]:
ie.jointPosterior({"Z2","Z1"})
Out[16]:
Z1
Z2
0
1
0
0.03430.0467
1
0.05380.8651
In [17]:
# this will not work
try:
    ie.jointPosterior({"Z3","Z1"})
except gum.UndefinedElement:
    print("Indeed, there is no joint target which contains {4,5} !")
Indeed, there is no joint target which contains {4,5} !
In [18]:
ie.addJointTarget({"Z2","Z1"})
gnb.sideBySide(ie,
               gnb.getDot(ie.joinTree().toDotWithNames(bn)),
              captions=['','JoinTree'])
G Y Y X X Y->X Z3 Z3 T2 T2 Z3->T2 Z1 Z1 Z1->Z3 T1 T1 Z1->T1 Z1->X X->T1 Z2 Z2 Z2->X
Lazy Propagation on this BN
  • hard evidence
    Y, X
  • soft evidence
    Z2, Z1
  • target(s)
    T1, Z1
  • Joint target(s)
    [T1, Z2, Z1]

Evidence and targets

G (0) 2-3-4 T1 Z2 Z1
JoinTree
In [ ]: