Skip to content

Commit 86d874d

Browse files
committed
Make auto generation of CPTs also compatible with DBN networks
1 parent db6c115 commit 86d874d

File tree

3 files changed

+123
-22
lines changed

3 files changed

+123
-22
lines changed

bnlearn/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
get_parents,
3030
generate_cpt,
3131
build_cpts_from_structure,
32+
convert_edges_with_time_slice,
3233
)
3334

3435
# Import function in new level

bnlearn/bnlearn.py

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
from itertools import product
2525
from collections import defaultdict
2626

27-
from pgmpy.models import BayesianNetwork, NaiveBayes, DynamicBayesianNetwork, MarkovNetwork
27+
from pgmpy.models import BayesianNetwork, NaiveBayes, MarkovNetwork
28+
from pgmpy.models import DynamicBayesianNetwork as DBN
2829
from pgmpy.factors.discrete import TabularCPD
2930
from pgmpy.metrics import structure_score
3031

@@ -110,6 +111,14 @@ def make_DAG(DAG, CPD=None, methodtype='bayes', checkmodel=True, verbose=3):
110111
>>> DAG = bn.make_DAG(edges, methodtype='naivebayes')
111112
>>> fig = bn.plot(DAG)
112113
114+
Examples
115+
--------
116+
>>> import bnlearn as bn
117+
>>> edges = [('A', 'B'), ('A', 'C'), ('A', 'D')]
118+
>>> CPD = build_cpts_from_structure(DAG, variable_card=3)
119+
>>> DAG = bn.make_DAG(edges, CPD=CPD, methodtype='naivebayes')
120+
>>> fig = bn.plot(DAG)
121+
113122
Examples
114123
--------
115124
>>> import bnlearn as bn
@@ -130,11 +139,15 @@ def make_DAG(DAG, CPD=None, methodtype='bayes', checkmodel=True, verbose=3):
130139
>>> fig = bn.plot(DAG)
131140
132141
"""
133-
if (CPD is not None) and (not isinstance(CPD, list)):
134-
CPD = [CPD]
135-
142+
# Set names to lower
136143
if methodtype == 'nb': methodtype = 'naivebayes'
137144
if methodtype == 'dbn': methodtype = 'DBN'
145+
# Automatically generate placeholder values for the CPTs
146+
if CPD is None and isinstance(DAG, list):
147+
CPD = build_cpts_from_structure(DAG, variable_card=2, methodtype=methodtype)
148+
# Make list if required
149+
if (CPD is not None) and (not isinstance(CPD, list)):
150+
CPD = [CPD]
138151

139152
if isinstance(DAG, dict):
140153
DAG = DAG.get('model', None)
@@ -153,30 +166,42 @@ def make_DAG(DAG, CPD=None, methodtype='bayes', checkmodel=True, verbose=3):
153166
edges = DAG
154167
DAG = NaiveBayes()
155168
DAG.add_edges_from(edges)
156-
# modeel.add_nodes_from(DAG)
169+
DAG.add_nodes_from(CPD)
170+
for cpd in CPD: DAG.add_cpds(cpd)
157171
elif isinstance(DAG, list) and methodtype == 'bayes':
158172
if verbose>=3: print('[bnlearn] >%s DAG created.' %(methodtype))
159-
DAG = BayesianNetwork(DAG)
173+
edges = DAG
174+
DAG = BayesianNetwork()
175+
DAG.add_edges_from(edges)
176+
DAG.add_nodes_from(CPD)
177+
for cpd in CPD: DAG.add_cpds(cpd)
160178
elif isinstance(DAG, list) and methodtype == 'markov':
161179
if verbose>=3: print('[bnlearn] >%s DAG created.' %(methodtype))
162-
DAG = MarkovNetwork(DAG)
180+
edges = DAG
181+
DAG = MarkovNetwork()
182+
DAG.add_edges_from(edges)
183+
DAG.add_nodes_from(CPD)
184+
# for cpd in CPD: DAG.add_cpds(cpd)
163185
elif isinstance(DAG, list) and methodtype == 'DBN':
164-
if verbose>=3: print('[bnlearn] >DynamicBayesianNetwork DAG created.')
186+
if verbose>=3: print('[bnlearn] >DynamicBayesianNetwork (DBN) DAG created.')
165187
# Make edges with time slice
166188
if not has_valid_time_slice(DAG):
167-
edges = convert_edges_with_time_slice(DAG)
189+
edges = convert_edges_with_time_slice(DAG, verbose=verbose)
168190
else:
169191
edges = DAG
170192

171-
DAG = DynamicBayesianNetwork()
172-
# dbn.add_nodes_from(edges)
193+
DAG = DBN()
173194
DAG.add_edges_from(edges)
195+
# DAG.add_nodes_from(CPD)
196+
# DAG.add_cpds(CPD)
197+
for cpd in CPD: DAG.add_cpds(cpd)
198+
174199
# Print edges
175200
# print("Edges in the DBN:", DAG.edges())
176201

177202
if CPD is not None:
178203
for cpd in CPD:
179-
DAG.add_cpds(cpd)
204+
# DAG.add_cpds(cpd)
180205
if verbose>=3: print(f'[bnlearn] >[Conditional Probability Table (CPT)] >[Update Probabilities] >[Node {cpd.variable}]')
181206
# Check model
182207
if checkmodel:
@@ -192,14 +217,15 @@ def make_DAG(DAG, CPD=None, methodtype='bayes', checkmodel=True, verbose=3):
192217

193218

194219
# %%
195-
def convert_edges_with_time_slice(edges, time_slice=0):
220+
def convert_edges_with_time_slice(edges, time_slice=0, verbose=3):
221+
if verbose>=3: print(f'[bnlearn]> Converting edges to time slice.')
196222
return [( (u, time_slice), (v, time_slice) ) for u, v in edges]
197223

198224
def has_valid_time_slice(edges):
199-
""" Example usage
225+
""" Example usage.
200226
edges = [('A', 'B'), ('A', 'C'), ('A', 'D')]
201227
edgest = [(('A', 0), ('B', 0)), (('A', 0), ('C', 0)), (('A', 0), ('D', 0))]
202-
228+
203229
# Check edges
204230
has_valid_time_slice(edges)
205231
@@ -320,14 +346,21 @@ def check_model(DAG, verbose=3):
320346
None.
321347
322348
"""
323-
if isinstance(DAG, dict): DAG = DAG.get('model', None)
324-
if DAG is not None:
349+
# Get the model
350+
if isinstance(DAG, dict):
351+
DAG = DAG.get('model', None)
352+
353+
# Not all models do have get_cpds function
354+
if DAG is not None and hasattr(DAG, 'get_cpds'):
325355
for cpd in DAG.get_cpds():
326356
if not np.all(cpd.values.astype(Decimal).sum(axis=0)==1):
327357
if verbose>=3: print(f'[bnlearn] >[Conditional Probability Table (CPT)] >[Check Probabilities] >[Node {cpd.variable}] >Table Error: Does not sum to 1 but is [{cpd.values.sum(axis=0)}]')
328358
else:
329359
if verbose>=3: print(f'[bnlearn] >[Conditional Probability Table (CPT)] >[Check Probabilities] >[Node {cpd.variable}] >OK')
330360
# if verbose>=3: print('[bnlearn] >Check whether CPDs associated with the nodes are consistent: %s' %(DAG.check_model()))
361+
elif 'markovnetwork' in str(type(DAG)).lower():
362+
pass
363+
# if verbose>=3: print(f'[bnlearn] >[Conditional Probability Table (CPT)] >[Check Probabilities] >Unknown')
331364
else:
332365
if verbose>=2: print('[bnlearn] >No model found containing CPDs.')
333366

