Optimizer is an interface that enables the use of declearn 's optimizers for Federated Learning inside Fed-BioMed
Classes
BaseOptimizer
BaseOptimizer(model, optimizer)
Bases: Generic[OT]
Abstract base class for Optimizer and Model wrappers.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
model | Model | model to train, interfaced via a framework-specific Model. | required |
optimizer | OT | optimizer that will be used for optimizing the model. | required |
Raises:
Type | Description |
---|---|
FedbiomedOptimizerError | Raised if model is not an instance of |
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def __init__(self, model: Model, optimizer: OT):
"""Constuctor of the optimizer wrapper that sets a reference to model and optimizer.
Args:
model: model to train, interfaced via a framework-specific Model.
optimizer: optimizer that will be used for optimizing the model.
Raises:
FedbiomedOptimizerError:
Raised if model is not an instance of `_model_cls` (which may
be a subset of the generic Model type).
"""
if not isinstance(model, self._model_cls):
raise FedbiomedOptimizerError(
f"{ErrorNumbers.FB626.value}, in `model` argument, expected an instance "
f"of {self._model_cls} but got an object of type {type(model)}."
)
self._model: Model = model
self.optimizer: OT = optimizer
Attributes
optimizer instance-attribute
optimizer = optimizer
Functions
count_nb_auxvar
count_nb_auxvar()
Counts number of auxiliary variables needed for the given optimizer
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def count_nb_auxvar(self) -> int:
"""Counts number of auxiliary variables needed for the given optimizer"""
return 0
init_training
init_training()
Sets up training and misceallenous parameters so the model is ready for training
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def init_training(self):
"""Sets up training and misceallenous parameters so the model is ready for training
"""
self._model.init_training()
load_state
load_state(optim_state, load_from_state=False)
Reconfigures optimizer from a given state.
This is the default method for optimizers that don't support state. Does nothing.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
optim_state | Dict | not used | required |
load_from_state | optional | not used | False |
Returns:
Type | Description |
---|---|
Union[BaseOptimizer, None] | None |
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def load_state(self, optim_state: Dict, load_from_state: bool = False) -> Union['BaseOptimizer', None]:
"""Reconfigures optimizer from a given state.
This is the default method for optimizers that don't support state. Does nothing.
Args:
optim_state: not used
load_from_state (optional): not used
Returns:
None
"""
logger.warning("load_state method of optimizer not implemented, cannot load optimizer status")
return None
save_state
save_state()
Gets optimizer state.
This is the default method for optimizers that don't support state. Does nothing.
Returns:
Type | Description |
---|---|
Union[Dict, None] | None |
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def save_state(self) -> Union[Dict, None]:
"""Gets optimizer state.
This is the default method for optimizers that don't support state. Does nothing.
Returns:
None
"""
logger.warning("save_state method of optimizer not implemented, cannot save optimizer status")
return None
send_to_device
send_to_device(device, idx=None)
GPU support
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def send_to_device(self, device: str, idx: Optional[int] = None):
"""GPU support"""
step abstractmethod
step()
Performs an optimisation step and updates model weights.
Source code in fedbiomed/common/optimizers/generic_optimizers.py
@abstractmethod
def step(self):
"""Performs an optimisation step and updates model weights.
"""
DeclearnOptimizer
DeclearnOptimizer(model, optimizer)
Bases: BaseOptimizer
Base Optimizer subclass to use a declearn-backed Optimizer.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
model | Model | Model that wraps the actual model | required |
optimizer | Union[Optimizer, Optimizer] | declearn optimizer, or fedbiomed optimizer (that wraps declearn optimizer) | required |
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def __init__(self, model: Model, optimizer: Union[FedOptimizer, declearn.optimizer.Optimizer]):
"""Constructor of Optimizer wrapper for declearn's optimizers
Args:
model: Model that wraps the actual model
optimizer: declearn optimizer,
or fedbiomed optimizer (that wraps declearn optimizer)
"""
logger.debug("Using declearn optimizer")
if isinstance(optimizer, declearn.optimizer.Optimizer):
# convert declearn optimizer into a fedbiomed optimizer wrapper
optimizer = FedOptimizer.from_declearn_optimizer(optimizer)
elif not isinstance(optimizer, FedOptimizer):
raise FedbiomedOptimizerError(
f"{ErrorNumbers.FB626.value}: expected a declearn optimizer,"
f" but got an object with type {type(optimizer)}."
)
super().__init__(model, optimizer)
Attributes
optimizer class-attribute
instance-attribute
optimizer = None
Functions
count_nb_auxvar
count_nb_auxvar()
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def count_nb_auxvar(self) -> int:
return len(self.optimizer.get_aux_names())
get_aux
get_aux()
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def get_aux(self) -> Optional[Dict[str, AuxVar]]:
aux = self.optimizer.get_aux()
return aux
init_training
init_training()
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def init_training(self):
super().init_training()
self.optimizer.init_round()
load_state
load_state(optim_state, load_from_state=False)
Reconfigures optimizer from a given state (contained in optim_state
argument). Usage:
>>> import torch.nn as nn
>>> from fedbiomed.common.optimizers import Optimizer
>>> from fedbiomed.common.models import TorchModel
>>> model = TorchModel(nn.Linear(4, 2))
>>> optimizer = Optimizer(lr=.1)
>>> optim = DeclearnOptimizer(model, optimizer)
>>> optim.load_state(state) # provided state contains the state one wants to load the optimizer with
load_from_state
argument is True, it completes the current optimizer state with optim_state
argument >>> import torch.nn as nn
>>> from fedbiomed.common.optimizers import Optimizer
>>> from fedbiomed.common.optimizers.declearn import MomentumModule, AdamModule
>>> from fedbiomed.common.models import TorchModel
>>> model = TorchModel(nn.Linear(4, 2))
>>> optimizer = Optimizer(lr=.1, modules=[MomentumModule(), AdamModule()])
>>> optim_1 = DeclearnOptimizer(model, optimizer)
>>> optimizer = Optimizer(lr=.1, modules=[AdamModule(), MomentumModule()])
>>> optim_2 = DeclearnOptimizer(model, optimizer)
>>> optim_2.load_state(optim_1.save_state())
>>> optim_2.save_state()['states']
{'modules': [('momentum', {'velocity': 0.0}),
('adam',
{'steps': 0,
'vmax': None,
'momentum': {'state': 0.0},
'velocity': {'state': 0.0}})]}
modules=[AdamModule(), AdagradModule(), MomemtumModule()]
And the Optimizer contained in the TrainingPlan has the following modules: modules=[AdamModule(), MomemtumModule()]
Then only AdamModule
module will be reloaded, MomentumModule
will be set with default argument (they don't share the same index in the modules list). Parameters:
Name | Type | Description | Default |
---|---|---|---|
optim_state | Dict[str, Any] | state of the Optimizer to be loaded. It will change the current state of the optimizer with the one loaded | required |
load_from_state | optional | strategy for loading states: whether to load from saved states (True) or from breakpoint (False). If set to True, loading is done partially in the sense that if some of the OptimModules is different in the optim_state and the original state of the optimizer, it loads only the OptiModule(s) from the latest state that both state has in common. Defaults to False. | False |
Raises:
Type | Description |
---|---|
FedbiomedOptimizerError | raised if state is not of dict type. |
Returns:
Type | Description |
---|---|
DeclearnOptimizer | Optimizer wrapper reloaded from |
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def load_state(self, optim_state: Dict[str, Any], load_from_state: bool = False) -> 'DeclearnOptimizer':
"""Reconfigures optimizer from a given state (contained in `optim_state` argument).
Usage:
```python
>>> import torch.nn as nn
>>> from fedbiomed.common.optimizers import Optimizer
>>> from fedbiomed.common.models import TorchModel
>>> model = TorchModel(nn.Linear(4, 2))
>>> optimizer = Optimizer(lr=.1)
>>> optim = DeclearnOptimizer(model, optimizer)
>>> optim.load_state(state) # provided state contains the state one wants to load the optimizer with
```
If `load_from_state` argument is True, it completes the current optimizer state with `optim_state` argument
```python
>>> import torch.nn as nn
>>> from fedbiomed.common.optimizers import Optimizer
>>> from fedbiomed.common.optimizers.declearn import MomentumModule, AdamModule
>>> from fedbiomed.common.models import TorchModel
>>> model = TorchModel(nn.Linear(4, 2))
>>> optimizer = Optimizer(lr=.1, modules=[MomentumModule(), AdamModule()])
>>> optim_1 = DeclearnOptimizer(model, optimizer)
>>> optimizer = Optimizer(lr=.1, modules=[AdamModule(), MomentumModule()])
>>> optim_2 = DeclearnOptimizer(model, optimizer)
>>> optim_2.load_state(optim_1.save_state())
>>> optim_2.save_state()['states']
{'modules': [('momentum', {'velocity': 0.0}),
('adam',
{'steps': 0,
'vmax': None,
'momentum': {'state': 0.0},
'velocity': {'state': 0.0}})]}
```
Modules of DeclearnOptimizer will be reloaded provided that Module is the same and occupying the same index.
Eg if the state contains following modules:
```modules=[AdamModule(), AdagradModule(), MomemtumModule()]```
And the Optimizer contained in the TrainingPlan has the following modules:
```modules=[AdamModule(), MomemtumModule()]```
Then only `AdamModule` module will be reloaded, `MomentumModule` will be set with default argument (they don't
share the same index in the modules list).
Args:
optim_state: state of the Optimizer to be loaded. It will change the current state of the optimizer
with the one loaded
load_from_state (optional): strategy for loading states: whether to load from saved states (True) or
from breakpoint (False).
If set to True, loading is done partially in the sense that if some of the OptimModules is different in
the optim_state and the original state of the optimizer, it loads only the OptiModule(s) from the
latest state that both state has in common. Defaults to False.
Raises:
FedbiomedOptimizerError: raised if state is not of dict type.
Returns:
Optimizer wrapper reloaded from `optim_state` argument.
"""
# state: breakpoint content for optimizer
if not isinstance(optim_state, Dict):
raise FedbiomedOptimizerError(f"{ErrorNumbers.FB626.value}, incorrect type of argument `optim_state`: "
f"expecting a dict, but got {type(optim_state)}")
if load_from_state:
# first get Optimizer detailed in the TrainingPlan.
init_optim_state = self.optimizer.get_state() # we have to get states since it is the only way we can
# gather modules (other methods of `Optimizer are private`)
optim_state_copy = copy.deepcopy(optim_state)
optim_state.update(init_optim_state) # optim_state will be updated with current optimizer state
# check if opimizer state has changed from last optimizer to the current one
# if it has changed, find common modules and update common states
for component in ( 'modules', 'regularizers',):
components_to_keep: List[Tuple[str, int]] = [] # we store here common Module between current Optimizer
# and the ones in the `optim_state` tuple (common Module name, index in List)
if not init_optim_state['states'].get(component) or not optim_state_copy['states'].get(component):
continue
self._collect_common_optimodules(
init_optim_state,
optim_state_copy,
component,
components_to_keep
)
for mod in components_to_keep:
for mod_state in optim_state_copy['states'][component]:
if mod[0] == mod_state[0]:
# if we do find same module in the current optimizer than the previous one,
# we load the previous optimizer module state into the current one
optim_state['states'][component][mod[1]] = mod_state
logger.info("Loading optimizer state from saved state")
reloaded_optim = FedOptimizer.load_state(optim_state)
self.optimizer = reloaded_optim
return self
optimizer_processing
optimizer_processing()
Provides a context manager able to do some actions before and after setting up an Optimizer, mainly disabling scikit-learn internal optimizer.
Also, checks if model_args
dictionary contains training parameters that won't be used or have any effect on the training, because of disabling the scikit-learn optimizer ( such as initial learning rate, learnig rate scheduler, ...). If disabling the internal optimizer leads to such changes, displays a warning.
Returns:
Name | Type | Description |
---|---|---|
SklearnOptimizerProcessing | SklearnOptimizerProcessing | context manager providing extra logic |
Usage:
>>> dlo = DeclearnSklearnOptimizer(model, optimizer)
>>> with dlo.optimizer_processing():
model.train(inputs,targets)
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def optimizer_processing(self) -> SklearnOptimizerProcessing:
"""Provides a context manager able to do some actions before and after setting up an Optimizer, mainly
disabling scikit-learn internal optimizer.
Also, checks if `model_args` dictionary contains training parameters that
won't be used or have any effect on the training, because of disabling the scikit-learn optimizer (
such as initial learning rate, learnig rate scheduler, ...). If disabling the internal optimizer leads
to such changes, displays a warning.
Returns:
SklearnOptimizerProcessing: context manager providing extra logic
Usage:
```python
>>> dlo = DeclearnSklearnOptimizer(model, optimizer)
>>> with dlo.optimizer_processing():
model.train(inputs,targets)
```
"""
if isinstance(self._model, SkLearnModel):
return SklearnOptimizerProcessing(self._model, disable_internal_optimizer=True)
else:
raise FedbiomedOptimizerError(f"{ErrorNumbers.FB626.value}: Method optimizer_processing should be used "
f"only with SkLearnModel, but model is {self._model}")
save_state
save_state()
Gets optimizer state.
Returns:
Type | Description |
---|---|
Dict | optimizer state |
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def save_state(self) -> Dict:
"""Gets optimizer state.
Returns:
optimizer state
"""
optim_state = self.optimizer.get_state()
return optim_state
send_to_device
send_to_device(device, idx=None)
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def send_to_device(self, device: str, idx: int | None = None):
self.optimizer.send_to_device(device, idx)
set_aux
set_aux(aux)
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def set_aux(self, aux: Dict[str, AuxVar]):
# FIXME: for imported tensors in PyTorch sent as auxiliary variables,
# we should push it on the appropriate device (ie cpu/gpu)
# TODO-PAUL: call the proper declearn routines
self.optimizer.set_aux(aux)
step
step()
Performs one optimization step
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def step(self):
"""Performs one optimization step"""
# NOTA: for sklearn, gradients retrieved are unscaled because we are using learning rate equal to 1.
# Therefore, it is necessary to disable the sklearn internal optimizer beforehand
# otherwise, computation will be incorrect
grad = declearn.model.api.Vector.build(self._model.get_gradients())
weights = declearn.model.api.Vector.build(self._model.get_weights(
only_trainable=False,
exclude_buffers=True
))
updates = self.optimizer.step(grad, weights)
self._model.apply_updates(updates.coefs)
zero_grad
zero_grad()
Zeroes gradients of the Pytorch model. Basically calls the zero_grad
method of the model.
Raises:
Type | Description |
---|---|
FedbiomedOptimizerError | triggered if model has no method called |
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def zero_grad(self):
"""Zeroes gradients of the Pytorch model. Basically calls the `zero_grad`
method of the model.
Raises:
FedbiomedOptimizerError: triggered if model has no method called `zero_grad`
"""
# warning: specific for pytorch
if not isinstance(self._model, TorchModel):
raise FedbiomedOptimizerError(f"{ErrorNumbers.FB626.value}. This method can only be used for TorchModel, "
f"but got {self._model}")
self._model.model.zero_grad()
EncryptedAuxVar
EncryptedAuxVar(encrypted, enc_specs, cleartext, clear_cls)
Container for encrypted optimizer auxiliary variables.
This ad hoc data structure is designed to enable performing secure aggregation over auxiliary variables of declearn-backed optimizers.
It is designed to be used in four steps:
- Encrypt the outputs of a declearn optimizer's
collect_aux_var
call, usingflatten_auxvar_for_secagg
and aSecaggCrypter
, then wrap the results into anEncryptedAuxVar
- Convert the
EncryptedAuxVar
to and from a serializable dict, enabling to transmit it across network communications (from nodes to researcher). - Aggregate node-wise encrypted values by summing nodes'
EncryptedAuxVar
instances (or calling directly theiraggregate
method). - Decrypt the resulting instance's
encrypted
values with aSecaggCrypter
and useunflatten_auxvar_after_secagg
on the decrypted values and the rest of the instance's attributes to recover auxiliary variables that can be passed to the researcher's optimizer'sprocess_aux_var
method.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
encrypted | List[List[int]] | List of node-wise flattened, encrypted values. | required |
enc_specs | List[ValueSpec] | List of module-wise specifications describing the flattened values' initial names, types and shapes. | required |
cleartext | List[Optional[Dict[str, Any]]] | List of module-wise optional cleartext values, that need sharing and aggregation but not encryption. | required |
clear_cls | List[Tuple[str, Type[AuxVar]]] | List of module-wise tuples storing module name and source | required |
Source code in fedbiomed/common/optimizers/_secagg.py
def __init__(
self,
encrypted: List[List[int]],
enc_specs: List[ValueSpec],
cleartext: List[Optional[Dict[str, Any]]],
clear_cls: List[Tuple[str, Type[AuxVar]]],
) -> None:
"""Instantiate a container from already encrypted information.
Args:
encrypted: List of node-wise flattened, encrypted values.
enc_specs: List of module-wise specifications describing the
flattened values' initial names, types and shapes.
cleartext: List of module-wise optional cleartext values, that
need sharing and aggregation but not encryption.
clear_cls: List of module-wise tuples storing module name and
source `AuxVar` subtype.
"""
self.encrypted = encrypted
self.enc_specs = enc_specs
self.cleartext = cleartext
self.clear_cls = clear_cls
Attributes
clear_cls instance-attribute
clear_cls = clear_cls
cleartext instance-attribute
cleartext = cleartext
enc_specs instance-attribute
enc_specs = enc_specs
encrypted instance-attribute
encrypted = encrypted
Functions
concatenate
concatenate(other)
Concatenates a pair of EncryptedAuxVar into a single instance.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
other | Self |
| required |
Returns:
Type | Description |
---|---|
Self |
|
Self | concatenated encrypted values, and aggregated cleartext ones). |
Raises:
Type | Description |
---|---|
TypeError | if |
ValueError | if |
Source code in fedbiomed/common/optimizers/_secagg.py
def concatenate(
self,
other: Self,
) -> Self:
"""Concatenates a pair of EncryptedAuxVar into a single instance.
Args:
other: `EncryptedAuxVar` instance to be aggregated with this one.
Returns:
`EncryptedAuxVar` instance resulting from the aggregation (with
concatenated encrypted values, and aggregated cleartext ones).
Raises:
TypeError: if `other` is not an `EncryptedAuxVar` instance.
ValueError: if `other` has distinct specs from this one.
"""
# Raise if instances do not match.
if not isinstance(other, self.__class__):
raise TypeError(
f"'{self.__class__.__name__}.aggregate' expects an input "
f"with the same type, but received '{type(other)}'."
)
if self.enc_specs != other.enc_specs:
raise ValueError(
f"Cannot sum '{self.__class__.__name__}' instances with"
" distinct specs for encrypted values."
)
if self.clear_cls != other.clear_cls:
raise ValueError(
f"Cannot sum '{self.__class__.__name__}' instances with"
" distinct specs for base AuxVar classes."
)
# Concatenate lists of encrypted values for future sum-decryption.
encrypted = self.encrypted + other.encrypted
# Perform aggregation of cleartext values, using type-specific rules.
cleartext = [
self._aggregate_cleartext(
aux_cls, self.cleartext[i], other.cleartext[i]
)
for i, (_, aux_cls) in enumerate(self.clear_cls)
]
# Wrap up results in an EncryptedAuxVar instance and return it.
return self.__class__(
encrypted=encrypted,
enc_specs=self.enc_specs,
cleartext=cleartext,
clear_cls=self.clear_cls,
)
concatenate_from_dict classmethod
concatenate_from_dict(data)
Source code in fedbiomed/common/optimizers/_secagg.py
@classmethod
def concatenate_from_dict(cls, data: Dict[str, Self]) -> Self:
auxvar = list(data.values())
# this converts List[List[List[int]]] -> List[List[int]]
obj = sum(auxvar[1:], start=auxvar[0])
return cls(obj.encrypted, obj.enc_specs, obj.cleartext, obj.clear_cls)
from_dict classmethod
from_dict(data)
Instantiate from a dict representation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
data | Dict[str, Any] | Dict representation, as emitted by this class's | required |
Raises:
Type | Description |
---|---|
TypeError | If any required key is missing or improper. |
Source code in fedbiomed/common/optimizers/_secagg.py
@classmethod
def from_dict(
cls,
data: Dict[str, Any],
) -> Self:
"""Instantiate from a dict representation.
Args:
data: Dict representation, as emitted by this class's `to_dict`.
Raises:
TypeError: If any required key is missing or improper.
"""
try:
# Recover wrapped AuxVar classes from the type registry.
clear_cls = [
(name, access_registered(*info))
for name, info in data["clear_cls"]
]
# Ensure tuples are preserved (as serialization converts to list).
enc_specs = [
[tuple(value_specs) for value_specs in module_specs]
for module_specs in data["enc_specs"]
]
# Try instantiating from the input data.
return cls(
encrypted=data["encrypted"],
enc_specs=enc_specs, # type: ignore
cleartext=data["cleartext"],
clear_cls=clear_cls,
)
except Exception as exc:
raise TypeError(
f"Cannot instantiate '{cls.__name__}' from input dict: "
f"raised '{repr(exc)}'."
) from exc
get_mapping_encrypted_aux_var
get_mapping_encrypted_aux_var()
Source code in fedbiomed/common/optimizers/_secagg.py
def get_mapping_encrypted_aux_var(self) -> Dict[str, List[int]]:
nodes_id = list(self.cleartext[0]['clients'])
return {n: p for n,p in zip(nodes_id, self.encrypted)}
get_num_expected_params
get_num_expected_params()
Return the number of flat values that should be decrypted.
Source code in fedbiomed/common/optimizers/_secagg.py
def get_num_expected_params(
self,
) -> int:
"""Return the number of flat values that should be decrypted."""
return sum(
size
for module_specs in self.enc_specs
for _, size, _ in module_specs
)
to_dict
to_dict()
Return a dict representation of this instance.
Returns:
Type | Description |
---|---|
Dict[str, Any] | Dict representation of this instance. |
Source code in fedbiomed/common/optimizers/_secagg.py
def to_dict(
self,
) -> Dict[str, Any]:
"""Return a dict representation of this instance.
Returns:
Dict representation of this instance.
"""
aux_cls_info = [
(name, access_registration_info(aux_cls))
for name, aux_cls in self.clear_cls
]
return {
"encrypted": self.encrypted,
"enc_specs": self.enc_specs,
"cleartext": self.cleartext,
"clear_cls": aux_cls_info,
}
NativeSkLearnOptimizer
NativeSkLearnOptimizer(model, optimizer=None)
Bases: BaseOptimizer
Optimizer wrapper for scikit-learn native models.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
model | SkLearnModel | SkLearnModel model that builds a scikit-learn model. | required |
optimizer | Optional[None] | unused. Defaults to None. | None |
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def __init__(self, model: SkLearnModel, optimizer: Optional[None] = None):
"""Constructor of the Optimizer wrapper for scikit-learn native models.
Args:
model: SkLearnModel model that builds a scikit-learn model.
optimizer: unused. Defaults to None.
"""
if optimizer is not None:
logger.info(f"Passed Optimizer {optimizer} won't be used (using only native scikit learn optimization)")
super().__init__(model, None)
logger.debug("Using native Sklearn Optimizer")
Functions
optimizer_processing
optimizer_processing()
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def optimizer_processing(self) -> SklearnOptimizerProcessing:
return SklearnOptimizerProcessing(self._model, disable_internal_optimizer=False)
step
step()
Performs an optimization step and updates model weights.
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def step(self):
"""Performs an optimization step and updates model weights."""
gradients = self._model.get_gradients()
updates = {k: -v for k, v in gradients.items()}
self._model.apply_updates(updates)
NativeTorchOptimizer
NativeTorchOptimizer(model, optimizer)
Bases: BaseOptimizer
Optimizer wrapper for pytorch native optimizers and models.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
model | TorchModel | fedbiomed model wrapper that warps the pytorch model | required |
optimizer | Optimizer | pytorch native optimizers (inhereting from | required |
Raises:
Type | Description |
---|---|
FedbiomedOptimizerError | raised if optimizer is not a pytorch native optimizer ie a |
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def __init__(self, model: TorchModel, optimizer: torch.optim.Optimizer):
"""Constructor of the optimizer wrapper
Args:
model: fedbiomed model wrapper that warps the pytorch model
optimizer: pytorch native optimizers (inhereting from `torch.optim.Optimizer`)
Raises:
FedbiomedOptimizerError: raised if optimizer is not a pytorch native optimizer ie a `torch.optim.Optimizer`
object.
"""
if not isinstance(optimizer, torch.optim.Optimizer):
raise FedbiomedOptimizerError(f"{ErrorNumbers.FB626.value} Expected a native pytorch `torch.optim` "
f"optimizer, but got {type(optimizer)}")
super().__init__(model, optimizer)
logger.debug("using native torch optimizer")
Functions
get_learning_rate
get_learning_rate()
Gets learning rates from param groups in Pytorch optimizer.
For each optimizer param group, it iterates over all parameters in that parameter group and searches for the " corresponding parameter of the model by iterating over all model parameters. If it finds a correspondence, it saves the learning rate value. This function assumes that the parameters in the optimizer and the model have the same reference.
Warning
This function gathers the base learning rate applied to the model weights, including alterations due to any LR scheduler. However, it does not catch any adaptive component, e.g. due to RMSProp, Adam or such.
Returns:
Type | Description |
---|---|
Dict[str, float] | List[float]: list of single learning rate or multiple learning rates (as many as the number of the layers contained in the model) |
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def get_learning_rate(self) -> Dict[str, float]:
"""Gets learning rates from param groups in Pytorch optimizer.
For each optimizer param group, it iterates over all parameters in that parameter group and searches for the "
corresponding parameter of the model by iterating over all model parameters. If it finds a correspondence,
it saves the learning rate value. This function assumes that the parameters in the optimizer and the model
have the same reference.
!!! warning
This function gathers the base learning rate applied to the model weights,
including alterations due to any LR scheduler. However, it does not catch
any adaptive component, e.g. due to RMSProp, Adam or such.
Returns:
List[float]: list of single learning rate or multiple learning rates
(as many as the number of the layers contained in the model)
"""
logger.warning(
"`get_learning_rate` is deprecated and will be removed in future Fed-BioMed releases",
broadcast=True)
mapping_lr_layer_name: Dict[str, float] = {}
for param_group in self.optimizer.param_groups:
for layer_params in param_group['params']:
for layer_name, tensor in self._model.model.named_parameters():
if layer_params is tensor:
mapping_lr_layer_name[layer_name] = param_group['lr']
return mapping_lr_layer_name
step
step()
Performs an optimization step and updates model weights
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def step(self):
"""Performs an optimization step and updates model weights
"""
self.optimizer.step()
zero_grad
zero_grad()
Zeroes gradients of the Pytorch model. Basically calls the zero_grad
method of the optimizer.
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def zero_grad(self):
"""Zeroes gradients of the Pytorch model. Basically calls the `zero_grad`
method of the optimizer.
"""
self.optimizer.zero_grad()
Optimizer
Optimizer(lr, decay=0.0, modules=None, regularizers=None)
Optimizer class with a declearn-backed modular SGD-core algorithm.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
lr | float | Base learning rate (i.e. step size) applied to gradients-based updates upon applying them to a model's weights. | required |
decay | float | Optional weight decay parameter, used to parameterize a decoupled weight decay regularization term (see [1]) added to the updates right before the learning rate is applied and model weights are effectively updated. | 0.0 |
modules | Optional[Sequence[Union[OptiModule, str, Tuple[str, Dict[str, Any]]]]] | Optional list of plug-in modules implementing gradients' alteration into model weights' udpates. Modules will be applied to gradients following this list's ordering. See | None |
regularizers | Optional[Sequence[Union[Regularizer, str, Tuple[str, Dict[str, Any]]]]] | Optional list of plug-in loss regularizers. Regularizers will be applied to gradients following this list's order, prior to any other alteration (see | None |
Note
Regularizer
and OptiModule
to be used by this optimizer, specified using the regularizers
and modules
parameters, may be passed as ready-for-use instances, or be instantiated from specs, consisting either of a single string (the name
attribute of the class to build) or a tuple grouping this name and a config dict (to specify some hyper-parameters).
References
[1] Loshchilov & Hutter, 2019. Decoupled Weight Decay Regularization. https://arxiv.org/abs/1711.05101
Source code in fedbiomed/common/optimizers/optimizer.py
def __init__(
self,
lr: float,
decay: float = 0.0,
modules: Optional[
Sequence[Union[OptiModule, str, Tuple[str, Dict[str, Any]]]]
] = None,
regularizers: Optional[
Sequence[Union[Regularizer, str, Tuple[str, Dict[str, Any]]]]
] = None,
) -> None:
"""Instantiate the declearn-issued gradient-descent optimizer.
Args:
lr: Base learning rate (i.e. step size) applied to gradients-based
updates upon applying them to a model's weights.
decay: Optional weight decay parameter, used to parameterize a
decoupled weight decay regularization term (see [1]) added to
the updates right before the learning rate is applied and model
weights are effectively updated.
modules: Optional list of plug-in modules implementing gradients'
alteration into model weights' udpates. Modules will be applied
to gradients following this list's ordering.
See `declearn.optimizer.modules.OptiModule` for details.
See Notes section below for details on the "specs" format.
regularizers: Optional list of plug-in loss regularizers.
Regularizers will be applied to gradients following this list's
order, prior to any other alteration (see `modules` above).
See `declearn.optimizer.regularizers.Regularizer` for details.
See Notes section below for details on the "specs" format.
!!! info "Note"
`Regularizer` and `OptiModule` to be used by this optimizer,
specified using the `regularizers` and `modules` parameters,
may be passed as ready-for-use instances, or be instantiated
from specs, consisting either of a single string (the `name`
attribute of the class to build) or a tuple grouping this
name and a config dict (to specify some hyper-parameters).
!!! info "References"
[1] Loshchilov & Hutter, 2019.
Decoupled Weight Decay Regularization.
https://arxiv.org/abs/1711.05101
"""
try:
self._optimizer = DeclearnOptimizer(
lrate=lr,
w_decay=decay,
modules=modules,
regularizers=regularizers,
)
except (KeyError, TypeError) as exc:
raise FedbiomedOptimizerError(
f"{ErrorNumbers.FB621.value}: declearn Optimizer instantiation"
f" raised the following exception: {repr(exc)}"
) from exc
Functions
from_declearn_optimizer classmethod
from_declearn_optimizer(declearn_optimizer)
Wrap a declearn Optimizer into a fed-biomed one.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
declearn_optimizer | Optimizer | [declearn.optimizer.Optimizer][] instance that needs to be wrapped. | required |
Returns:
Type | Description |
---|---|
Self | Fed-BioMed |
Source code in fedbiomed/common/optimizers/optimizer.py
@classmethod
def from_declearn_optimizer(
cls,
declearn_optimizer: DeclearnOptimizer,
) -> Self:
"""Wrap a declearn Optimizer into a fed-biomed one.
Args:
declearn_optimizer: [declearn.optimizer.Optimizer][] instance that
needs to be wrapped.
Returns:
Fed-BioMed `Optimizer` instance wrapping a copy of the input
declearn optimizer.
"""
config = declearn_optimizer.get_config()
optim = cls(
lr=config["lrate"],
decay=config["w_decay"],
modules=config["modules"],
regularizers=config["regularizers"],
)
optim._optimizer.set_state(declearn_optimizer.get_state())
return optim
get_aux
get_aux()
Return auxiliary variables that need to be shared across network.
Returns:
Type | Description |
---|---|
Dict[str, AuxVar] | Aux-var dict that associates |
Note
"Auxiliary variables" are information that needs to be shared between the nodes and the researcher between training rounds, to synchronize some optimizer plug-ins that work by pair. Their production via this method can have internal side effects; get_aux
should therefore be called sparingly.
Source code in fedbiomed/common/optimizers/optimizer.py
def get_aux(self) -> Dict[str, AuxVar]:
"""Return auxiliary variables that need to be shared across network.
Returns:
Aux-var dict that associates `module.collect_aux_var()` values to
`module.name` keys for each and every module plugged in this
Optimizer that has some auxiliary variables to share.
!!! info "Note"
"Auxiliary variables" are information that needs to be shared
between the nodes and the researcher between training rounds, to
synchronize some optimizer plug-ins that work by pair. Their
production via this method can have internal side effects;
`get_aux` should therefore be called sparingly.
"""
try:
return self._optimizer.collect_aux_var()
except Exception as exc:
raise FedbiomedOptimizerError(
f"{ErrorNumbers.FB621.value}: error in 'get_aux': {exc}"
) from exc
get_aux_names
get_aux_names()
Gathers list of names of modules requiring auxiliary variables
Source code in fedbiomed/common/optimizers/optimizer.py
def get_aux_names(self) -> List[str]:
"""Gathers list of names of modules requiring auxiliary variables"""
aux_names = []
for module in self._optimizer.modules:
if module.aux_name is not None:
aux_names.append(module.aux_name)
return aux_names
get_state
get_state()
Return the configuration and current states of this Optimizer.
This method is to be used for creating breakpoints.
Returns:
Type | Description |
---|---|
Dict[str, Any] | State-and-config dict that may be saved as part of a breakpoint file, and used to re-create this Optimizer using the |
Source code in fedbiomed/common/optimizers/optimizer.py
def get_state(self) -> Dict[str, Any]:
"""Return the configuration and current states of this Optimizer.
This method is to be used for creating breakpoints.
Returns:
State-and-config dict that may be saved as part of a breakpoint
file, and used to re-create this Optimizer using the
`Optimizer.load_state` classmethod constructor.
"""
try:
config = self._optimizer.get_config()
states = self._optimizer.get_state()
return {"config": config, "states": states}
except Exception as exc:
raise FedbiomedOptimizerError(
f"{ErrorNumbers.FB621.value}: error in 'get_state': {exc}"
) from exc
init_round
init_round()
Trigger start-of-training-round behavior of wrapped regularizers.
Source code in fedbiomed/common/optimizers/optimizer.py
def init_round(self) -> None:
"""Trigger start-of-training-round behavior of wrapped regularizers."""
try:
self._optimizer.start_round()
except Exception as exc:
raise FedbiomedOptimizerError(
f"{ErrorNumbers.FB621.value}: error in 'init_round': {exc}"
) from exc
load_state classmethod
load_state(state)
Instantiate an Optimizer from its breakpoint state dict.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
state | Dict[str, Any] | state-and-config dict created using the | required |
Returns:
Type | Description |
---|---|
Self | Optimizer instance re-created from the |
Raises:
Type | Description |
---|---|
FedbiomedOptimizerError | If the input |
Source code in fedbiomed/common/optimizers/optimizer.py
@classmethod
def load_state(cls, state: Dict[str, Any]) -> Self:
"""Instantiate an Optimizer from its breakpoint state dict.
Args:
state: state-and-config dict created using the `get_state` method.
Returns:
Optimizer instance re-created from the `state` dict.
Raises:
FedbiomedOptimizerError: If the input `state` dict has improper keys
or fails to set up a declearn Optimizer and set back its state.
"""
try:
optim = DeclearnOptimizer.from_config(state["config"])
optim.set_state(state["states"])
except KeyError as exc:
raise FedbiomedOptimizerError(
f"{ErrorNumbers.FB621.value}: Missing field in the breakpoints state: {exc}"
) from exc
except Exception as exc:
raise FedbiomedOptimizerError(
f"{ErrorNumbers.FB621.value}: `Optimizer.load_state`: {exc}"
) from exc
return cls(
lr=optim.lrate,
decay=optim.w_decay,
modules=optim.modules,
regularizers=optim.regularizers,
)
send_to_device
send_to_device(device, idx=None)
GPU support
Source code in fedbiomed/common/optimizers/optimizer.py
def send_to_device(self, device: Union[str, bool], idx: Optional[int] = None):
"""GPU support"""
# for now GPU support on Researcher side is disabled
set_device_policy(device, idx)
set_aux
set_aux(aux)
Update plug-in modules based on received shared auxiliary variables.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
aux | Dict[str, AuxVar] | Auxiliary variables received from the counterpart optimizer (on the other side of the node-researcher frontier). On the researcher side, values must have been pre-aggregated based on the ones sent by nodes. | required |
Raises:
Type | Description |
---|---|
FedbiomedOptimizerError | If a key from |
Note
"Auxiliary variables" are information that is shared between the nodes and researcher between training rounds, to synchronize some optimizer plug-ins that work by pair. The inputs to this method are not simply stored by the Optimizer, but are processed into internal side effects; this method should therefore be called sparingly.
Source code in fedbiomed/common/optimizers/optimizer.py
def set_aux(self, aux: Dict[str, AuxVar]) -> None:
"""Update plug-in modules based on received shared auxiliary variables.
Args:
aux: Auxiliary variables received from the counterpart optimizer
(on the other side of the node-researcher frontier). On the
researcher side, values must have been pre-aggregated based
on the ones sent by nodes.
Raises:
FedbiomedOptimizerError: If a key from `aux_var` does not match the
name of any module plugged in this optimizer (i.e. if received
variables cannot be mapped to a destinatory module).
!!! info "Note"
"Auxiliary variables" are information that is shared between the
nodes and researcher between training rounds, to synchronize some
optimizer plug-ins that work by pair. The inputs to this method are
not simply stored by the Optimizer, but are processed into internal
side effects; this method should therefore be called sparingly.
"""
try:
self._optimizer.process_aux_var(aux)
except Exception as exc:
raise FedbiomedOptimizerError(
f"{ErrorNumbers.FB621.value}: `Optimizer.set_aux`: {exc}"
) from exc
step
step(grads, weights)
Run an optimization step to compute and return model weight updates.
Use the pre-assigned weights
and grads
(set using the set_weights
and set_grads
methods) to compute weight updates, using the pipeline defined by this instance.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
grads | Vector | Raw gradients based on which to compute weights updates, wrapped into a declearn Vector structure. | required |
weights | Vector | Current values of the weights with respect to which the gradients were computed, wrapped into a declearn Vector with the same concrete type as | required |
Returns:
Type | Description |
---|---|
Vector | Updates to be applied to the model weights, computed by: - running wrapped gradients and weights through the regularizer plug-ins (that add loss-regularization terms' derivatives); - running resulting gradients through the optimodule plug-ins (that perform any defined gradient-alteration operation); - adding a decoupled weight-decay term, if one is to be used; - scaling the updates by the base learning rate. The results are wrapped into a declearn Vector structure, the concrete type of which is same as input |
Source code in fedbiomed/common/optimizers/optimizer.py
def step(self, grads: Vector, weights: Vector) -> Vector:
"""Run an optimization step to compute and return model weight updates.
Use the pre-assigned `weights` and `grads` (set using the `set_weights`
and `set_grads` methods) to compute weight updates, using the pipeline
defined by this instance.
Args:
grads: Raw gradients based on which to compute weights updates,
wrapped into a declearn Vector structure.
weights: Current values of the weights with respect to which the
gradients were computed, wrapped into a declearn Vector with
the same concrete type as `grads`.
Returns:
Updates to be applied to the model weights, computed by:
- running wrapped gradients and weights through the regularizer
plug-ins (that add loss-regularization terms' derivatives);
- running resulting gradients through the optimodule plug-ins
(that perform any defined gradient-alteration operation);
- adding a decoupled weight-decay term, if one is to be used;
- scaling the updates by the base learning rate.
The results are wrapped into a declearn Vector structure, the
concrete type of which is same as input `grads` and `weights`.
"""
# This code mostly replicates that of
# `declearn.optimizer.Optimizer.compute_updates_from_gradients`.
try:
# Add loss-regularization terms' derivatives to the raw gradients.
for reg in self._optimizer.regularizers:
grads = reg.run(grads, weights)
# Iteratively refine updates by running them through the optimodules.
for mod in self._optimizer.modules:
grads = mod.run(grads)
# Apply the base learning rate.
updates = - self._optimizer.lrate * grads
# Optionally add the decoupled weight decay term.
if self._optimizer.w_decay:
updates -= self._optimizer.w_decay * weights
# Return the model updates.
return updates
except Exception as exc:
raise FedbiomedOptimizerError(
f"{ErrorNumbers.FB621.value}: error in 'step': {exc}"
) from exc
SklearnOptimizerProcessing
SklearnOptimizerProcessing(model, disable_internal_optimizer)
Context manager used for scikit-learn model, that checks if model parameter(s) has(ve) been changed when disabling scikit-learn internal optimizer - ie when calling disable_internal_optimizer
method
Parameters:
Name | Type | Description | Default |
---|---|---|---|
model | SkLearnModel | a SkLearnModel that wraps a scikit-learn model | required |
disable_internal_optimizer | bool | whether to disable scikit-learn model internal optimizer (True) in order to apply declearn one or to keep it (False) | required |
Source code in fedbiomed/common/optimizers/generic_optimizers.py
def __init__(
self,
model: SkLearnModel,
disable_internal_optimizer: bool
) -> None:
"""Constructor of the object. Sets internal variables
Args:
model: a SkLearnModel that wraps a scikit-learn model
disable_internal_optimizer: whether to disable scikit-learn model internal optimizer (True) in order
to apply declearn one or to keep it (False)
"""
self._model = model
self._disable_internal_optimizer = disable_internal_optimizer
Functions
flatten_auxvar_for_secagg
flatten_auxvar_for_secagg(aux_var)
Flatten a node's optimizer auxiliary variables for secure aggregation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
aux_var | Dict[str, AuxVar] | Optimizer auxiliary variables that are meant to be encrypted, formatted as a | required |
Returns:
Name | Type | Description |
---|---|---|
cryptable | List[float] | List of flattened encryptable values. |
enc_specs | List[ValueSpec] | List of module-wise specifications describing the flattened values' initial names, types and shapes. |
cleartext | List[Optional[Dict[str, Any]]] | List of module-wise optional cleartext values, that need sharing and aggregation but not encryption. |
clear_cls | List[Tuple[str, Type[AuxVar]]] | List of module-wise tuples storing module name and source |
Raises:
Type | Description |
---|---|
NotImplementedError | if a module is not compatible with SecAgg. |
Source code in fedbiomed/common/optimizers/_secagg.py
def flatten_auxvar_for_secagg(
aux_var: Dict[str, AuxVar],
) -> Tuple[
List[float],
List[ValueSpec],
List[Optional[Dict[str, Any]]],
List[Tuple[str, Type[AuxVar]]],
]:
"""Flatten a node's optimizer auxiliary variables for secure aggregation.
Args:
aux_var: Optimizer auxiliary variables that are meant to be encrypted,
formatted as a `{module_name: module_aux_var}` dict.
Returns:
cryptable: List of flattened encryptable values.
enc_specs: List of module-wise specifications describing the
flattened values' initial names, types and shapes.
cleartext: List of module-wise optional cleartext values, that
need sharing and aggregation but not encryption.
clear_cls: List of module-wise tuples storing module name and
source `AuxVar` subtype.
Raises:
NotImplementedError: if a module is not compatible with SecAgg.
"""
# Iteratively flatten and gather specs from module-wise AuxVar objects.
flattened = [] # type: List[float]
enc_specs = [] # type: List[ValueSpec]
cleartext = [] # type: List[Optional[Dict[str, Any]]]
clear_cls = [] # type: List[Tuple[str, Type[AuxVar]]]
for module_name, module_auxv in aux_var.items():
flat, spec, clrt = _flatten_aux_var(module_auxv)
flattened.extend(flat)
enc_specs.append(spec)
cleartext.append(clrt)
clear_cls.append((module_name, type(module_auxv)))
# Wrap up the results into an EncryptedAuxVar instance.
return flattened, enc_specs, cleartext, clear_cls
unflatten_auxvar_after_secagg
unflatten_auxvar_after_secagg(decrypted, enc_specs, cleartext, clear_cls)
Unflatten secure-aggregate optimizer auxiliary variables.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
decrypted | List[float] | List of flattened decrypted (encryptable) values. | required |
enc_specs | List[ValueSpec] | List of module-wise specifications describing the flattened values' initial names, types and shapes. | required |
cleartext | List[Optional[Dict[str, Any]]] | List of module-wise optional cleartext values, that need sharing and aggregation but not encryption. | required |
clear_cls | List[Tuple[str, Type[AuxVar]]] | List of module-wise tuples storing module name and source | required |
Returns:
Type | Description |
---|---|
Dict[str, AuxVar] | Unflattened optimizer auxiliary variables, as a dict |
Dict[str, AuxVar] | with format |
Raises:
Type | Description |
---|---|
RuntimeError | if auxiliary variables unflattening fails. |
Source code in fedbiomed/common/optimizers/_secagg.py
def unflatten_auxvar_after_secagg(
decrypted: List[float],
enc_specs: List[ValueSpec],
cleartext: List[Optional[Dict[str, Any]]],
clear_cls: List[Tuple[str, Type[AuxVar]]],
) -> Dict[str, AuxVar]:
"""Unflatten secure-aggregate optimizer auxiliary variables.
Args:
decrypted: List of flattened decrypted (encryptable) values.
enc_specs: List of module-wise specifications describing the
flattened values' initial names, types and shapes.
cleartext: List of module-wise optional cleartext values, that
need sharing and aggregation but not encryption.
clear_cls: List of module-wise tuples storing module name and
source `AuxVar` subtype.
Returns:
Unflattened optimizer auxiliary variables, as a dict
with format `{module_name: module_aux_var}`.
Raises:
RuntimeError: if auxiliary variables unflattening fails.
"""
# Iteratively rebuild AuxVar instances, then return.
aux_var = {} # type: Dict[str, AuxVar]
indx = 0
for i, (name, aux_cls) in enumerate(clear_cls):
size = sum(size for _, size, _ in enc_specs[i])
aux_var[name] = _unflatten_aux_var(
aux_cls=aux_cls,
flattened=decrypted[indx:indx+size],
enc_specs=enc_specs[i],
cleartext=cleartext[i],
)
return aux_var