Deep learning of contagion dynamics on complex networks in pytorch
torch>=1.6.0
torch_geometric>=1.6.3
First, clone this repository.
git clone https://github.com/DynamicaLab/code-dynalearn
Second, use pip to install the module.
pip install ./code-dynalearn
Please cite:
Deep learning of contagion dynamics on complex networks
Charles Murphy,
Edward Laurence and
Antoine Allard,
Nature Communications 12, 4720 (2021)
This Python module defines different classes for the purpose of learning dynamics on networks, such as Experiment
, Config
, Metrics
, Dynamics
, Model
, Network
and Dataset
. We review these classes and show how to use them in this section.
The scripts to re-run all the experiments presented in the papers are here. To run a script, for instance the script run-synthetic-discrete.py that generates the data for Figs. 2, 3 and 4 for the simple, complex and interaction dynamics, simply run the following command:
python run-synthetic-discrete.py
To remake the figures, we refer to these notebooks.
To reproduce the COVID-19 experiment, run the script run-covid.py. Note that, prior to running this script, a datafile containing the incidence data must be downloaded. In this runner script, it is assumed that the name of this datafile is spain-covid19cases.h5, which is in the HDF5 format. To download it, we refer to the data-dynalearn repository.
The building blocks of this modules include classes for dynamical models (Dynamics
), neural network models (Model
), graph / network models (Network
) and dataset managers (Dataset
).
The Dynamics
class is a virtual class, which can only be used to define subclasses. To define a subclass of Dynamics
, one needs to define the inital_state(self)
method, which returns an initial state, the predict(self, x)
method, which computes the local transition probabilities of each node into a np.array
given the current state x, the loglikelihood(self, x)
method, which computes the log-probability of generating the time series x, the sample(self, x)
method, which samples a next state given x using the transition probabilities, and the is_dead(self, x)
method, which determines if the state x has reached a fixed point.
To define a Dynamics
subclass object, such as a SIS
object for example, one can procede in this way
config = dynalearn.config.DynamicsConfig.sis()
dynamics = dynalearn.dynamics.SIS(config=config)
dynamics = dynalearn.dynamics.SIS(infection=0.5, recovery=0.5)
To run the dynamics a specific network, simply pass a Network
object to it:
dynamics.network = dynalearn.networks.Network(data=nx.gnp_random_graph(100, 0.1))
x0 = dynamics.initial_state()
x = [x0]
for t in range(10):
x.append(dynamics.sample(x[-1]))
The graph neural network models are also defined as Dynamics
, as an overhead of the Model
class.
To construct a Dynamics
objects, one can either give the parameters of the model in keywords, or pass a DynamicsConfig
object. If some parameters are not defined, an error will be raised.
The Network
class is an overhead of the nx.Graph
class in networkx. It contains a nx.Graph
in the attribute data
, and defines and computes automatically the attributes as needed, such as node / edge attributes, and the edge list. To define a Network
object, use
g = dynalearn.networks.Network(data=nx.Graph())
The NetworkGenerator
class generates network using the method generate(self, seed=None)
and returns Network
object.
The WeightGenerator
class generates edge weights for a network by using the magic method __call__(self, g)
.
The NetworkTransform
class applies a transformation (e.g., removing a fraction p of the edges randomly) to a network using the magic method __call__(self, g)
.
To construct any of these objects, one can either give the parameters of the model in keywords, or pass a NetworkConfig
object. One can also construct easily a NetworkGenerator
object using the dynalearn.networks.getter.get(config)
function, which also takes a NetworkConfig
object.
The Model
class is subclass of the nn.Module
class in pytorch, which is also virtual. To define a subclass of Model
, one needs to define the forward(self, x, net_attr)
method, which takes a state x and a network attributes tuple net_attr containing the node / edge attributes and the edge list, and the loss(self, y_true, y_pred)
method. A Model
subclass works similarly to how keras models work, i.e., where a fit
method is used to train the model. A model can also save / load its weights easily.
We define a general GraphNeuralNetwork
class that inherits from the Model
class, which is also an overhead for the torch_geometric layers and the DynamicsGATConv
layer designed in the paper.
The Dataset
class manages the dynamics and network models in order to generate datasets and split it into training, validation and / or test datasets. It also manages a Sampler
and a Weight
objects for the importance sampling procedure used in the paper. To define a Dataset
subclass, one must pass DatasetConfig
object:
config = dynalearn.config.DiscreteDatasetConfig.plain()
dataset = dynalearn.dataset.DiscreteDataset(config)
To generate some data and split it, one needs to pass in inputs the Experiment
object as follows:
dataset.generate(exp)
val_dataset = dataset.partition(type="random", fraction=0.5)
This generates another Dataset
subclass object with half of the data contained in dataset
selected at random.
We define a set of experimentation tools using these building blocks, for easy written, readable and versatile scripts, based on the Experiment
, Config
and Metrics
classes. The example of experiment is presented in the notebook example.
The role of an Experiment
object is to manage all the other objects, that is, to generate a dataset, to train a model, to compute some postprocessing metrics, etc.. Therefore this object contains all the information needed to perform an experiment including all the parameters of the different models, but also the paths where the data generated within the experiment can be recovered.
To define an Experiment
object, one passes a Config
object (see below) in input to the experiment as follows:
config = dynalearn.config.ExperimentConfig.default("exp-name", "sis", "gnp")
exp = dynalearn.experiments.Experiment(config, verbose=0)
At this point, the exp
object can be used to run a complete experiment, by running the run
method:
exp.run(tasks=None)
The run
method takes in input the set of tasks one wants to perform, represented as a str
, a list of str
or None
(where it runs all the tasks). All the available tasks are contains in the __tasks__
attribute of exp
, which include data generation, training, saving, loading, etc..
A Config
object contains all the information needed to define an Experiment
object. It can also be used to define Dynamics
, Networks
and Dataset
objects, among others.
To define a Config
object, procede in this way:
config = dynalearn.config.Config()
Then, one can add any attributes to the Config
object. For instance, assume that we want to define a Config
object for a random graph, such as G(N, p). Then, we might want the Config
to contain N, the number of nodes, p, the connection probability, and the name of the network model:
network_config = Config()
network_config.name = "G(N, p)"
network_config.N = 100
network_config.p = 0.1
A Config
object can be displayed using print(network_config)
, and can be represented by a dictionary with the attribute network_config.state_dict
. It is also possible to give another Config
object as a parameter of network_config
. In the state_dict
attribute, the Config
hierarchy is encoded using /
. This is an effective way of defining a Config
object for an Experiment
.
It is recommended that the parameters of a Config
object are picklable, as it is convenient to save it using pickle
.
We define different Config
subclasses for each building block class, such as NetworkConfig
, DynamicsConfig
, DatasetConfig
, among others, which include different classmethods.
Within an Experiment
object, we define Metrics
objects that can be computed, for instance, after training a model. The Metrics
class is virtual, and can be used only in the context of defining subclasses of it. A subclass of Metrics
must define the method compute(self, exp)
, which takes an Experiment
object in input and, as the name intends, computes the values of the metrics for this experiment. A Metrics
subclass can also save / load its data to a HDF5 file.