Training an ML-DFT model
For a first glimpse into MALA, let’s assume you already have input data (i.e., bispectrum descriptors encoding the atomic structure on a real space grid) and output data (the LDOS representing the electronic structure on the real space grid) for a system. For now, we will be using the example data provided in the test data repository.
You will learn how to create your own data sets in the following section.
This guide follows the example file basic/ex01_train_network.py
and
basic/ex02_test_network.py
.
Setting parameters
The central object of MALA workflows is the mala.Parameters()
object.
All options necessary to control a MALA workflow are accessible through
this object. To do this, it has several subobjects for its various tasks,
that we will get familiar with in this tutorial.
MALA provides reasonable choices for a lot of parameters of interest. For a full list, please refer to the API reference - for now, we select a few options to train a simple network with example data, namely
parameters = mala.Parameters() parameters.data.input_rescaling_type = "feature-wise-standard" parameters.data.output_rescaling_type = "minmax" parameters.network.layer_activations = ["ReLU"] parameters.running.max_number_epochs = 100 parameters.running.mini_batch_size = 40 parameters.running.learning_rate = 0.00001 parameters.running.optimizer = "Adam" parameters.verbosity = 1 # level of output; 1 is standard, 0 is low, 2 is debug.
Here, we can see that the Parameters
object contains multiple
sub-objects dealing with the individual aspects of the workflow. In the first
two lines, which data scaling MALA should employ. Scaling data greatly
improves the performance of NN based ML models. Options are
None
: No scaling is applied.standard
: Standardization (Scale to mean 0, standard deviation 1) is applied to the entire array.minmax
: Min-Max scaling (Scale to be in range 0…1) is applied to the entire array.feature-wise-standard
: Standardization (Scale to mean 0, standard deviation 1) is applied to each feature dimension individually.feature-wise-minmax
: Min-Max scaling (Scale to be in range 0…1) is applied to each feature dimension individually.
Here, we specify that MALA should standardize the input (=descriptors) by feature (i.e., each entry of the vector separately on the grid) and normalize the entire LDOS.
The third line tells MALA which activation function to use, and the last lines specify the training routine employed by MALA.
For now, we will assume these values to be correct. Of course, for new data sets, optimal values have to be determined via hyperparameter optimization. Finally, it is useful to also save some information on how the LDOS and bispectrum descriptors were calculated into the parameters object - this helps at inference time, when this info is required. You will learn what these values mean data generation part of this guide, for now we use the values consistent with the example data.
parameters.targets.target_type = "LDOS" parameters.targets.ldos_gridsize = 11 parameters.targets.ldos_gridspacing_ev = 2.5 parameters.targets.ldos_gridoffset_ev = -5 parameters.descriptors.descriptor_type = "Bispectrum" parameters.descriptors.bispectrum_twojmax = 10 parameters.descriptors.bispectrum_cutoff = 4.67637
Adding training data
As with any ML library, MALA is a data-driven framework. So before we can
train a model, we need to add data. The central object to manage data for any
MALA workflow is the DataHandler
class.
MALA manages data “per snapshot”. One snapshot is one atomic configuration,
for which volumetric input and output data has been calculated. Data has to
be added to the DataHandler
object per snapshot, pointing to the
where the volumetric data files are saved on disk. This is done via
data_handler = mala.DataHandler(parameters) data_handler.add_snapshot("Be_snapshot0.in.npy", data_path, "Be_snapshot0.out.npy", data_path, "tr") data_handler.add_snapshot("Be_snapshot1.in.npy", data_path, "Be_snapshot1.out.npy", data_path, "va")
The "tr"
and "va"
flag signal that the respective snapshots are added as
training and validation data, respectively. Training data is data the model
is directly tuned on; validation data is data used to verify the model
performance during the run time and make sure that no overfitting occurs.
After data has been added to the DataHandler
, it has to be actually loaded
and scaled via
data_handler.prepare_data()
The DataHandler
object can now be used for Machine learning.
Building and training a model
MALA uses neural networks (NNs) as a backbone for the ML-DFT models. To
construct those, we have to specify the number of neurons. This is also done
via the Parameters
object. In principle, we can specify the layer sizes
whenever we want, however, it makes sense to do this after the data has been
loaded, because then it is easier to make sure that the dimensions of the
layers agree. To build a NN, we specify
parameters.network.layer_sizes = [data_handler.input_dimension, 100, data_handler.output_dimension] network = mala.Network(parameters)
Now, we can easily train this network with the parameters specified above by doing
trainer = mala.Trainer(parameters, network, data_handler) trainer.train_network()
Afterwards, we want to save this model for future use. MALA saves models
in a *.zip
format. Within each model archive, information like scaling
coefficients, the model weights itself, etc. are stored in one place where MALA
can easily access it. Additionally, it makes sense to provide MALA with a
sample calculation output (from the simulations used to gather the training
data), so that critical parameters like simulation temperature, grid
coarseness, etc., are available at inference time. By
additional_calculation_data = os.path.join(data_path, "Be_snapshot0.out") trainer.save_run("be_model", additional_calculation_data=additional_calculation_data)
This information is set and the resulting model is saved. It is now ready to be used.
Testing a model
Before using a model in production, it is wise to test its performance. To that
end, MALA provides a Tester
class, that allows users to load a model,
give it some data unseen during training, and verify the models performance
on that data.
This verification is done by selecting observables of interest (e.g., the band
energy, total energy or number of electrons) and comparing ML-DFT predictions
with the ground truth. To instantiate a Tester
object, call
parameters, network, data_handler, tester = mala.Tester.load_run("be_model")
There are a few useful options we should set when testing a network.
Firstly, we need to specify which observables to test. Secondly, we have to
decide if we want the resulting accuracy measures per each individual snapshot
("list"
) or as an average across all snapshots ("mae"
).
Finally, it is useful to enable lazy-loading. Lazy-loading is a feature that
incrementally loads data into memory. It is necessary when operating on large
amounts of data; its usage in the training routine is further discussed in
the advanced training section.
For testing a model, it is prudent to enable, since a lot of data may
be involved. The accompanying syntax for these three options is
tester.observables_to_test = ["band_energy", "number_of_electrons"] tester.output_format = "list" parameters.data.use_lazy_loading = True
Afterwards, new data can be added just as shown above, now with the data
function being "te"
for testing data. Once this is done, testing can
be done via
results = tester.test_all_snapshots()
Resulting in a dictionary, which can either be saved into a .csv
file or
directly processed.