Skip to content

Commit aa5799d

Browse files
authored
Merge pull request #92 from diningphil/utilities_qualitative_results
Added utilities to quickly play and run analyses on automatically trained models
2 parents 0f03845 + 6ee5c85 commit aa5799d

10 files changed

Lines changed: 183 additions & 12 deletions

File tree

.github/workflows/interrogate-docstring.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ jobs:
1212
runs-on: ubuntu-latest
1313

1414
steps:
15-
- uses: actions/checkout@v3
15+
- uses: actions/checkout@v4
1616

1717
- name: Set up Python 3.11
18-
uses: actions/setup-python@v3
18+
uses: actions/setup-python@v4
1919
with:
2020
python-version: 3.11
2121

.github/workflows/python-publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ jobs:
2323
runs-on: ubuntu-latest
2424

2525
steps:
26-
- uses: actions/checkout@v3
26+
- uses: actions/checkout@v4
2727
- name: Set up Python
28-
uses: actions/setup-python@v3
28+
uses: actions/setup-python@v4
2929
with:
3030
python-version: '3.11'
3131
- name: Install dependencies

.github/workflows/python-test-and-coverage.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ jobs:
2020
python-version: ["3.8", "3.9", "3.10", "3.11"]
2121

2222
steps:
23-
- uses: actions/checkout@v3
23+
- uses: actions/checkout@v4
2424

2525
- name: Set up Python ${{ matrix.python-version }}
26-
uses: actions/setup-python@v3
26+
uses: actions/setup-python@v4
2727
with:
2828
python-version: ${{ matrix.python-version }}
2929

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [1.5.4] New post-processing tutorial
4+
5+
### Added
6+
7+
- TODO
8+
39
## [1.5.3] Minor fix
410

511
### Fixed

