DPG 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dpg-0.2.0 → dpg-0.3.0}/PKG-INFO +59 -20
- {dpg-0.2.0 → dpg-0.3.0}/README.md +58 -19
- {dpg-0.2.0 → dpg-0.3.0}/dpg/__init__.py +2 -0
- dpg-0.3.0/dpg/cli.py +96 -0
- dpg-0.3.0/dpg/context_order.py +114 -0
- {dpg-0.2.0 → dpg-0.3.0}/dpg/core.py +383 -114
- {dpg-0.2.0 → dpg-0.3.0}/dpg/explainer.py +60 -59
- {dpg-0.2.0 → dpg-0.3.0}/dpg/sklearn_dpg.py +14 -5
- {dpg-0.2.0 → dpg-0.3.0}/metrics/graph.py +13 -0
- {dpg-0.2.0 → dpg-0.3.0}/pyproject.toml +23 -2
- {dpg-0.2.0 → dpg-0.3.0}/LICENSE +0 -0
- {dpg-0.2.0 → dpg-0.3.0}/dpg/py.typed +0 -0
- {dpg-0.2.0 → dpg-0.3.0}/dpg/sklearn_normalizer.py +0 -0
- {dpg-0.2.0 → dpg-0.3.0}/dpg/themes.py +0 -0
- {dpg-0.2.0 → dpg-0.3.0}/dpg/utils.py +0 -0
- {dpg-0.2.0 → dpg-0.3.0}/dpg/visualizer.py +0 -0
- {dpg-0.2.0 → dpg-0.3.0}/metrics/__init__.py +0 -0
- {dpg-0.2.0 → dpg-0.3.0}/metrics/edges.py +0 -0
- {dpg-0.2.0 → dpg-0.3.0}/metrics/nodes.py +0 -0
- {dpg-0.2.0 → dpg-0.3.0}/metrics/py.typed +0 -0
{dpg-0.2.0 → dpg-0.3.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dpg
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A Python library for extracting Decision Predicate Graphs and global explanations from ensemble models.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -54,12 +54,12 @@ Description-Content-Type: text/markdown
|
|
|
54
54
|
|
|
55
55
|
[](LICENSE)
|
|
56
56
|
[](pyproject.toml)
|
|
57
|
-
[](https://pypi.org/project/dpg/)
|
|
58
57
|
[](https://github.com/Meta-Group/DPG/actions/workflows/ci.yml)
|
|
59
58
|
[](https://dpg.readthedocs.io/en/latest/)
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
<p align="center">
|
|
61
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/DPG.png" alt="DPG logo" width="300">
|
|
62
|
+
</p>
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
DPG is a model-agnostic framework for global and local explanation of tree-based ensemble models.
|
|
@@ -72,7 +72,9 @@ insightful points. DPG enables graph-based evaluations and the identification of
|
|
|
72
72
|
towards facilitating comparisons between features and their associated values while offering insights
|
|
73
73
|
into the entire model. DPG provides descriptive metrics that enhance the understanding of the
|
|
74
74
|
decisions inherent in the model, offering valuable insights.
|
|
75
|
-
|
|
75
|
+
<p align="center">
|
|
76
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/image.png" width="600" />
|
|
77
|
+
</p>
|
|
76
78
|
|
|
77
79
|
---
|
|
78
80
|
|
|
@@ -98,7 +100,9 @@ The concept behind DPG is to convert a generic tree-based ensemble model for cla
|
|
|
98
100
|
- Nodes represent predicates, i.e., the feature-value associations present in each node of every tree;
|
|
99
101
|
- Edges denote the frequency with which these predicates are satisfied during the model training phase by the samples of the dataset.
|
|
100
102
|
|
|
101
|
-
|
|
103
|
+
<p align="center">
|
|
104
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example.png" width="600" />
|
|
105
|
+
</p>
|
|
102
106
|
|
|
103
107
|
## Metrics
|
|
104
108
|
The graph-based nature of DPG provides significant enhancements in the direction of a complete mapping of the ensemble structure.
|
|
@@ -112,7 +116,7 @@ The graph-based nature of DPG provides significant enhancements in the direction
|
|
|
112
116
|
|
|
113
117
|
|Constraints | Betweenness centrality | Local reaching centrality | Community|
|
|
114
118
|
|------------|------------|--------------|--------------------|
|
|
115
|
-
 |  |  |  |
|
|
116
120
|
|Constraints(Class 1) = val3 < F1 ≤ val1, F2 ≤ val2 | BC(F2 ≤ val2) = 4/24 | LRC(F1 ≤ val1) = 6 / 7 | Community(Class 1) = F1 ≤ val1, F2 ≤ val2 |
|
|
117
121
|
|
|
118
122
|
---
|
|
@@ -220,6 +224,7 @@ explainer = DPGExplainer(
|
|
|
220
224
|
},
|
|
221
225
|
"graph_construction": {
|
|
222
226
|
"mode": "execution_trace", # or "aggregated_transitions"
|
|
227
|
+
"context_order": 1, # 1, an integer > 1, or "auto"
|
|
223
228
|
},
|
|
224
229
|
}
|
|
225
230
|
},
|
|
@@ -229,6 +234,31 @@ explainer = DPGExplainer(
|
|
|
229
234
|
- `"aggregated_transitions"`: current default behavior; filters path variants first, then discovers the DPG.
|
|
230
235
|
- `"execution_trace"`: builds directly from raw traces and filters edges instead of whole-path variants when `perc_var > 0`.
|
|
231
236
|
|
|
237
|
+
`context_order` controls predicate identity in `execution_trace` mode. `1`
|
|
238
|
+
preserves the legacy graph exactly; an order greater than one uses the last k
|
|
239
|
+
executed predicates as context and keeps one sink per class. `"auto"` resolves
|
|
240
|
+
the smallest order for which every pooled graph path is supported by an
|
|
241
|
+
observed execution trace, including recombinations that only appear after
|
|
242
|
+
multiple hops. Orders greater than one require `mode: "execution_trace"`.
|
|
243
|
+
`context_order="auto"` also requires `execution_trace`; in
|
|
244
|
+
`aggregated_transitions` mode context resolution is not applied.
|
|
245
|
+
|
|
246
|
+
A phantom path is a multi-hop path assembled by pooling edges from different
|
|
247
|
+
tree executions even though no single execution produced it. DPG-k removes
|
|
248
|
+
these recombinations at the resolved context order while retaining one graph.
|
|
249
|
+
The effective order and each node's context are available through
|
|
250
|
+
`get_context_order()` and `get_node_context(node)`.
|
|
251
|
+
|
|
252
|
+
**Regression scope (0.3.0):** `context_order` builds without raising for
|
|
253
|
+
regressors (regression leaves, labeled `"Pred <value>"`, are treated as
|
|
254
|
+
terminal sinks just like class leaves), but "one sink per output" is not a
|
|
255
|
+
defined guarantee for regression the way it is for classification: two
|
|
256
|
+
leaves collapse into the same sink only when their rounded values coincide,
|
|
257
|
+
which is an artifact of label rounding, not a modeled invariant. A
|
|
258
|
+
principled regression sink policy is deferred past 0.3.0. `class_boundaries`
|
|
259
|
+
and `communities` remain classifier-only; calling `explain_global`
|
|
260
|
+
with `communities=True` on a regressor raises a clear `ValueError`.
|
|
261
|
+
|
|
232
262
|
#### Minimal local workflow
|
|
233
263
|
|
|
234
264
|
```python
|
|
@@ -314,8 +344,8 @@ Important:
|
|
|
314
344
|
|
|
315
345
|
## CLI scripts
|
|
316
346
|
The library contains two different scripts to apply DPG:
|
|
317
|
-
- `run_dpg_standard.py`: with this script it is possible to test DPG on a standard classification dataset provided by `sklearn` such as `iris`, `digits`, `wine`, `breast cancer`, and `diabetes`.
|
|
318
|
-
- `run_dpg_custom.py`: with this script it is possible to apply DPG to your classification dataset, specifying the target class.
|
|
347
|
+
- `examples/run_dpg_standard.py`: with this script it is possible to test DPG on a standard classification dataset provided by `sklearn` such as `iris`, `digits`, `wine`, `breast cancer`, and `diabetes`.
|
|
348
|
+
- `examples/run_dpg_custom.py`: with this script it is possible to apply DPG to your classification dataset, specifying the target class.
|
|
319
349
|
|
|
320
350
|
### Implementation notes
|
|
321
351
|
The library also contains two other essential scripts:
|
|
@@ -323,14 +353,17 @@ The library also contains two other essential scripts:
|
|
|
323
353
|
- `visualizer.py` contains the functions used to manage the visualization of DPG.
|
|
324
354
|
|
|
325
355
|
### Output
|
|
326
|
-
The DPG output, through `run_dpg_standard.py` or `run_dpg_custom.py`, produces several files:
|
|
356
|
+
The DPG output, through `examples/run_dpg_standard.py` or `examples/run_dpg_custom.py`, produces several files:
|
|
327
357
|
- the visualization of DPG in a dedicated environment, which can be zoomed and saved;
|
|
328
358
|
- a `.txt` file containing the DPG metrics;
|
|
329
359
|
- a `.csv` file containing the information about all the nodes of the DPG and their associated metrics;
|
|
330
360
|
- a `.txt` file containing the Random Forest statistics (accuracy, confusion matrix, classification report)
|
|
331
361
|
|
|
332
362
|
### CLI parameter reference
|
|
333
|
-
Usage: `
|
|
363
|
+
Usage: `dpg --dataset <dataset_name> --n_learners <integer_number> --pv <threshold_value> --t <integer_number> --model_name <str_model_name> --dir <save_dir_path> --plot --save_plot_dir <save_plot_dir_path> --attribute <attribute> --communities --clusters --threshold_clusters <float> --class_flag --seed <int>`
|
|
364
|
+
|
|
365
|
+
After installing DPG, the `dpg` command is the packaged equivalent of
|
|
366
|
+
`python examples/run_dpg_standard.py`.
|
|
334
367
|
Where:
|
|
335
368
|
- `dataset` is the name of the standard classification `sklearn` dataset to be analyzed;
|
|
336
369
|
- `n_learners` is the number of base learners for the ensemble model;
|
|
@@ -357,23 +390,29 @@ Where:
|
|
|
357
390
|
|
|
358
391
|
Disclaimer: `attribute`, `communities`, and `clusters` are mutually exclusive: DPG supports just one visualization mode at a time.
|
|
359
392
|
|
|
360
|
-
The usage of `run_dpg_custom.py` is similar, but it requires another parameter:
|
|
393
|
+
The usage of `examples/run_dpg_custom.py` is similar, but it requires another parameter:
|
|
361
394
|
- `target_column`, which is the name of the column to be used as the target variable;
|
|
362
395
|
- while `ds` is the path of the directory where the dataset is.
|
|
363
396
|
|
|
364
|
-
### Example `run_dpg_standard.py`
|
|
397
|
+
### Example `examples/run_dpg_standard.py`
|
|
365
398
|
Some examples can be appreciated in the `examples` folder: https://github.com/Meta-Group/DPG/tree/main/examples
|
|
366
399
|
|
|
367
400
|
In particular, the following DPG is obtained by transforming a Random Forest with 5 base learners, trained on Iris dataset.
|
|
368
|
-
The used command is `
|
|
369
|
-
|
|
401
|
+
The used command is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples`.
|
|
402
|
+
<p align="center">
|
|
403
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2.png" width="800" />
|
|
404
|
+
</p>
|
|
370
405
|
|
|
371
406
|
The following visualizations are obtained using the same parameters as the previous example, but they show two different metrics: _Community_ and _Betweenness centrality_.
|
|
372
|
-
The used command for showing communities is `
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
407
|
+
The used command for showing communities is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --communities`.
|
|
408
|
+
<p align="center">
|
|
409
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2_communities.png" width="800" />
|
|
410
|
+
</p>
|
|
411
|
+
|
|
412
|
+
The used command for showing a specific property is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --attribute "Betweenness centrality" --class_flag`.
|
|
413
|
+
<p align="center">
|
|
414
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2_Betweennesscentrality.png" width="800" />
|
|
415
|
+
</p>
|
|
377
416
|
|
|
378
417
|
***
|
|
379
418
|
## Citation
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[](LICENSE)
|
|
4
4
|
[](pyproject.toml)
|
|
5
|
-
[](https://pypi.org/project/dpg/)
|
|
6
5
|
[](https://github.com/Meta-Group/DPG/actions/workflows/ci.yml)
|
|
7
6
|
[](https://dpg.readthedocs.io/en/latest/)
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
<p align="center">
|
|
9
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/DPG.png" alt="DPG logo" width="300">
|
|
10
|
+
</p>
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
DPG is a model-agnostic framework for global and local explanation of tree-based ensemble models.
|
|
@@ -20,7 +20,9 @@ insightful points. DPG enables graph-based evaluations and the identification of
|
|
|
20
20
|
towards facilitating comparisons between features and their associated values while offering insights
|
|
21
21
|
into the entire model. DPG provides descriptive metrics that enhance the understanding of the
|
|
22
22
|
decisions inherent in the model, offering valuable insights.
|
|
23
|
-
|
|
23
|
+
<p align="center">
|
|
24
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/image.png" width="600" />
|
|
25
|
+
</p>
|
|
24
26
|
|
|
25
27
|
---
|
|
26
28
|
|
|
@@ -46,7 +48,9 @@ The concept behind DPG is to convert a generic tree-based ensemble model for cla
|
|
|
46
48
|
- Nodes represent predicates, i.e., the feature-value associations present in each node of every tree;
|
|
47
49
|
- Edges denote the frequency with which these predicates are satisfied during the model training phase by the samples of the dataset.
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
<p align="center">
|
|
52
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/example.png" width="600" />
|
|
53
|
+
</p>
|
|
50
54
|
|
|
51
55
|
## Metrics
|
|
52
56
|
The graph-based nature of DPG provides significant enhancements in the direction of a complete mapping of the ensemble structure.
|
|
@@ -60,7 +64,7 @@ The graph-based nature of DPG provides significant enhancements in the direction
|
|
|
60
64
|
|
|
61
65
|
|Constraints | Betweenness centrality | Local reaching centrality | Community|
|
|
62
66
|
|------------|------------|--------------|--------------------|
|
|
63
|
-
 |  |  |  |
|
|
64
68
|
|Constraints(Class 1) = val3 < F1 ≤ val1, F2 ≤ val2 | BC(F2 ≤ val2) = 4/24 | LRC(F1 ≤ val1) = 6 / 7 | Community(Class 1) = F1 ≤ val1, F2 ≤ val2 |
|
|
65
69
|
|
|
66
70
|
---
|
|
@@ -168,6 +172,7 @@ explainer = DPGExplainer(
|
|
|
168
172
|
},
|
|
169
173
|
"graph_construction": {
|
|
170
174
|
"mode": "execution_trace", # or "aggregated_transitions"
|
|
175
|
+
"context_order": 1, # 1, an integer > 1, or "auto"
|
|
171
176
|
},
|
|
172
177
|
}
|
|
173
178
|
},
|
|
@@ -177,6 +182,31 @@ explainer = DPGExplainer(
|
|
|
177
182
|
- `"aggregated_transitions"`: current default behavior; filters path variants first, then discovers the DPG.
|
|
178
183
|
- `"execution_trace"`: builds directly from raw traces and filters edges instead of whole-path variants when `perc_var > 0`.
|
|
179
184
|
|
|
185
|
+
`context_order` controls predicate identity in `execution_trace` mode. `1`
|
|
186
|
+
preserves the legacy graph exactly; an order greater than one uses the last k
|
|
187
|
+
executed predicates as context and keeps one sink per class. `"auto"` resolves
|
|
188
|
+
the smallest order for which every pooled graph path is supported by an
|
|
189
|
+
observed execution trace, including recombinations that only appear after
|
|
190
|
+
multiple hops. Orders greater than one require `mode: "execution_trace"`.
|
|
191
|
+
`context_order="auto"` also requires `execution_trace`; in
|
|
192
|
+
`aggregated_transitions` mode context resolution is not applied.
|
|
193
|
+
|
|
194
|
+
A phantom path is a multi-hop path assembled by pooling edges from different
|
|
195
|
+
tree executions even though no single execution produced it. DPG-k removes
|
|
196
|
+
these recombinations at the resolved context order while retaining one graph.
|
|
197
|
+
The effective order and each node's context are available through
|
|
198
|
+
`get_context_order()` and `get_node_context(node)`.
|
|
199
|
+
|
|
200
|
+
**Regression scope (0.3.0):** `context_order` builds without raising for
|
|
201
|
+
regressors (regression leaves, labeled `"Pred <value>"`, are treated as
|
|
202
|
+
terminal sinks just like class leaves), but "one sink per output" is not a
|
|
203
|
+
defined guarantee for regression the way it is for classification: two
|
|
204
|
+
leaves collapse into the same sink only when their rounded values coincide,
|
|
205
|
+
which is an artifact of label rounding, not a modeled invariant. A
|
|
206
|
+
principled regression sink policy is deferred past 0.3.0. `class_boundaries`
|
|
207
|
+
and `communities` remain classifier-only; calling `explain_global`
|
|
208
|
+
with `communities=True` on a regressor raises a clear `ValueError`.
|
|
209
|
+
|
|
180
210
|
#### Minimal local workflow
|
|
181
211
|
|
|
182
212
|
```python
|
|
@@ -262,8 +292,8 @@ Important:
|
|
|
262
292
|
|
|
263
293
|
## CLI scripts
|
|
264
294
|
The library contains two different scripts to apply DPG:
|
|
265
|
-
- `run_dpg_standard.py`: with this script it is possible to test DPG on a standard classification dataset provided by `sklearn` such as `iris`, `digits`, `wine`, `breast cancer`, and `diabetes`.
|
|
266
|
-
- `run_dpg_custom.py`: with this script it is possible to apply DPG to your classification dataset, specifying the target class.
|
|
295
|
+
- `examples/run_dpg_standard.py`: with this script it is possible to test DPG on a standard classification dataset provided by `sklearn` such as `iris`, `digits`, `wine`, `breast cancer`, and `diabetes`.
|
|
296
|
+
- `examples/run_dpg_custom.py`: with this script it is possible to apply DPG to your classification dataset, specifying the target class.
|
|
267
297
|
|
|
268
298
|
### Implementation notes
|
|
269
299
|
The library also contains two other essential scripts:
|
|
@@ -271,14 +301,17 @@ The library also contains two other essential scripts:
|
|
|
271
301
|
- `visualizer.py` contains the functions used to manage the visualization of DPG.
|
|
272
302
|
|
|
273
303
|
### Output
|
|
274
|
-
The DPG output, through `run_dpg_standard.py` or `run_dpg_custom.py`, produces several files:
|
|
304
|
+
The DPG output, through `examples/run_dpg_standard.py` or `examples/run_dpg_custom.py`, produces several files:
|
|
275
305
|
- the visualization of DPG in a dedicated environment, which can be zoomed and saved;
|
|
276
306
|
- a `.txt` file containing the DPG metrics;
|
|
277
307
|
- a `.csv` file containing the information about all the nodes of the DPG and their associated metrics;
|
|
278
308
|
- a `.txt` file containing the Random Forest statistics (accuracy, confusion matrix, classification report)
|
|
279
309
|
|
|
280
310
|
### CLI parameter reference
|
|
281
|
-
Usage: `
|
|
311
|
+
Usage: `dpg --dataset <dataset_name> --n_learners <integer_number> --pv <threshold_value> --t <integer_number> --model_name <str_model_name> --dir <save_dir_path> --plot --save_plot_dir <save_plot_dir_path> --attribute <attribute> --communities --clusters --threshold_clusters <float> --class_flag --seed <int>`
|
|
312
|
+
|
|
313
|
+
After installing DPG, the `dpg` command is the packaged equivalent of
|
|
314
|
+
`python examples/run_dpg_standard.py`.
|
|
282
315
|
Where:
|
|
283
316
|
- `dataset` is the name of the standard classification `sklearn` dataset to be analyzed;
|
|
284
317
|
- `n_learners` is the number of base learners for the ensemble model;
|
|
@@ -305,23 +338,29 @@ Where:
|
|
|
305
338
|
|
|
306
339
|
Disclaimer: `attribute`, `communities`, and `clusters` are mutually exclusive: DPG supports just one visualization mode at a time.
|
|
307
340
|
|
|
308
|
-
The usage of `run_dpg_custom.py` is similar, but it requires another parameter:
|
|
341
|
+
The usage of `examples/run_dpg_custom.py` is similar, but it requires another parameter:
|
|
309
342
|
- `target_column`, which is the name of the column to be used as the target variable;
|
|
310
343
|
- while `ds` is the path of the directory where the dataset is.
|
|
311
344
|
|
|
312
|
-
### Example `run_dpg_standard.py`
|
|
345
|
+
### Example `examples/run_dpg_standard.py`
|
|
313
346
|
Some examples can be appreciated in the `examples` folder: https://github.com/Meta-Group/DPG/tree/main/examples
|
|
314
347
|
|
|
315
348
|
In particular, the following DPG is obtained by transforming a Random Forest with 5 base learners, trained on Iris dataset.
|
|
316
|
-
The used command is `
|
|
317
|
-
|
|
349
|
+
The used command is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples`.
|
|
350
|
+
<p align="center">
|
|
351
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2.png" width="800" />
|
|
352
|
+
</p>
|
|
318
353
|
|
|
319
354
|
The following visualizations are obtained using the same parameters as the previous example, but they show two different metrics: _Community_ and _Betweenness centrality_.
|
|
320
|
-
The used command for showing communities is `
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
355
|
+
The used command for showing communities is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --communities`.
|
|
356
|
+
<p align="center">
|
|
357
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2_communities.png" width="800" />
|
|
358
|
+
</p>
|
|
359
|
+
|
|
360
|
+
The used command for showing a specific property is `dpg --dataset iris --n_learners 5 --pv 0.001 --t 2 --dir examples --plot --save_plot_dir examples --attribute "Betweenness centrality" --class_flag`.
|
|
361
|
+
<p align="center">
|
|
362
|
+
<img src="https://raw.githubusercontent.com/Meta-Group/DPG/main/dpg_image_examples/iris_bl5_perc0.001_dec2_Betweennesscentrality.png" width="800" />
|
|
363
|
+
</p>
|
|
325
364
|
|
|
326
365
|
***
|
|
327
366
|
## Citation
|
|
@@ -13,6 +13,7 @@ from .visualizer import (
|
|
|
13
13
|
classwise_feature_bounds_from_communities,
|
|
14
14
|
plot_class_feature_complexity,
|
|
15
15
|
plot_dpg,
|
|
16
|
+
plot_dpg_communities,
|
|
16
17
|
plot_dpg_class_bounds_vs_dataset_feature_ranges,
|
|
17
18
|
plot_dpg_constraints_overview,
|
|
18
19
|
plot_dpg_local_paths_aggregate,
|
|
@@ -35,6 +36,7 @@ __all__ = [
|
|
|
35
36
|
"DPG_OLIVE_CLASS_PALETTE",
|
|
36
37
|
"resolve_theme_context",
|
|
37
38
|
"plot_dpg",
|
|
39
|
+
"plot_dpg_communities",
|
|
38
40
|
"plot_dpg_local_paths_aggregate",
|
|
39
41
|
"plot_dpg_reg",
|
|
40
42
|
"plot_dpg_constraints_overview",
|
dpg-0.3.0/dpg/cli.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Packaged command-line interface for the standard DPG demonstration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Sequence
|
|
8
|
+
|
|
9
|
+
from .sklearn_dpg import test_dpg
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
13
|
+
"""Build the parser used by the installed ``dpg`` command."""
|
|
14
|
+
parser = argparse.ArgumentParser(
|
|
15
|
+
description="Train a sklearn tree ensemble and export its DPG metrics."
|
|
16
|
+
)
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--dataset",
|
|
19
|
+
"--ds",
|
|
20
|
+
dest="dataset",
|
|
21
|
+
default="iris",
|
|
22
|
+
help="sklearn dataset name or path to a CSV file",
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--target_column",
|
|
26
|
+
default=None,
|
|
27
|
+
help="target column when --dataset points to a CSV file",
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument("--n_learners", "--l", dest="n_learners", type=int, default=5)
|
|
30
|
+
parser.add_argument("--model_name", default="RandomForestClassifier")
|
|
31
|
+
parser.add_argument("--dir", default="examples/results", help="output directory")
|
|
32
|
+
parser.add_argument("--plot", action="store_true")
|
|
33
|
+
parser.add_argument("--save_plot_dir", default="examples/results")
|
|
34
|
+
parser.add_argument("--attribute", default=None)
|
|
35
|
+
parser.add_argument("--communities", action="store_true")
|
|
36
|
+
parser.add_argument("--clusters", action="store_true")
|
|
37
|
+
parser.add_argument("--threshold_clusters", type=float, default=None)
|
|
38
|
+
parser.add_argument("--t", type=int, default=3, help="threshold decimal precision")
|
|
39
|
+
parser.add_argument("--class_flag", action="store_true")
|
|
40
|
+
parser.add_argument("--seed", type=int, default=160898)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--pv",
|
|
43
|
+
type=float,
|
|
44
|
+
default=1e-9,
|
|
45
|
+
help="minimum path frequency proportion",
|
|
46
|
+
)
|
|
47
|
+
return parser
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
51
|
+
"""Run the standard DPG workflow and write metrics to disk."""
|
|
52
|
+
args = build_parser().parse_args(argv)
|
|
53
|
+
output_dir = Path(args.dir)
|
|
54
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
|
|
56
|
+
result = test_dpg(
|
|
57
|
+
datasets=args.dataset,
|
|
58
|
+
target_column=args.target_column,
|
|
59
|
+
n_learners=args.n_learners,
|
|
60
|
+
perc_var=args.pv,
|
|
61
|
+
decimal_threshold=args.t,
|
|
62
|
+
n_jobs=-1,
|
|
63
|
+
model_name=args.model_name,
|
|
64
|
+
file_name=str(output_dir / f"{Path(args.dataset).stem}_seed{args.seed}_stats.txt"),
|
|
65
|
+
plot=args.plot,
|
|
66
|
+
save_plot_dir=args.save_plot_dir,
|
|
67
|
+
attribute=args.attribute,
|
|
68
|
+
communities=args.communities,
|
|
69
|
+
clusters_flag=args.clusters,
|
|
70
|
+
threshold_clusters=args.threshold_clusters,
|
|
71
|
+
class_flag=args.class_flag,
|
|
72
|
+
seed=args.seed,
|
|
73
|
+
)
|
|
74
|
+
# ``test_dpg`` signals "insufficient nodes for DPG analysis" with
|
|
75
|
+
# ``(None, None)`` rather than a bare ``None``; guard on shape, not identity.
|
|
76
|
+
if result is None or len(result) != 6:
|
|
77
|
+
return 1
|
|
78
|
+
|
|
79
|
+
df, df_edges, graph_metrics, clusters, node_prob, confidence = result
|
|
80
|
+
stem = Path(args.dataset).stem
|
|
81
|
+
df.to_csv(output_dir / f"{stem}_seed{args.seed}_node_metrics.csv", index=False)
|
|
82
|
+
df_edges.to_csv(output_dir / f"{stem}_seed{args.seed}_edge_metrics.csv", index=False)
|
|
83
|
+
with (output_dir / f"{stem}_seed{args.seed}_dpg_metrics.txt").open("w", encoding="utf-8") as handle:
|
|
84
|
+
for key, value in graph_metrics.items():
|
|
85
|
+
handle.write(f"{key}: {value}\n")
|
|
86
|
+
|
|
87
|
+
if clusters is not None:
|
|
88
|
+
with (output_dir / f"{stem}_seed{args.seed}_clusters.txt").open("w", encoding="utf-8") as handle:
|
|
89
|
+
handle.write(f"Clusters: {clusters}\n")
|
|
90
|
+
handle.write(f"Probability: {node_prob}\n")
|
|
91
|
+
handle.write(f"Confidence: {confidence}\n")
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Fast resolution of the smallest context order without path enumeration."""
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
import math
|
|
5
|
+
from typing import Iterable, Sequence
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _node_windows(sequence: Sequence[str], k: int | float) -> list[object]:
|
|
9
|
+
"""Represent a trace as contextual predicate nodes and class sinks."""
|
|
10
|
+
nodes: list[object] = []
|
|
11
|
+
for index, label in enumerate(sequence):
|
|
12
|
+
if str(label).startswith(("Class ", "Pred ")):
|
|
13
|
+
nodes.append(("sink", str(label)))
|
|
14
|
+
continue
|
|
15
|
+
if math.isinf(k):
|
|
16
|
+
context = tuple(sequence[: index + 1])
|
|
17
|
+
else:
|
|
18
|
+
context = tuple(sequence[max(0, index - int(k) + 1) : index + 1])
|
|
19
|
+
nodes.append(("ctx", context))
|
|
20
|
+
return nodes
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def path_violations(traces: Iterable[Sequence[str]], k: int | float) -> int:
|
|
24
|
+
"""Count pooled-graph paths that are not observed trace prefixes.
|
|
25
|
+
|
|
26
|
+
A pooled DFG can be locally consistent while still recombining after
|
|
27
|
+
several hops. For example, observing ``A-B-C-D`` and ``X-B-C-E`` gives
|
|
28
|
+
every adjacent transition a witness, but also creates the unobserved
|
|
29
|
+
path ``A-B-C-E``. This check builds a trie of observed contextual traces,
|
|
30
|
+
computes each prefix state's future signature bottom-up, and verifies
|
|
31
|
+
that states merged by the same contextual node have the same future
|
|
32
|
+
language. It avoids both exponential simple-path enumeration and the
|
|
33
|
+
potentially large graph-by-trie product.
|
|
34
|
+
|
|
35
|
+
The returned value is a diagnostic count, not a count of all phantom
|
|
36
|
+
paths. A value of zero is the useful acceptance condition: every graph
|
|
37
|
+
path is represented by an observed trace.
|
|
38
|
+
"""
|
|
39
|
+
materialized = [tuple(trace) for trace in traces if trace]
|
|
40
|
+
if not materialized:
|
|
41
|
+
return 0
|
|
42
|
+
|
|
43
|
+
trie_children: list[dict[object, int]] = [{}]
|
|
44
|
+
trie_terminal: set[int] = set()
|
|
45
|
+
trie_context: list[object | None] = [None]
|
|
46
|
+
|
|
47
|
+
for sequence in materialized:
|
|
48
|
+
nodes = _node_windows(sequence, k)
|
|
49
|
+
trie_node = 0
|
|
50
|
+
for node in nodes:
|
|
51
|
+
next_node = trie_children[trie_node].get(node)
|
|
52
|
+
if next_node is None:
|
|
53
|
+
next_node = len(trie_children)
|
|
54
|
+
trie_children[trie_node][node] = next_node
|
|
55
|
+
trie_children.append({})
|
|
56
|
+
trie_context.append(node)
|
|
57
|
+
trie_node = next_node
|
|
58
|
+
trie_terminal.add(trie_node)
|
|
59
|
+
|
|
60
|
+
signatures: list[tuple[bool, tuple[tuple[object, object], ...]]] = [
|
|
61
|
+
(False, ())
|
|
62
|
+
] * len(trie_children)
|
|
63
|
+
for trie_node in range(len(trie_children) - 1, -1, -1):
|
|
64
|
+
children = tuple(
|
|
65
|
+
sorted(
|
|
66
|
+
(
|
|
67
|
+
(child_label, signatures[child_node])
|
|
68
|
+
for child_label, child_node in trie_children[trie_node].items()
|
|
69
|
+
),
|
|
70
|
+
key=repr,
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
signatures[trie_node] = (trie_node in trie_terminal, children)
|
|
74
|
+
|
|
75
|
+
future_by_context: defaultdict[object, set[tuple[bool, tuple]]] = defaultdict(set)
|
|
76
|
+
for trie_node in range(1, len(trie_children)):
|
|
77
|
+
context = trie_context[trie_node]
|
|
78
|
+
assert context is not None
|
|
79
|
+
future_by_context[context].add(signatures[trie_node])
|
|
80
|
+
|
|
81
|
+
return sum(max(0, len(futures) - 1) for futures in future_by_context.values())
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def resolve_context_order(
|
|
85
|
+
traces: Iterable[Sequence[str]], max_k: int | None = None
|
|
86
|
+
) -> tuple[int | float, dict[int | float, int]]:
|
|
87
|
+
"""Return the smallest order with no global trace recombination.
|
|
88
|
+
|
|
89
|
+
``max_k`` defaults to the longest observed trace. At that order every
|
|
90
|
+
observed prefix is distinct, which is a proof-based bound rather than a
|
|
91
|
+
user-facing cap. When a caller supplies a smaller cap, resolution must
|
|
92
|
+
still succeed within that cap; otherwise ``ValueError`` is raised instead
|
|
93
|
+
of returning an order whose history still contains violations.
|
|
94
|
+
"""
|
|
95
|
+
if max_k is not None and (
|
|
96
|
+
isinstance(max_k, bool) or not isinstance(max_k, int) or max_k < 1
|
|
97
|
+
):
|
|
98
|
+
raise ValueError("max_k must be a positive integer or None")
|
|
99
|
+
|
|
100
|
+
materialized = [tuple(trace) for trace in traces if trace]
|
|
101
|
+
if not materialized:
|
|
102
|
+
return 1, {1: 0}
|
|
103
|
+
if max_k is None:
|
|
104
|
+
max_k = max(len(trace) for trace in materialized)
|
|
105
|
+
|
|
106
|
+
history: dict[int | float, int] = {}
|
|
107
|
+
for k in range(1, max_k + 1):
|
|
108
|
+
violations = path_violations(materialized, k)
|
|
109
|
+
history[k] = violations
|
|
110
|
+
if violations == 0:
|
|
111
|
+
return k, history
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"no context order <= max_k={max_k} eliminates all global trace violations"
|
|
114
|
+
)
|