Source code for metatrain.scaler
import logging
from copy import deepcopy
from typing import List, Optional, Sequence, Union
import torch
from torch import nn
from metatrain.utils.architectures import get_default_hypers
from metatrain.utils.data import Dataset
from metatrain.utils.data.dataset import Subset
from metatrain.utils.io import load_model
from .documentation import FixedScalerWeights
from .model import Scaler
from .trainer import Trainer
__model__ = Scaler
__trainer__ = Trainer
__authors__ = [
("Paolo Pegolo <paolo.pegolo@epfl.ch>", "@ppegolo"),
("Joseph W. Abbott <joseph.william.abbott@gmail.com>", "@jwa7"),
]
__maintainers__ = [
("Pol Febrer <pol.febrer@epfl.ch>", "@pfebrer"),
]
[docs]
def train_or_load_scaler(
scaler: Scaler,
train_datasets: List[Union[Dataset, Subset]],
additive_models: List[nn.Module],
batch_size: int,
is_distributed: bool,
fixed_weights: Optional[FixedScalerWeights | str] = None,
per_structure_targets: Sequence[str] = (),
trainer_hypers: Optional[dict] = None,
checkpoint_dir: str = "",
) -> None:
"""
Train the scaler from data or load pre-trained weights.
This is the single source of truth for how to set up a scaler
for use for preprocessing by any architecture.
:param scaler: The scaler to train or load into
:param train_datasets: Training datasets
:param additive_models: Additive models to
subtract before fitting
:param batch_size: Batch size for data loading
:param is_distributed: Whether training is distributed
:param fixed_weights: Fixed weights dict, or path to a checkpoint.
If it is a dictionary, the keys of the dict are the target names, and the
values are either a single float value to be applied to all atomic types,
or a dict mapping atomic type (int) to weight (float). If not provided, all
scales will be computed based on the accumulated quantities. Fixed weights
are not currently supported for atom-pair targets: their scales are always
left at the identity value ``1.0``.
:param per_structure_targets: Target names that should be treated as
per-structure quantities and therefore not divided by the number of atoms.
:param trainer_hypers: Additional hyperparameters for the trainer.
:param checkpoint_dir: Directory to save the scaler checkpoint
"""
if isinstance(fixed_weights, str):
logging.info(f"Loading scaler from {fixed_weights}")
loaded = load_model(fixed_weights)
if not isinstance(loaded, Scaler):
raise ValueError(
f"The model loaded from {fixed_weights} is a "
f"{type(loaded).__name__}, not a Scaler."
)
if loaded.atomic_types != scaler.atomic_types:
raise ValueError(
"Scaler checkpoint atomic types "
f"({loaded.atomic_types}) do not match the current model's "
f"atomic types ({scaler.atomic_types})."
)
loaded_targets = loaded.dataset_info.targets
current_targets = scaler.dataset_info.targets
if set(loaded_targets) != set(current_targets):
raise ValueError(
"Scaler checkpoint targets "
f"({sorted(loaded_targets)}) do not match the current model's "
f"targets ({sorted(current_targets)})."
)
for name, target_info in current_targets.items():
loaded_info = loaded_targets[name]
if (loaded_info.quantity, loaded_info.unit) != (
target_info.quantity,
target_info.unit,
):
raise ValueError(
f"Target '{name}' from the scaler checkpoint has "
f"quantity '{loaded_info.quantity}' and unit "
f"'{loaded_info.unit}', while the current model expects "
f"quantity '{target_info.quantity}' and unit "
f"'{target_info.unit}'."
)
scaler.load_state_dict(loaded.state_dict())
scaler.sync_tensor_maps()
loaded.check_correct_additive_models(additive_models)
scaler.training_additive_models = loaded.training_additive_models
else:
hypers = deepcopy(get_default_hypers("scaler")["training"])
if fixed_weights is None:
fixed_weights = {}
hypers.update(
{
"fixed_weights": fixed_weights,
"batch_size": batch_size,
"distributed": is_distributed,
"additive_models": additive_models,
"per_structure_targets": list(per_structure_targets),
**(trainer_hypers or {}),
}
)
trainer = Trainer(hypers)
logging.info("Calculating scaler weights")
trainer.train(
model=scaler,
dtype=torch.float64,
devices=[scaler.dummy_buffer.device],
train_datasets=train_datasets,
val_datasets=train_datasets,
checkpoint_dir=checkpoint_dir,
)