THANKS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ Many thanks to **Alessio Gravina** ([Github](https://github.com/gravins)
1313
/[Homepage](http://pages.di.unipi.it/gravina/)) for his invaluable help on the temporal graph implementation.
1414

1515
Many thanks to **Francesco Landolfi** ([Github](https://github.com/flandolfi)) for the suggestions on how to make this library more user-friendly!
16+
17+
Many thanks to **Henrik Christiansen** ([Github](https://github.com/christiansenh)) for his help with the post-processing of results!

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
author = "Federico Errica"
2525

2626
# The full version, including alpha/beta/rc tags
27-
release = "1.5.3"
27+
release = "1.5.4"
2828

2929

3030
# -- General configuration ---------------------------------------------------

docs/tutorial.rst

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,8 @@ And we get:
448448
:width: 600
449449

450450

451-
Filtering Configurations for Successive Analyses
452-
-----------------------
451+
Filtering Configurations for Post-processing of Results
452+
----------------------------------------------------------
453453

454454
You can use some utilities we provide to focus on a specific set of configurations after your experiments are terminated.
455455
Assuming you run `pydgn-train --config-file examples/MODEL_CONFIGS/config_SupToyDGN.yml` inside the PyDGN repo, you can
@@ -460,10 +460,49 @@ then do something like
460460
from pydgn.evaluation.util import retrieve_experiments, filter_experiments
461461
462462
configs = retrieve_experiments('RESULTS/supervised_grid_search_toy_NCI1/MODEL_ASSESSMENT/OUTER_FOLD_1/MODEL_SELECTION/')
463-
print(len(configs)) # will return 16
463+
print(len(configs)) # will return 32
464464
465465
filtered_configs = filter_experiments(configs, logic='OR', parameters={'Multiclass Classification': 1, 'lr': 0.001})
466-
print(len(filtered_configs)) # will return 12
466+
print(len(filtered_configs)) # will return 24
467+
468+
469+
470+
Loading Model for Inspection in a Notebook
471+
----------------------------------------------
472+
473+
We provide utilities to use your model immediately after experiments end to run additional analyses. Here's how:
474+
475+
.. code-block:: python3
476+
477+
from pydgn.evaluation.util import *
478+
479+
config = retrieve_best_configuration('RESULTS/supervised_grid_search_toy_NCI1/MODEL_ASSESSMENT/OUTER_FOLD_1/MODEL_SELECTION/')
480+
splits_filepath = 'examples/DATA_SPLITS/CHEMICAL/NCI1/NCI1_outer10_inner1.splits'
481+
device = 'cpu'
482+
483+
# instantiate dataset
484+
dataset = instantiate_dataset_from_config(config)
485+
486+
# instantiate model
487+
model = instantiate_model_from_config(config, dataset, config_type="supervised_config")
488+
489+
# load model's checkpoint, assuming the best configuration has been loaded
490+
checkpoint_location = 'RESULTS/supervised_grid_search_toy_NCI1/MODEL_ASSESSMENT/OUTER_FOLD_1/final_run1/best_checkpoint.pth'
491+
load_checkpoint(checkpoint_location, model, device=device)
492+
493+
# you can now call the forward method of your model
494+
y, embeddings = model(dataset[0])
495+
496+
# ------------------------------------------------------------------ #
497+
# OPTIONAL: you can also instantiate a DataProvider to load TR/VL/TE splits specific to each fold
498+
data_provider = instantiate_data_provider_from_config(config, splits_filepath)
499+
# select outer fold 1 (indices start from 0)
500+
data_provider.set_outer_k(0)
501+
# select inner fold 1 (indices start from 0)
502+
data_provider.set_inner_k(0)
503+
504+
# Please refer to the DataProvider documentation to use it properly.
505+
# ------------------------------------------------------------------ #
467506
468507
469508
Telegram Bot

examples/Result Analysis.ipynb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"id": "initial_id",
7+
"metadata": {
8+
"collapsed": true
9+
},
10+
"outputs": [],
11+
"source": [
12+
""
13+
]
14+
}
15+
],
16+
"metadata": {
17+
"kernelspec": {
18+
"display_name": "Python 3",
19+
"language": "python",
20+
"name": "python3"
21+
},
22+
"language_info": {
23+
"codemirror_mode": {
24+
"name": "ipython",
25+
"version": 2
26+
},
27+
"file_extension": ".py",
28+
"mimetype": "text/x-python",
29+
"name": "python",
30+
"nbconvert_exporter": "python",
31+
"pygments_lexer": "ipython2",
32+
"version": "2.7.6"
33+
}
34+
},
35+
"nbformat": 4,
36+
"nbformat_minor": 5
37+
}

pydgn/evaluation/util.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
import random
66
from typing import Tuple, Callable, List
77

8+
import torch
89
import tqdm
910

11+
from pydgn.data.dataset import DatasetInterface
1012
from pydgn.experiment.util import s2c
13+
from pydgn.model.interface import ModelInterface
1114
from pydgn.static import *
1215

1316

@@ -349,6 +352,9 @@ def retrieve_experiments(model_selection_folder) -> List[dict]:
349352
"""
350353
config_directory = os.path.join(model_selection_folder)
351354

355+
if not os.path.exists(config_directory):
356+
raise FileNotFoundError(f"Directory not found: {config_directory}")
357+
352358
folder_names = []
353359
for _, dirs, _ in os.walk(config_directory):
354360
for d in dirs:
@@ -448,3 +454,81 @@ def _finditem(obj, key):
448454
filtered_config_list.append(config)
449455

450456
return filtered_config_list
457+
458+
459+
def retrieve_best_configuration(model_selection_folder) -> dict:
460+
"""
461+
Once the experiments are done, retrieves the winning configuration from
462+
a specific model selection folder, and returns it as a dictionaries
463+
464+
:param model_selection_folder: path to the folder of a model selection,
465+
that is, your_results_path/..../MODEL_SELECTION/
466+
:return: a dictionary with info about the best configuration
467+
"""
468+
config_directory = os.path.join(model_selection_folder)
469+
470+
if not os.path.exists(config_directory):
471+
raise FileNotFoundError(f"Directory not found: {config_directory}")
472+
473+
best_config = json.load(
474+
open(os.path.join(config_directory, "winner_config.json"), "rb")
475+
)
476+
return best_config
477+
478+
479+
def instantiate_dataset_from_config(config: dict) -> DatasetInterface:
480+
"""
481+
Instantiate a dataset from a configuration file.
482+
:param config (dict): the configuration file
483+
:return: an instance of DatasetInterface, i.e., the dataset
484+
"""
485+
data_root = config[CONFIG][DATA_ROOT]
486+
dataset_name = config[CONFIG][DATASET]
487+
dataset_class = s2c(config[CONFIG][DATASET_CLASS])
488+
489+
return dataset_class(data_root, dataset_name)
490+
491+
492+
def instantiate_model_from_config(config: dict,
493+
dataset: DatasetInterface,
494+
config_type: str = "supervised_config") -> ModelInterface:
495+
"""
496+
Instantiate a model from a configuration file.
497+
:param config (dict): the configuration file
498+
:param dataset (DatasetInterface): the dataset used in the experiment
499+
:param config_type (str): the type of model in ["supervised_config", "unsupervised_config"],
500+
as written on the YAML experiment configuration file. Defaults to "supervised_config"
501+
:return: an instance of ModelInterface, i.e., the model
502+
"""
503+
config_ = config[CONFIG][config_type]
504+
readout_class = s2c(config_["readout"])
505+
506+
model_class = s2c(config_[MODEL])
507+
model = model_class(dataset.dim_node_features,
508+
dataset.dim_edge_features,
509+
dataset.dim_target,
510+
readout_class,
511+
config=config_)
512+
513+
return model
514+
515+
516+
def load_checkpoint(checkpoint_path: str, model: ModelInterface,
517+
device: torch.device):
518+
"""
519+
Load a checkpoint from a checkpoint file into a model.
520+
:param checkpoint_path: the checkpoint file path
521+
:param model (ModelInterface): the model
522+
:param device (torch.device): the device, e.g, "cpu" or "cuda"
523+
"""
524+
ckpt_dict = torch.load(
525+
checkpoint_path, map_location="cpu" if device == "cpu" else None
526+
)
527+
model_state = ckpt_dict[MODEL_STATE]
528+
529+
# Needed only when moving from cpu to cuda (due to changes in config
530+
# file). Move all parameters to cuda.
531+
for param in model_state.keys():
532+
model_state[param] = model_state[param].to(device)
533+
534+
model.load_state_dict(model_state)

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
requires = ["setuptools"]
33
build-backend = "setuptools.build_meta"
44

5+
[tool.setuptools.packages.find]
6+
where = ["pydgn"]
7+
58
[project]
69
name = "pydgn"
7-
version = "1.5.3"
10+
version = "1.5.4"
811
description = "A Python Package for Deep Graph Networks"
912
authors = [ { name="Federico Errica", email="f.errica@protonmail.com" } ]
1013
readme = "README.md"

0 commit comments

Comments
 (0)