@@ -2254,7 +2287,7 @@ def probs_rulebook(node, rulebook, variable_card, all_combos):
22542287
elif isinstance(out, (list, tuple)) and len(out) == variable_card:
22552288
row = list(out)
22562289
else:
2257-
raise ValueError(f"Rule for '{node}' must return float or list of len {variable_card}")
2290+
raise ValueError(f"[bnlearn] >Rule for '{node}' must return float or list of len {variable_card}")
22582291
raw_probs.append(row)
22592292
probs = list(map(list, zip(*raw_probs)))
22602293
return probs
@@ -2299,7 +2332,30 @@ def generate_cpt(node, parents, variable_card=2, rulebook=None, verbose=3):
22992332
>>> DAG = bn.make_DAG(edges, CPD=[cpt_Rain, cpt_Sprinkler])
23002333
>>> bn.plot(DAG)
23012334
2335+
Examples
2336+
--------
2337+
>>> # Example with DBN
2338+
>>> #
2339+
>>> # Import library
2340+
>>> import bnlearn as bn
2341+
>>> #
2342+
>>> edges = [('Cloudy', 'Rain'), ('Cloudy', 'Sprinkler')]
2343+
>>> edges = convert_edges_with_time_slice(edges)
2344+
>>> #
2345+
>>> # Get parents
2346+
>>> parents = bn.get_parents(edges)
2347+
>>> # {('Rain', 0): [('Cloudy', 0)], ('Sprinkler', 0): [('Cloudy', 0)], ('Cloudy', 0): []}
2348+
>>> #
2349+
>>> # Generate the CPTs
2350+
>>> cpt_Rain = bn.generate_cpt(('Rain', 0), parents.get(('Rain', 0)), variable_card=2)
2351+
>>> cpt_Rain = bn.generate_cpt(('Sprinkler', 0), parents.get(('Sprinkler', 0)), variable_card=2)
2352+
>>> #
2353+
>>> # Create DAG with default CPD values
2354+
>>> DAG = bn.make_DAG(edges, CPD=[cpt_Rain, cpt_Sprinkler])
2355+
>>> bn.plot(DAG)
2356+
23022357
"""
2358+
parents = parents or []
23032359
n_parents = len(parents)
23042360
parent_card = [variable_card] * n_parents
23052361
all_combos = list(product(range(variable_card), repeat=n_parents))
@@ -2317,12 +2373,33 @@ def generate_cpt(node, parents, variable_card=2, rulebook=None, verbose=3):
23172373
evidence_card=parent_card if parents else None)
23182374

23192375
if verbose >= 3:
2320-
print(f'CPT for {node}:')
2376+
print(f'[bnlearn] >CPT for {node}:')
23212377
print(cpt)
2378+
23222379
return cpt
2380+
# n_parents = len(parents)
2381+
# parent_card = [variable_card] * n_parents
2382+
# all_combos = list(product(range(variable_card), repeat=n_parents))
2383+
# n_combos = len(all_combos)
2384+
2385+
# if rulebook and node in rulebook:
2386+
# probs = probs_rulebook(node, rulebook, variable_card, all_combos)
2387+
# else:
2388+
# probs = [[1 / variable_card] * n_combos for _ in range(variable_card)]
2389+
2390+
# cpt = TabularCPD(variable=node,
2391+
# variable_card=variable_card,
2392+
# values=probs,
2393+
# evidence=parents if parents else None,
2394+
# evidence_card=parent_card if parents else None)
23232395

2396+
# if verbose >= 3:
2397+
# print(f'[bnlearn] >CPT for {node}:')
2398+
# print(f'{cpt}')
2399+
# return cpt
23242400

2325-
def build_cpts_from_structure(edges, variable_card=2, rulebook=None, verbose=3):
2401+
2402+
def build_cpts_from_structure(edges, variable_card=2, rulebook=None, methodtype=None, verbose=3):
23262403
"""
23272404
Automatically generates placeholder CPTs for all nodes in a network structure.
23282405
@@ -2354,7 +2431,12 @@ def build_cpts_from_structure(edges, variable_card=2, rulebook=None, verbose=3):
23542431
>>> # Create DAG with default CPD values
23552432
>>> DAG = bn.make_DAG(edges, CPD=CPD)
23562433
>>> bn.plot(DAG)
2434+
23572435
"""
2436+
if verbose>=3: print('[bnlearn]> Auto generate placeholders for the CPTs.')
2437+
# Convert edges with time for DBN
2438+
if methodtype=='DBN' and not has_valid_time_slice(edges):
2439+
edges = convert_edges_with_time_slice(edges, verbose=verbose)
23582440

23592441
cpts = []
23602442
parents_map = get_parents(edges)

bnlearn/examples.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
#%%
22

3+
import bnlearn as bn
4+
5+
edges = [('A', 'B'), ('A', 'C'), ('A', 'D')]
6+
# edges = bn.convert_edges_with_time_slice(edges)
7+
8+
DAG = bn.make_DAG(edges, CPD=None, methodtype='DBN')
9+
10+
DAG = bn.make_DAG(edges, CPD=None, methodtype='markov')
11+
12+
edges = [('A', 'B'), ('A', 'C'), ('A', 'D')]
13+
DAG = bn.make_DAG(edges, CPD=None)
14+
DAG = bn.make_DAG(edges, CPD=None, methodtype='naivebayes')
15+
DAG = bn.make_DAG(edges, CPD=None, methodtype='bayes')
16+
17+
18+
319

420
# %% Example from sphinx
521
# Import the library
@@ -23,8 +39,8 @@
2339
]
2440

2541

26-
# Genreate Placeholder CPDs
27-
CPD = bn.build_cpts_from_structure(edges, variable_card=4)
42+
# Genrate Placeholder CPDs
43+
CPD = bn.build_cpts_from_structure(edges, variable_card=2)
2844
# Create DAG with default CPD values
2945
DAG = bn.make_DAG(edges, CPD=CPD)
3046
bn.plot(DAG)
@@ -40,6 +56,8 @@
4056
DAG = bn.make_DAG(edges, CPD=[cpt_A, cpt_B, cpt_C, cpt_D])
4157

4258
bn.print_CPD(DAG)
59+
bn.plot(DAG)
60+
4361

4462
# Import the library
4563
# Cloudy

0 commit comments

Comments
 (0)