From 500bd6f1e700ba0977439ad79de1ec5570db9d4f Mon Sep 17 00:00:00 2001 From: David Emerson <43939939+emersodb@users.noreply.github.com> Date: Thu, 19 Jun 2025 16:20:05 -0400 Subject: [PATCH 1/4] Fixing some magic numbers --- fl4health/model_bases/ensemble_base.py | 7 +++++-- .../masked_layers/masked_normalization_layers.py | 10 +++++++--- fl4health/model_bases/pca.py | 5 ++++- fl4health/preprocessing/autoencoders/loss.py | 5 ++++- fl4health/strategies/fedpca.py | 7 +++++-- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/fl4health/model_bases/ensemble_base.py b/fl4health/model_bases/ensemble_base.py index c0fba2129..a0eb7c2ad 100644 --- a/fl4health/model_bases/ensemble_base.py +++ b/fl4health/model_bases/ensemble_base.py @@ -9,6 +9,9 @@ class EnsembleAggregationMode(Enum): AVERAGE = "AVERAGE" +EXPECTED_PRED_N_DIMS = 2 + + class EnsembleModel(nn.Module): def __init__( self, @@ -70,7 +73,7 @@ def ensemble_vote(self, preds_list: list[torch.Tensor]) -> torch.Tensor: preds_dimension = list(preds_list[0].shape) # If larger than two dimensions, we map to 2D to perform voting operation (and reshape later) - if len(preds_dimension) > 2: + if len(preds_dimension) > EXPECTED_PRED_N_DIMS: preds_list = [preds.reshape(-1, preds_dimension[-1]) for preds in preds_list] # For each model prediction, compute the argmax of the model over the classes and stack column-wise into matrix @@ -85,7 +88,7 @@ def ensemble_vote(self, preds_list: list[torch.Tensor]) -> torch.Tensor: vote_preds = nn.functional.one_hot(indices_with_highest_counts, num_classes=preds_dimension[-1]) # If larger than two dimensions, map back to original dimensions - if len(preds_dimension) > 2: + if len(preds_dimension) > EXPECTED_PRED_N_DIMS: vote_preds = vote_preds.reshape(*preds_dimension) return vote_preds diff --git a/fl4health/model_bases/masked_layers/masked_normalization_layers.py b/fl4health/model_bases/masked_layers/masked_normalization_layers.py index 91da44f53..966ed8f7a 100644 --- a/fl4health/model_bases/masked_layers/masked_normalization_layers.py +++ b/fl4health/model_bases/masked_layers/masked_normalization_layers.py @@ -11,6 +11,10 @@ TorchShape = int | list[int] | torch.Size +BATCH_NORM_3D_INPUT_LENGTH = 5 +BATCH_NORM_2D_INPUT_LENGTH = 4 +BATCH_NORM_1D_INPUT_LENGTHS = {2, 3} + class MaskedLayerNorm(nn.LayerNorm): def __init__( @@ -287,7 +291,7 @@ class MaskedBatchNorm1d(_MaskedBatchNorm): """ def _check_input_dim(self, input: Tensor) -> None: - if input.dim() != 2 and input.dim() != 3: + if input.dim() not in BATCH_NORM_1D_INPUT_LENGTHS: raise ValueError(f"expected 2D or 3D input (got {input.dim()}D input)") @@ -298,7 +302,7 @@ class MaskedBatchNorm2d(_MaskedBatchNorm): """ def _check_input_dim(self, input: Tensor) -> None: - if input.dim() != 4: + if input.dim() != BATCH_NORM_2D_INPUT_LENGTH: raise ValueError(f"expected 4D input (got {input.dim()}D input)") @@ -309,5 +313,5 @@ class MaskedBatchNorm3d(_MaskedBatchNorm): """ def _check_input_dim(self, input: Tensor) -> None: - if input.dim() != 5: + if input.dim() != BATCH_NORM_3D_INPUT_LENGTH: raise ValueError(f"expected 5D input (got {input.dim()}D input)") diff --git a/fl4health/model_bases/pca.py b/fl4health/model_bases/pca.py index 17cebf809..fb8f74204 100644 --- a/fl4health/model_bases/pca.py +++ b/fl4health/model_bases/pca.py @@ -6,6 +6,9 @@ from torch.nn.parameter import Parameter +TWO_D_TENSOR_SHAPE_LENGTH = 2 + + class PcaModule(nn.Module): def __init__(self, low_rank: bool = False, full_svd: bool = False, rank_estimation: int = 6) -> None: """ @@ -101,7 +104,7 @@ def maybe_reshape(self, x: Tensor) -> Tensor: Returns: Tensor: tensor flattened to be 2D """ - if len(x.size()) == 2: + if len(x.size()) == TWO_D_TENSOR_SHAPE_LENGTH: return torch.squeeze(x.float()) dim0 = x.size(0) return torch.squeeze(x.view(dim0, -1).float()) diff --git a/fl4health/preprocessing/autoencoders/loss.py b/fl4health/preprocessing/autoencoders/loss.py index 637938042..f46a0e80c 100644 --- a/fl4health/preprocessing/autoencoders/loss.py +++ b/fl4health/preprocessing/autoencoders/loss.py @@ -2,6 +2,9 @@ from torch.nn.modules.loss import _Loss +REQUIRED_PREDS_DIMENSIONS = 2 + + class VaeLoss(_Loss): def __init__( self, @@ -49,7 +52,7 @@ def unpack_model_output(self, preds: torch.Tensor) -> tuple[torch.Tensor, torch. tuple[torch.Tensor, torch.Tensor, torch.Tensor]: Unpacked output containing predictions, mu, and logvar. """ # This methods assumes "preds" are batch first, and preds are 2D dimensional (already flattened). - assert preds.dim() == 2, ( + assert preds.dim() == REQUIRED_PREDS_DIMENSIONS, ( f"Expected a 2D tensor for VaeLoss, but got {preds.dim()}D tensor with shape {preds.shape}." ) # The order of logvar and mu in the output tensor is important. diff --git a/fl4health/strategies/fedpca.py b/fl4health/strategies/fedpca.py index 17ecdfc08..4b459411e 100644 --- a/fl4health/strategies/fedpca.py +++ b/fl4health/strategies/fedpca.py @@ -11,6 +11,9 @@ from fl4health.utils.functions import decode_and_pseudo_sort_results +MINIMUM_PCA_ClIENTS = 2 + + class FedPCA(BasicFedAvg): def __init__( self, @@ -236,8 +239,8 @@ def merge_subspaces_qr( Returns: tuple[NDArray, NDArray]: merged PCs and corresponding singular values. """ - assert len(client_singular_values) >= 2 - if len(client_singular_values) == 2: + assert len(client_singular_values) >= MINIMUM_PCA_ClIENTS + if len(client_singular_values) == MINIMUM_PCA_ClIENTS: u1, s1 = client_singular_vectors[0], np.diag(client_singular_values[0]) u2, s2 = client_singular_vectors[1], np.diag(client_singular_values[1]) return self.merge_two_subspaces_qr((u1, s1), (u2, s2)) From 8076b7a24ef6b06c106e5c7032150cf83fd89299 Mon Sep 17 00:00:00 2001 From: David Emerson <43939939+emersodb@users.noreply.github.com> Date: Fri, 20 Jun 2025 11:13:21 -0400 Subject: [PATCH 2/4] Relaxing some of the ruff ignored components --- examples/fedllm_example/model.py | 8 +- fl4health/checkpointing/state_checkpointer.py | 10 +- fl4health/clients/basic_client.py | 19 ++- fl4health/clients/constrained_fenda_client.py | 1 + fl4health/clients/evaluate_client.py | 3 +- fl4health/clients/fedrep_client.py | 15 +- fl4health/clients/nnunet_client.py | 12 +- fl4health/clients/scaffold_client.py | 31 +++- .../string_columns_transformer.py | 16 +-- .../tab_features_info_encoder.py | 17 ++- .../tab_features_preprocessor.py | 4 + fl4health/losses/deep_mmd_loss.py | 16 +-- fl4health/losses/mkmmd_loss.py | 13 +- fl4health/losses/perfcl_loss.py | 11 ++ fl4health/metrics/efficient_metrics_base.py | 18 ++- fl4health/metrics/metrics.py | 6 +- fl4health/mixins/personalized/utils.py | 11 +- .../model_bases/masked_layers/masked_conv.py | 18 +++ .../masked_layers/masked_linear.py | 2 + .../masked_normalization_layers.py | 26 ++-- fl4health/model_bases/pca.py | 18 +-- .../parameter_exchange/fedpm_exchanger.py | 4 + .../parameter_exchange/packing_exchanger.py | 8 ++ .../parameter_selection_criteria.py | 2 +- .../partial_parameter_exchanger.py | 10 ++ .../autoencoders/dim_reduction.py | 6 + fl4health/preprocessing/warmed_up_module.py | 2 +- fl4health/privacy/fl_accountants.py | 9 ++ fl4health/privacy/moments_accountant.py | 21 +++ fl4health/reporting/reports_manager.py | 8 ++ fl4health/strategies/basic_fedavg.py | 3 +- fl4health/strategies/client_dp_fedavgm.py | 6 + fl4health/strategies/feddg_ga.py | 6 + fl4health/strategies/fedpca.py | 25 ++-- fl4health/strategies/flash.py | 34 ++--- fl4health/strategies/model_merge_strategy.py | 5 +- fl4health/utils/client.py | 4 +- fl4health/utils/config.py | 10 +- fl4health/utils/dataset.py | 133 ++++++++++++++++++ fl4health/utils/dataset_converter.py | 32 ++++- fl4health/utils/nnunet_utils.py | 51 +++++-- fl4health/utils/privacy_utilities.py | 6 +- pyproject.toml | 14 +- research/picai/data/data_utils.py | 10 +- research/picai/data/prepare_data.py | 30 ++-- research/picai/data/preprocessing.py | 6 +- .../picai/data/preprocessing_transforms.py | 9 +- research/picai/fl_nnunet/nnunet_utils.py | 4 +- research/picai/fl_nnunet/transforms.py | 1 + research/picai/monai_scripts/auto3dseg.py | 10 +- research/picai/nnunet_scripts/eval.py | 24 ++-- .../predict_and_eval_old.py | 59 ++++---- .../old_nnunet_inference/predict_old.py | 60 ++++---- research/picai/nnunet_scripts/predict.py | 63 ++++----- .../picai/nnunet_scripts/transfer_train.py | 2 +- tests/smoke_tests/run_smoke_test.py | 116 +++++++++------ 56 files changed, 713 insertions(+), 355 deletions(-) diff --git a/examples/fedllm_example/model.py b/examples/fedllm_example/model.py index 98b78b907..5cd508e6c 100644 --- a/examples/fedllm_example/model.py +++ b/examples/fedllm_example/model.py @@ -7,6 +7,10 @@ from transformers import AutoModelForCausalLM, BitsAndBytesConfig +EIGHT_BIT = 8 +FOUR_BIT = 4 + + def cosine_annealing( total_round: int, current_round: int = 0, @@ -48,9 +52,9 @@ def get_model(model_cfg: dict[str, Any]) -> torch.nn.Module: torch.nn.Module: The model. """ quantization_config = model_cfg["quantization"] - if quantization_config == 4: + if quantization_config == FOUR_BIT: quantization_config = BitsAndBytesConfig(load_in_4bit=True) - elif quantization_config == 8: + elif quantization_config == EIGHT_BIT: quantization_config = BitsAndBytesConfig(load_in_8bit=True) else: raise ValueError(f"Use 4-bit or 8-bit quantization. You passed: {quantization_config}/") diff --git a/fl4health/checkpointing/state_checkpointer.py b/fl4health/checkpointing/state_checkpointer.py index e6053a02d..2f0544df8 100644 --- a/fl4health/checkpointing/state_checkpointer.py +++ b/fl4health/checkpointing/state_checkpointer.py @@ -488,13 +488,13 @@ def maybe_load_server_state( Load the state of the server from checkpoint. Args: - server (FlServer): server into which the attributes will be loaded - model nn.Module: The model structure to be loaded as part of the server state. - attributes (list[str] | None): List of attributes to load from the checkpoint. If None, all attributes - specified in ``snapshot_attrs`` are loaded. Defaults to None. + server (FlServer): Server into which the attributes will be loaded. + model (nn.Module): The model structure to be loaded as part of the server state. + attributes (list[str] | None, optional): List of attributes to load from the checkpoint. If None, all + attributes specified in ``snapshot_attrs`` are loaded. Defaults to None. Returns: - nn.Module | None: Returns a model if a checkpoint exists to load from. Otherwise returns None + nn.Module | None: Returns a model if a checkpoint exists to load from. Otherwise returns None. """ # Store server for access in functions self.server = server diff --git a/fl4health/clients/basic_client.py b/fl4health/clients/basic_client.py index 80e341f95..7a1eeb0e5 100644 --- a/fl4health/clients/basic_client.py +++ b/fl4health/clients/basic_client.py @@ -37,6 +37,9 @@ from fl4health.utils.typing import LogLevel, TorchFeatureType, TorchInputType, TorchPredType, TorchTargetType +EXPECTED_OUTPUT_TUPLE_SIZE = 2 + + class BasicClient(NumPyClient): def __init__( self, @@ -140,8 +143,10 @@ def _maybe_checkpoint(self, loss: float, metrics: dict[str, Scalar], checkpoint_ If checkpointer exists, maybe checkpoint model based on the provided metric values. Args: - loss (float): validation loss to potentially be used for checkpointing - metrics (dict[str, float]): validation metrics to potentially be used for checkpointing + loss (float): Validation loss to potentially be used for checkpointing. + metrics (dict[str, Scalar]): Validation metrics to potentially be used for checkpointing + checkpoint_mode (CheckpointMode): Whether we're doing checkpointing pre- or post-aggregation on the server + side. """ self.checkpoint_and_state_module.maybe_checkpoint(self.model, loss, metrics, checkpoint_mode) @@ -439,10 +444,10 @@ def _log_header_str( the round if training by steps. Args: - current_round (int | None, optional): The current FL round. (Ie current - server round). Defaults to None. - current_epoch (int | None, optional): The current epoch of local - training. Defaults to None. + current_round (int | None, optional): The current FL round. (Ie current server round). Defaults to None. + current_epoch (int | None, optional): The current epoch of local training. Defaults to None. + logging_mode (LoggingMode, optional): The logging mode to be used in logging. This mainly changes the + way in which logging is decorated. Defaults to LoggingMode.TRAIN. """ log_str = f"Current FL Round: {int(current_round)} " if current_round is not None else "" log_str += f"Current Epoch: {int(current_epoch)} " if current_epoch is not None else "" @@ -1002,7 +1007,7 @@ def predict(self, input: TorchInputType) -> tuple[TorchPredType, TorchFeatureTyp if isinstance(output, torch.Tensor): return {"prediction": output}, {} if isinstance(output, tuple): - if len(output) != 2: + if len(output) != EXPECTED_OUTPUT_TUPLE_SIZE: raise ValueError(f"Output tuple should have length 2 but has length {len(output)}") preds, features = output return preds, features diff --git a/fl4health/clients/constrained_fenda_client.py b/fl4health/clients/constrained_fenda_client.py index 78a3d33bd..881f2cd76 100644 --- a/fl4health/clients/constrained_fenda_client.py +++ b/fl4health/clients/constrained_fenda_client.py @@ -152,6 +152,7 @@ def update_after_train(self, local_steps: int, loss_dict: dict[str, float], conf Args: local_steps (int): Number of steps performed during training loss_dict (dict[str, float]): Losses computed during training. + config (Config): FL training configuration object. """ # Save the parameters of the old model assert isinstance(self.model, FendaModelWithFeatureState) diff --git a/fl4health/clients/evaluate_client.py b/fl4health/clients/evaluate_client.py index fc45c8b88..78dc8f9c4 100644 --- a/fl4health/clients/evaluate_client.py +++ b/fl4health/clients/evaluate_client.py @@ -45,7 +45,8 @@ def __init__( "cuda" loss_meter_type (LossMeterType, optional): Type of meter used to track and compute the losses over each batch. Defaults to ``LossMeterType.AVERAGE``. - model_checkpoint_path (Path | None, optional): _description_. Defaults to None. + model_checkpoint_path (Path | None, optional): path to which the model should be checkpointed. Defaults to + None. reporters (Sequence[BaseReporter] | None, optional): A sequence of FL4Health reporters which the client should send data to. Defaults to None. client_name (str | None, optional): An optional client name that uniquely identifies a client. diff --git a/fl4health/clients/fedrep_client.py b/fl4health/clients/fedrep_client.py index a7862c125..2ece53f24 100644 --- a/fl4health/clients/fedrep_client.py +++ b/fl4health/clients/fedrep_client.py @@ -122,7 +122,7 @@ def _prefix_loss_and_metrics_dictionaries( Args: prefix (str): Prefix to be attached to all keys of the provided dictionaries. loss_dict (dict[str, float]): Dictionary of loss values obtained during training. - metrics (dict[str, Scalar]): Dictionary of metrics values measured during training + metrics_dict (dict[str, Scalar]): Dictionary of metrics values measured during training. """ for loss_key in list(loss_dict): loss_dict[f"{prefix}_{loss_key}"] = loss_dict.pop(loss_key) @@ -317,12 +317,13 @@ def train_fedrep_by_epochs( Train locally for the specified number of epochs. Args: - epochs (int): The number of epochs for local training. - current_round (int | None): The current FL round. + head_epochs (int): The number of epochs for local training of the head module. + rep_epochs (int): The number of epochs for local training of the representation module + current_round (int | None, optional): The current FL round. Defaults to None. Returns: tuple[dict[str, float], dict[str, Scalar]]: The loss and metrics dictionary from the local training. - Loss is a dictionary of one or more losses that represent the different components of the loss. + L oss is a dictionary of one or more losses that represent the different components of the loss. """ # First we train the head module for head_epochs with the representations frozen in place self._prepare_train_head() @@ -356,11 +357,13 @@ def train_fedrep_by_steps( Train locally for the specified number of steps. Args: - steps (int): The number of steps to train locally. + head_steps (int): The number of steps to train locally for the head model. + rep_steps (int): The number of steps to train locally for the representation model + current_round (int | None, optional): What round of FL training we're currently on. Defaults to None. Returns: tuple[dict[str, float], dict[str, Scalar]]: The loss and metrics dictionary from the local training. - Loss is a dictionary of one or more losses that represent the different components of the loss. + Loss is a dictionary of one or more losses that represent the different components of the loss. """ assert isinstance(self.model, FedRepModel) # First we train the head module for head_steps with the representations frozen in place diff --git a/fl4health/clients/nnunet_client.py b/fl4health/clients/nnunet_client.py index ec42ad4ea..2e30efc7c 100644 --- a/fl4health/clients/nnunet_client.py +++ b/fl4health/clients/nnunet_client.py @@ -138,6 +138,8 @@ def __init__( No checkpointing (state or model) is done if not provided. Defaults to None. reporters (Sequence[BaseReporter], optional): A sequence of FL4Health reporters which the client should send data to. + client_name (str | None, optional): Name of the client. If None the class will set a default and random + name. Defaults to None. nnunet_trainer_class (type[nnUNetTrainer]): A ``nnUNetTrainer`` constructor. Useful for passing custom ``nnUNetTrainer``. Defaults to the standard nnUNetTrainer class. Must match the ``nnunet_trainer_class`` passed to the ``NnunetServer``. @@ -340,7 +342,13 @@ def get_lr_scheduler(self, optimizer_key: str, config: Config) -> _LRScheduler: this method to set your own LR scheduler. Args: - config (Config): The server config. This method will look for the + optimizer_key (str): Key of the optimizer to which the scheduler will be applied. + config (Config): The server config. This method will determine the total number of steps that will be + applied across all server rounds on FL training. + + Raises: + ValueError: Thrown if the configuration does not contain either a steps or epochs specification. + Returns: _LRScheduler: The default nnunet LR Scheduler for nnunetv2 2.5.1 """ @@ -904,7 +912,7 @@ def update_before_train(self, current_server_round: int) -> None: gc.collect() # Cleans up unused variables # As the linked issue above points out, calling gc.freeze() greatly reduces the # overhead of garbage collection. (from 1.5s to 0.005s) - if current_server_round == 2: + if current_server_round == 2: # noqa: PLR2004 # Collect runs even faster if we freeze after the end of the first iteration # Likely because a lot of variables are created in the first pass. If we # freeze before the first pass, gc.collect has to check all those variables diff --git a/fl4health/clients/scaffold_client.py b/fl4health/clients/scaffold_client.py index 1445cee3f..544073885 100644 --- a/fl4health/clients/scaffold_client.py +++ b/fl4health/clients/scaffold_client.py @@ -123,6 +123,7 @@ def set_parameters(self, parameters: NDArrays, config: Config, fitting_round: bo parameters (NDArrays): Parameters have information about model state to be added to the relevant client model and also the server control variates (initial or after aggregation) config (Config): The config is sent by the FL server to allow for customization in the function if desired. + fitting_round (bool): Which fitting round (i.e. server round of fitting) that we're on. """ assert self.model is not None and self.parameter_exchanger is not None @@ -309,12 +310,6 @@ def setup_client(self, config: Config) -> None: class DPScaffoldClient(ScaffoldClient, InstanceLevelDpClient): - """ - Federated Learning client for Instance Level Differentially Private Scaffold strategy. - - Implemented as specified in https://arxiv.org/abs/2111.09278 - """ - def __init__( self, data_path: Path, @@ -326,6 +321,30 @@ def __init__( progress_bar: bool = False, client_name: str | None = None, ) -> None: + """ + Federated Learning client for Instance Level Differentially Private Scaffold strategy. + + Implemented as specified in https://arxiv.org/abs/2111.09278 + + Args: + data_path (Path): path to the data to be used to load the data for client-side training + metrics (Sequence[Metric]): Metrics to be computed based on the labels and predictions of the client model + device (torch.device): Device indicator for where to send the model, batches, labels etc. Often "cpu" or + "cuda" + loss_meter_type (LossMeterType, optional): Type of meter used to track and compute the losses over + each batch. Defaults to ``LossMeterType.AVERAGE``. + checkpoint_and_state_module (ClientCheckpointAndStateModule | None, optional): A module meant to handle + both checkpointing and state saving. The module, and its underlying model and state checkpointing + components will determine when and how to do checkpointing during client-side training. + No checkpointing (state or model) is done if not provided. Defaults to None. + reporters (Sequence[BaseReporter] | None, optional): A sequence of FL4Health reporters which the client + should send data to. Defaults to None. + progress_bar (bool, optional): Whether or not to display a progress bar during client training and + validation. Uses ``tqdm``. Defaults to False. + client_name (str | None, optional): n optional client name that uniquely identifies a client. + If not passed, a hash is randomly generated. Client state will use this as part of its state file + name. Defaults to None. + """ ScaffoldClient.__init__( self, data_path=data_path, diff --git a/fl4health/feature_alignment/string_columns_transformer.py b/fl4health/feature_alignment/string_columns_transformer.py index 927689f84..7e1e6a07b 100644 --- a/fl4health/feature_alignment/string_columns_transformer.py +++ b/fl4health/feature_alignment/string_columns_transformer.py @@ -20,10 +20,10 @@ def __init__(self, transformer: TextFeatureTransformer): def fit(self, x: pd.DataFrame, y: pd.DataFrame | None = None) -> TextMulticolumnTransformer: """ Fit the transformer to the provided dataframe. The dataframe should have multiple string columns - The transformer is fit on the appended text from all columns in the ``X`` dataframe. + The transformer is fit on the appended text from all columns in the ``x`` dataframe. Args: - X (pd.DataFrame): Columns on which to fit the transformer + x (pd.DataFrame): Columns on which to fit the transformer y (pd.DataFrame | None, optional): Not used. Defaults to None. Returns: @@ -35,10 +35,10 @@ def fit(self, x: pd.DataFrame, y: pd.DataFrame | None = None) -> TextMulticolumn def transform(self, x: pd.DataFrame) -> pd.DataFrame: """ - Transforms the concatenation of all columns of text in the ``X`` dataframe. + Transforms the concatenation of all columns of text in the ``x`` dataframe. Args: - X (pd.DataFrame): Dataframe of text-based columns to be transformed + x (pd.DataFrame): Dataframe of text-based columns to be transformed Returns: pd.DataFrame: Transformed dataframe. @@ -61,10 +61,10 @@ def __init__(self, transformer: TextFeatureTransformer): def fit(self, x: pd.DataFrame, y: pd.DataFrame | None = None) -> TextColumnTransformer: """ Fit the transformer to the provided dataframe. The dataframe should have a single string column - The transformer is fit on the text from the single columns in the ``X`` dataframe. + The transformer is fit on the text from the single columns in the ``x`` dataframe. Args: - X (pd.DataFrame): Column on which to fit the transformer + x (pd.DataFrame): Column on which to fit the transformer y (pd.DataFrame | None, optional): Not used. Defaults to None. Returns: @@ -76,10 +76,10 @@ def fit(self, x: pd.DataFrame, y: pd.DataFrame | None = None) -> TextColumnTrans def transform(self, x: pd.DataFrame) -> pd.DataFrame: """ - Transforms the concatenation of a single column of text in the ``X`` dataframe. + Transforms the concatenation of a single column of text in the ``x`` dataframe. Args: - X (pd.DataFrame): Dataframe of text-based column to be transformed + x (pd.DataFrame): Dataframe of text-based column to be transformed Returns: pd.DataFrame: Transformed dataframe. diff --git a/fl4health/feature_alignment/tab_features_info_encoder.py b/fl4health/feature_alignment/tab_features_info_encoder.py index 22656559c..214420849 100644 --- a/fl4health/feature_alignment/tab_features_info_encoder.py +++ b/fl4health/feature_alignment/tab_features_info_encoder.py @@ -12,17 +12,16 @@ class TabularFeaturesInfoEncoder: - """ - This class encodes all the information required to perform feature alignment on tabular datasets. - - **NOTE:** targets are not included in tabular_features + def __init__(self, tabular_features: list[TabularFeature], tabular_targets: list[TabularFeature]) -> None: + """ + This class encodes all the information required to perform feature alignment on tabular datasets. - Args: - tabular_features (list[TabularFeature]): List of all tabular features. - tabular_targets (list[TabularFeature]): List of all targets. - """ + **NOTE:** targets are not included in tabular_features - def __init__(self, tabular_features: list[TabularFeature], tabular_targets: list[TabularFeature]) -> None: + Args: + tabular_features (list[TabularFeature]): List of all tabular features. + tabular_targets (list[TabularFeature]): List of all targets. + """ self.tabular_features = sorted(tabular_features, key=TabularFeature.get_feature_name) self.tabular_targets = sorted(tabular_targets, key=TabularFeature.get_feature_name) diff --git a/fl4health/feature_alignment/tab_features_preprocessor.py b/fl4health/feature_alignment/tab_features_preprocessor.py index 33e3f31db..bbdfc723f 100644 --- a/fl4health/feature_alignment/tab_features_preprocessor.py +++ b/fl4health/feature_alignment/tab_features_preprocessor.py @@ -119,6 +119,10 @@ def initialize_default_pipelines( Args: tabular_features (list[TabularFeature]): list of tabular features in the data columns. + one_hot (bool): Whether or not to apply a default one-hot pipeline. + + Returns: + dict[str, Pipeline]: Default feature processing pipeline per feature in the list """ columns_to_pipelines = {} for tab_feature in tabular_features: diff --git a/fl4health/losses/deep_mmd_loss.py b/fl4health/losses/deep_mmd_loss.py index 57f5d5e03..34a675316 100644 --- a/fl4health/losses/deep_mmd_loss.py +++ b/fl4health/losses/deep_mmd_loss.py @@ -104,8 +104,8 @@ def pairwise_distance_squared(self, x: torch.Tensor, y: torch.Tensor) -> torch.T Compute the paired distance between x and y. Args: - X (torch.Tensor): The input tensor X. - Y (torch.Tensor): The input tensor Y. + x (torch.Tensor): The input tensor x. + y (torch.Tensor): The input tensor y. Returns: torch.Tensor: The paired distance between X and Y. @@ -225,8 +225,8 @@ def train_kernel(self, x: torch.Tensor, y: torch.Tensor) -> None: Train the Deep MMD kernel. Args: - X (torch.Tensor): The input tensor X. - Y (torch.Tensor): The input tensor Y. + x (torch.Tensor): The input tensor x. + y (torch.Tensor): The input tensor y. """ self.featurizer.train() self.sigma_q_opt.requires_grad = True @@ -276,8 +276,8 @@ def compute_kernel(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: Compute the Deep MMD Loss. Args: - X (torch.Tensor): The input tensor X. - Y (torch.Tensor): The input tensor Y. + x (torch.Tensor): The input tensor x. + y (torch.Tensor): The input tensor y. Returns: torch.Tensor: The value of Deep MMD Loss. @@ -314,8 +314,8 @@ def forward(self, x_s: torch.Tensor, x_t: torch.Tensor) -> torch.Tensor: steps and then computes the MMD loss. Args: - Xs (torch.Tensor): The source input tensor. - Xt (torch.Tensor): The target input tensor. + x_s (torch.Tensor): The source input tensor. + x_t (torch.Tensor): The target input tensor. Returns: torch.Tensor: The value of Deep MMD Loss. diff --git a/fl4health/losses/mkmmd_loss.py b/fl4health/losses/mkmmd_loss.py index 598cd7e4c..604748e76 100644 --- a/fl4health/losses/mkmmd_loss.py +++ b/fl4health/losses/mkmmd_loss.py @@ -5,6 +5,9 @@ from qpth.qp import QPFunction, QPSolvers +BETA_CONSTRAINT_EPSILON = 0.00001 + + class MkMmdLoss(torch.nn.Module): def __init__( self, @@ -55,7 +58,7 @@ def __init__( else: assert betas.shape == (self.kernel_num, 1) self.betas = betas.to(self.device) - assert torch.abs(torch.sum(self.betas) - 1) < 0.00001 + assert torch.abs(torch.sum(self.betas) - 1) < BETA_CONSTRAINT_EPSILON self.minimize_type_two_error = minimize_type_two_error self.normalize_features = normalize_features @@ -78,8 +81,8 @@ def construct_quadruples(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor **NOTE**: that if ``n_samples`` is not divisible by 2, we leave off the modulus Args: - X (torch.Tensor): First set of feature tensors - Y (torch.Tensor): Second set of feature tensors + x (torch.Tensor): First set of feature tensors + y (torch.Tensor): Second set of feature tensors Returns: torch.Tensor: Quadruples of the form described above. @@ -440,8 +443,8 @@ def forward(self, x_s: torch.Tensor, x_t: torch.Tensor) -> torch.Tensor: Compute the multi-kernel maximum mean discrepancy (MK-MMD) between the source and target domains. Args: - Xs (torch.Tensor): Source domain data, shape (n_samples, n_features) - Xt (torch.Tensor): Target domain data, shape (n_samples, n_features) + x_s (torch.Tensor): Source domain data, shape (n_samples, n_features) + x_t (torch.Tensor): Target domain data, shape (n_samples, n_features) Returns: torch.Tensor: MK-MMD value diff --git a/fl4health/losses/perfcl_loss.py b/fl4health/losses/perfcl_loss.py index 302b430a7..3633498bb 100644 --- a/fl4health/losses/perfcl_loss.py +++ b/fl4health/losses/perfcl_loss.py @@ -11,6 +11,17 @@ def __init__( global_feature_loss_temperature: float = 0.5, local_feature_loss_temperature: float = 0.5, ) -> None: + """ + Loss function for local model training with the PerFCL Method: https://ieeexplore.ieee.org/document/10020518/ + It is essentially a combination of two separate MOON contrastive losses. + + Args: + device (torch.device): Device onto which this loss should be transferred. + global_feature_loss_temperature (float, optional): Temperature for the contrastive loss associated with + the global features. Defaults to 0.5. + local_feature_loss_temperature (float, optional): Temperature for the contrastive loss associated with + the local features. Defaults to 0.5. + """ super().__init__() self.global_feature_contrastive_loss = MoonContrastiveLoss(device, global_feature_loss_temperature) self.local_feature_contrastive_loss = MoonContrastiveLoss(device, local_feature_loss_temperature) diff --git a/fl4health/metrics/efficient_metrics_base.py b/fl4health/metrics/efficient_metrics_base.py index 7c6ce21f3..5cb37d00c 100644 --- a/fl4health/metrics/efficient_metrics_base.py +++ b/fl4health/metrics/efficient_metrics_base.py @@ -11,6 +11,12 @@ from fl4health.metrics.utils import align_pred_and_target_shapes +N_DIM_2D_TENSOR = 2 +EXPECTED_BINARY_LABEL_DIM_SIZE = 2 +BINARY_COUNT_TENSOR_MAX_SIZE = 2 +BINARY_COUNT_TENSOR_N_DIMS = 2 + + class MetricOutcome(Enum): TRUE_POSITIVE = "true_positive" FALSE_POSITIVE = "false_positive" @@ -473,7 +479,7 @@ def _postprocess_count_tensor(self, count_tensor: torch.Tensor) -> torch.Tensor: if self.batch_dim is not None and self.label_dim is not None: # Both a batch and label dim have been specified. So tensor should be 2D and either have 1 or 2 columns - assert count_ndims == 2, ( + assert count_ndims == BINARY_COUNT_TENSOR_N_DIMS, ( f"Batch and label dims have been specified, tensor should be 2D, but got {count_ndims}" ) @@ -481,7 +487,7 @@ def _postprocess_count_tensor(self, count_tensor: torch.Tensor) -> torch.Tensor: # reshape so that batch dimension comes first count_tensor = count_tensor.transpose(0, 1) - if count_tensor.shape[1] == 2: + if count_tensor.shape[1] == BINARY_COUNT_TENSOR_MAX_SIZE: # Always return the class with label 1 (pos_label 0 will be handled by rearranging counts if necessary) return count_tensor[:, 1].unsqueeze(1) if count_tensor.shape[1] == 1: @@ -498,7 +504,7 @@ def _postprocess_count_tensor(self, count_tensor: torch.Tensor) -> torch.Tensor: # there is a dimension for an "implied" label (i.e. 0.8 representing vector predictions [0.2, 0.8]). If # there is no label dimension specified, there should only be a single element as well. assert count_ndims <= 1, f"Batch dim has not been specified, tensor should be 0 or 1D but got {count_ndims}" - if count_tensor.numel() == 2: + if count_tensor.numel() == BINARY_COUNT_TENSOR_MAX_SIZE: assert self.label_dim is not None, "self.label_dim is None but got two elements in the count_tensor" # Always return the class with label 1 return count_tensor[1].unsqueeze(0) @@ -543,11 +549,11 @@ def count_tp_fp_tn_fn( # Assert that the label dimension for these tensors is of size 2 at most. if self.label_dim is not None: - assert preds.shape[self.label_dim] <= 2, ( + assert preds.shape[self.label_dim] <= EXPECTED_BINARY_LABEL_DIM_SIZE, ( f"Label dimension for preds tensor is greater than 2 {preds.shape[self.label_dim]}. This class is " "meant for binary metric computation only" ) - assert targets.shape[self.label_dim] <= 2, ( + assert targets.shape[self.label_dim] <= EXPECTED_BINARY_LABEL_DIM_SIZE, ( f"Label dimension for targets tensor is greater than 2 {targets.shape[self.label_dim]}. This class is " "meant for binary metric computation only" ) @@ -748,7 +754,7 @@ def _transpose_2d_matrix_unless_empty(cls, matrix: torch.Tensor) -> torch.Tensor torch.Tensor: transposed tensor if 2D, unchanged tensor if empty, and throw error if shape differs from those two expected settings """ - if matrix.ndim == 2: + if matrix.ndim == N_DIM_2D_TENSOR: return matrix.transpose(0, 1) if matrix.numel() == 0: return matrix diff --git a/fl4health/metrics/metrics.py b/fl4health/metrics/metrics.py index d58135ab9..b184785ec 100644 --- a/fl4health/metrics/metrics.py +++ b/fl4health/metrics/metrics.py @@ -163,11 +163,13 @@ def __init__(self, name: str = "accuracy"): """ super().__init__(name) - def __call__(self, logits: torch.Tensor, target: torch.Tensor) -> Scalar: + def __call__(self, logits: torch.Tensor, target: torch.Tensor, threshold: float = 0.5) -> Scalar: # assuming batch first assert logits.shape[0] == target.shape[0] # Single value output, assume binary logits - preds = (logits > 0.5).int() if len(logits.shape) == 1 or logits.shape[1] == 1 else torch.argmax(logits, 1) + preds = ( + (logits > threshold).int() if len(logits.shape) == 1 or logits.shape[1] == 1 else torch.argmax(logits, 1) + ) target = target.cpu().detach() preds = preds.cpu().detach() return sklearn_metrics.accuracy_score(target, preds) diff --git a/fl4health/mixins/personalized/utils.py b/fl4health/mixins/personalized/utils.py index dcd052daa..0494f4d16 100644 --- a/fl4health/mixins/personalized/utils.py +++ b/fl4health/mixins/personalized/utils.py @@ -11,20 +11,19 @@ def ensure_protocol_compliance(func: Callable, instance: Any | None, args: Any, """ Wrapper to ensure that a the instance is of `BasicClient` type. - NOTE: this should only be used within a `BasicClient`. + NOTE: This should only be used within a `BasicClient`. Params specified and supplied by the `wrapt` decorator Args: - # are params specified and supplied by the `wrapt` decorator - func (Callable): the function to be wrapped - instance (Any | None): the associated instance if it is a method belonging to a class or a standalone + func (Callable): The function to be wrapped + instance (Any | None): The associated instance if it is a method belonging to a class or a standalone args (Any): args passed to func kwargs (Any): kwargs passed to func Raises: - TypeError: we raise error if the instance is not a `BasicClient`. + TypeError: Thrown if the protocol requirements are not met Returns: - _type_: _description_ + Any: Application of the function to the args and kwargs. """ # validate self is a BasicClient if not isinstance(instance, BasicClient): diff --git a/fl4health/model_bases/masked_layers/masked_conv.py b/fl4health/model_bases/masked_layers/masked_conv.py index f9fa77e0d..b6d4504fc 100644 --- a/fl4health/model_bases/masked_layers/masked_conv.py +++ b/fl4health/model_bases/masked_layers/masked_conv.py @@ -50,6 +50,8 @@ def __init__( dilation (int or tuple, optional): Spacing between kernel elements. Default: 1 groups (int, optional): Number of blocked connections from input channels to output channels. Default: 1 bias (bool, optional): If ``True``, adds a learnable bias to the output. Default: ``True`` + device (torch.device | None, optional): Device to which this module should be sent. Defaults to None. + dtype (torch.dtype | None, optional): Type of the tensors. Defaults to None. """ # Attributes: # weight: weights of the module. @@ -176,6 +178,8 @@ def __init__( dilation (int or tuple, optional): Spacing between kernel elements. Default: 1 groups (int, optional): Number of blocked connections from input channels to output channels. Default: 1 bias (bool, optional): If ``True``, adds a learnable bias to the output. Default: ``True`` + device (torch.device | None, optional): Device to which this module should be sent. Defaults to None. + dtype (torch.dtype | None, optional): Type of the tensors. Defaults to None. """ # Attributes: # weight: weights of the module. @@ -300,6 +304,8 @@ def __init__( dilation (int or tuple, optional): Spacing between kernel elements. Default: 1 groups (int, optional): Number of blocked connections from input channels to output channels. Default: 1 bias (bool, optional): If ``True``, adds a learnable bias to the output. Default: ``True`` + device (torch.device | None, optional): Device to which this module should be sent. Defaults to None. + dtype (torch.dtype | None, optional): Type of the tensors. Defaults to None. """ # Attributes: # weight: weights of the module. @@ -428,6 +434,10 @@ def __init__( groups (int, optional): Number of blocked connections from input channels to output channels. Default: 1 bias (bool, optional): If ``True``, adds a learnable bias to the output. Default: ``True`` dilation (int or tuple, optional): Spacing between kernel elements. Default: 1 + padding_mode (str, optional): Mode to be used in padding the input image for processing. Defaults to + "zeros" + device (torch.device | None, optional): Device to which this module should be sent. Defaults to None. + dtype (torch.dtype | None, optional): Type of the tensors. Defaults to None. """ # Attributes: # weight (Tensor): weights of the module. @@ -588,6 +598,10 @@ def __init__( groups (int, optional): Number of blocked connections from input channels to output channels. Default: 1 bias (bool, optional): If ``True``, adds a learnable bias to the output. Default: ``True`` dilation (int or tuple, optional): Spacing between kernel elements. Default: 1 + padding_mode (str, optional): Mode to be used in padding the input image for processing. Defaults to + "zeros" + device (torch.device | None, optional): Device to which this module should be sent. Defaults to None. + dtype (torch.dtype | None, optional): Type of the tensors. Defaults to None. """ # Attributes: # weight (Tensor): weights of the module. @@ -745,6 +759,10 @@ def __init__( groups (int, optional): Number of blocked connections from input channels to output channels. Default: 1 bias (bool, optional): If ``True``, adds a learnable bias to the output. Default: ``True`` dilation (int or tuple, optional): Spacing between kernel elements. Default: 1 + padding_mode (str, optional): Mode to be used in padding the input image for processing. Defaults to + "zeros" + device (torch.device | None, optional): Device to which this module should be sent. Defaults to None. + dtype (torch.dtype | None, optional): Type of the tensors. Defaults to None. """ # Attributes: # weight (Tensor): weights of the module. diff --git a/fl4health/model_bases/masked_layers/masked_linear.py b/fl4health/model_bases/masked_layers/masked_linear.py index 51e2de20f..6bf0c14e7 100644 --- a/fl4health/model_bases/masked_layers/masked_linear.py +++ b/fl4health/model_bases/masked_layers/masked_linear.py @@ -33,6 +33,8 @@ def __init__( in_features: size of each input sample out_features: size of each output sample bias: If set to ``False``, the layer will not learn an additive bias. Default: ``True`` + device (torch.device | None, optional): Device to which this module should be sent. Defaults to None. + dtype (torch.dtype | None, optional): Type of the tensors. Defaults to None. """ # Attributes: # weight: weights of the module. diff --git a/fl4health/model_bases/masked_layers/masked_normalization_layers.py b/fl4health/model_bases/masked_layers/masked_normalization_layers.py index 966ed8f7a..834457e3e 100644 --- a/fl4health/model_bases/masked_layers/masked_normalization_layers.py +++ b/fl4health/model_bases/masked_layers/masked_normalization_layers.py @@ -49,6 +49,8 @@ def __init__( affine parameters initialized to ones (for weights) and zeros (for biases). Default: ``True``. bias: If set to ``False``, the layer will not learn an additive bias (only relevant if ``elementwise_affine`` is ``True``). Default: ``True``. + device (torch.device | None, optional): Device to which this module should be sent. Defaults to None. + dtype (torch.dtype | None, optional): Type of the tensors. Defaults to None. """ # Attributes: # weight: the weights of the module. The values are initialized to 1. @@ -168,17 +170,19 @@ def __init__( **NOTE:** The scores are not assumed to be bounded between 0 and 1. Args: - num_features: number of features or channels :math:`C` of the input - eps: a value added to the denominator for numerical stability. Default: 1e-5 - momentum: the value used for the running_mean and ``running_var`` computation. Can be set to ``None`` for - cumulative moving average (i.e. simple average). Default: 0.1 - affine: a boolean value that when set to ``True``, this module has learnable affine parameters. - Default: ``True`` - track_running_stats: a boolean value that when set to ``True``, this module tracks the running mean and - variance, and when set to ``False``, this module does not track such statistics, and initializes - statistics buffers :attr:`running_mean` and :attr:`running_var` as ``None``. When these buffers - are ``None``, this module always uses batch statistics. in both training and eval modes. - Default: ``True`` + num_features (int): Number of features or channels :math:`C` of the input + eps (float, optional): A value added to the denominator for numerical stability. Defaults to 1e-5. + momentum (float | None, optional): The value used for the running_mean and ``running_var`` computation. + Can be set to ``None`` for cumulative moving average (i.e. simple average). Defaults to 0.1. + affine (bool, optional): A boolean value that when set to ``True``, this module has learnable affine + parameters. Defaults to True. + track_running_stats (bool, optional): A boolean value that when set to ``True``, this module tracks the + running mean and variance, and when set to ``False``, this module does not track such statistics, and + initializes statistics buffers :attr:`running_mean` and :attr:`running_var` as ``None``. When these + buffers are ``None``, this module always uses batch statistics. in both training and eval modes. + Defaults to True. + device (torch.device | None, optional): Device to which this module should be sent. Defaults to None. + dtype (torch.dtype | None, optional): Type of the tensors. Defaults to None. """ # Attributes: # weight: the weights of the module. The values are initialized to 1. diff --git a/fl4health/model_bases/pca.py b/fl4health/model_bases/pca.py index fb8f74204..95c1b7681 100644 --- a/fl4health/model_bases/pca.py +++ b/fl4health/model_bases/pca.py @@ -60,14 +60,14 @@ def __init__(self, low_rank: bool = False, full_svd: bool = False, rank_estimati def forward(self, x: Tensor, center_data: bool) -> tuple[Tensor, Tensor]: """ - Perform PCA on the data matrix X by computing its SVD. + Perform PCA on the data matrix x by computing its SVD. **NOTE**: the algorithm assumes that the rows of X are the data points (after reshaping as needed). Consequently, the principal components, which are the eigenvectors of X.T @ X, are the right singular vectors in the SVD of X. Args: - X (Tensor): Data matrix. + x (Tensor): Data matrix. center_data (bool): If true, then the data mean will be subtracted from all data points prior to performing PCA. If ``center_data`` is false, it is expected that the data has already been centered and an exception will be thrown if it is not. @@ -99,7 +99,7 @@ def maybe_reshape(self, x: Tensor) -> Tensor: N-dimensional tensor because PCA requires X to be a 2D data matrix. Args: - X (Tensor): Data matrix + x (Tensor): Data matrix Returns: Tensor: tensor flattened to be 2D @@ -115,7 +115,7 @@ def set_data_mean(self, x: Tensor) -> None: validation/test data later, if needed. Args: - X (Tensor): Data matrix + x (Tensor): Data matrix """ self.data_mean = torch.mean(x, dim=0) @@ -128,7 +128,7 @@ def prepare_data_forward(self, x: Tensor, center_data: bool) -> Tensor: Prepare input data X for PCA by reshaping and centering it as needed. Args: - X (Tensor): Data matrix. + x (Tensor): Data matrix. center_data (bool): If true, then the data mean will be subtracted from all data points prior to performing PCA. If center_data is false, it is expected that the data has already been centered and an exception will be thrown if it is not. @@ -154,7 +154,7 @@ def project_lower_dim(self, x: Tensor, k: int | None = None, center_data: bool = are the data points while the columns of U are the principal components. Args: - X (Tensor): Input data matrix whose rows are the data points. + x (Tensor): Input data matrix whose rows are the data points. k (int | None, optional): The number of principal components onto which projection is done. If k is None, then all principal components will be used in the projection. Defaults to None. center_data (bool, optional): If true, then the *training* data mean (learned in the forward pass) @@ -177,7 +177,7 @@ def project_back(self, x_lower_dim: Tensor, add_mean: bool = False) -> Tensor: of data points. Args: - X_lower_dim (Tensor): Matrix whose rows are low-dimensional principal representations of the original data. + x_lower_dim (Tensor): Matrix whose rows are low-dimensional principal representations of the original data. add_mean (bool, optional): Indicates whether the training data mean should be added to the projection result. This can be set to True if the user centered the data prior to dimensionality reduction and now wish to add back the data mean. Defaults to False. @@ -204,7 +204,7 @@ def compute_reconstruction_error(self, x: Tensor, k: int | None, center_data: bo of X are the data points while the columns of U are the principal components. Args: - X (Tensor): Input data tensor whose rows represent data points. + x (Tensor): Input data tensor whose rows represent data points. k (int | None): The number of principal components onto which projection is applied. center_data (bool, optional): Indicates whether to subtract data mean prior to projecting the data into a lower-dimensional subspace, and whether to add the data mean after projecting back. Defaults to False. @@ -224,7 +224,7 @@ def compute_projection_variance(self, x: Tensor, k: int | None, center_data: boo The variance is defined as ``| X @ U |_F ** 2`` Args: - X (Tensor): input data tensor whose rows represent data points. + x (Tensor): input data tensor whose rows represent data points. k (int | None): the number of principal components onto which projection is applied. center_data (bool, optional): Indicates whether to subtract data mean prior to projecting the data into a lower-dimensional subspace, and whether to add the data mean after projecting back. Defaults to False. diff --git a/fl4health/parameter_exchange/fedpm_exchanger.py b/fl4health/parameter_exchange/fedpm_exchanger.py index a7cea4612..e6b074757 100644 --- a/fl4health/parameter_exchange/fedpm_exchanger.py +++ b/fl4health/parameter_exchange/fedpm_exchanger.py @@ -9,6 +9,10 @@ class FedPmExchanger(DynamicLayerExchanger): def __init__(self) -> None: + """ + Exchanger specifically tailored to exchange parameters and other information between FedPM servers and + clients. FedPM has a special set of information that needs to be exchanged, which is handled by this class. + """ super().__init__(select_scores_and_sample_masks) def pull_parameters(self, parameters: NDArrays, model: nn.Module, config: Config | None = None) -> None: diff --git a/fl4health/parameter_exchange/packing_exchanger.py b/fl4health/parameter_exchange/packing_exchanger.py index 12e5974e0..38618b92d 100644 --- a/fl4health/parameter_exchange/packing_exchanger.py +++ b/fl4health/parameter_exchange/packing_exchanger.py @@ -11,6 +11,14 @@ class FullParameterExchangerWithPacking(FullParameterExchanger, Generic[T]): def __init__(self, parameter_packer: ParameterPacker[T]) -> None: + """ + Parameter exchanger for when sending the entire set of model weights between the client and server with + potential side information packed in as well. + + Args: + parameter_packer (ParameterPacker[T]): Parameter packer used to pack and unpack auxiliary information + alongside the model weights. + """ super().__init__() self.parameter_packer = parameter_packer diff --git a/fl4health/parameter_exchange/parameter_selection_criteria.py b/fl4health/parameter_exchange/parameter_selection_criteria.py index ea4b1de8f..d81fa554e 100644 --- a/fl4health/parameter_exchange/parameter_selection_criteria.py +++ b/fl4health/parameter_exchange/parameter_selection_criteria.py @@ -62,7 +62,7 @@ class via the ``LayerSelectionFunctionConstructor`` class. normalize (bool): Whether to divide the difference between the tensors by their number of elements. Returns: - float: _description_ + float: Norm of the difference between ``t1`` and ``t2`` based on the specified calculation """ t_diff = (t1 - t2).float() drift_norm = torch.linalg.norm(t_diff) diff --git a/fl4health/parameter_exchange/partial_parameter_exchanger.py b/fl4health/parameter_exchange/partial_parameter_exchanger.py index 6e84bfa49..b316d9467 100644 --- a/fl4health/parameter_exchange/partial_parameter_exchanger.py +++ b/fl4health/parameter_exchange/partial_parameter_exchanger.py @@ -13,6 +13,16 @@ class PartialParameterExchanger(ParameterExchanger, Generic[T]): def __init__(self, parameter_packer: ParameterPacker[T]) -> None: + """ + Base class meant to properly facilitate partial parameter exchange through a selection criterion. This + mechanism is more complicated than, for example, that used by the ``FixedLayerExchanger`` where the subset + parameters to exchange do not change dynamically from round to round. + + Args: + parameter_packer (ParameterPacker[T]): Parameter packer that can be used to pack in more information + than just the parameters being exchange. This is important, for example, when exchanging different + sets of layers in each round. + """ super().__init__() self.parameter_packer = parameter_packer diff --git a/fl4health/preprocessing/autoencoders/dim_reduction.py b/fl4health/preprocessing/autoencoders/dim_reduction.py index fd9ed0d4e..e4378da94 100644 --- a/fl4health/preprocessing/autoencoders/dim_reduction.py +++ b/fl4health/preprocessing/autoencoders/dim_reduction.py @@ -30,6 +30,12 @@ def load_autoencoder(self) -> None: self.autoencoder = autoencoder.to(self.device) def __repr__(self) -> str: + """ + Printable representation of the object. + + Returns: + str: Printable representation of the object. + """ return f"{self.__class__.__name__}" diff --git a/fl4health/preprocessing/warmed_up_module.py b/fl4health/preprocessing/warmed_up_module.py index c42577e37..9f418b470 100644 --- a/fl4health/preprocessing/warmed_up_module.py +++ b/fl4health/preprocessing/warmed_up_module.py @@ -24,7 +24,7 @@ def __init__( ``pretrained_model_path``. pretrained_model_path (Path | None): Path of the pretrained model. This is mutually exclusive with ``pretrained_model``. - weights_mapping_dir (str | None, optional): Path of to json file of the weights mapping dict. If models + weights_mapping_path (str | None, optional): Path of to json file of the weights mapping dict. If models are not exactly the same, a weights mapping dict is needed to map the weights of the pretrained model to the target model. """ diff --git a/fl4health/privacy/fl_accountants.py b/fl4health/privacy/fl_accountants.py index 56a1ee83b..7fde8dac1 100644 --- a/fl4health/privacy/fl_accountants.py +++ b/fl4health/privacy/fl_accountants.py @@ -96,6 +96,15 @@ def get_delta(self, server_updates: int, epsilon: float) -> float: class ClientLevelAccountant(ABC): def __init__(self, noise_multiplier: float | list[float], moment_orders: list[float] | None = None) -> None: + """ + Accountant to be used when measuring Client Level DP in FL training. + + Args: + noise_multiplier (float | list[float]): The noise multiplier being applied to weights before transfer to + the server. + moment_orders (list[float] | None, optional): Basis orders to be used by the accountant for approximation. + of the RDP values. Defaults to None. + """ self.noise_multiplier = noise_multiplier self.accountant = MomentsAccountant(moment_orders) diff --git a/fl4health/privacy/moments_accountant.py b/fl4health/privacy/moments_accountant.py index f450dac5e..e39a55d78 100644 --- a/fl4health/privacy/moments_accountant.py +++ b/fl4health/privacy/moments_accountant.py @@ -14,6 +14,12 @@ class SamplingStrategy(ABC): def __init__(self, neighbor_relation: NeighborRel) -> None: + """ + Abstract base class. It holds information about the privacy accountant NeighborRel applied. + + Args: + neighbor_relation (NeighborRel): The kind of neighbor relation that should be used in accounting. + """ self.neighbor_relation = neighbor_relation @abstractmethod @@ -23,6 +29,13 @@ def get_dp_event(self, noise_event: DpEvent) -> DpEvent: class PoissonSampling(SamplingStrategy): def __init__(self, sampling_ratio: float) -> None: + """ + DP Event type that stores important information about sampling statistics and sets the proper NeighborRel value + This class is specific to Poisson sampling. + + Args: + sampling_ratio (float): Poisson sampling ratio used. + """ self.sampling_ratio = sampling_ratio super().__init__(NeighborRel.ADD_OR_REMOVE_ONE) @@ -32,6 +45,14 @@ def get_dp_event(self, noise_event: DpEvent) -> DpEvent: class FixedSamplingWithoutReplacement(SamplingStrategy): def __init__(self, population_size: int, sample_size: int) -> None: + """ + DP Event type that stores important information about sampling statistics and sets the proper NeighborRel value + This class is specific to fixed sampling without replacement. + + Args: + population_size (int): Size of the total population from which sampling is performed + sample_size (int): Size of the desired sample + """ self.population_size = population_size self.sample_size = sample_size super().__init__(NeighborRel.REPLACE_ONE) diff --git a/fl4health/reporting/reports_manager.py b/fl4health/reporting/reports_manager.py index 1e2b44edf..ecc3dc698 100644 --- a/fl4health/reporting/reports_manager.py +++ b/fl4health/reporting/reports_manager.py @@ -6,6 +6,14 @@ class ReportsManager: def __init__(self, reporters: Sequence[BaseReporter] | None = None) -> None: + """ + Basic class for managing sequences of reporters. Generally, this class orchestrates initializing, calling, + and gracefully shutting down all reporters provided to the class. + + Args: + reporters (Sequence[BaseReporter] | None, optional): Reporters of ``BaseReporter`` to be managed by + this class. Defaults to None. + """ self.reporters = [] if reporters is None else list(reporters) def initialize(self, **kwargs: Any) -> None: diff --git a/fl4health/strategies/basic_fedavg.py b/fl4health/strategies/basic_fedavg.py index 1c3b8e73c..c6d9a066d 100644 --- a/fl4health/strategies/basic_fedavg.py +++ b/fl4health/strategies/basic_fedavg.py @@ -273,10 +273,11 @@ def aggregate_evaluate( Aggregate the metrics and losses returned from the clients as a result of the evaluation round. Args: + server_round (int): Current FL server Round. results (list[tuple[ClientProxy, EvaluateRes]]): The client identifiers and the results of their local evaluation that need to be aggregated on the server-side. These results are loss values and the metrics dictionary. - failures (list[tuple[ClientProxy, EvaluateRes] | BaseException]): These are the results and + failures (list[tuple[ClientProxy, EvaluateRes] | BaseException]): These are the results and exceptions from clients that experienced an issue during evaluation, such as timeouts or exceptions. Returns: diff --git a/fl4health/strategies/client_dp_fedavgm.py b/fl4health/strategies/client_dp_fedavgm.py index 931c915a3..183979316 100644 --- a/fl4health/strategies/client_dp_fedavgm.py +++ b/fl4health/strategies/client_dp_fedavgm.py @@ -156,6 +156,12 @@ def __init__( self.m_t: NDArrays | None = None def __repr__(self) -> str: + """ + Printable representation of the object. + + Returns: + str: Printable representation of the object. + """ return f"ClientLevelDPFedAvgM(accept_failures={self.accept_failures})" def modify_noise_multiplier(self) -> float: diff --git a/fl4health/strategies/feddg_ga.py b/fl4health/strategies/feddg_ga.py index 3ba6eb0fa..f2178d9b9 100644 --- a/fl4health/strategies/feddg_ga.py +++ b/fl4health/strategies/feddg_ga.py @@ -86,6 +86,12 @@ def __init__( self.signal = FairnessMetricType.signal_for_type(metric_type) def __str__(self) -> str: + """ + String produced when calling str(...) on a Fairness metric object. + + Returns: + str: Custom string describing the object attributes. + """ return f"Metric Type: {self.metric_type}, Metric Name: '{self.metric_name}', Signal: {self.signal}" diff --git a/fl4health/strategies/fedpca.py b/fl4health/strategies/fedpca.py index 4b459411e..8f4a4a950 100644 --- a/fl4health/strategies/fedpca.py +++ b/fl4health/strategies/fedpca.py @@ -12,6 +12,7 @@ MINIMUM_PCA_ClIENTS = 2 +EVALUATE_FN_TYPE = Callable[[int, NDArrays, dict[str, Scalar]], tuple[float, dict[str, Scalar]] | None] | None class FedPCA(BasicFedAvg): @@ -23,9 +24,7 @@ def __init__( min_fit_clients: int = 2, min_evaluate_clients: int = 2, min_available_clients: int = 2, - evaluate_fn: ( - Callable[[int, NDArrays, dict[str, Scalar]], tuple[float, dict[str, Scalar]] | None] | None - ) = None, + evaluate_fn: EVALUATE_FN_TYPE = None, on_fit_config_fn: Callable[[int], dict[str, Scalar]] | None = None, on_evaluate_config_fn: Callable[[int], dict[str, Scalar]] | None = None, accept_failures: bool = True, @@ -43,27 +42,29 @@ def __init__( Args: fraction_fit (float, optional): Fraction of clients used during training. Defaults to 1.0. fraction_evaluate (float, optional): Fraction of clients used during validation. Defaults to 1.0. - min_available_clients (int, optional): Minimum number of clients used during validation. Defaults to 2. - evaluate_fn (Callable[[int, NDArrays, dict[str, Scalar]], tuple[float, dict[str, Scalar]] | None] | None): - Optional function used for central server-side evaluation. Defaults to None. + min_fit_clients (int, optional): Minimum number of clients used during fit. Defaults to 2. + min_evaluate_clients (int, optional): Minimum number of clients used during validation. Defaults to 2. + min_available_clients (int, optional): Minimum number of clients before starting FL. Defaults to 2. + evaluate_fn (EVALUATE_FN_TYPE, optional): Optional function used for central server-side evaluation. + Defaults to None. on_fit_config_fn (Callable[[int], dict[str, Scalar]] | None, optional): Function used to configure training by providing a configuration dictionary. Defaults to None. on_evaluate_config_fn (Callable[[int], dict[str, Scalar]] | None, optional): Function used to configure client-side validation by providing a ``Config`` dictionary. Defaults to None. accept_failures (bool, optional): Whether or not accept rounds containing failures. Defaults to True. initial_parameters (Parameters | None, optional): Initial global model parameters. Defaults to None. - fit_metrics_aggregation_fn (MetricsAggregationFn | None, optional): Metrics aggregation function. - Defaults to None. + fit_metrics_aggregation_fn (MetricsAggregationFn | None, optional): Metrics aggregation function. Defaults + to None. evaluate_metrics_aggregation_fn (MetricsAggregationFn | None, optional): Metrics aggregation function. Defaults to None. weighted_aggregation (bool, optional): Determines whether parameter aggregation is a linearly weighted - average or a uniform average. FedAvg default is weighted average by client dataset counts. - Defaults to True. + average or a uniform average. FedAvg default is weighted average by client dataset counts. Defaults to + True. weighted_eval_losses (bool, optional): Determines whether losses during evaluation are linearly weighted averages or a uniform average. FedAvg default is weighted average of the losses by client dataset counts. Defaults to True. - svd_merging (bool): Indicates whether merging of client principal components is done by directly performing - SVD or using a procedure based on QR decomposition. Defaults to True. + svd_merging (bool, optional): Indicates whether merging of client principal components is done by directly + performing SVD or using a procedure based on QR decomposition. Defaults to True. """ super().__init__( fraction_fit=fraction_fit, diff --git a/fl4health/strategies/flash.py b/fl4health/strategies/flash.py index da18cad7c..56c9ad8ed 100644 --- a/fl4health/strategies/flash.py +++ b/fl4health/strategies/flash.py @@ -15,6 +15,9 @@ from fl4health.strategies.basic_fedavg import BasicFedAvg +EVALUATE_FN_TYPE = Callable[[int, NDArrays, dict[str, Scalar]], tuple[float, dict[str, Scalar]] | None] | None + + class Flash(BasicFedAvg): def __init__( self, @@ -24,9 +27,7 @@ def __init__( min_fit_clients: int = 2, min_evaluate_clients: int = 2, min_available_clients: int = 2, - evaluate_fn: ( - Callable[[int, NDArrays, dict[str, Scalar]], tuple[float, dict[str, Scalar]] | None] | None - ) = None, + evaluate_fn: EVALUATE_FN_TYPE = None, on_fit_config_fn: Callable[[int], dict[str, Scalar]] | None = None, on_evaluate_config_fn: Callable[[int], dict[str, Scalar]] | None = None, accept_failures: bool = True, @@ -48,26 +49,25 @@ def __init__( Args: initial_parameters (Parameters): Initial global model parameters. - fraction_fit (float, optional): Fraction of clients used during training.. Defaults to 1.0. - fraction_evaluate (float, optional): Fraction of clients used during validation.. Defaults to 1.0. - min_fit_clients (int, optional): Minimum number of clients used during training.. Defaults to 2. - min_evaluate_clients (int, optional): Minimum number of clients used during validation.. Defaults to 2. - min_available_clients (int, optional): Minimum number of total clients in the system.. Defaults to 2. - evaluate_fn (Callable[[int, NDArrays, dict[str, Scalar]], tuple[float, dict[str, Scalar]] | - None] | None, optional): Optional function used for validation.. Defaults to None. + fraction_fit (float, optional): Fraction of clients used during training. Defaults to 1.0. + fraction_evaluate (float, optional): Fraction of clients used during validation. Defaults to 1.0. + min_fit_clients (int, optional): Minimum number of clients used during training. Defaults to 2. + min_evaluate_clients (int, optional): Minimum number of clients used during validation. Defaults to 2. + min_available_clients (int, optional): Minimum number of total clients in the system. Defaults to 2. + evaluate_fn (EVALUATE_FN_TYPE, optional): Optional function used for validation. Defaults to None. on_fit_config_fn (Callable[[int], dict[str, Scalar]] | None, optional): Function used to configure training. Defaults to None. on_evaluate_config_fn (Callable[[int], dict[str, Scalar]] | None, optional): Function used to configure validation. Defaults to None. - accept_failures (bool, optional): Whether or not accept rounds containing failures.. Defaults to True. - fit_metrics_aggregation_fn (MetricsAggregationFn | None, optional): Metrics aggregation function. - Defaults to None. + accept_failures (bool, optional): Whether or not accept rounds containing failures. Defaults to True. + fit_metrics_aggregation_fn (MetricsAggregationFn | None, optional): Metrics aggregation function. Defaults + to None. evaluate_metrics_aggregation_fn (MetricsAggregationFn | None, optional): Metrics aggregation function. Defaults to None. - eta (float, optional): Server-side learning rate.. Defaults to 1e-1. - eta_l (float, optional): Client-side learning rate.. Defaults to 1e-1. - beta_1 (float, optional): Momentum parameter.. Defaults to 0.9. - beta_2 (float, optional): Second moment parameter.. Defaults to 0.99. + eta (float, optional): Server-side learning rate. Defaults to 1e-1. + eta_l (float, optional): Client-side learning rate. Defaults to 1e-1. + beta_1 (float, optional): Momentum parameter. Defaults to 0.9. + beta_2 (float, optional): Second moment parameter. Defaults to 0.99. tau (float, optional): Controls the algorithm's degree of adaptability. Defaults to 1e-9. weighted_aggregation (bool, optional): Determines whether parameter aggregation is a linearly weighted average or a uniform average. Flash default is a uniform average by the number of clients. diff --git a/fl4health/strategies/model_merge_strategy.py b/fl4health/strategies/model_merge_strategy.py index dd8de0c8c..bcc09a73c 100644 --- a/fl4health/strategies/model_merge_strategy.py +++ b/fl4health/strategies/model_merge_strategy.py @@ -221,11 +221,12 @@ def aggregate_evaluate( assumes only metrics will be computed on client and loss is set to None. Args: + server_round (int): Which server round we're currently on. results (list[tuple[ClientProxy, EvaluateRes]]): The client identifiers and the results of their local evaluation that need to be aggregated on the server-side. These results are loss values (None in this case) and the metrics dictionary. - failures (list[tuple[ClientProxy, EvaluateRes] | BaseException]): These are the results and - exceptions from clients that experienced an issue during evaluation, such as timeouts or exceptions. + failures (list[tuple[ClientProxy, EvaluateRes] | BaseException]): These are the results and exceptions + from clients that experienced an issue during evaluation, such as timeouts or exceptions. Returns: tuple[float | None, dict[str, Scalar]]: Aggregated loss values and the aggregated metrics. The metrics diff --git a/fl4health/utils/client.py b/fl4health/utils/client.py index 8808934c8..113c8bfcb 100644 --- a/fl4health/utils/client.py +++ b/fl4health/utils/client.py @@ -124,8 +124,10 @@ def maybe_progress_bar(iterable: Iterable, display_progress_bar: bool) -> Iterab Args: iterable (Iterable): The iterable to wrap + display_progress_bar (bool): Whether we want to display a progress bar or not. + Returns: - Iterable: an iterator which acts exactly like the original iterable, but prints a dynamically updating + Iterable: An iterator which acts exactly like the original iterable, but prints a dynamically updating progress bar every time a value is requested. Or the original iterable if ``self.progress_bar`` is False """ if not display_progress_bar: diff --git a/fl4health/utils/config.py b/fl4health/utils/config.py index 20572bb81..40ff317e9 100644 --- a/fl4health/utils/config.py +++ b/fl4health/utils/config.py @@ -83,12 +83,12 @@ def narrow_dict_type_and_set_attribute( Args: self (object): The object to set attribute to dictionary[dictionary_key]. - dictionary (dict[str, Any]): A dictionary with string keys. - dictionary_key (str): The key to check dictionary for. + dictionary (dict): A dictionary with string keys. + dictionary_key (str): A dictionary with string keys. + attribute_name (str): The key to check dictionary for. narrow_type_to (type[T]): The expected type of dictionary[key]. - - Raises: - ValueError: If dictionary[key] is not of type ``narrow_type_to`` or if the key is not present in dictionary. + func (Callable[[Any], Any] | None, optional): Function to operate on the extracted value if desired. Defaults + to None. """ val = narrow_dict_type(dictionary, dictionary_key, narrow_type_to) val = func(val) if func is not None else val diff --git a/fl4health/utils/dataset.py b/fl4health/utils/dataset.py index d70ef154b..bf05f852c 100644 --- a/fl4health/utils/dataset.py +++ b/fl4health/utils/dataset.py @@ -9,6 +9,17 @@ class BaseDataset(ABC, Dataset): def __init__(self, transform: Callable | None, target_transform: Callable | None) -> None: + """ + Abstract base class for datasets used in this library. This class inherits from the torch Dataset base class. + + Args: + transform (Callable | None, optional): Optional transformation to be applied to the input data. + NOTE: This transformation is applied at load time within ``__get_item__`` + Defaults to None. + target_transform (Callable | None, optional): Optional transformation to be applied to the target data. + NOTE: This transformation is applied at load time within ``__get_item__`` + Defaults to None. + """ self.transform = transform self.target_transform = target_transform @@ -28,10 +39,32 @@ def update_target_transform(self, g: Callable) -> None: @abstractmethod def __getitem__(self, index: int) -> tuple[torch.Tensor, torch.Tensor]: + """ + Abstract method to be implemented by any inheriting dataset to produce a data value at provided index from + the underlying data. + + Args: + index (int): Index at which to extract the data from the dataset + + Raises: + NotImplementedError: Throws if one attempts to use this function. + + Returns: + tuple[torch.Tensor, torch.Tensor]: input and target tensors extracted at the provided index. + """ raise NotImplementedError @abstractmethod def __len__(self) -> int: + """ + Abstract method to be implemented by any inheriting dataset to produce a length value for the underlying data. + + Raises: + NotImplementedError: Throws if one attempts to use this function. + + Returns: + int: Length of the underlying data. + """ raise NotImplementedError @@ -43,11 +76,38 @@ def __init__( transform: Callable | None = None, target_transform: Callable | None = None, ) -> None: + """ + Basic dataset where the data and targets are assumed to be torch tensors. Optionally, this class allows the + user to perform transformations on both the data and the targets. This is useful, for example, in performing + data augmentation, label blurring, etc. + + Args: + data (torch.Tensor): input data for training + targets (torch.Tensor | None, optional): target data for training. Defaults to None. + transform (Callable | None, optional): Optional transformation to be applied to the input data. + NOTE: This transformation is applied at load time within ``__get_item__`` + Defaults to None. + target_transform (Callable | None, optional): Optional transformation to be applied to the target data. + NOTE: This transformation is applied at load time within ``__get_item__`` + Defaults to None. + """ super().__init__(transform, target_transform) self.data = data self.targets = targets def __getitem__(self, index: int) -> tuple[torch.Tensor, torch.Tensor]: + """ + Extracts the data and targets from the dataset at the provided index. Transformations are performed, as + specified in this datasets ``transform`` and ``target_transform`` functions. These are independently + applied to the data and targets, respectively. + + Args: + index (int): Index in the dataset to extract. + + Returns: + tuple[torch.Tensor, torch.Tensor]: Input data at the index after applying ``transform`` if any, targets + after applying ``target_transform`` if any. + """ assert self.targets is not None data, target = self.data[index], self.targets[index] @@ -61,6 +121,12 @@ def __getitem__(self, index: int) -> tuple[torch.Tensor, torch.Tensor]: return data, target def __len__(self) -> int: + """ + Length of the dataset as determined by len() applied to torch dataset. + + Returns: + int: length of dataset. + """ return len(self.data) @@ -72,11 +138,42 @@ def __init__( transform: Callable | None = None, target_transform: Callable | None = None, ) -> None: + """ + Dataset specifically designed to perform self-supervised learning, where we don't have a specific set of + targets, because targets are derived from the data tensors. + + Args: + data (torch.Tensor): Tensor representing the input data for the dataset. + targets (torch.Tensor | None, optional): REQUIRED TO BE NONE. The type and argument here is simply to + maintain compatibility with our TensorDataset base. Defaults to None. + transform (Callable | None, optional): Any transform to be applied to the data tensors. This transform is + performed BEFORE and target transforms that produce the self-supervised targets from the data. + NOTE: These transformations and the ``target_transform`` functions are applied AT LOAD TIME + Defaults to None. + target_transform (Callable | None, optional): Any transform to be applied to the data tensors to produce + target tensors for training. This transform is performed after and transforms for the data tensors + themselves to produce the self-supervised targets from the data. + NOTE: These transformation functions are applied AT LOAD TIME. + Defaults to None. + """ assert targets is None, "SslTensorDataset targets must be None" super().__init__(data, targets, transform, target_transform) def __getitem__(self, index: int) -> tuple[torch.Tensor, torch.Tensor]: + """ + Extracts the data and targets from the dataset at the provided index. Because this is an self-supervised + learning dataset. The input data also serves as the target (subject to transformations). Transformations + are performed, as specified in this datasets ``transform`` and ``target_transform`` functions. These are + applied first to the data, then targets are created by applying ``target_transform`` to the result. + + Args: + index (int): Index in the dataset to extract. + + Returns: + tuple[torch.Tensor, torch.Tensor]: Input data at the index after applying ``transform`` if any, targets + derived from data after applying ``transform`` and ``target_transform`` in sequence. + """ data = self.data[index] assert self.target_transform is not None, "Target transform cannot be None." @@ -108,9 +205,30 @@ def __init__(self, data: dict[str, list[torch.Tensor]], targets: torch.Tensor) - self.targets = targets def __getitem__(self, index: int) -> tuple[dict[str, torch.Tensor], torch.Tensor]: + """ + Extracts data from the ``DictionaryDataset`` at the provided index. The targets are simply extracted directly + using index. The input data dictionary is iterated through and each piece of data in the dictionary values + is extracted at the provided index and "re-wrapped" as a dictionary. + + Args: + index (int): Index of the data to be extracted from the dataset. + + Returns: + tuple[dict[str, torch.Tensor], torch.Tensor]: Dictionary with the same keys as the dataset data dictionary + with data extracted at the provided index, target data extracted from the targets tensor at index. + """ return {key: val[index] for key, val in self.data.items()}, self.targets[index] def __len__(self) -> int: + """ + Gets the length of the dataset as extracted from the first piece of data in the data dictionary. + + NOTE: This implicitly assumes that the length of the data in each entry of the dictionary of data is + uniform. + + Returns: + int: Dataset length. + """ first_key = list(self.data.keys())[0] return len(self.data[first_key]) @@ -134,12 +252,27 @@ def __init__( self.targets = targets def __getitem__(self, index: int) -> tuple[torch.Tensor, torch.Tensor]: + """ + Extracts the data at the provided index. + + Args: + index (int): Index of the data in the dataset to be returned + + Returns: + tuple[torch.Tensor, torch.Tensor]: input and targets at the provided index + """ assert self.targets is not None data, target = self.data[index], self.targets[index] return data, target def __len__(self) -> int: + """ + Returns the length of the dataset. Identical to the pytorch dataset length function. + + Returns: + int: Length of the data. + """ return len(self.data) diff --git a/fl4health/utils/dataset_converter.py b/fl4health/utils/dataset_converter.py index 2bb2bf734..6a5fb618c 100644 --- a/fl4health/utils/dataset_converter.py +++ b/fl4health/utils/dataset_converter.py @@ -27,13 +27,29 @@ def __init__( self.dataset = dataset def __getitem__(self, index: int) -> tuple[torch.Tensor, torch.Tensor]: - # Overriding this function from BaseDataset allows the converter to be compatible with the data transformers. - # converter_function is applied after the transformers. + """ + Overriding this function from BaseDataset allows the converter to be compatible with the data transformers. + ``converter_function`` is applied after the transformers. + + Args: + index (int): The index of the batch of data to be extracted + + Returns: + tuple[torch.Tensor, torch.Tensor]: Get the raw batch data (input, target) and apply the + ``converter_function`` before returning. + """ assert self.dataset is not None, "Error: no dataset is set, use convert_dataset(your_dataset: TensorDataset)" data, target = self.dataset.__getitem__(index) return self.converter_function(data, target) def __len__(self) -> int: + """ + Returns the length of the dataset. Mostly just a wrapper on the standard pytorch dataset length to ensure that + the dataset is not None. + + Returns: + int: Dataset length. + """ assert self.dataset is not None, "Error: dataset is should be either converted or initiated." return len(self.dataset) @@ -65,10 +81,15 @@ def __init__( other converter functions can be added or passed to support other conditions. Args: - condition (str | torch.Tensor | None): Could be a fixed tensor used for all the data samples, + condition (str | torch.Tensor | None, optional): Could be a fixed tensor used for all the data samples, None for non-conditional models, or a name (str) passed for other custom conversions like "label". + Defaults to None. do_one_hot_encoding (bool, optional): Should converter perform one hot encoding on the condition or not. - custom_converter_function (Callable | None, optional): User can define a new converter function. + Defaults to False. + custom_converter_function (Callable | None, optional): User can define a new converter function. Defaults + to None. + condition_vector_size (int | None, optional): Size of the conditioning vector if available. Defaults to + None. """ self.condition = condition if isinstance(self.condition, torch.Tensor): @@ -190,6 +211,9 @@ def unpack_input_condition( Args: packed_data (torch.Tensor): Data tensor used in the training loop as the input to the model. + cond_vec_size (int): Size of the conditional vector that has been packed into the ``packed_data`` variable. + data_shape (torch.Size): Expected shape of the original data tensor after unpacking the conditioning + vector. Returns: tuple[torch.Tensor, torch.Tensor]: Data in its original shape, and the condition vector to be fed into the diff --git a/fl4health/utils/nnunet_utils.py b/fl4health/utils/nnunet_utils.py index ed9b9f9bf..9e8ceec36 100644 --- a/fl4health/utils/nnunet_utils.py +++ b/fl4health/utils/nnunet_utils.py @@ -93,7 +93,7 @@ def reload_modules(packages: Sequence[str]) -> None: package or the modules themselves if a module was specified. Args: - package (Sequence[str]): The absolute names of the packages, subpackages or modules to reload. The entire + packages (Sequence[str]): The absolute names of the packages, subpackages or modules to reload. The entire import hierarchy must be specified. Eg. ``package.subpackage`` to reload all modules in subpackage, not just ``subpackage``. Packages are reloaded in the order they are given. """ @@ -212,6 +212,7 @@ def collapse_one_hot_tensor(input: torch.Tensor, dim: int = 0) -> torch.Tensor: Args: input (torch.Tensor): The binary one hot encoded tensor + dim (int, optional): Dimension over which to collapse the one-hot tensor. Defaults to 0. Returns: torch.Tensor: Integer tensor with the specified dim collapsed @@ -278,20 +279,20 @@ def __init__( should only be used for training and validation, not final testing. Args: - nnunet_dataloader (SingleThreadedAugmenter | NonDetMultiThreadedAugmenter | MultiThreadedAugmenter): The + nnunet_augmenter (SingleThreadedAugmenter | NonDetMultiThreadedAugmenter | MultiThreadedAugmenter): The dataloader used by nnunet - nnunet_config (NnUNetConfig): The nnunet config. Enum type helps ensure that nnunet config is valid + nnunet_config (NnunetConfig | str): The nnunet config. Enum type helps ensure that nnunet config is valid infinite (bool, optional): Whether or not to treat the dataset as infinite. The dataloaders sample data with replacement either way. The only difference is that if set to False, a ``StopIteration`` is generated after ``num_samples``/``batch_size`` steps. Defaults to False. - set_len (int | None): If specified overrides the dataloaders estimate of its own length with the provided - value. A ``StopIteration`` will be raised after ``set_len`` steps. If not specified the length is - determined by scaling the number of samples by the ratio of image size to the networks input patch - size. - ref_image_shape (Sequence | None): The image shape to use when computing the scaling factor used in - determining the length of the dataloader. Should be representative of the median or average image size - in the data set. If not specified a random image is loaded and its shape is used in the calculation of - the scaling factor. + set_len (int | None, optional): If specified overrides the dataloaders estimate of its own length with the + provided value. A ``StopIteration`` will be raised after ``set_len`` steps. If not specified the + length is determined by scaling the number of samples by the ratio of image size to the networks input + patch size. Defaults to None. + ref_image_shape (Sequence | None, optional): The image shape to use when computing the scaling factor used + in determining the length of the dataloader. Should be representative of the median or average image + size in the data set. If not specified a random image is loaded and its shape is used in the + calculation of the scaling factor. Defaults to None. """ # The augmenter is a wrapper on the nnunet dataloader self.nnunet_augmenter = nnunet_augmenter @@ -315,6 +316,18 @@ def __init__( self.set_len = set_len def __next__(self) -> tuple[torch.Tensor, torch.Tensor | dict[str, torch.Tensor]]: + """ + Define how the NnUNetDataLoaderWrapper selects the next item as part of standard iteration through the data + in the data loader. This is slightly more complicated due to the potentially "infinite" nature of these + data loaders within nnUnet and the use of deep supervision. See class description for more information. + + Raises: + StopIteration: When we hit the "end" of the dataset through iteration. + TypeError: Raised when the targets extracted from the batch objects are not of the right types. + + Returns: + tuple[torch.Tensor, torch.Tensor | dict[str, torch.Tensor]]: A batch of input and target data. + """ if not self.infinite and self.current_step == self.__len__(): self.reset() raise StopIteration # Raise stop iteration after epoch has completed @@ -364,6 +377,12 @@ def reset(self) -> None: self.current_step = 0 def __iter__(self) -> DataLoader: # type: ignore + """ + Define the iter conversion for an NnUNetDataLoaderWrapper. + + Returns: + DataLoader: The iterator, which is just the NnUNetDataLoaderWrapper itself + """ # mypy gets angry that the return type is different return self @@ -376,9 +395,15 @@ def shutdown(self) -> None: class Module2LossWrapper(_Loss): - """Converts a `` nn.Module`` subclass to a ``_Loss`` subclass.""" - def __init__(self, loss: nn.Module, **kwargs: Any) -> None: + """ + Converts a `` nn.Module`` subclass to a ``_Loss`` subclass. NnUnet defines their loss functions as modules + rather than true losses. This provides a type conversion. + + Args: + loss (nn.Module): Loss to be wrapped. + **kwargs (Any): Any other key word arguments that need to go to the _Loss base class. + """ super().__init__(**kwargs) self.loss = loss diff --git a/fl4health/utils/privacy_utilities.py b/fl4health/utils/privacy_utilities.py index 0bd70119c..93ed5055d 100644 --- a/fl4health/utils/privacy_utilities.py +++ b/fl4health/utils/privacy_utilities.py @@ -56,6 +56,8 @@ def convert_model_to_opacus_model( grad_sample_mode (str, optional): This determines how Opacus performs the conversion under the hood. The standard mechanism is indicated by "hooks" but other approaches may be necessary depending on how the pytorch module is defined. Defaults to "hooks". + *args (Any): Any other args that need to go to the wrap function. + **kwargs (Any): Another other kwargs that need to go to the wrap function. Returns: GradSampleModule: The Opacus wrapped ``GradSampleModule`` @@ -76,9 +78,11 @@ def map_model_to_opacus_model( Args: model (nn.Module): Pytorch model to be converted to an Opacus compliant ``GradSampleModule`` - grad_sample_mode (str, optional): This determines how Opacus performs the conversion under the hood. The + grad_sample_mode (str, optional): his determines how Opacus performs the conversion under the hood. The standard mechanism is indicated by "hooks" but other approaches may be necessary depending on how the pytorch module is defined. Defaults to "hooks". + *args (Any): Any other args that need to go to the conversion function. + **kwargs (Any): Another other kwargs that need to go to the conversion function. Returns: GradSampleModule: The Opacus-compliant, wrapped ``GradSampleModule`` diff --git a/pyproject.toml b/pyproject.toml index 71cda01fc..616c9b5b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -166,7 +166,6 @@ ignore = [ "D203", # 1 blank line required before class docstring "D205", # 1 blank line required between summary line and description "ERA001", # Found commented-out code (too many false positives with math comments) - "PLR2004", # Replace magic number with named constant "PLR0913", # Too many arguments "COM812", # Missing trailing comma "A001", # Ignore variable `input` is shadowing a Python builtin @@ -179,16 +178,19 @@ ignore = [ "D101", # Public class requires documentation. Ignoring since we document our inits "D100", # TEMPORARY IGNORE: Ignore module level docstrings requirement "D104", # TEMPORARY IGNORE: Ignore package level docstrings requirement - "D102", # TEMPORARY IGNORE WILL FIX ONE BY ONE - "D103", # TEMPORARY IGNORE WILL FIX ONE BY ONE - "D107", # TEMPORARY IGNORE WILL FIX ONE BY ONE - "D417", # TEMPORARY IGNORE WILL FIX ONE BY ONE - "D105", # TEMPORARY IGNORE WILL FIX ONE BY ONE + "D103", # TEMPORARY IGNORE WILL FIX: Undocumented public function + "D102", # TEMPORARY IGNORE WILL FIX: Undocumented public method ] # Ignore import violations in all `__init__.py` files. [tool.ruff.lint.per-file-ignores] "__init__.py" = ["E402", "F401", "F403", "F811"] +# Ignoring undocumented public functions, public init, magic method, and magic numbers +"tests/*" = ["D103", "D105", "D107", "PLR2004"] +# Ignoring undocumented, public init, magic method, and magic numbers +"research/*" = ["PLR2004", "D105", "D107"] +# Ignoring undocumented public init +"examples/*" = ["D107"] [tool.ruff.lint.pep8-naming] ignore-names = ["SimpleITK", "F", "NN"] diff --git a/research/picai/data/data_utils.py b/research/picai/data/data_utils.py index 471a43a33..4a32a62f4 100644 --- a/research/picai/data/data_utils.py +++ b/research/picai/data/data_utils.py @@ -249,17 +249,17 @@ def get_dataloader( Initializes and returns MONAI Dataloader. Args: - img_paths (Sequence[Sequence[str]]: List of list of strings where the outer list represents a + img_paths (Sequence[Sequence[str]] | Sequence[str]): List of list of strings where the outer list represents a list of file paths corresponding to the different MRI Sequences for a given patient exam. seg_paths (Sequence[str]): List of strings representing the segmentation labels associated with images. - batch_size (str): The number of samples per batch yielded by the DataLoader. + batch_size (int): The number of samples per batch yielded by the DataLoader. img_transform (Compose): The series of transformations applied to input images during dataloading. seg_transform (Compose): The series of transformations applied to the segmentation labels during dataloading. - shuffle (bool): Whether or not to shuffle the dataset. - num_workers (int): The number of workers used by the DataLoader. + shuffle (bool, optional): Whether or not to shuffle the dataset. Defaults to False. + num_workers (int, optional): The number of workers used by the DataLoader. Defaults to 2. Returns: - DataLoader: MONAI dataloader. + DataLoader: MONAI dataloader. """ # Ignoring type of image_files because Sequence[Sequence[str]] is valid input # list of files interpreted as multi-parametric sequence. Supported by image loader: diff --git a/research/picai/data/prepare_data.py b/research/picai/data/prepare_data.py index 1edbcfed3..a3db27996 100644 --- a/research/picai/data/prepare_data.py +++ b/research/picai/data/prepare_data.py @@ -164,24 +164,24 @@ def prepare_data( Args: scans_read_dir (Path): The path to read the scans from. Should be a directory with subdirectories for each patient_id. Inside the subdirectories should be all the scan files for a given patient. - annotation_read_dir (Path): The path to read the annotations from. Should be a flat directory with all + annotation_read_dir (Path): he path to read the annotations from. Should be a flat directory with all annotation files. scans_write_dir (Path): The path to write the scans to. All scans are written into same directory. - annotation_write_dir (Path): The path to write the annotations to. - All annotations are written into same directory. - overviews_write_dir (Path): The path where the dataset json files are located. For each split 1-5, + annotation_write_dir (Path): The path to write the annotations to. All annotations are written into same + directory. + overview_write_dir (Path): The path where the dataset json files are located. For each split 1-5, there is a train and validation file with scan paths, label paths and case labels. - size (tuple[int, int, int] | None): Desired dimensions of preprocessed scans in voxels. - Triplet of the form: Depth x Height x Width. - physical_size (tuple[float, float, float] | None): Desired dimensions of preprocessed scans in mm. - Simply the product of the number of voxels by the spacing along a particular - dimension: Triplet of the form: Depth x Height x Width. - spacing (tuple[float, float, float] | None): Desired spacing of preprocessed scans in in mm/voxel. - Triplet of the form: Depth x Height x Width. - scan_extension (str): The expected extension of scan file paths. - annotation_extension (str): The expected extension of annotation file paths. - num_threads (str): The number of threads to use during preprocessing. - splits_path (Path | None): The path to the file containing the splits. + size (tuple[int, int, int] | None, optional): Desired dimensions of preprocessed scans in voxels. + Triplet of the form: Depth x Height x Width. Defaults to None. + physical_size (tuple[float, float, float] | None, optional): Desired dimensions of preprocessed scans in mm. + Simply the product of the number of voxels by the spacing along a particular dimension: Triplet of the + form: Depth x Height x Width. Defaults to None. + spacing (tuple[float, float, float] | None, optional): Desired spacing of preprocessed scans in in mm/voxel. + Triplet of the form: Depth x Height x Width. Defaults to None. + scan_extension (str, optional): The expected extension of scan file paths. Defaults to "mha". + annotation_extension (str, optional): The expected extension of annotation file paths. Defaults to ".nii.gz". + num_threads (int, optional): The number of threads to use during preprocessing. Defaults to 4. + splits_path (Path | None, optional): The path to the file containing the splits. Defaults to None. """ settings = PreprocessingSettings( scans_write_dir, diff --git a/research/picai/data/preprocessing.py b/research/picai/data/preprocessing.py index 96afcd036..c3f653c39 100644 --- a/research/picai/data/preprocessing.py +++ b/research/picai/data/preprocessing.py @@ -72,7 +72,7 @@ def __init__( Args: scan_paths (Sequence[Path]): The set of paths where the scans associated with the Case are located. - annotation_write_dir (Path): The path where the annotation associated with the Case is located. + annotations_path (Path): The path where the annotation associated with the Case is located. settings (PreprocessingSettings): The settings determining how the case is preprocessed. """ self.scan_paths = scan_paths @@ -128,7 +128,7 @@ def __init__( Args: scan_paths (Sequence[Path]): The set of paths where the scans associated with the Case are located. **NOTE:** self.scans will inherit the ordering of scan_paths and must remain consistently ordered. - annotation_write_dir (Path): The path where the annotation associated with the Case is located. + annotations_path (Path): The path where the annotation associated with the Case is located. settings (PreprocessingSettings): The settings determining how the case is preprocessed. """ super().__init__(scan_paths, annotations_path, settings) @@ -367,7 +367,7 @@ def preprocess( Args: cases (list[Case]): A list of cases to be preprocessed. transforms (Sequence[PreprocessingTransform]): The sequence of transformation to be applied. - nums_threads (int): The number of threads to use for preprocessing. + num_threads (int): The number of threads to use for preprocessing. Returns: Sequence[tuple[Sequence[Path], Path]]: A sequence of tuples in which the first entry is a sequence of diff --git a/research/picai/data/preprocessing_transforms.py b/research/picai/data/preprocessing_transforms.py index 7e05f560a..b495d445e 100644 --- a/research/picai/data/preprocessing_transforms.py +++ b/research/picai/data/preprocessing_transforms.py @@ -141,10 +141,11 @@ def crop_or_pad( Args: image (sitk.Image): Image to be resized. - size (tuple[int, int, int]): Target size in voxels. - Expected to be in Depth x Height x Width format. - physical_size (tuple[float, float, float]): Target size in mm. (Number of Voxels x Spacing) - Expected to be in Depth x Height x Width format. + size (tuple[int, int, int]): Target size in voxels. Expected to be in Depth x Height x Width format. + physical_size (tuple[float, float, float] | None, optional): Target size in mm. (Number of Voxels x Spacing) + Expected to be in Depth x Height x Width format. Defaults to None. + crop_only (bool, optional): Whether to only crop the image (True) or also perform padding (False). Defaults t + o False. Returns: sitk.Image: Cropped or padded image. diff --git a/research/picai/fl_nnunet/nnunet_utils.py b/research/picai/fl_nnunet/nnunet_utils.py index 061d260ed..cac3804f9 100644 --- a/research/picai/fl_nnunet/nnunet_utils.py +++ b/research/picai/fl_nnunet/nnunet_utils.py @@ -151,9 +151,9 @@ def __init__( should only be used for training and validation, not final testing. Args: - nnunet_dataloader (SingleThreadedAugmenter | NonDetMultiThreadedAugmenter | MultiThreadedAugmenter): The + nnunet_augmenter (SingleThreadedAugmenter | NonDetMultiThreadedAugmenter | MultiThreadedAugmenter): The dataloader used by nnunet - nnunet_config (NnUNetConfig): The nnunet config. Enum type helps ensure that nnunet config is valid + nnunet_config (NnunetConfig | str): The nnunet config. Enum type helps ensure that nnunet config is valid. infinite (bool, optional): Whether or not to treat the dataset as infinite. The dataloaders sample data with replacement either way. The only difference is that if set to False, a StopIteration is generated after num_samples/batch_size steps. Defaults to False. diff --git a/research/picai/fl_nnunet/transforms.py b/research/picai/fl_nnunet/transforms.py index 7fe0b0a79..c3200b7c2 100644 --- a/research/picai/fl_nnunet/transforms.py +++ b/research/picai/fl_nnunet/transforms.py @@ -57,6 +57,7 @@ def collapse_one_hot_tensor(input: torch.Tensor, dim: int = 0) -> torch.Tensor: Args: input (torch.Tensor): The binary one hot encoded tensor + dim (int, optional): Dimension over which to collapse the one-hot tensor. Defaults to 0. Returns: torch.Tensor: Integer tensor with the specified dim collapsed diff --git a/research/picai/monai_scripts/auto3dseg.py b/research/picai/monai_scripts/auto3dseg.py index 3df4cf14f..4627f01a8 100644 --- a/research/picai/monai_scripts/auto3dseg.py +++ b/research/picai/monai_scripts/auto3dseg.py @@ -18,14 +18,14 @@ def gen_dataset_list(data_dir: str, output_path: str | None = None, ext: str = " """ Generates a MONAI dataset list for an nnUNet structured dataset. - **NOTE:** Rather than having a single image and label, this checks for multiple - channels following the nnunet dataset formatting guidelines, and passes - a list of filepaths for each channel as the value for the image key + NOTE: Rather than having a single image and label, this checks for multiple channels following the nnunet dataset + formatting guidelines, and passes a list of filepaths for each channel as the value for the image key Args: data_dir (str): Path to the nnUNet_raw dataset. - output_path (str | None): Where and what to save the file as. Must be a json. - Default is to save as datalist.json in the data_dir + output_path (str | None, optional): Where and what to save the file as. Must be a json. Default is to save as + datalist.json in the data_dir. Defaults to None. + ext (str, optional): Extention to use. Defaults to ".nii.gz". Returns: str: The path to where the datalist file was saved diff --git a/research/picai/nnunet_scripts/eval.py b/research/picai/nnunet_scripts/eval.py index d3dff7c4c..24584a99b 100644 --- a/research/picai/nnunet_scripts/eval.py +++ b/research/picai/nnunet_scripts/eval.py @@ -321,19 +321,17 @@ def get_picai_metrics( annotations. Extends picai_eval to allow multiclass evaluation. Args: - detection_maps_folder (str | Path): Path to the folder - containing the detection maps - ground_truth_annotations_folder (NDArray): The ground truth - annotations. Must have shape (num_samples, num_classes or - num_lesion_classes, ...). If num_classes is provided, the function - will attempt to remove the background class from index 0 for you - case_identifiers (list[str] | None, optional): A list of case - identifiers. If not provided the subjects will be identified by - their index Defaults to None. - verbose (bool): Whether or not to print a log statement summarizing - results. Defaults to True - **kwargs: Keyword arguments for the picai_eval.eval.evaluate_case - function + detection_map_folder (str | Path): Path to the folder containing the detection maps + ground_truth_annotations_folder (NDArray): The ground truth annotations. Must have shape + (num_samples, num_classes or num_lesion_classes, ...). If num_classes is provided, the function will + attempt to remove the background class from index 0 for you + num_threads (int | None): Number of threads to be used in the computations. Defaults to None. + sample_weights: (list[float] | None): Weights on each sample to be used in metrics calculations. Defaults to + None. + case_identifiers (list[str] | None, optional): A list of case identifiers. If not provided the subjects will + be identified by their index Defaults to None. + verbose (bool): Whether or not to print a log statement summarizing results. Defaults to True + **kwargs: Keyword arguments for the picai_eval.eval.evaluate_case function Raises: KeyError: If you try to use the y_det, y_true, or subject_list keyword diff --git a/research/picai/nnunet_scripts/old_nnunet_inference/predict_and_eval_old.py b/research/picai/nnunet_scripts/old_nnunet_inference/predict_and_eval_old.py index db58e231e..80e76f774 100644 --- a/research/picai/nnunet_scripts/old_nnunet_inference/predict_and_eval_old.py +++ b/research/picai/nnunet_scripts/old_nnunet_inference/predict_and_eval_old.py @@ -27,42 +27,33 @@ def pred_and_eval( verbose: bool = True, ) -> None: """ - Runs inference on raw input data given a number of compatible nnunet - models. Then extracts detection maps from those predictions and evaluates - the model using the standardPICAI evaluation metrics. + Runs inference on raw input data given a number of compatible nnunet models. Then extracts detection maps from + those predictions and evaluates the model using the standardPICAI evaluation metrics. Args: - config_path (str): Path to a yaml config file. The three required keys - are plans, dataset_json and one or more nnunet_configs (e.g. 2d, - 3d_fullres etc.). The nnunet config keys should contain a list of - paths. If the path points to a file it should be a model - checkpoint. The model checkpoints can be dicts with the - 'network_weights' key or nn.Modules. If the path points to a - directory it should be an nnunet results folder for a particular - dataset-config-trainer combo. The plans key should be the path to - the nnunet model plans json file. The dataset_json key should be - the path to the dataset json of one of the training datasets. Or - create a new json yourself with the 'label' and 'file_ending' keys - and their corresponding values as specified by nnunet. - inputs_folder (str): Path to the folder containing the raw input data - that has not been processed by nnunet yet. File names must follow - the nnunet convention where each channel modality is stored as a - separate file. File names should be case-identifier_0000 where - 0000 is a 4 digit integer representing the channel/modality of the - image. All cases must have the same N channels numbered from 0 to - N. - labels_folder (str): Path to the folder containing the ground truth - annotation maps. File names must match the case identifiers of the input images - output_folder (str | None, optional): Path to the output folder. By - default the only output is a 'metrics.json' file containing the - evaluation results. If left as none then nothing is saved. - Defaults to None. - save_probability_maps (bool, optional): Whether or not to save the - predicted probability maps. Defaults to False. - save_detection_maps (bool, optional): Whether or not to save the - predicted lesion detection maps. Defaults to False. - verbose (bool, optional): Whether or not to print logs to stdout. - Defaults to True. + config_path (str): Path to a yaml config file. The three required keys are plans, dataset_json and one or more + nnunet_configs (e.g. 2d, 3d_fullres etc.). The nnunet config keys should contain a list of paths. If the + path points to a file it should be a model checkpoint. The model checkpoints can be dicts with the + 'network_weights' key or nn.Modules. If the path points to a directory it should be an nnunet results + folder for a particular dataset-config-trainer combo. The plans key should be the path to the nnunet model + plans json file. The dataset_json key should be the path to the dataset json of one of the training + datasets. Or create a new json yourself with the 'label' and 'file_ending' keys and their corresponding + values as specified by nnunet. + inputs_folder (str): Path to the folder containing the raw input data that has not been processed by nnunet + yet. File names must follow the nnunet convention where each channel modality is stored as a separate + file. File names should be case-identifier_0000 where 0000 is a 4 digit integer representing the + channel/modality of the image. All cases must have the same N channels numbered from 0 to N. + labels_folder (str): Path to the folder containing the ground truth annotation maps. File names must match + the case identifiers of the input images + output_folder (str | None, optional): Path to the output folder. By default the only output is a + 'metrics.json' file containing the evaluation results. If left as none then nothing is saved. + save_probability_maps (bool, optional): Whether or not to save the predicted probability maps. + Defaults to False. + save_detection_maps (bool, optional): Whether or not to save the predicted lesion detection maps. + Defaults to False. + save_annotations (bool, optional): Whether or not to save the predicted lesion annotations maps. + Defaults to False. + verbose (bool, optional): Whether or not to print logs to stdout. Defaults to True. """ # Ensure an output folder is specified if save_probability_maps or save_detection_maps or save_annotations: diff --git a/research/picai/nnunet_scripts/old_nnunet_inference/predict_old.py b/research/picai/nnunet_scripts/old_nnunet_inference/predict_old.py index 4614d2f32..b197fd244 100644 --- a/research/picai/nnunet_scripts/old_nnunet_inference/predict_old.py +++ b/research/picai/nnunet_scripts/old_nnunet_inference/predict_old.py @@ -227,44 +227,34 @@ def predict( verbose: bool = True, ) -> tuple[NDArray, NDArray, list[str]]: """ - Uses multiprocessing to quickly do model inference for a single model, a - group of models with the same nnunet config or an ensemble of different - nnunet configs each with one or more models. + Uses multiprocessing to quickly do model inference for a single model, a group of models with the same nnunet + config or an ensemble of different nnunet configs each with one or more models. Args: - config_path (str): Path to a yaml config file. The three required keys - are plans, dataset_json and one or more nnunet_configs (e.g. 2d, - 3d_fullres etc.). The nnunet config keys should contain a list of - paths. If the path points to a file it should be a model - checkpoint. The model checkpoints can be dicts with the - 'network_weights' key or nn.Modules. If the path points to a - directory it should be an nnunet results folder for a particular - dataset-config-trainer combo. The plans key should be the path to - the nnunet model plans json file. The dataset_json key should be - the path to the dataset json of one of the training datasets. Or - create a new json yourself with the 'label' and 'file_ending' keys - and their corresponding values as specified by nnunet - input_folder (str): Path to the folder containing the raw input data - that has not been processed by nnunet yet. File names must follow the - nnunet convention where each channel modality is stored as a - separate file.File names should be case-identifier_0000 where 0000 - is a 4 digit integer representing the channel/modality of the - image. All cases must have the same number of channels N numbered - from 0 to N. - preds_folder (str | None): [OPTIONAL] Path to the output folder to - save the model predicted probabilities. If not provided the - probabilities are not saved - annotations_folder (str | None): [OPTIONAL] Path to the output - folder to save the model predicted annotations. If not provided the - annotations are not saved + config_path (str): Path to a yaml config file. The three required keys are plans, dataset_json and one or more + nnunet_configs (e.g. 2d, 3d_fullres etc.). The nnunet config keys should contain a list of paths. If the + path points to a file it should be a model checkpoint. The model checkpoints can be dicts with the + 'network_weights' key or nn.Modules. If the path points to a directory it should be an nnunet results + folder for a particular dataset-config-trainer combo. The plans key should be the path to the nnunet model + plans json file. The dataset_json key should be the path to the dataset json of one of the training + datasets. Or create a new json yourself with the 'label' and 'file_ending' keys and their + corresponding values as specified by nnunet + input_folder (str): Path to the folder containing the raw input data that has not been processed by nnunet + yet. File names must follow the nnunet convention where each channel modality is stored as a separate + file. File names should be case-identifier_0000 where 0000 is a 4 digit integer representing the + channel/modality of the image. All cases must have the same number of channels N numbered from 0 to N. + probs_folder (str | None, optional): Path to the output folder to save the model predicted probabilities. If + not provided the probabilities are not saved. Defaults to None. + annotations_folder (str | None, optional):Path to the output folder to save the model predicted annotations. + Defaults to None. + verbose (bool, optional): Setting this to false will limit the amount of logging produced by this function. + Defaults to True. + Returns: - NDArray[float]: a numpy array with a single predicted probability map - for each input image. Shape: (num_samples, num_classes, ...). - NDArray[int]: a numpy array with a single predicted annotation map for - each input image. Unlike the predicted probabilities these are NOT - one hot encoded. Shape: (num_samples, spatial_dims...) - list[str]: A list containing the unique case identifier for - each prediction + tuple[NDArray, NDArray, list[str]]: A numpy array with a single predicted probability map for each input + image. Shape: (num_samples, num_classes, ...). A numpy array with a single predicted annotation map for + each input image. Unlike the predicted probabilities these are NOT one hot encoded. + Shape: (num_samples, spatial_dims...). A list containing the unique case identifier for each prediction. """ t_start = time.time() # Load config and nnunet required dicts diff --git a/research/picai/nnunet_scripts/predict.py b/research/picai/nnunet_scripts/predict.py index 14a23f3c7..868c03017 100644 --- a/research/picai/nnunet_scripts/predict.py +++ b/research/picai/nnunet_scripts/predict.py @@ -134,45 +134,34 @@ def predict( verbose: bool = True, ) -> None: """ - Uses multiprocessing to quickly do model inference for a single model, a - group of models with the same nnunet config or an ensemble of different - nnunet configs each with one or more models. + Uses multiprocessing to quickly do model inference for a single model, a group of models with the same nnunet + config or an ensemble of different nnunet configs each with one or more models. Args: - config_path (str): Path to a yaml config file. The three required keys - are plans, dataset_json and one or more nnunet_configs (e.g. 2d, - 3d_fullres etc.). The nnunet config keys should contain a list of - paths. If the path points to a file it should be a model - checkpoint. The model checkpoints can be dicts with the - 'network_weights' key or nn.Modules. If the path points to a - directory it should be an nnunet results folder for a particular - dataset-config-trainer combo. The plans key should be the path to - the nnunet model plans json file. The dataset_json key should be - the path to the dataset json of one of the training datasets. Or - create a new json yourself with the 'label' and 'file_ending' keys - and their corresponding values as specified by nnunet. A !join - constructor that maps to os.path.join has been defined when - loading the config to allow the user to make their configs more - readable. Eg. - base_path: &base_path /home/user/data - dataset_json: !join [*base_path, 'PICAI', 'dataset.json'] - input_folder (str): Path to the folder containing the raw input data - that has not been processed by nnunet yet. File names must follow the - nnunet convention where each channel modality is stored as a - separate file.File names should be case-identifier_0000 where 0000 - is a 4 digit integer representing the channel/modality of the - image. All cases must have the same number of channels N numbered - from 0 to N. - output_folder (str): Path to save the predicted probabilities and - predicted annotations. Each will be stored in a separate - subdirectory. Probabilities will be stored as .npz files. - The NPZ file object will have the key 'probabilities'. The - predicted annotations will be saved as the original input image - file format - probs_folder_name (str): What to name the folder within the - output folder that the probabilities will be stored in - annotations_folder_name (str): What to name the folder within the - output folder that the predicted annotations will be stored in + config_path (str): Path to a yaml config file. The three required keys are plans, dataset_json and one or + more nnunet_configs (e.g. 2d, 3d_fullres etc.). The nnunet config keys should contain a list of paths. If + the path points to a file it should be a model checkpoint. The model checkpoints can be dicts with the + 'network_weights' key or nn.Modules. If the path points to a directory it should be an nnunet results + folder for a particular dataset-config-trainer combo. The plans key should be the path to the nnunet + model plans json file. The dataset_json key should be the path to the dataset json of one of the training + datasets. Or create a new json yourself with the 'label' and 'file_ending' keys and their corresponding + values as specified by nnunet. A !join constructor that maps to os.path.join has been defined when loading + the config to allow the user to make their configs more readable. Eg. + base_path: &base_path /home/user/data + dataset_json: !join [*base_path, 'PICAI', 'dataset.json'] + input_folder (str): Path to the folder containing the raw input data that has not been processed by nnunet + yet. File names must follow the nnunet convention where each channel modality is stored as a separate file. + File names should be case-identifier_0000 where 0000 is a 4 digit integer representing the + channel/modality of the image. All cases must have the same number of channels N numbered from 0 to N. + output_folder (str): Path to save the predicted probabilities and predicted annotations. Each will be stored + in a separate subdirectory. Probabilities will be stored as .npz files. The NPZ file object will have the + key 'probabilities'. The predicted annotations will be saved as the original input image file format + probs_folder_name (str, optional): What to name the folder within the output folder that the probabilities + will be stored in. Defaults to "predicted_probability_maps". + annotations_folder_name (str, optional): What to name the folder within the output folder that the predicted + annotations will be stored in. Defaults to "predicted_annotations". + verbose (bool, optional): Setting this to false will limit the amount of logging produced by this function. + Defaults to True. """ # Note: I should split output folder into two separate paths for model outputs t_start = time.time() diff --git a/research/picai/nnunet_scripts/transfer_train.py b/research/picai/nnunet_scripts/transfer_train.py index 3c161a3e9..213253dc1 100644 --- a/research/picai/nnunet_scripts/transfer_train.py +++ b/research/picai/nnunet_scripts/transfer_train.py @@ -142,7 +142,7 @@ def check_configs(plans_path: str, configs: list) -> None: Raises an error if configs are not found in plans json. Args: - plans (str): Path to the plans file to check + plans_path (str): Path to the plans file to check configs (list): A list of nnunet configs to check for """ plans = load_json(plans_path) diff --git a/tests/smoke_tests/run_smoke_test.py b/tests/smoke_tests/run_smoke_test.py index 92a455591..dbb206e1c 100644 --- a/tests/smoke_tests/run_smoke_test.py +++ b/tests/smoke_tests/run_smoke_test.py @@ -190,32 +190,51 @@ async def run_smoke_test( loop.close() Args: - server_python_path (str): the path for the executable server module - client_python_path (str): the path for the executable client module - config_path (str): the path for the config yaml file. The following attributes are required - by this function: - `n_clients`: the number of clients to be started - `n_server_rounds`: the number of rounds to be ran by the server - `batch_size`: the size of the batch, to be used by the dataset preloader - dataset_path (str): the path of the dataset. Depending on which dataset is being used, it will ty to preload it + server_python_path (str): The path for the executable server module + client_python_path (str): The path for the executable client module + config_path (str): The path for the config yaml file. The following attributes are required by this function: + + - `n_clients`: the number of clients to be started + - `n_server_rounds`: the number of rounds to be ran by the server + - `batch_size`: the size of the batch, to be used by the dataset preloader + + partial_config_path (str): The path for the partial config yaml file. Used to pass to server and client to + partially execute an FL run to validate checkpointing. The following attributes are required by this + function and should be the same as those at config_path except n_server_rounds which should be 1: + + - `n_clients`: the number of clients to be started + - `n_server_rounds`: the number of rounds to be ran by the server + - `batch_size`: the size of the batch, to be used by the dataset preloader + + dataset_path (str): The path of the dataset. Depending on which dataset is being used, it will ty to preload it to avoid problems when running on different runtimes. - checkpoint_path (str | None): Optional, default None. If set, it will send that path as a checkpoint model - to the client. - assert_evaluation_logs (bool | None): Optional, default `False`. Set this to `True` if testing an - evaluation model, which produces different log outputs. - skip_assert_client_fl_rounds (str | None): Optional, default `False`. If set to `True`, will skip the - assertion of the "Current FL Round" message on the clients' logs. This is necessary because some clients - (namely client_level_dp, client_level_dp_weighted, instance_level_dp) do not reliably print that message. - seed (int | None): The random seed to be passed in to both the client and the server. - server_metrics (dict[str, Any] | None): A dictionary of metrics to be checked against the metrics file - saved by the server. Should be in the same format as fl4health.reporting.metrics.MetricsReporter. - Default is None. - client_metrics (dict[str, Any] | None): A dictionary of metrics to be checked against the metrics file - saved by the clients. Should be in the same format as fl4health.reporting.metrics.MetricsReporter. - Default is None. + checkpoint_path (str | None, optional): If set, it will send that path as a model checkpoint path + to the client. Defaults to None. + assert_evaluation_logs (bool | None, optional): Set this to `True` if testing an evaluation model, which + produces different log outputs. Defaults to False. + skip_assert_client_fl_rounds (bool | None, optional): If set to `True`, will skip the assertion of the + "Current FL Round" message on the clients' logs. This is necessary because some clients (namely + ``client_level_dp``, ``client_level_dp_weighted``, ``instance_level_dp``) do not reliably print that + message. Defaults to False. + seed (int | None, optional): The random seed to be passed in to both the client and the server. + Defaults to None. + server_metrics (dict[str, Any] | None, optional): A dictionary of metrics to be checked against the metrics + file saved by the server. Should be in the same format as ``fl4health.reporting.metrics.MetricsReporter``. + Defaults to None. + client_metrics (dict[str, Any] | None, optional): A dictionary of metrics to be checked against the metrics + file saved by the clients. Should be in the same format as ``fl4health.reporting.metrics.MetricsReporter.`` + Defaults to None. + tolerance (float, optional): Tolerance associated with natural metrics fluctuations. Defaults to + DEFAULT_TOLERANCE. + read_logs_timeout (int, optional):Timeout in seconds associated with reading the logs of the clients and + servers. Defaults to DEFAULT_READ_LOGS_TIMEOUT + + Raises: + SmokeTestAssertError: Various assertions are raised when metrics do not match or the clients/server processes + do not finish correctly Returns: - (server_errors, client_errors): (list[str], list[str]): list of errors from server and client processes, + tuple[list[str], list[str]]: (server_errors, client_errors) list of errors from server and client processes, respectively. """ try: @@ -373,32 +392,41 @@ async def run_fault_tolerance_smoke_test( Runs a smoke test for a given server, client, and dataset configuration. Args: - server_python_path (str): the path for the executable server module - client_python_path (str): the path for the executable client module - config_path (str): the path for the config yaml file. The following attributes are required - by this function: - `n_clients`: the number of clients to be started - `n_server_rounds`: the number of rounds to be ran by the server - `batch_size`: the size of the batch, to be used by the dataset preloader - partial_config_path (str): the path for the partial config yaml file. - Used to pass to server and client to partially execute an FL run to validate checkpointing. - - The following attributes are required - by this function and should be the same as those at config_path except n_server_rounds which should be 1: - `n_clients`: the number of clients to be started - `n_server_rounds`: the number of rounds to be ran by the server - `batch_size`: the size of the batch, to be used by the dataset preloader - dataset_path (str): the path of the dataset. Depending on which dataset is being used, it will ty to preload it + server_python_path (str): The path for the executable server module + client_python_path (str): The path for the executable client module + config_path (str): The path for the config yaml file. The following attributes are required by this function: + + - `n_clients`: the number of clients to be started + - `n_server_rounds`: the number of rounds to be ran by the server + - `batch_size`: the size of the batch, to be used by the dataset preloader + + partial_config_path (str): The path for the partial config yaml file. Used to pass to server and client to + partially execute an FL run to validate checkpointing. The following attributes are required by this + function and should be the same as those at config_path except n_server_rounds which should be 1: + + - `n_clients`: the number of clients to be started + - `n_server_rounds`: the number of rounds to be ran by the server + - `batch_size`: the size of the batch, to be used by the dataset preloader + + dataset_path (str): The path of the dataset. Depending on which dataset is being used, it will ty to preload it to avoid problems when running on different runtimes. - intermediate_checkpoint_dir (str): Path to store intermediate checkpoints for server and client. - seed (int | None): The random seed to be passed in to both the client and the server. server_metrics (dict[str, Any]): A dictionary of metrics to be checked against the metrics file - saved by the server. Should be in the same format as fl4health.reporting.metrics.MetricsReporter. + saved by the server. Should be in the same format as ``fl4health.reporting.metrics.MetricsReporter``. client_metrics (dict[str, Any]): A dictionary of metrics to be checked against the metrics file - saved by the clients. Should be in the same format as fl4health.reporting.metrics.MetricsReporter. + saved by the clients. Should be in the same format as ``fl4health.reporting.metrics.MetricsReporter.`` + seed (int | None, optional): The random seed to be passed in to both the client and the server. + Defaults to None. + intermediate_checkpoint_dir (str, optional): Path to store intermediate checkpoints for server and client. + Defaults to "./". + server_name (str, optional): Name of the server class. This is often used for naming checkpoints and other + artifacts. Defaults to "server". + tolerance (float, optional): Tolerance associated with natural metrics fluctuations. Defaults to + DEFAULT_TOLERANCE. + read_logs_timeout (int, optional): Timeout in seconds associated with reading the logs of the clients and + servers. Defaults to DEFAULT_READ_LOGS_TIMEOUT. Returns: - (server_errors, client_errors): (list[str], list[str]): list of errors from server and client processes, + tuple[list[str], list[str]]: (server_errors, client_errors) list of errors from server and client processes, respectively. """ try: From 37f221faebc415be6c166e36e4a6fd84b9ca1548 Mon Sep 17 00:00:00 2001 From: David Emerson <43939939+emersodb@users.noreply.github.com> Date: Fri, 20 Jun 2025 11:49:13 -0400 Subject: [PATCH 3/4] Some small docs updates --- fl4health/clients/evaluate_client.py | 2 +- fl4health/clients/fedrep_client.py | 2 +- .../feature_alignment/tab_features_preprocessor.py | 2 +- fl4health/model_bases/ensemble_base.py | 6 +++--- .../parameter_selection_criteria.py | 6 +++--- fl4health/privacy/moments_accountant.py | 4 ++-- fl4health/utils/config.py | 2 +- fl4health/utils/dataset.py | 12 ++++++------ fl4health/utils/dataset_converter.py | 2 +- fl4health/utils/nnunet_utils.py | 2 +- fl4health/utils/privacy_utilities.py | 4 ++-- research/picai/data/prepare_data.py | 2 +- research/picai/data/preprocessing_transforms.py | 4 ++-- research/picai/nnunet_scripts/transfer_train.py | 4 ++-- tests/smoke_tests/run_smoke_test.py | 8 ++++---- 15 files changed, 31 insertions(+), 31 deletions(-) diff --git a/fl4health/clients/evaluate_client.py b/fl4health/clients/evaluate_client.py index 78dc8f9c4..94e157255 100644 --- a/fl4health/clients/evaluate_client.py +++ b/fl4health/clients/evaluate_client.py @@ -45,7 +45,7 @@ def __init__( "cuda" loss_meter_type (LossMeterType, optional): Type of meter used to track and compute the losses over each batch. Defaults to ``LossMeterType.AVERAGE``. - model_checkpoint_path (Path | None, optional): path to which the model should be checkpointed. Defaults to + model_checkpoint_path (Path | None, optional): Path to which the model should be checkpointed. Defaults to None. reporters (Sequence[BaseReporter] | None, optional): A sequence of FL4Health reporters which the client should send data to. Defaults to None. diff --git a/fl4health/clients/fedrep_client.py b/fl4health/clients/fedrep_client.py index 2ece53f24..799a4d17a 100644 --- a/fl4health/clients/fedrep_client.py +++ b/fl4health/clients/fedrep_client.py @@ -323,7 +323,7 @@ def train_fedrep_by_epochs( Returns: tuple[dict[str, float], dict[str, Scalar]]: The loss and metrics dictionary from the local training. - L oss is a dictionary of one or more losses that represent the different components of the loss. + Loss is a dictionary of one or more losses that represent the different components of the loss. """ # First we train the head module for head_epochs with the representations frozen in place self._prepare_train_head() diff --git a/fl4health/feature_alignment/tab_features_preprocessor.py b/fl4health/feature_alignment/tab_features_preprocessor.py index bbdfc723f..d9635fc3f 100644 --- a/fl4health/feature_alignment/tab_features_preprocessor.py +++ b/fl4health/feature_alignment/tab_features_preprocessor.py @@ -122,7 +122,7 @@ def initialize_default_pipelines( one_hot (bool): Whether or not to apply a default one-hot pipeline. Returns: - dict[str, Pipeline]: Default feature processing pipeline per feature in the list + dict[str, Pipeline]: Default feature processing pipeline per feature in the list. """ columns_to_pipelines = {} for tab_feature in tabular_features: diff --git a/fl4health/model_bases/ensemble_base.py b/fl4health/model_bases/ensemble_base.py index a0eb7c2ad..56a66448c 100644 --- a/fl4health/model_bases/ensemble_base.py +++ b/fl4health/model_bases/ensemble_base.py @@ -9,7 +9,7 @@ class EnsembleAggregationMode(Enum): AVERAGE = "AVERAGE" -EXPECTED_PRED_N_DIMS = 2 +EXPECTED_MAX_PRED_N_DIMS = 2 class EnsembleModel(nn.Module): @@ -73,7 +73,7 @@ def ensemble_vote(self, preds_list: list[torch.Tensor]) -> torch.Tensor: preds_dimension = list(preds_list[0].shape) # If larger than two dimensions, we map to 2D to perform voting operation (and reshape later) - if len(preds_dimension) > EXPECTED_PRED_N_DIMS: + if len(preds_dimension) > EXPECTED_MAX_PRED_N_DIMS: preds_list = [preds.reshape(-1, preds_dimension[-1]) for preds in preds_list] # For each model prediction, compute the argmax of the model over the classes and stack column-wise into matrix @@ -88,7 +88,7 @@ def ensemble_vote(self, preds_list: list[torch.Tensor]) -> torch.Tensor: vote_preds = nn.functional.one_hot(indices_with_highest_counts, num_classes=preds_dimension[-1]) # If larger than two dimensions, map back to original dimensions - if len(preds_dimension) > EXPECTED_PRED_N_DIMS: + if len(preds_dimension) > EXPECTED_MAX_PRED_N_DIMS: vote_preds = vote_preds.reshape(*preds_dimension) return vote_preds diff --git a/fl4health/parameter_exchange/parameter_selection_criteria.py b/fl4health/parameter_exchange/parameter_selection_criteria.py index d81fa554e..e90e35799 100644 --- a/fl4health/parameter_exchange/parameter_selection_criteria.py +++ b/fl4health/parameter_exchange/parameter_selection_criteria.py @@ -57,12 +57,12 @@ def _calculate_drift_norm(t1: torch.Tensor, t2: torch.Tensor, normalize: bool) - class via the ``LayerSelectionFunctionConstructor`` class. Args: - t1 (torch.Tensor): First tensor - t2 (torch.Tensor): Second tensor + t1 (torch.Tensor): First tensor. + t2 (torch.Tensor): Second tensor. normalize (bool): Whether to divide the difference between the tensors by their number of elements. Returns: - float: Norm of the difference between ``t1`` and ``t2`` based on the specified calculation + float: Norm of the difference between ``t1`` and ``t2`` based on the specified calculation. """ t_diff = (t1 - t2).float() drift_norm = torch.linalg.norm(t_diff) diff --git a/fl4health/privacy/moments_accountant.py b/fl4health/privacy/moments_accountant.py index e39a55d78..a37edd463 100644 --- a/fl4health/privacy/moments_accountant.py +++ b/fl4health/privacy/moments_accountant.py @@ -50,8 +50,8 @@ def __init__(self, population_size: int, sample_size: int) -> None: This class is specific to fixed sampling without replacement. Args: - population_size (int): Size of the total population from which sampling is performed - sample_size (int): Size of the desired sample + population_size (int): Size of the total population from which sampling is performed. + sample_size (int): Size of the desired sample. """ self.population_size = population_size self.sample_size = sample_size diff --git a/fl4health/utils/config.py b/fl4health/utils/config.py index 40ff317e9..bc58ba817 100644 --- a/fl4health/utils/config.py +++ b/fl4health/utils/config.py @@ -85,7 +85,7 @@ def narrow_dict_type_and_set_attribute( self (object): The object to set attribute to dictionary[dictionary_key]. dictionary (dict): A dictionary with string keys. dictionary_key (str): A dictionary with string keys. - attribute_name (str): The key to check dictionary for. + attribute_name (str): The key for which to check in dictionary. narrow_type_to (type[T]): The expected type of dictionary[key]. func (Callable[[Any], Any] | None, optional): Function to operate on the extracted value if desired. Defaults to None. diff --git a/fl4health/utils/dataset.py b/fl4health/utils/dataset.py index bf05f852c..9877301c6 100644 --- a/fl4health/utils/dataset.py +++ b/fl4health/utils/dataset.py @@ -44,13 +44,13 @@ def __getitem__(self, index: int) -> tuple[torch.Tensor, torch.Tensor]: the underlying data. Args: - index (int): Index at which to extract the data from the dataset + index (int): Index at which to extract the data from the dataset. Raises: NotImplementedError: Throws if one attempts to use this function. Returns: - tuple[torch.Tensor, torch.Tensor]: input and target tensors extracted at the provided index. + tuple[torch.Tensor, torch.Tensor]: Input and target tensors extracted at the provided index. """ raise NotImplementedError @@ -82,8 +82,8 @@ def __init__( data augmentation, label blurring, etc. Args: - data (torch.Tensor): input data for training - targets (torch.Tensor | None, optional): target data for training. Defaults to None. + data (torch.Tensor): Input data for training. + targets (torch.Tensor | None, optional): Target data for training. Defaults to None. transform (Callable | None, optional): Optional transformation to be applied to the input data. NOTE: This transformation is applied at load time within ``__get_item__`` Defaults to None. @@ -125,7 +125,7 @@ def __len__(self) -> int: Length of the dataset as determined by len() applied to torch dataset. Returns: - int: length of dataset. + int: Length of dataset. """ return len(self.data) @@ -259,7 +259,7 @@ def __getitem__(self, index: int) -> tuple[torch.Tensor, torch.Tensor]: index (int): Index of the data in the dataset to be returned Returns: - tuple[torch.Tensor, torch.Tensor]: input and targets at the provided index + tuple[torch.Tensor, torch.Tensor]: Input and targets at the provided index. """ assert self.targets is not None diff --git a/fl4health/utils/dataset_converter.py b/fl4health/utils/dataset_converter.py index 6a5fb618c..711eb700a 100644 --- a/fl4health/utils/dataset_converter.py +++ b/fl4health/utils/dataset_converter.py @@ -32,7 +32,7 @@ def __getitem__(self, index: int) -> tuple[torch.Tensor, torch.Tensor]: ``converter_function`` is applied after the transformers. Args: - index (int): The index of the batch of data to be extracted + index (int): The index of the batch of data to be extracted. Returns: tuple[torch.Tensor, torch.Tensor]: Get the raw batch data (input, target) and apply the diff --git a/fl4health/utils/nnunet_utils.py b/fl4health/utils/nnunet_utils.py index 9e8ceec36..f83811365 100644 --- a/fl4health/utils/nnunet_utils.py +++ b/fl4health/utils/nnunet_utils.py @@ -381,7 +381,7 @@ def __iter__(self) -> DataLoader: # type: ignore Define the iter conversion for an NnUNetDataLoaderWrapper. Returns: - DataLoader: The iterator, which is just the NnUNetDataLoaderWrapper itself + DataLoader: The iterator, which is just the NnUNetDataLoaderWrapper itself. """ # mypy gets angry that the return type is different return self diff --git a/fl4health/utils/privacy_utilities.py b/fl4health/utils/privacy_utilities.py index 93ed5055d..aa571dfb9 100644 --- a/fl4health/utils/privacy_utilities.py +++ b/fl4health/utils/privacy_utilities.py @@ -78,14 +78,14 @@ def map_model_to_opacus_model( Args: model (nn.Module): Pytorch model to be converted to an Opacus compliant ``GradSampleModule`` - grad_sample_mode (str, optional): his determines how Opacus performs the conversion under the hood. The + grad_sample_mode (str, optional): This determines how Opacus performs the conversion under the hood. The standard mechanism is indicated by "hooks" but other approaches may be necessary depending on how the pytorch module is defined. Defaults to "hooks". *args (Any): Any other args that need to go to the conversion function. **kwargs (Any): Another other kwargs that need to go to the conversion function. Returns: - GradSampleModule: The Opacus-compliant, wrapped ``GradSampleModule`` + GradSampleModule: The Opacus-compliant, wrapped ``GradSampleModule``. """ model, _ = privacy_validate_and_fix_modules(model) return convert_model_to_opacus_model(model, grad_sample_mode, *args, **kwargs) diff --git a/research/picai/data/prepare_data.py b/research/picai/data/prepare_data.py index a3db27996..4719a8473 100644 --- a/research/picai/data/prepare_data.py +++ b/research/picai/data/prepare_data.py @@ -164,7 +164,7 @@ def prepare_data( Args: scans_read_dir (Path): The path to read the scans from. Should be a directory with subdirectories for each patient_id. Inside the subdirectories should be all the scan files for a given patient. - annotation_read_dir (Path): he path to read the annotations from. Should be a flat directory with all + annotation_read_dir (Path): The path to read the annotations from. Should be a flat directory with all annotation files. scans_write_dir (Path): The path to write the scans to. All scans are written into same directory. annotation_write_dir (Path): The path to write the annotations to. All annotations are written into same diff --git a/research/picai/data/preprocessing_transforms.py b/research/picai/data/preprocessing_transforms.py index b495d445e..76c02fb57 100644 --- a/research/picai/data/preprocessing_transforms.py +++ b/research/picai/data/preprocessing_transforms.py @@ -144,8 +144,8 @@ def crop_or_pad( size (tuple[int, int, int]): Target size in voxels. Expected to be in Depth x Height x Width format. physical_size (tuple[float, float, float] | None, optional): Target size in mm. (Number of Voxels x Spacing) Expected to be in Depth x Height x Width format. Defaults to None. - crop_only (bool, optional): Whether to only crop the image (True) or also perform padding (False). Defaults t - o False. + crop_only (bool, optional): Whether to only crop the image (True) or also perform padding (False). Defaults + to False. Returns: sitk.Image: Cropped or padded image. diff --git a/research/picai/nnunet_scripts/transfer_train.py b/research/picai/nnunet_scripts/transfer_train.py index 213253dc1..a4f18d6eb 100644 --- a/research/picai/nnunet_scripts/transfer_train.py +++ b/research/picai/nnunet_scripts/transfer_train.py @@ -142,8 +142,8 @@ def check_configs(plans_path: str, configs: list) -> None: Raises an error if configs are not found in plans json. Args: - plans_path (str): Path to the plans file to check - configs (list): A list of nnunet configs to check for + plans_path (str): Path to the plans file to check. + configs (list): A list of nnunet configs for which to check. """ plans = load_json(plans_path) for c in configs: diff --git a/tests/smoke_tests/run_smoke_test.py b/tests/smoke_tests/run_smoke_test.py index dbb206e1c..6f4e52fc2 100644 --- a/tests/smoke_tests/run_smoke_test.py +++ b/tests/smoke_tests/run_smoke_test.py @@ -190,8 +190,8 @@ async def run_smoke_test( loop.close() Args: - server_python_path (str): The path for the executable server module - client_python_path (str): The path for the executable client module + server_python_path (str): The path for the executable server module. + client_python_path (str): The path for the executable client module. config_path (str): The path for the config yaml file. The following attributes are required by this function: - `n_clients`: the number of clients to be started @@ -392,8 +392,8 @@ async def run_fault_tolerance_smoke_test( Runs a smoke test for a given server, client, and dataset configuration. Args: - server_python_path (str): The path for the executable server module - client_python_path (str): The path for the executable client module + server_python_path (str): The path for the executable server module. + client_python_path (str): The path for the executable client module. config_path (str): The path for the config yaml file. The following attributes are required by this function: - `n_clients`: the number of clients to be started From f08602c84e91002224bf03d46693de478288037f Mon Sep 17 00:00:00 2001 From: David Emerson <43939939+emersodb@users.noreply.github.com> Date: Sat, 21 Jun 2025 14:37:54 -0400 Subject: [PATCH 4/4] Fixing ruff formmatting check --- fl4health/clients/flexible_client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fl4health/clients/flexible_client.py b/fl4health/clients/flexible_client.py index bc4dd0f69..b522810e7 100644 --- a/fl4health/clients/flexible_client.py +++ b/fl4health/clients/flexible_client.py @@ -17,6 +17,9 @@ from fl4health.utils.typing import TorchFeatureType, TorchInputType, TorchPredType, TorchTargetType +EXPECTED_OUTPUT_TUPLE_SIZE = 2 + + class FlexibleClient(BasicClient): def __init__( self, @@ -296,7 +299,7 @@ def predict_with_model( if isinstance(output, torch.Tensor): return {"prediction": output}, {} if isinstance(output, tuple): - if len(output) != 2: + if len(output) != EXPECTED_OUTPUT_TUPLE_SIZE: raise ValueError(f"Output tuple should have length 2 but has length {len(output)}") preds, features = output return preds, features