Dirichlet prior
![]() | ![]() |
Dirichlet prior as database
Section titled “Dirichlet prior as database”BNLearner gives access of many priors for the parameters and structural learning. One of them is the Dirichlet prior which needs a a prior for every possible parameter in a BN. aGrUM/pyAgrum allows to use a database as a source of Dirichlet prior.
%matplotlib inlinefrom pylab import *import matplotlib.pyplot as plt
import pyagrum as gumimport pyagrum.lib.notebook as gnbimport pyagrum.explain as explain
sizePrior = 30000sizeData = 20000
## the bases will be saved in "out/dirichlet_database.csv" and "out/observaition_database.csv"dirichletDatabase = "out/dirichlet_database.csv"obsDatabase = "out/observation_database.csv"Generating databases for Dirichlet prior and for the learning
Section titled “Generating databases for Dirichlet prior and for the learning”bnPrior = gum.fastBN("A->B;C;D")bnData = gum.fastBN("A->B->C->D")bnData.cpt("B").fillWith([0.99, 0.01, 0.01, 0.99])bnData.cpt("C").fillWith([0.9, 0.1, 0.1, 0.9])bnData.cpt("D").fillWith([0.9, 0.1, 0.1, 0.9])bnPrior.cpt("B").fillWith(bnData.cpt("B"))
gum.generateSample(bnPrior, sizePrior, dirichletDatabase, with_labels=True, random_order=True)
gum.generateSample(bnData, sizeData, obsDatabase, with_labels=True, random_order=False)
gnb.sideBySide(bnData, bnPrior, captions=[f"Database ({sizeData} cases)", f"Prior ({sizePrior} cases)"])Learning databases
Section titled “Learning databases”## bnPrior is used to give the variables and their domainslearnerData = gum.BNLearner(obsDatabase)learnerPrior = gum.BNLearner(dirichletDatabase)learnerData.useScoreBIC()learnerPrior.useScoreBIC()gnb.sideBySide(learnerData.learnBN(), learnerPrior.learnBN(), captions=["Learning from Data", "Learning from Prior"])Learning with Dirichlet prior
Section titled “Learning with Dirichlet prior”Now we use the Dirichlet prior. In order to have an idea of the influence of the priori, we change the weights of Data and Prior from [0,1] to [1,0] using a . The weight of a database is considered equal to the sum of the weights of each row. It is therefore in fact an equivalent sample size that is given.
learner = gum.BNLearner(obsDatabase, bnPrior)print(learner)Filename : out/observation_database.csvSize : (20000,4)Variables : A[2], B[2], C[2], D[2]Induced types : FalseMissing values : FalseAlgorithm : MIICCorrection : MDLPrior : -def learnWithRatio(ratio): # bnPrior is used to give the variables and their domains
learner = gum.BNLearner(obsDatabase, bnPrior) learner.useGreedyHillClimbing() learner.useDirichletPrior(dirichletDatabase, ratio * sizeData) learner.setDatabaseWeight((1 - ratio) * sizeData) learner.useScoreBIC() # or another score with no included prior return learner.learnBN()
ratios = [0.0, 0.01, 0.05, 0.2, 0.5, 0.8, 0.9, 0.95, 0.99, 1.0]bns = [learnWithRatio(r) for r in ratios]gnb.sideBySide( *bns, captions=[*[f"with ratio {r}<br/> [datasize : {(int(r * sizeData), int((1 - r) * sizeData))}]" for r in ratios]], valign="bottom",)The BNs learned when mixing the 2 data sources look much more complex than the data and the Dirichlet structures (with ). It may seem odd. However, if one looks at the mutual information,
gnb.sideBySide( *[explain.getInformation(bn) for bn in bns], captions=[*[f"with ratio {r}<br/> [datasize : {r * sizePrior + (1 - r) * sizeData}]" for r in ratios]], valign="bottom",)It is obvious that these arcs represent weak and spurious correlations due to mixing probabilities (see Pennock and Wellman, 1999), that become weaker when the weight of the prior increases.
Another way to look at the mixing is to plot the Kullback-Leibler divergence between the learned BNs and the 2 templates ( and r)
ratios = [i / 100.0 for i in range(101)]bns = [learnWithRatio(r) for r in ratios]
def kls(i): kl = gum.ExactBNdistance(bnPrior, bns[i]) y1 = kl.compute() kl = gum.ExactBNdistance(bnData, bns[i]) y2 = kl.compute() return y1["klPQ"], y2["klPQ"], y1["klQP"], y2["klQP"]
fig = figure(figsize=(10, 6))ax = fig.add_subplot(1, 1, 1)
x = ratiosy1, y2, y3, y4 = zip(*[kls(i) for i in range(len(ratios))])ax.plot(x, y1, label="M-projection with bnPrior")ax.plot(x, y3, label="I-projection with bnPrior")ax.plot(x, y2, label="M-projection with bnData")ax.plot(x, y4, label="I-projection with bnData")
ax.set_xlabel("weight ratio between data and prior")ax.set_ylabel("KL")ax.legend(bbox_to_anchor=(0.15, 0.88, 0.7, 0.102), loc=3, ncol=2, mode="expand", borderaxespad=0.0)t = ax.set_title("Weight ratio's Impact on KLs")plt.show()We can use other divergences (or distances)
def distances(i): kl = gum.ExactBNdistance(bnPrior, bns[i]) y1 = kl.compute() kl = gum.ExactBNdistance(bnData, bns[i]) y2 = kl.compute() return ( y1["hellinger"], y2["hellinger"], y1["bhattacharya"], y2["bhattacharya"], y1["jensen-shannon"], y2["jensen-shannon"], )
fig = figure(figsize=(10, 6))ax = fig.add_subplot(1, 1, 1)
x = ratiosy1, y2, y3, y4, y5, y6 = zip(*[distances(i) for i in range(len(ratios))])ax.plot(x, y1, label="Hellinger with bnPrior")ax.plot(x, y3, label="Bhattacharya with bnPrior")ax.plot(x, y5, label="Jensen-Shannon with bnPrior")ax.plot(x, y2, label="Hellinger with bnData")ax.plot(x, y4, label="Bhattacharya with bnData")ax.plot(x, y6, label="Jensen-Shannon with bnData")
ax.set_xlabel("weight ratio between data and prior")ax.set_ylabel("distances")ax.legend(bbox_to_anchor=(0.15, 0.85, 0.7, 0.102), loc=3, ncol=2, mode="expand", borderaxespad=0.0)t = ax.set_title("Weight ratio's Impact on distances")plt.show()Less informative but still possible, we can trace the scores (precision, etc.) from a pyagrum.lib.bn_vs_bn.GraphicalBNComparator (see 07-ComparingBN for more)
import pyagrum.lib.bn_vs_bn as gcm
def scores(i): cmp = gcm.GraphicalBNComparator(bnPrior, bns[i]) y1 = cmp.scores() cmp = gcm.GraphicalBNComparator(bnData, bns[i]) y2 = cmp.scores() return ( y1["recall"], y2["recall"], y1["precision"], y2["precision"], y1["fscore"], y2["fscore"], y1["dist2opt"], y2["dist2opt"], )
fig = figure(figsize=(20, 6))ax1 = fig.add_subplot(1, 2, 1)ax2 = fig.add_subplot(1, 2, 2)
x = ratiosy1, y2, y3, y4, y5, y6, y7, y8 = zip(*[scores(i) for i in range(len(ratios))])ax1.plot(x, y1, label="recall with bnPrior")ax1.plot(x, y3, label="precision with bnPrior")ax1.plot(x, y5, label="fscore with bnPrior")ax1.plot(x, y7, label="dist2opt with bnPrior")
ax2.plot(x, y2, label="recall with bnData")ax2.plot(x, y4, label="precision with bnData")ax2.plot(x, y6, label="fscore with bnData")ax2.plot(x, y8, label="dist2opt with bnData")
ax1.set_xlabel("weight ratio between data and prior")ax1.set_ylabel("KL")ax1.legend(bbox_to_anchor=(0.15, 0.88, 0.7, 0.102), loc=3, ncol=2, mode="expand", borderaxespad=0.0)ax1.set_title("Weight ratio's Impact on scores")
ax2.set_xlabel("weight ratio between data and prior")ax2.set_ylabel("KL")ax2.legend(bbox_to_anchor=(0.15, 0.88, 0.7, 0.102), loc=3, ncol=2, mode="expand", borderaxespad=0.0)ax2.set_title("Weight ratio's Impact on scores")
plt.show()Weighted database and records
Section titled “Weighted database and records”Database can be weighted as done above. But you can also fix the weight record by record in the database. Note that the weight of database is the sum of all weights for each record. And then
learner.setDatabaseWeight(2.5)is equivalent to
siz=learner.nbRows()for i in range(siz): learner.setRecordWeight(i,2.5/siz)bn = gum.fastBN("X->Y")bn1 = gum.BayesNet(bn)bn2 = gum.BayesNet(bn)## the base will be saved in basefile="out/dataW.csv"basefile = "out/dataW.csv"learning Parameters with weighted records
Section titled “learning Parameters with weighted records”In the 2 next cells, we compute the parameters of bn using 2 bases : in the next cell, the base contains 8 rows of weight 1. In the next one, the base contains only 4 rows but two of them have different weights. The sum of the weights in this second base is 8 as well …
So the parameters are exactly the same.
%%writefile 'out/dataW.csv'X,Y1,00,10,10,01,00,11,10,1Overwriting out/dataW.csvlearner = gum.BNLearner(basefile)learner.fitParameters(bn1)gnb.flow.row(bn1.cpt("X"), bn1.cpt("Y"))|
|
|
|---|---|
| 0.6250 | 0.3750 |
|
|
| |
|---|---|---|
| 0.2000 | 0.8000 | |
| 0.6667 | 0.3333 | |
%%writefile 'out/dataW.csv'X,Y0,01,00,11,1Overwriting out/dataW.csvlearner = gum.BNLearner(basefile)learner.setRecordWeight(1, 2.0) # line #1 as a weight 2learner.setRecordWeight(2, 4.0) # line #2 as a weight 4
learner.fitParameters(bn2)gnb.flow.row(bn2.cpt("X"), bn2.cpt("Y"))|
|
|
|---|---|
| 0.6250 | 0.3750 |
|
|
| |
|---|---|---|
| 0.2000 | 0.8000 | |
| 0.6667 | 0.3333 | |
learning Structure with weighted records
Section titled “learning Structure with weighted records”In the 2 next cells, we compute the parameters of bn using 2 bases : in the next cell, the base contains 8 rows of weight 1. In the next one, the base contains only 4 rows but two of them have different weights. The sum of the weights in this second base is 8 as well …
So the parameters are exactly the same.
%%writefile 'out/dataW.csv'X,Y,Z1,0,11,0,11,0,01,0,11,0,10,1,00,1,00,0,11,0,00,1,11,1,00,1,0Overwriting out/dataW.csvlearner = gum.BNLearner(basefile)bn1 = learner.learnBN()gnb.flow.row(bn1, bn1.cpt("Z"), bn1.cpt("X"), bn1.cpt("Y"))|
|
|
|---|---|
| 0.5000 | 0.5000 |
|
|
| |
|---|---|---|
| 0.1667 | 0.8333 | |
| 0.7727 | 0.2273 | |
|
|
| |
|---|---|---|
| 0.3462 | 0.6538 | |
| 0.8077 | 0.1923 | |
%%writefile 'out/dataW.csv'X,Y,Z0,0,10,1,00,1,11,0,01,0,11,1,0Overwriting out/dataW.csvlearner = gum.BNLearner(basefile)learner.setRecordWeight(1, 3.0) # line #1 as a weight 3learner.setRecordWeight(3, 2.0) # line #2 as a weight 2learner.setRecordWeight(4, 4.0) # line #2 as a weight 4## others lines as a weight 1
bn2 = learner.learnBN()gnb.flow.row(bn2, bn2.cpt("Z"), bn2.cpt("X"), bn2.cpt("Y"))|
|
|
|---|---|
| 0.5000 | 0.5000 |
|
|
| |
|---|---|---|
| 0.1667 | 0.8333 | |
| 0.7727 | 0.2273 | |
|
|
| |
|---|---|---|
| 0.3462 | 0.6538 | |
| 0.8077 | 0.1923 | |
