From 7e2cd5c56a2eb5bb1a8942d1325cb6d7fd38681b Mon Sep 17 00:00:00 2001 From: Omar Mokhtar Date: Sun, 17 Dec 2023 18:54:23 +0200 Subject: [PATCH 01/39] Added Fedpara (#1) * added the baseline folder * Added Boilerplate code * Updated config file * cross finger: first commit on Cifar-10 * dimension fix * minor fix * vgg-base with overfit bug * vgg with overfit bug * fixed low rank convergence * output cleaned * Low rank, cifar iid fixed and added * fedpara cifar done --------- Co-authored-by: Omar Mokhtar Co-authored-by: Roeia Amr <60478505+Roeia99@users.noreply.github.com> Co-authored-by: Yahia Shaaban Co-authored-by: Yahia Salaheldin Shaaban <62369984+yehias21@users.noreply.github.com> Co-authored-by: yahia Co-authored-by: = <=> --- baselines/fedpara/EXTENDED_README.md | 123 ++++++++ baselines/fedpara/LICENSE | 202 ++++++++++++ baselines/fedpara/README.md | 87 ++++++ baselines/fedpara/fedpara/__init__.py | 1 + baselines/fedpara/fedpara/client.py | 87 ++++++ baselines/fedpara/fedpara/conf/base.yaml | 44 +++ baselines/fedpara/fedpara/conf/cifar10.yaml | 45 +++ baselines/fedpara/fedpara/conf/cifar100.yaml | 46 +++ baselines/fedpara/fedpara/conf/femnist.yaml | 45 +++ baselines/fedpara/fedpara/dataset.py | 132 ++++++++ baselines/fedpara/fedpara/main.py | 115 +++++++ baselines/fedpara/fedpara/models.py | 308 +++++++++++++++++++ baselines/fedpara/fedpara/server.py | 58 ++++ baselines/fedpara/fedpara/strategy.py | 28 ++ baselines/fedpara/fedpara/utils.py | 63 ++++ baselines/fedpara/pyproject.toml | 137 +++++++++ baselines/fedpara/run.sh | 4 + 17 files changed, 1525 insertions(+) create mode 100644 baselines/fedpara/EXTENDED_README.md create mode 100644 baselines/fedpara/LICENSE create mode 100644 baselines/fedpara/README.md create mode 100644 baselines/fedpara/fedpara/__init__.py create mode 100644 baselines/fedpara/fedpara/client.py create mode 100644 baselines/fedpara/fedpara/conf/base.yaml create mode 100644 baselines/fedpara/fedpara/conf/cifar10.yaml create mode 100644 baselines/fedpara/fedpara/conf/cifar100.yaml create mode 100644 baselines/fedpara/fedpara/conf/femnist.yaml create mode 100644 baselines/fedpara/fedpara/dataset.py create mode 100644 baselines/fedpara/fedpara/main.py create mode 100644 baselines/fedpara/fedpara/models.py create mode 100644 baselines/fedpara/fedpara/server.py create mode 100644 baselines/fedpara/fedpara/strategy.py create mode 100644 baselines/fedpara/fedpara/utils.py create mode 100644 baselines/fedpara/pyproject.toml create mode 100644 baselines/fedpara/run.sh diff --git a/baselines/fedpara/EXTENDED_README.md b/baselines/fedpara/EXTENDED_README.md new file mode 100644 index 000000000000..9c8f5bc72fa9 --- /dev/null +++ b/baselines/fedpara/EXTENDED_README.md @@ -0,0 +1,123 @@ + +# Extended Readme + +> The baselines are expected to run in a machine running Ubuntu 22.04 + +While `README.md` should include information about the baseline you implement and how to run it, this _extended_ readme provides info on what's the expected directory structure for a new baseline and more generally the instructions to follow before your baseline can be merged into the Flower repository. Please follow closely these instructions. It is likely that you have already completed steps 1-2. + +1. Fork the Flower repository and clone it. +2. Navigate to the `baselines/` directory and from there run: + ```bash + # This will create a new directory with the same structure as this `baseline_template` directory. + ./dev/create-baseline.sh + ``` +3. All your code and configs should go into a sub-directory with the same name as the name of your baseline. + * The sub-directory contains a series of Python scripts that you can edit. Please stick to these files and consult with us if you need additional ones. + * There is also a basic config structure in `/conf` ready be parsed by [Hydra](https://hydra.cc/) when executing your `main.py`. +4. Therefore, the directory structure in your baseline should look like: + ```bash + baselines/ + ├── README.md # describes your baseline and everything needed to use it + ├── EXTENDED_README.md # to remove before creating your PR + ├── pyproject.toml # details your Python environment + └── + ├── *.py # several .py files including main.py and __init__.py + └── conf + └── *.yaml # one or more Hydra config files + + ``` +> :warning: Make sure the variable `name` in `pyproject.toml` is set to the name of the sub-directory containing all your code. + +5. Add your dependencies to the `pyproject.toml` (see below a few examples on how to do it). Read more about Poetry below in this `EXTENDED_README.md`. +6. Regularly check that your coding style and the documentation you add follow good coding practices. To test whether your code meets the requirements, please run the following: + ```bash + # After activating your environment and from your baseline's directory + cd .. # to go to the top-level directory of all baselines + ./dev/test-baseline.sh + ./dev/test-baseline-structure.sh + ``` + Both `test-baseline.sh` and `test-baseline-structure.sh` will also be automatically run when you create a PR, and both tests need to pass for the baseline to be merged. + To automatically solve some formatting issues and apply easy fixes, please run the formatting script: + ```bash + # After activating your environment and from your baseline's directory + cd .. # to go to the top-level directory of all baselines + ./dev/format-baseline.sh + ``` +7. Ensure that the Python environment for your baseline can be created without errors by simply running `poetry install` and that this is properly described later when you complete the `Environment Setup` section in `README.md`. This is specially important if your environment requires additional steps after doing `poetry install`. +8. Ensure that your baseline runs with default arguments by running `poetry run python -m .main`. Then, describe this and other forms of running your code in the `Running the Experiments` section in `README.md`. +9. Once your code is ready and you have checked: + * that following the instructions in your `README.md` the Python environment can be created correctly + + * that running the code following your instructions can reproduce the experiments in the paper + + , then you just need to create a Pull Request (PR) to kickstart the process of merging your baseline into the Flower repository. + +> Once you are happy to merge your baseline contribution, please delete this `EXTENDED_README.md` file. + + +## About Poetry + +We use Poetry to manage the Python environment for each individual baseline. You can follow the instructions [here](https://python-poetry.org/docs/) to install Poetry in your machine. + + +### Specifying a Python Version (optional) +By default, Poetry will use the Python version in your system. In some settings, you might want to specify a particular version of Python to use inside your Poetry environment. You can do so with [`pyenv`](https://github.com/pyenv/pyenv). Check the documentation for the different ways of installing `pyenv`, but one easy way is using the [automatic installer](https://github.com/pyenv/pyenv-installer): +```bash +curl https://pyenv.run | bash # then, don't forget links to your .bashrc/.zshrc +``` + +You can then install any Python version with `pyenv install ` (e.g. `pyenv install 3.9.17`). Then, in order to use that version for your baseline, you'd do the following: + +```bash +# cd to your baseline directory (i.e. where the `pyproject.toml` is) +pyenv local + +# set that version for poetry +poetry env use + +# then you can install your Poetry environment (see the next setp) +``` + +### Installing Your Environment +With the Poetry tool already installed, you can create an environment for this baseline with commands: +```bash +# run this from the same directory as the `pyproject.toml` file is +poetry install +``` + +This will create a basic Python environment with just Flower and additional packages, including those needed for simulation. Next, you should add the dependencies for your code. It is **critical** that you fix the version of the packages you use using a `=` not a `=^`. You can do so via [`poetry add`](https://python-poetry.org/docs/cli/#add). Below are some examples: + +```bash +# For instance, if you want to install tqdm +poetry add tqdm==4.65.0 + +# If you already have a requirements.txt, you can add all those packages (but ensure you have fixed the version) in one go as follows: +poetry add $( cat requirements.txt ) +``` +With each `poetry add` command, the `pyproject.toml` gets automatically updated so you don't need to keep that `requirements.txt` as part of this baseline. + + +More critically however, is adding your ML framework of choice to the list of dependencies. For some frameworks you might be able to do so with the `poetry add` command. Check [the Poetry documentation](https://python-poetry.org/docs/cli/#add) for how to add packages in various ways. For instance, let's say you want to use PyTorch: + +```bash +# with plain `pip` you'd run a command such as: +pip install torch==1.13.1+cu117 torchvision==0.14.1+cu117 torchaudio==0.13.1 --extra-index-url https://download.pytorch.org/whl/cu117 + +# to add the same 3 dependencies to your Poetry environment you'd need to add the URL to the wheel that the above pip command auto-resolves for you. +# You can find those wheels in `https://download.pytorch.org/whl/cu117`. Copy the link and paste it after the `poetry add` command. +# For instance to add `torch==1.13.1+cu117` and a x86 Linux system with Python3.8 you'd: +poetry add https://download.pytorch.org/whl/cu117/torch-1.13.1%2Bcu117-cp38-cp38-linux_x86_64.whl +# you'll need to repeat this for both `torchvision` and `torchaudio` +``` +The above is just an example of how you can add these dependencies. Please refer to the Poetry documentation to extra reference. + +If all attempts fail, you can still install packages via standard `pip`. You'd first need to source/activate your Poetry environment. +```bash +# first ensure you have created your environment +# and installed the base packages provided in the template +poetry install + +# then activate it +poetry shell +``` +Now you are inside your environment (pretty much as when you use `virtualenv` or `conda`) so you can install further packages with `pip`. Please note that, unlike with `poetry add`, these extra requirements won't be captured by `pyproject.toml`. Therefore, please ensure that you provide all instructions needed to: (1) create the base environment with Poetry and (2) install any additional dependencies via `pip` when you complete your `README.md`. \ No newline at end of file diff --git a/baselines/fedpara/LICENSE b/baselines/fedpara/LICENSE new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/baselines/fedpara/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md new file mode 100644 index 000000000000..682952717426 --- /dev/null +++ b/baselines/fedpara/README.md @@ -0,0 +1,87 @@ +--- +title: title of the paper +url: URL to the paper page (not the pdf) +labels: [label1, label2] # please add between 4 and 10 single-word (maybe two-words) labels (e.g. "system heterogeneity", "image classification", "asynchronous", "weight sharing", "cross-silo") +dataset: [dataset1, dataset2] # list of datasets you include in your baseline +--- + +# :warning:*_Title of your baseline_* + +> Note: If you use this baseline in your work, please remember to cite the original authors of the paper as well as the Flower paper. + +> :warning: This is the template to follow when creating a new Flower Baseline. Please follow the instructions in `EXTENDED_README.md` + +> :warning: Please follow the instructions carefully. You can see the [FedProx-MNIST baseline](https://github.com/adap/flower/tree/main/baselines/fedprox) as an example of a baseline that followed this guide. + +> :warning: Please complete the metadata section at the very top of this README. This generates a table at the top of the file that will facilitate indexing baselines. + +****Paper:**** :warning: *_add the URL of the paper page (not to the .pdf). For instance if you link a paper on ArXiv, add here the URL to the abstract page (e.g. https://arxiv.org/abs/1512.03385). If your paper is in from a journal or conference proceedings, please follow the same logic._* + +****Authors:**** :warning: *_list authors of the paper_* + +****Abstract:**** :warning: *_add here the abstract of the paper you are implementing_* + + +## About this baseline + +****What’s implemented:**** :warning: *_Concisely describe what experiment(s) in the publication can be replicated by running the code. Please only use a few sentences. Start with: “The code in this directory …”_* + +****Datasets:**** :warning: *_List the datasets you used (if you used a medium to large dataset, >10GB please also include the sizes of the dataset)._* + +****Hardware Setup:**** :warning: *_Give some details about the hardware (e.g. a server with 8x V100 32GB and 256GB of RAM) you used to run the experiments for this baseline. Someone out there might not have access to the same resources you have so, could list the absolute minimum hardware needed to run the experiment in a reasonable amount of time ? (e.g. minimum is 1x 16GB GPU otherwise a client model can’t be trained with a sufficiently large batch size). Could you test this works too?_* + +****Contributors:**** :warning: *_let the world know who contributed to this baseline. This could be either your name, your name and affiliation at the time, or your GitHub profile name if you prefer. If multiple contributors signed up for this baseline, please list yourself and your colleagues_* + + +## Experimental Setup + +****Task:**** :warning: *_what’s the primary task that is being federated? (e.g. image classification, next-word prediction). If you have experiments for several, please list them_* + +****Model:**** :warning: *_provide details about the model you used in your experiments (if more than use a list). If your model is small, describing it as a table would be :100:. Some FL methods do not use an off-the-shelve model (e.g. ResNet18) instead they create your own. If this is your case, please provide a summary here and give pointers to where in the paper (e.g. Appendix B.4) is detailed._* + +****Dataset:**** :warning: *_Earlier you listed already the datasets that your baseline uses. Now you should include a breakdown of the details about each of them. Please include information about: how the dataset is partitioned (e.g. LDA with alpha 0.1 as default and all clients have the same number of training examples; or each client gets assigned a different number of samples following a power-law distribution with each client only instances of 2 classes)? if your dataset is naturally partitioned just state “naturally partitioned”; how many partitions there are (i.e. how many clients)? Please include this an all information relevant about the dataset and its partitioning into a table._* + +****Training Hyperparameters:**** :warning: *_Include a table with all the main hyperparameters in your baseline. Please show them with their default value._* + + +## Environment Setup + +:warning: _The Python environment for all baselines should follow these guidelines in the `EXTENDED_README`. Specify the steps to create and activate your environment. If there are any external system-wide requirements, please include instructions for them too. These instructions should be comprehensive enough so anyone can run them (if non standard, describe them step-by-step)._ + + +## Running the Experiments + +:warning: _Provide instructions on the steps to follow to run all the experiments._ +```bash +# The main experiment implemented in your baseline using default hyperparameters (that should be setup in the Hydra configs) should run (including dataset download and necessary partitioning) by executing the command: + +poetry run python -m .main # where is the name of this directory and that of the only sub-directory in this directory (i.e. where all your source code is) + +# If you are using a dataset that requires a complicated download (i.e. not using one natively supported by TF/PyTorch) + preprocessing logic, you might want to tell people to run one script first that will do all that. Please ensure the download + preprocessing can be configured to suit (at least!) a different download directory (and use as default the current directory). The expected command to run to do this is: + +poetry run python -m .dataset_preparation + +# It is expected that you baseline supports more than one dataset and different FL settings (e.g. different number of clients, dataset partitioning methods, etc). Please provide a list of commands showing how these experiments are run. Include also a short explanation of what each one does. Here it is expected you'll be using the Hydra syntax to override the default config. + +poetry run python -m .main +. +. +. +poetry run python -m .main +``` + + +## Expected Results + +:warning: _Your baseline implementation should replicate several of the experiments in the original paper. Please include here the exact command(s) needed to run each of those experiments followed by a figure (e.g. a line plot) or table showing the results you obtained when you ran the code. Below is an example of how you can present this. Please add command followed by results for all your experiments._ + +```bash +# it is likely that for one experiment you need to sweep over different hyperparameters. You are encouraged to use Hydra's multirun functionality for this. This is an example of how you could achieve this for some typical FL hyperparameteres + +poetry run python -m .main --multirun num_client_per_round=5,10,50 dataset=femnist,cifar10 +# the above command will run a total of 6 individual experiments (because 3client_configs x 2datasets = 6 -- you can think of it as a grid). + +[Now show a figure/table displaying the results of the above command] + +# add more commands + plots for additional experiments. +``` diff --git a/baselines/fedpara/fedpara/__init__.py b/baselines/fedpara/fedpara/__init__.py new file mode 100644 index 000000000000..a5e567b59135 --- /dev/null +++ b/baselines/fedpara/fedpara/__init__.py @@ -0,0 +1 @@ +"""Template baseline package.""" diff --git a/baselines/fedpara/fedpara/client.py b/baselines/fedpara/fedpara/client.py new file mode 100644 index 000000000000..408ac2bc52ab --- /dev/null +++ b/baselines/fedpara/fedpara/client.py @@ -0,0 +1,87 @@ +"""Client for FedPara.""" + +import copy +from collections import OrderedDict +from typing import Callable, Dict, List, Tuple + +import flwr as fl +import torch +from flwr.common import NDArrays, Scalar +from hydra.utils import instantiate +from omegaconf import DictConfig +from torch.nn.utils import parameters_to_vector +from torch.utils.data import DataLoader + +from fedpara.models import train + + +class FlowerClient(fl.client.NumPyClient): + """Standard Flower client for CNN training.""" + + def __init__( + self, + cid: int, + net: torch.nn.Module, + train_loader: DataLoader, + device: str, + num_epochs: int, + ): # pylint: disable=too-many-arguments + print(f"Initializing Client {cid}") + self.cid = cid + self.net = net + self.train_loader = train_loader + self.device = torch.device(device) + self.num_epochs = num_epochs + + def get_parameters(self, config: Dict[str, Scalar]) -> NDArrays: + """Returns the parameters of the current net.""" + return [val.cpu().numpy() for _, val in self.net.state_dict().items()] + + def _set_parameters(self, parameters: NDArrays) -> None: + params_dict = zip(self.net.state_dict().keys(), parameters) + state_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) + self.net.load_state_dict(state_dict, strict=True) + + def fit( + self, parameters: NDArrays, config: Dict[str, Scalar] + ) -> Tuple[NDArrays, int, Dict]: + """Train the network on the training set.""" + self._set_parameters(parameters) + print(f"Client {self.cid} Training...") + + train( + self.net, + self.train_loader, + self.device, + epochs=self.num_epochs, + hyperparams=config, + round=config["curr_round"], + ) + + return ( + self.get_parameters({}), + len(self.train_loader), + {}, + ) + + +def gen_client_fn( + train_loaders: List[DataLoader], + model: DictConfig, + num_epochs: int, + args: Dict, +) -> Callable[[str], FlowerClient]: + """Return a function which creates a new FlowerClient for a given cid.""" + + def client_fn(cid: str) -> FlowerClient: + """Create a new FlowerClient for a given cid.""" + cid = int(cid) + return FlowerClient( + cid=cid, + net=instantiate(model).to(args["device"]), + train_loader=train_loaders[cid], + device=args["device"], + num_epochs=num_epochs, + ) + + return client_fn diff --git a/baselines/fedpara/fedpara/conf/base.yaml b/baselines/fedpara/fedpara/conf/base.yaml new file mode 100644 index 000000000000..12aaebf68483 --- /dev/null +++ b/baselines/fedpara/fedpara/conf/base.yaml @@ -0,0 +1,44 @@ +--- +seed: 17 + +num_clients: 100 +num_rounds: 200 +clients_per_round: 16 +num_epochs: 5 +batch_size: 64 + +server_device: cuda +client_device: cuda + +client_resources: + num_cpus: 2 + num_gpus: 0.25 + +dataset_config: + name: CIFAR10 + partition: non-iid + num_classes: 10 + alpha: 0.5 + +model: + _target_: fedpara.models.VGG + num_classes: ${dataset_config.num_classes} + conv_type: lowrank # lowrank or standard + activation: relu # relu or leaky_relu + +hyperparams: + eta_l: 0.1 + learning_decay: 0.992 + momentum: 0.0 + weight_decay: 0 + +strategy: + _target_: fedpara.strategy.FedPara + algorithm: FedPara + fraction_fit: 0.00001 + fraction_evaluate: 0.0 + min_evaluate_clients: 0 + min_fit_clients: ${clients_per_round} + min_available_clients: ${clients_per_round} + accept_failures: false + diff --git a/baselines/fedpara/fedpara/conf/cifar10.yaml b/baselines/fedpara/fedpara/conf/cifar10.yaml new file mode 100644 index 000000000000..3dabc0f07ed1 --- /dev/null +++ b/baselines/fedpara/fedpara/conf/cifar10.yaml @@ -0,0 +1,45 @@ +--- +seed: 17 + +num_clients: 100 +num_rounds: 200 +clients_per_round: 16 +num_epochs: 5 +batch_size: 64 + +server_device: cuda +client_device: cuda + +client_resources: + num_cpus: 2 + num_gpus: 0.0625 + +dataset_config: + name: CIFAR10 + partition: non-iid + num_classes: 10 + alpha: 0.5 + +model: + _target_: fedpara.models.VGG + num_classes: ${dataset_config.num_classes} + conv_type: lowrank # lowrank or standard + activation: relu # relu or leaky_relu + ratio: 0.1 # lowrank ratio + +hyperparams: + eta_l: 0.1 + learning_decay: 0.992 + momentum: 0.0 + weight_decay: 0 + +strategy: + _target_: fedpara.strategy.FedPara + algorithm: FedPara + fraction_fit: 0.00001 + fraction_evaluate: 0.0 + min_evaluate_clients: 0 + min_fit_clients: ${clients_per_round} + min_available_clients: ${clients_per_round} + accept_failures: false + diff --git a/baselines/fedpara/fedpara/conf/cifar100.yaml b/baselines/fedpara/fedpara/conf/cifar100.yaml new file mode 100644 index 000000000000..b30a93f3a8c9 --- /dev/null +++ b/baselines/fedpara/fedpara/conf/cifar100.yaml @@ -0,0 +1,46 @@ +--- +seed: 34213 + +num_clients: 50 +num_rounds: 400 +clients_per_round: 8 +num_epochs: 5 +batch_size: 64 + +server_device: cuda +client_device: cuda + +client_resources: + num_cpus: 2 + num_gpus: 0.125 + +dataset_config: + name: CIFAR100 + partition: -iid + num_classes: 100 + alpha: 0.5 + + +model: + _target_: fedpara.models.VGG + num_classes: ${dataset_config.num_classes} + conv_type: lowrank # lowrank or standard + activation: relu # relu or leaky_relu + ratio: 0.4 # lowrank ratio + +hyperparams: + eta_l: 0.1 + learning_decay: 0.992 + momentum: 0.0 + weight_decay: 0 + +strategy: + _target_: fedpara.strategy.FedPara + algorithm: FedPara + fraction_fit: 0.00001 + fraction_evaluate: 0.0 + min_evaluate_clients: 0 + min_fit_clients: ${clients_per_round} + min_available_clients: ${clients_per_round} + accept_failures: false + diff --git a/baselines/fedpara/fedpara/conf/femnist.yaml b/baselines/fedpara/fedpara/conf/femnist.yaml new file mode 100644 index 000000000000..5ce343368b5b --- /dev/null +++ b/baselines/fedpara/fedpara/conf/femnist.yaml @@ -0,0 +1,45 @@ +--- +seed: 17 + +num_clients: 100 +num_rounds: 100 +clients_per_round: 10 +num_epochs: 5 +batch_size: 10 + +server_device: cuda +client_device: cuda + +client_resources: + num_cpus: 2 + num_gpus: 0.0625 + +dataset_config: + name: FEMNIST + partition: non-iid #redundent + num_classes: 62 + alpha: 0 # redundant + +model: + _target_: fedpara.models.VGG + num_classes: ${dataset_config.num_classes} + conv_type: lowrank # lowrank or standard + activation: relu # relu or leaky_relu + ratio: 0.1 # lowrank ratio + +hyperparams: + eta_l: 0.1 + learning_decay: 0.999 + momentum: 0.0 + weight_decay: 0 + +strategy: + _target_: fedpara.strategy.FedPara + algorithm: FedPara + fraction_fit: 0.00001 + fraction_evaluate: 0.0 + min_evaluate_clients: 0 + min_fit_clients: ${clients_per_round} + min_available_clients: ${clients_per_round} + accept_failures: false + diff --git a/baselines/fedpara/fedpara/dataset.py b/baselines/fedpara/fedpara/dataset.py new file mode 100644 index 000000000000..2ff2f28a30c1 --- /dev/null +++ b/baselines/fedpara/fedpara/dataset.py @@ -0,0 +1,132 @@ +"""Dataset loading and processing utilities.""" + +import pickle +from typing import List, Tuple +import random +import numpy as np +from collections import defaultdict +import torch +from torch.utils.data import DataLoader, Dataset +from torchvision import datasets, transforms +import omegaconf + +class DatasetSplit(Dataset): + def __init__(self, dataset, idxs): + self.dataset = dataset + self.targets = dataset.targets + self.idxs = list(idxs) + + def __len__(self): + return len(self.idxs) + + def __getitem__(self, item): + image, label = self.dataset[self.idxs[item]] + return image, label + +def iid(dataset, num_users): + """ + Sample I.I.D. client data from CIFAR dataset + :param dataset: + :param num_users: + :return: dict of image index + """ + num_items = int(len(dataset)/num_users) + dict_users, all_idxs = {}, [i for i in range(len(dataset))] + for i in range(num_users): + dict_users[i] = set(np.random.choice(all_idxs, num_items, replace=False)) + all_idxs = list(set(all_idxs) - dict_users[i]) + return dict_users + + +def noniid(dataset, no_participants, alpha=0.5): + """ + Input: Number of participants and alpha (param for distribution) + Output: A list of indices denoting data in CIFAR training set. + Requires: cifar_classes, a preprocessed class-indice dictionary. + Sample Method: take a uniformly sampled 10/100-dimension vector as parameters for + dirichlet distribution to sample number of images in each class. + """ + np.random.seed(666) + random.seed(666) + cifar_classes = {} + for ind, x in enumerate(dataset): + _, label = x + if label in cifar_classes: + cifar_classes[label].append(ind) + else: + cifar_classes[label] = [ind] + + per_participant_list = defaultdict(list) + no_classes = len(cifar_classes.keys()) + class_size = len(cifar_classes[0]) + datasize = {} + for n in range(no_classes): + random.shuffle(cifar_classes[n]) + sampled_probabilities = class_size * np.random.dirichlet( + np.array(no_participants * [alpha])) + for user in range(no_participants): + no_imgs = int(round(sampled_probabilities[user])) + datasize[user, n] = no_imgs + sampled_list = cifar_classes[n][:min(len(cifar_classes[n]), no_imgs)] + per_participant_list[user].extend(sampled_list) + cifar_classes[n] = cifar_classes[n][min(len(cifar_classes[n]), no_imgs):] + train_img_size = np.zeros(no_participants) + for i in range(no_participants): + train_img_size[i] = sum([datasize[i,j] for j in range(no_classes)]) + clas_weight = np.zeros((no_participants,no_classes)) + for i in range(no_participants): + for j in range(no_classes): + clas_weight[i,j] = float(datasize[i,j])/float((train_img_size[i])) + return per_participant_list, clas_weight + + +def load_datasets( + config, num_clients, batch_size +) -> Tuple[List[DataLoader], DataLoader]: + + """Load the dataset and return the dataloaders for the clients and the server.""" + print("Loading data...") + if config.name == "CIFAR10": + Dataset = datasets.CIFAR10 + elif config.name == "CIFAR100": + Dataset = datasets.CIFAR100 + else: + raise NotImplementedError + data_directory = f"./data/{config.name.lower()}/" + ds_path = f"{data_directory}train_{num_clients}_{config.alpha:.2f}.pkl" + transform_train = transforms.Compose([ + transforms.RandomCrop(32, padding=4), + transforms.RandomHorizontalFlip(), + transforms.ToTensor(), + transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), + ]) + transform_test = transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), + ]) + try: + with open(ds_path, "rb") as file: + train_datasets = pickle.load(file) + except FileNotFoundError: + dataset_train = Dataset( + data_directory, train=True, download=True, transform=transform_train) + if config.partition == "iid": + train_datasets = iid( + dataset_train, + num_clients) + else: + train_datasets, _ = noniid( + dataset_train, + num_clients, + config.alpha) + dataset_test = Dataset( + data_directory, train=False, download=True, transform=transform_test + ) + test_loader = DataLoader(dataset_test, batch_size=batch_size, num_workers=2) + train_loaders = [ + DataLoader(DatasetSplit(dataset_train, ids), batch_size=batch_size, shuffle=True, num_workers=2) + for ids in train_datasets.values() + ] + + return train_loaders, test_loader + diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py new file mode 100644 index 000000000000..a4045a82f413 --- /dev/null +++ b/baselines/fedpara/fedpara/main.py @@ -0,0 +1,115 @@ +"""Main script for running FedPara.""" +from comet_ml import Experiment +import flwr as fl +import hydra +import numpy as np +from hydra.core.hydra_config import HydraConfig +from hydra.utils import instantiate +from omegaconf import DictConfig, OmegaConf +from fedpara import client, server, utils +from fedpara.dataset import load_datasets +from fedpara.utils import get_parameters, seed_everything + +@hydra.main(config_path="conf", config_name="cifar10", version_base=None) +def main(cfg: DictConfig) -> None: + """Run the baseline. + + Parameters + ---------- + cfg : DictConfig + An omegaconf object that stores the hydra config. + """ + # 1. Print parsed config + print(OmegaConf.to_yaml(cfg)) + seed_everything(cfg.seed) + # # Comet ML tracking + credentials = OmegaConf.load("fedpara/conf/credentials.yaml") + experiment = Experiment( + api_key=credentials.api_key, + project_name=credentials.project_name, + workspace=credentials.workspace, + ) + experiment.set_name(f"flower | {cfg.strategy.algorithm} | {cfg.dataset_config.name} | Seed {cfg.seed}") + hyper_params = { + "dataset": cfg.dataset_config.name, + "seed": cfg.seed, + } + # experiment.log_parameters(hyper_params) + # 2. Prepare dataset + train_loaders, test_loader = load_datasets( + config=cfg.dataset_config, + num_clients=cfg.num_clients, + batch_size=cfg.batch_size, + ) + + # 3. Define clients + client_fn = client.gen_client_fn( + train_loaders=train_loaders, + model=cfg.model, + num_epochs=cfg.num_epochs, + args={"device": cfg.client_device}, + ) + + evaluate_fn = server.gen_evaluate_fn( + test_loader=test_loader, + model=cfg.model, + device=cfg.server_device, + experiment=experiment, + ) + + def get_on_fit_config(): + def fit_config_fn(server_round: int): + fit_config = OmegaConf.to_container(cfg.hyperparams, resolve=True) + fit_config["curr_round"] = server_round + return fit_config + + return fit_config_fn + + net_glob = instantiate(cfg.model) + + # 4. Define strategy + strategy = instantiate( + cfg.strategy, + evaluate_fn=evaluate_fn, + on_fit_config_fn=get_on_fit_config(), + initial_parameters=fl.common.ndarrays_to_parameters(get_parameters(net_glob)), + ) + + # 5. Start Simulation + history = fl.simulation.start_simulation( + client_fn=client_fn, + num_clients=cfg.num_clients, + config=fl.server.ServerConfig(num_rounds=cfg.num_rounds), + strategy=strategy, + client_resources={ + "num_cpus": cfg.client_resources.num_cpus, + "num_gpus": cfg.client_resources.num_gpus, + }, + ray_init_args={ + "num_cpus": 40, + "num_gpus": 1, + "_memory": 30 * 1024 * 1024 * 1024, + }, + ) + + # 6. Save results + save_path = HydraConfig.get().runtime.output_dir + file_suffix = "_".join( + [ + repr(strategy), + cfg.dataset_config.name, + f"{cfg.seed}", + f"{cfg.dataset_config.alpha}", + f"{cfg.num_clients}", + f"{cfg.num_rounds}", + f"{cfg.clients_per_round}", + ] + ) + + utils.plot_metric_from_history( + hist=history, save_plot_path=save_path, suffix=file_suffix, cfg=cfg + ) + + +if __name__ == "__main__": + main() diff --git a/baselines/fedpara/fedpara/models.py b/baselines/fedpara/fedpara/models.py new file mode 100644 index 000000000000..b7df4295aba1 --- /dev/null +++ b/baselines/fedpara/fedpara/models.py @@ -0,0 +1,308 @@ +"""Model definitions for FedPara.""" + +from typing import Dict, Tuple +from torch.nn import init +import torch +import torch.nn.functional as F +from flwr.common import Scalar +from torch import nn +from torch.nn.utils import parameters_to_vector +from torch.utils.data import DataLoader +from torchvision import transforms +from tqdm import tqdm +import torchvision.models as models +import numpy as np +import math + +class LowRank(nn.Module): + def __init__(self, + in_channels: int, + out_channels: int, + low_rank: int, + kernel_size: int + ,activation: str = 'relu'): + super().__init__() + self.T = nn.Parameter( + torch.empty(size=(low_rank, low_rank, kernel_size, kernel_size)), + requires_grad=True + ) + self.X = nn.Parameter( + torch.empty(size=(low_rank, out_channels)), + requires_grad=True + ) + self.Y = nn.Parameter( + torch.empty(size=(low_rank, in_channels)), + requires_grad=True + ) + if activation == 'leakyrelu': activation = 'leaky_relu' + init.kaiming_normal_(self.T, mode='fan_out', nonlinearity=activation) + init.kaiming_normal_(self.X, mode='fan_out', nonlinearity=activation) + init.kaiming_normal_(self.Y, mode='fan_out', nonlinearity=activation) + + def forward(self): + # torch.einsum simplify the tensor produce (matrix multiplication) + return torch.einsum("xyzw,xo,yi->oizw", self.T, self.X, self.Y) +class Conv2d(nn.Module): + def __init__(self, + in_channels: int, + out_channels: int, + kernel_size: int = 3, + stride: int = 1, + padding: int = 0, + bias: bool = False, + ratio: float = 0.1, + add_nonlinear: bool = False, + activation: str = 'relu'): + super().__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.kernel_size = kernel_size + self.stride = stride + self.padding = padding + self.bias = bias + self.ratio = ratio + self.low_rank = self._calc_from_ratio() + self.add_nonlinear = add_nonlinear + self.activation = activation + self.W1 = LowRank(in_channels, out_channels, self.low_rank, kernel_size,activation) + self.W2 = LowRank(in_channels, out_channels, self.low_rank, kernel_size,activation) + self.bias = nn.Parameter(torch.zeros(out_channels)) if bias else None + self.tanh = nn.Tanh() + + def _calc_from_ratio(self): + # Return the low-rank of sub-matrices given the compression ratio + r1 = int(np.ceil(np.sqrt(self.out_channels))) + r2 = int(np.ceil(np.sqrt(self.in_channels))) + r = np.max((r1, r2)) + + num_target_params = self.out_channels * self.in_channels * \ + (self.kernel_size ** 2) * self.ratio + r3 = np.sqrt( + ((self.out_channels + self.in_channels) ** 2) / (4 * (self.kernel_size ** 4)) + \ + num_target_params / (2 * (self.kernel_size ** 2)) + ) - (self.out_channels + self.in_channels) / (2 * (self.kernel_size ** 2)) + r3 = int(np.ceil(r3)) + r = np.max((r, r3)) + + return r + + def forward(self, x): + # Hadamard product of two submatrices + if self.add_nonlinear: + W = self.tanh(self.W1()) * self.tanh(self.W2()) + else: + W = self.W1() * self.W2() + out = F.conv2d(input=x, weight=W, bias=self.bias, + stride=self.stride, padding=self.padding) + return out +class VGG(nn.Module): + def __init__(self,num_classes, num_groups=2, ratio=0.1, activation='relu', + conv_type='lowrank', add_nonlinear=False): + super(VGG, self).__init__() + if activation == 'relu': + self.activation = nn.ReLU(inplace=True) + elif activation == 'leaky_relu': + self.activation = nn.LeakyReLU(inplace=True) + self.conv_type = conv_type + self.num_groups = num_groups + self.num_classes = num_classes + self.ratio = ratio + self.add_nonlinear = add_nonlinear + self.features = self.make_layers([64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M' + , 512, 512, 512, 'M', 512, 512, 512, 'M']) + self.classifier = nn.Sequential( + nn.Dropout(), + nn.Linear(512, 512), + self.activation, + nn.Dropout(), + nn.Linear(512, 512), + self.activation, + nn.Linear(512, num_classes), + ) + self.init_weights() + + def init_weights(self): + for name, module in self.features.named_children(): + module = getattr(self.features, name) + if isinstance(module, nn.Conv2d): + if self.conv_type == 'lowrank': + num_channels = module.in_channels + setattr(self.features, name, Conv2d( + num_channels, + module.out_channels, + module.kernel_size[0], + module.stride[0], + module.padding[0], + module.bias is not None, + ratio=self.ratio, + add_nonlinear=self.add_nonlinear, + # send the name of the activation function to the Conv2d class + activation=self.activation.__class__.__name__.lower() + )) + elif self.conv_type == 'standard': + n = module.kernel_size[0] * module.kernel_size[1] * module.out_channels + module.weight.data.normal_(0, math.sqrt(2. / n)) + module.bias.data.zero_() + + def make_layers(self,cfg, group_norm=True): + layers = [] + in_channels = 3 + for v in cfg: + if v == 'M': + layers += [nn.MaxPool2d(kernel_size=2, stride=2)] + else: + conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1) + if group_norm: + layers += [conv2d, nn.GroupNorm(self.num_groups,v), self.activation] + else: + layers += [conv2d, self.activation] + in_channels = v + return nn.Sequential(*layers) + + def forward(self, input): + x = self.features(input) + x = x.view(x.size(0), -1) + x = self.classifier(x) + return x + + +# Create an instance of the VGG16GN model with Group Normalization, custom Conv2d, and modified classifier + +def test( + net: nn.Module, test_loader: DataLoader, device: torch.device +) -> Tuple[float, float]: + """Evaluate the network on the entire test set. + + Parameters + ---------- + net : nn.Module + The neural network to test. + test_loader : DataLoader + The DataLoader containing the data to test the network on. + device : torch.device + The device on which the model should be tested, either 'cpu' or 'cuda'. + + Returns + ------- + Tuple[float, float] + The loss and the accuracy of the input model on the given data. + """ + if len(test_loader.dataset) == 0: + raise ValueError("Testloader can't be 0, exiting...") + + criterion = torch.nn.CrossEntropyLoss() + correct, total, loss = 0, 0, 0.0 + net.eval() + with torch.no_grad(): + for images, labels in tqdm(test_loader, "Testing ..."): + images, labels = images.to(device), labels.to(device) + outputs = net(images) + loss += criterion(outputs, labels).item() + _, predicted = torch.max(outputs.data, 1) + total += labels.size(0) + correct += (predicted == labels).sum().item() + loss /= len(test_loader.dataset) + accuracy = correct / total + return loss, accuracy + + +def train( # pylint: disable=too-many-arguments + net: nn.Module, + trainloader: DataLoader, + device: torch.device, + epochs: int, + hyperparams: Dict[str, Scalar], + round: int, +) -> None: + """Train the network on the training set. + + Parameters + ---------- + net : nn.Module + The neural network to train. + trainloader : DataLoader + The DataLoader containing the data to train the network on. + device : torch.device + The device on which the model should be trained, either 'cpu' or 'cuda'. + epochs : int + The number of epochs the model should be trained for. + hyperparams : Dict[str, Scalar] + The hyperparameters to use for training. + """ + lr=hyperparams["eta_l"]*hyperparams["learning_decay"]**(round-1) + print(f"Learning rate: {lr}") + criterion = torch.nn.CrossEntropyLoss() + optimizer = torch.optim.SGD( + net.parameters(), + lr=lr, + momentum=hyperparams["momentum"], + weight_decay=hyperparams["weight_decay"], + ) + net.train() + for _ in tqdm(range(epochs), desc="Local Training ..."): + net = _train_one_epoch( + net=net, + trainloader=trainloader, + device=device, + criterion=criterion, + optimizer=optimizer, + hyperparams=hyperparams, + ) + + +def _train_one_epoch( # pylint: disable=too-many-arguments + net: nn.Module, + trainloader: DataLoader, + device: torch.device, + criterion, + optimizer, + hyperparams: Dict[str, Scalar], +) -> nn.Module: + """Train for one epoch. + + Parameters + ---------- + net : nn.Module + The neural network to train. + trainloader : DataLoader + The DataLoader containing the data to train the network on. + device : torch.device + The device on which the model should be trained, either 'cpu' or 'cuda'. + criterion : + The loss function to use for training + optimizer : + The optimizer to use for training + hyperparams : Dict[str, Scalar] + The hyperparameters to use for training. + + Returns + ------- + nn.Module + The model that has been trained for one epoch. + """ + for images, labels in trainloader: + images, labels = images.to(device), labels.to(device) + net.zero_grad() + log_probs = net(images) + loss = criterion(log_probs, labels) + loss.backward() + optimizer.step() + return net + + +if __name__ == "__main__": + model = VGG(num_classes=10, num_groups=2, conv_type='standard') + model = torch.nn.Sequential(*list(model.features.children())) + # Print the modified VGG16GN model architecture + print(model) + total_trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) + print(f"Total number of parameters: {total_trainable_params / 1e6}") + param_size = 0 + for param in model.parameters(): + param_size += param.nelement() * param.element_size() + buffer_size = 0 + for buffer in model.buffers(): + buffer_size += buffer.nelement() * buffer.element_size() + + size_all_mb = (param_size + buffer_size) / 1024**2 + print('model size: {:.3f}MB'.format(size_all_mb)) diff --git a/baselines/fedpara/fedpara/server.py b/baselines/fedpara/fedpara/server.py new file mode 100644 index 000000000000..f25732d22e18 --- /dev/null +++ b/baselines/fedpara/fedpara/server.py @@ -0,0 +1,58 @@ +"""Global evaluation function.""" + +from collections import OrderedDict +from typing import Callable, Dict, Optional, Tuple + +import torch +from flwr.common import NDArrays, Scalar +from hydra.utils import instantiate +from omegaconf import DictConfig +from torch.utils.data import DataLoader + +from fedpara.models import test + + +def gen_evaluate_fn( + test_loader: DataLoader, + model: DictConfig, + device, + experiment=None, +) -> Callable[ + [int, NDArrays, Dict[str, Scalar]], Optional[Tuple[float, Dict[str, Scalar]]] +]: + """Generate a centralized evaluation function. + + Parameters + ---------- + model: DictConfig + The model details to evaluate. + test_loader : DataLoader + The dataloader to test the model with. + device : torch.device + The device to test the model on. + + Returns + ------- + Callable[ [int, NDArrays, Dict[str, Scalar]], + Optional[Tuple[float, Dict[str, Scalar]]] ] + The centralized evaluation function. + """ + + def evaluate( + server_round, parameters_ndarrays: NDArrays, __ + ) -> Optional[Tuple[float, Dict[str, Scalar]]]: # pylint: disable=unused-argument + """Use the entire CIFAR-10/100 test set for evaluation.""" + net = instantiate(model) + params_dict = zip(net.state_dict().keys(), parameters_ndarrays) + state_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) + net.load_state_dict(state_dict, strict=True) + net.to(device) + + loss, accuracy = test(net, test_loader, device=device) + + experiment.log_metric("loss", loss, epoch=server_round) + experiment.log_metric("accuracy", accuracy*100, epoch=server_round) + + return loss, {"accuracy": accuracy} + + return evaluate diff --git a/baselines/fedpara/fedpara/strategy.py b/baselines/fedpara/fedpara/strategy.py new file mode 100644 index 000000000000..e4085f3f97fa --- /dev/null +++ b/baselines/fedpara/fedpara/strategy.py @@ -0,0 +1,28 @@ +"""FedPara strategy.""" + +from typing import Dict, List, Optional, Tuple, Union + +import numpy as np +import torch +from flwr.common import FitRes, Parameters, Scalar, ndarrays_to_parameters +from flwr.server.client_proxy import ClientProxy +from flwr.server.strategy import FedAvg +from torch.nn.utils import parameters_to_vector, vector_to_parameters + +from fedpara.utils import get_parameters + + +class FedPara(FedAvg): + """FedPara strategy.""" + + def __init__( + self, + algorithm: str, + **kwargs, + ) -> None: + super().__init__(**kwargs) + self.algorithm = algorithm + + def __repr__(self) -> str: + """Return the name of the strategy.""" + return self.algorithm diff --git a/baselines/fedpara/fedpara/utils.py b/baselines/fedpara/fedpara/utils.py new file mode 100644 index 000000000000..6c5cbd339123 --- /dev/null +++ b/baselines/fedpara/fedpara/utils.py @@ -0,0 +1,63 @@ +"""Utility functions for FedPara.""" + +import random +from pathlib import Path +from typing import Optional + +import matplotlib.pyplot as plt +import numpy as np +import torch +from flwr.common import NDArrays +from flwr.server import History +from omegaconf import DictConfig +from torch.nn import Module + + +def plot_metric_from_history( + hist: History, + save_plot_path: str, + suffix: Optional[str] = "", + cfg: Optional[DictConfig] = None, +) -> None: + """Plot the metrics from the history of the server. + + Parameters + ---------- + hist : History + Object containing evaluation for all rounds. + save_plot_path : str + Folder to save the plot to. + suffix: Optional[str] + Optional string to add at the end of the filename for the plot. + cfg : Optional[DictConfig] + Optional dictionary containing the configuration of the experiment. + """ + metric_type = "centralized" + metric_dict = ( + hist.metrics_centralized + if metric_type == "centralized" + else hist.metrics_distributed + ) + rounds, values_accuracy = zip(*metric_dict["accuracy"]) + _, axs = plt.subplots() + # Set the title + axs.set_title(f"{cfg.strategy.algorithm} | {cfg.dataset_config.name} | Seed {cfg.seed}") + axs.plot(np.asarray(rounds), np.asarray(values_accuracy)) + axs.set_ylabel("Accuracy") + axs.set_xlabel("Rounds") + fig_name = "_".join([metric_type, "metrics", suffix]) + ".png" + plt.savefig(Path(save_plot_path) / Path(fig_name)) + plt.close() + + +def seed_everything(seed): + """Seed everything for reproducibility.""" + np.random.seed(seed) + torch.manual_seed(seed) + random.seed(seed) + torch.backends.cudnn.deterministic = True + + +def get_parameters(net: Module) -> NDArrays: + """Get the parameters of the network.""" + return [val.cpu().numpy() for _, val in net.state_dict().items()] diff --git a/baselines/fedpara/pyproject.toml b/baselines/fedpara/pyproject.toml new file mode 100644 index 000000000000..e07a100940f3 --- /dev/null +++ b/baselines/fedpara/pyproject.toml @@ -0,0 +1,137 @@ +[build-system] +requires = ["poetry-core>=1.4.0"] +build-backend = "poetry.masonry.api" + +[tool.poetry] +name = "fedpara" # <----- Ensure it matches the name of your baseline directory containing all the source code +version = "1.0.0" +description = "Flower Baselines" +license = "Apache-2.0" +authors = ["The Flower Authors "] +readme = "README.md" +homepage = "https://flower.dev" +repository = "https://github.com/adap/flower" +documentation = "https://flower.dev" +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Scientific/Engineering :: Mathematics", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed", +] + +[tool.poetry.dependencies] +python = ">=3.8.15, <3.12.0" # don't change this +flwr = { extras = ["simulation"], version = "1.5.0" } +hydra-core = "1.3.2" # don't change this + +[tool.poetry.dev-dependencies] +isort = "==5.11.5" +black = "==23.1.0" +docformatter = "==1.5.1" +mypy = "==1.4.1" +pylint = "==2.8.2" +flake8 = "==3.9.2" +pytest = "==6.2.4" +pytest-watch = "==4.2.0" +ruff = "==0.0.272" +types-requests = "==2.27.7" + +[tool.isort] +line_length = 88 +indent = " " +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true + +[tool.black] +line-length = 88 +target-version = ["py38", "py39", "py310", "py311"] + +[tool.pytest.ini_options] +minversion = "6.2" +addopts = "-qq" +testpaths = [ + "flwr_baselines", +] + +[tool.mypy] +ignore_missing_imports = true +strict = false +plugins = "numpy.typing.mypy_plugin" + +[tool.pylint."MESSAGES CONTROL"] +disable = "bad-continuation,duplicate-code,too-few-public-methods,useless-import-alias" +good-names = "i,j,k,_,x,y,X,Y" +signature-mutators="hydra.main.main" + +[tool.pylint.typecheck] +generated-members="numpy.*, torch.*, tensorflow.*" + +[[tool.mypy.overrides]] +module = [ + "importlib.metadata.*", + "importlib_metadata.*", +] +follow_imports = "skip" +follow_imports_for_stubs = true +disallow_untyped_calls = false + +[[tool.mypy.overrides]] +module = "torch.*" +follow_imports = "skip" +follow_imports_for_stubs = true + +[tool.docformatter] +wrap-summaries = 88 +wrap-descriptions = 88 + +[tool.ruff] +target-version = "py38" +line-length = 88 +select = ["D", "E", "F", "W", "B", "ISC", "C4"] +fixable = ["D", "E", "F", "W", "B", "ISC", "C4"] +ignore = ["B024", "B027"] +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv", + "proto", +] + +[tool.ruff.pydocstyle] +convention = "numpy" diff --git a/baselines/fedpara/run.sh b/baselines/fedpara/run.sh new file mode 100644 index 000000000000..9ac265afdbed --- /dev/null +++ b/baselines/fedpara/run.sh @@ -0,0 +1,4 @@ + poetry run python -m fedpara.main > "cifar10noniidaug.txt" 2>&1 & + poetry run python -m fedpara.main --config-name cifar100 > "cifar100noniidaug.txt" 2>&1 & + +wait \ No newline at end of file From 8b1c04d8239eb5690cf66347b71d9004bfe7ec4d Mon Sep 17 00:00:00 2001 From: Roeia Amr <60478505+Roeia99@users.noreply.github.com> Date: Sun, 17 Dec 2023 20:46:44 +0200 Subject: [PATCH 02/39] edited the Readme file --- baselines/fedpara/README.md | 70 +++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 682952717426..be068e3faea7 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -1,47 +1,79 @@ --- title: title of the paper -url: URL to the paper page (not the pdf) -labels: [label1, label2] # please add between 4 and 10 single-word (maybe two-words) labels (e.g. "system heterogeneity", "image classification", "asynchronous", "weight sharing", "cross-silo") -dataset: [dataset1, dataset2] # list of datasets you include in your baseline +url: https://openreview.net/forum?id=d71n4ftoCBy +labels: [image classification, personalization, low-rank training, tensor decomposition] +dataset: [CIFAR10, CIFAR100, FEMNIST] --- -# :warning:*_Title of your baseline_* +# FedPara: Low-rank Hadamard Product for Communication-Efficient Federated Learning > Note: If you use this baseline in your work, please remember to cite the original authors of the paper as well as the Flower paper. -> :warning: This is the template to follow when creating a new Flower Baseline. Please follow the instructions in `EXTENDED_README.md` +**Paper:** [openreview.net/forum?id=d71n4ftoCBy](https://openreview.net/forum?id=d71n4ftoCBy) -> :warning: Please follow the instructions carefully. You can see the [FedProx-MNIST baseline](https://github.com/adap/flower/tree/main/baselines/fedprox) as an example of a baseline that followed this guide. +****Authors:**** Nam Hyeon-Woo, Moon Ye-Bin, Tae-Hyun Oh -> :warning: Please complete the metadata section at the very top of this README. This generates a table at the top of the file that will facilitate indexing baselines. +****Abstract:**** In this work, we propose a communication-efficient parameterization, FedPara, +for federated learning (FL) to overcome the burdens on frequent model uploads +and downloads. Our method re-parameterizes weight parameters of layers using +low-rank weights followed by the Hadamard product. Compared to the conventional low-rank parameterization, our FedPara method is not restricted to lowrank constraints, and thereby it has a far larger capacity. This property enables to +achieve comparable performance while requiring 3 to 10 times lower communication costs than the model with the original layers, which is not achievable by +the traditional low-rank methods. The efficiency of our method can be further improved by combining with other efficient FL optimizers. In addition, we extend +our method to a personalized FL application, pFedPara, which separates parameters into global and local ones. We show that pFedPara outperforms competing +personalized FL methods with more than three times fewer parameters. Project +page: https://github.com/South-hw/FedPara_ICLR22 -****Paper:**** :warning: *_add the URL of the paper page (not to the .pdf). For instance if you link a paper on ArXiv, add here the URL to the abstract page (e.g. https://arxiv.org/abs/1512.03385). If your paper is in from a journal or conference proceedings, please follow the same logic._* -****Authors:**** :warning: *_list authors of the paper_* -****Abstract:**** :warning: *_add here the abstract of the paper you are implementing_* +## About this baseline +****What’s implemented:**** The code in this directory replicates the experiments in FedPara paper implementing the Low-rank scheme for Convolution module inspired by the [author's code]( https://github.com/South-hw/FedPara_ICLR22). + +Specifically, it replicates the results for Cifar10 and Cifar100 in Figure 3 and the results for Feminist in Figure 5(a). -## About this baseline -****What’s implemented:**** :warning: *_Concisely describe what experiment(s) in the publication can be replicated by running the code. Please only use a few sentences. Start with: “The code in this directory …”_* +****Datasets:**** CIFAR10, CIFAR100, FEMNIST from PyTorch's Torchvision -****Datasets:**** :warning: *_List the datasets you used (if you used a medium to large dataset, >10GB please also include the sizes of the dataset)._* +****Hardware Setup:**** The experiment has been conducted on our server with the following specs: -****Hardware Setup:**** :warning: *_Give some details about the hardware (e.g. a server with 8x V100 32GB and 256GB of RAM) you used to run the experiments for this baseline. Someone out there might not have access to the same resources you have so, could list the absolute minimum hardware needed to run the experiment in a reasonable amount of time ? (e.g. minimum is 1x 16GB GPU otherwise a client model can’t be trained with a sufficiently large batch size). Could you test this works too?_* +- **GPU:** 1 Tesla V100 GPU 32GB VRAM +- **CPU:** 1x24 cores Intel Xeon(R) 6248R +- **RAM:** 150 GB -****Contributors:**** :warning: *_let the world know who contributed to this baseline. This could be either your name, your name and affiliation at the time, or your GitHub profile name if you prefer. If multiple contributors signed up for this baseline, please list yourself and your colleagues_* +****Contributors:**** Yahia Salaheldin Shaaban, Omar Mokhtar and Roeia Amr ## Experimental Setup -****Task:**** :warning: *_what’s the primary task that is being federated? (e.g. image classification, next-word prediction). If you have experiments for several, please list them_* +****Task:**** Image classification + +****Model:**** This directory implements Vgg16 with group normalization. + +****Dataset:**** + +In IID settings: + +| Dataset | #classes | #partitions | partitioning method | +|:---------|:--------:|:-----------:|:----------------------:| +| Cifar10 | 10 | 100 | random split | +| Cifar100 | 100 | 50 | randpm split| + +In non-IID settings: + +| Dataset | #classes | #partitions | partitioning method | +|:---------|:--------:|:-----------:|:----------------------:| +| Cifar10 | 10 | 100 | Dirichlet distribution | +| Cifar100 | 100 | 50 | Dirichlet distribution | + -****Model:**** :warning: *_provide details about the model you used in your experiments (if more than use a list). If your model is small, describing it as a table would be :100:. Some FL methods do not use an off-the-shelve model (e.g. ResNet18) instead they create your own. If this is your case, please provide a summary here and give pointers to where in the paper (e.g. Appendix B.4) is detailed._* +****Training Hyperparameters:**** -****Dataset:**** :warning: *_Earlier you listed already the datasets that your baseline uses. Now you should include a breakdown of the details about each of them. Please include information about: how the dataset is partitioned (e.g. LDA with alpha 0.1 as default and all clients have the same number of training examples; or each client gets assigned a different number of samples following a power-law distribution with each client only instances of 2 classes)? if your dataset is naturally partitioned just state “naturally partitioned”; how many partitions there are (i.e. how many clients)? Please include this an all information relevant about the dataset and its partitioning into a table._* +For Dataset: +Choice of alpha parameter for the Dirichlet distribution used to create heterogeneity in the client datasets for CIFAR -****Training Hyperparameters:**** :warning: *_Include a table with all the main hyperparameters in your baseline. Please show them with their default value._* +| Description | Default Value | +|-------------|---------------| +| alpha | 0.5 | ## Environment Setup From 221bcfabf4e89fd60fe03fcf13c9f338b969e88b Mon Sep 17 00:00:00 2001 From: Roeia Amr <60478505+Roeia99@users.noreply.github.com> Date: Sun, 17 Dec 2023 20:48:23 +0200 Subject: [PATCH 03/39] readme --- baselines/fedpara/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index be068e3faea7..afecd53427a1 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -1,5 +1,5 @@ --- -title: title of the paper +title: FedPara: Low-rank Hadamard Product for Communication-Efficient Federated Learning url: https://openreview.net/forum?id=d71n4ftoCBy labels: [image classification, personalization, low-rank training, tensor decomposition] dataset: [CIFAR10, CIFAR100, FEMNIST] From 7d61536a4f951529d51afed8e3d605bfa8929f1b Mon Sep 17 00:00:00 2001 From: Roeia Amr <60478505+Roeia99@users.noreply.github.com> Date: Sun, 17 Dec 2023 20:49:16 +0200 Subject: [PATCH 04/39] Update README.md --- baselines/fedpara/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index afecd53427a1..b4a21a8934dc 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -1,5 +1,5 @@ --- -title: FedPara: Low-rank Hadamard Product for Communication-Efficient Federated Learning +title: FedPara Low-rank Hadamard Product for Communication-Efficient Federated Learning url: https://openreview.net/forum?id=d71n4ftoCBy labels: [image classification, personalization, low-rank training, tensor decomposition] dataset: [CIFAR10, CIFAR100, FEMNIST] From 2a6c98090c45e7062a92afcc8ad5ae07ebe0973b Mon Sep 17 00:00:00 2001 From: Roeia Amr <60478505+Roeia99@users.noreply.github.com> Date: Sun, 17 Dec 2023 20:50:22 +0200 Subject: [PATCH 05/39] Update README.md --- baselines/fedpara/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index b4a21a8934dc..78bcde87b7a1 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -56,7 +56,7 @@ In IID settings: | Dataset | #classes | #partitions | partitioning method | |:---------|:--------:|:-----------:|:----------------------:| | Cifar10 | 10 | 100 | random split | -| Cifar100 | 100 | 50 | randpm split| +| Cifar100 | 100 | 50 | random split| In non-IID settings: From 8bcdfabec8749972cfe237b0273f359e81a59e24 Mon Sep 17 00:00:00 2001 From: Omar Mokhtar Date: Sun, 17 Dec 2023 20:57:10 +0200 Subject: [PATCH 06/39] linting --- baselines/fedpara/EXTENDED_README.md | 123 -------- baselines/fedpara/fedpara/client.py | 26 +- baselines/fedpara/fedpara/dataset.py | 112 ++------ .../fedpara/fedpara/dataset_preparation.py | 101 +++++++ baselines/fedpara/fedpara/main.py | 26 +- baselines/fedpara/fedpara/models.py | 264 +++++++++++------- baselines/fedpara/fedpara/server.py | 6 +- baselines/fedpara/fedpara/strategy.py | 13 +- baselines/fedpara/fedpara/utils.py | 4 +- 9 files changed, 322 insertions(+), 353 deletions(-) delete mode 100644 baselines/fedpara/EXTENDED_README.md create mode 100644 baselines/fedpara/fedpara/dataset_preparation.py diff --git a/baselines/fedpara/EXTENDED_README.md b/baselines/fedpara/EXTENDED_README.md deleted file mode 100644 index 9c8f5bc72fa9..000000000000 --- a/baselines/fedpara/EXTENDED_README.md +++ /dev/null @@ -1,123 +0,0 @@ - -# Extended Readme - -> The baselines are expected to run in a machine running Ubuntu 22.04 - -While `README.md` should include information about the baseline you implement and how to run it, this _extended_ readme provides info on what's the expected directory structure for a new baseline and more generally the instructions to follow before your baseline can be merged into the Flower repository. Please follow closely these instructions. It is likely that you have already completed steps 1-2. - -1. Fork the Flower repository and clone it. -2. Navigate to the `baselines/` directory and from there run: - ```bash - # This will create a new directory with the same structure as this `baseline_template` directory. - ./dev/create-baseline.sh - ``` -3. All your code and configs should go into a sub-directory with the same name as the name of your baseline. - * The sub-directory contains a series of Python scripts that you can edit. Please stick to these files and consult with us if you need additional ones. - * There is also a basic config structure in `/conf` ready be parsed by [Hydra](https://hydra.cc/) when executing your `main.py`. -4. Therefore, the directory structure in your baseline should look like: - ```bash - baselines/ - ├── README.md # describes your baseline and everything needed to use it - ├── EXTENDED_README.md # to remove before creating your PR - ├── pyproject.toml # details your Python environment - └── - ├── *.py # several .py files including main.py and __init__.py - └── conf - └── *.yaml # one or more Hydra config files - - ``` -> :warning: Make sure the variable `name` in `pyproject.toml` is set to the name of the sub-directory containing all your code. - -5. Add your dependencies to the `pyproject.toml` (see below a few examples on how to do it). Read more about Poetry below in this `EXTENDED_README.md`. -6. Regularly check that your coding style and the documentation you add follow good coding practices. To test whether your code meets the requirements, please run the following: - ```bash - # After activating your environment and from your baseline's directory - cd .. # to go to the top-level directory of all baselines - ./dev/test-baseline.sh - ./dev/test-baseline-structure.sh - ``` - Both `test-baseline.sh` and `test-baseline-structure.sh` will also be automatically run when you create a PR, and both tests need to pass for the baseline to be merged. - To automatically solve some formatting issues and apply easy fixes, please run the formatting script: - ```bash - # After activating your environment and from your baseline's directory - cd .. # to go to the top-level directory of all baselines - ./dev/format-baseline.sh - ``` -7. Ensure that the Python environment for your baseline can be created without errors by simply running `poetry install` and that this is properly described later when you complete the `Environment Setup` section in `README.md`. This is specially important if your environment requires additional steps after doing `poetry install`. -8. Ensure that your baseline runs with default arguments by running `poetry run python -m .main`. Then, describe this and other forms of running your code in the `Running the Experiments` section in `README.md`. -9. Once your code is ready and you have checked: - * that following the instructions in your `README.md` the Python environment can be created correctly - - * that running the code following your instructions can reproduce the experiments in the paper - - , then you just need to create a Pull Request (PR) to kickstart the process of merging your baseline into the Flower repository. - -> Once you are happy to merge your baseline contribution, please delete this `EXTENDED_README.md` file. - - -## About Poetry - -We use Poetry to manage the Python environment for each individual baseline. You can follow the instructions [here](https://python-poetry.org/docs/) to install Poetry in your machine. - - -### Specifying a Python Version (optional) -By default, Poetry will use the Python version in your system. In some settings, you might want to specify a particular version of Python to use inside your Poetry environment. You can do so with [`pyenv`](https://github.com/pyenv/pyenv). Check the documentation for the different ways of installing `pyenv`, but one easy way is using the [automatic installer](https://github.com/pyenv/pyenv-installer): -```bash -curl https://pyenv.run | bash # then, don't forget links to your .bashrc/.zshrc -``` - -You can then install any Python version with `pyenv install ` (e.g. `pyenv install 3.9.17`). Then, in order to use that version for your baseline, you'd do the following: - -```bash -# cd to your baseline directory (i.e. where the `pyproject.toml` is) -pyenv local - -# set that version for poetry -poetry env use - -# then you can install your Poetry environment (see the next setp) -``` - -### Installing Your Environment -With the Poetry tool already installed, you can create an environment for this baseline with commands: -```bash -# run this from the same directory as the `pyproject.toml` file is -poetry install -``` - -This will create a basic Python environment with just Flower and additional packages, including those needed for simulation. Next, you should add the dependencies for your code. It is **critical** that you fix the version of the packages you use using a `=` not a `=^`. You can do so via [`poetry add`](https://python-poetry.org/docs/cli/#add). Below are some examples: - -```bash -# For instance, if you want to install tqdm -poetry add tqdm==4.65.0 - -# If you already have a requirements.txt, you can add all those packages (but ensure you have fixed the version) in one go as follows: -poetry add $( cat requirements.txt ) -``` -With each `poetry add` command, the `pyproject.toml` gets automatically updated so you don't need to keep that `requirements.txt` as part of this baseline. - - -More critically however, is adding your ML framework of choice to the list of dependencies. For some frameworks you might be able to do so with the `poetry add` command. Check [the Poetry documentation](https://python-poetry.org/docs/cli/#add) for how to add packages in various ways. For instance, let's say you want to use PyTorch: - -```bash -# with plain `pip` you'd run a command such as: -pip install torch==1.13.1+cu117 torchvision==0.14.1+cu117 torchaudio==0.13.1 --extra-index-url https://download.pytorch.org/whl/cu117 - -# to add the same 3 dependencies to your Poetry environment you'd need to add the URL to the wheel that the above pip command auto-resolves for you. -# You can find those wheels in `https://download.pytorch.org/whl/cu117`. Copy the link and paste it after the `poetry add` command. -# For instance to add `torch==1.13.1+cu117` and a x86 Linux system with Python3.8 you'd: -poetry add https://download.pytorch.org/whl/cu117/torch-1.13.1%2Bcu117-cp38-cp38-linux_x86_64.whl -# you'll need to repeat this for both `torchvision` and `torchaudio` -``` -The above is just an example of how you can add these dependencies. Please refer to the Poetry documentation to extra reference. - -If all attempts fail, you can still install packages via standard `pip`. You'd first need to source/activate your Poetry environment. -```bash -# first ensure you have created your environment -# and installed the base packages provided in the template -poetry install - -# then activate it -poetry shell -``` -Now you are inside your environment (pretty much as when you use `virtualenv` or `conda`) so you can install further packages with `pip`. Please note that, unlike with `poetry add`, these extra requirements won't be captured by `pyproject.toml`. Therefore, please ensure that you provide all instructions needed to: (1) create the base environment with Poetry and (2) install any additional dependencies via `pip` when you complete your `README.md`. \ No newline at end of file diff --git a/baselines/fedpara/fedpara/client.py b/baselines/fedpara/fedpara/client.py index 408ac2bc52ab..21e62eeaed08 100644 --- a/baselines/fedpara/fedpara/client.py +++ b/baselines/fedpara/fedpara/client.py @@ -1,6 +1,5 @@ """Client for FedPara.""" -import copy from collections import OrderedDict from typing import Callable, Dict, List, Tuple @@ -9,7 +8,6 @@ from flwr.common import NDArrays, Scalar from hydra.utils import instantiate from omegaconf import DictConfig -from torch.nn.utils import parameters_to_vector from torch.utils.data import DataLoader from fedpara.models import train @@ -19,12 +17,12 @@ class FlowerClient(fl.client.NumPyClient): """Standard Flower client for CNN training.""" def __init__( - self, - cid: int, - net: torch.nn.Module, - train_loader: DataLoader, - device: str, - num_epochs: int, + self, + cid: int, + net: torch.nn.Module, + train_loader: DataLoader, + device: str, + num_epochs: int, ): # pylint: disable=too-many-arguments print(f"Initializing Client {cid}") self.cid = cid @@ -34,7 +32,7 @@ def __init__( self.num_epochs = num_epochs def get_parameters(self, config: Dict[str, Scalar]) -> NDArrays: - """Returns the parameters of the current net.""" + """Return the parameters of the current net.""" return [val.cpu().numpy() for _, val in self.net.state_dict().items()] def _set_parameters(self, parameters: NDArrays) -> None: @@ -43,7 +41,7 @@ def _set_parameters(self, parameters: NDArrays) -> None: self.net.load_state_dict(state_dict, strict=True) def fit( - self, parameters: NDArrays, config: Dict[str, Scalar] + self, parameters: NDArrays, config: Dict[str, Scalar] ) -> Tuple[NDArrays, int, Dict]: """Train the network on the training set.""" self._set_parameters(parameters) @@ -66,10 +64,10 @@ def fit( def gen_client_fn( - train_loaders: List[DataLoader], - model: DictConfig, - num_epochs: int, - args: Dict, + train_loaders: List[DataLoader], + model: DictConfig, + num_epochs: int, + args: Dict, ) -> Callable[[str], FlowerClient]: """Return a function which creates a new FlowerClient for a given cid.""" diff --git a/baselines/fedpara/fedpara/dataset.py b/baselines/fedpara/fedpara/dataset.py index 2ff2f28a30c1..5cd7e06b31ae 100644 --- a/baselines/fedpara/fedpara/dataset.py +++ b/baselines/fedpara/fedpara/dataset.py @@ -2,88 +2,16 @@ import pickle from typing import List, Tuple -import random -import numpy as np -from collections import defaultdict -import torch -from torch.utils.data import DataLoader, Dataset -from torchvision import datasets, transforms -import omegaconf - -class DatasetSplit(Dataset): - def __init__(self, dataset, idxs): - self.dataset = dataset - self.targets = dataset.targets - self.idxs = list(idxs) - - def __len__(self): - return len(self.idxs) - - def __getitem__(self, item): - image, label = self.dataset[self.idxs[item]] - return image, label - -def iid(dataset, num_users): - """ - Sample I.I.D. client data from CIFAR dataset - :param dataset: - :param num_users: - :return: dict of image index - """ - num_items = int(len(dataset)/num_users) - dict_users, all_idxs = {}, [i for i in range(len(dataset))] - for i in range(num_users): - dict_users[i] = set(np.random.choice(all_idxs, num_items, replace=False)) - all_idxs = list(set(all_idxs) - dict_users[i]) - return dict_users - -def noniid(dataset, no_participants, alpha=0.5): - """ - Input: Number of participants and alpha (param for distribution) - Output: A list of indices denoting data in CIFAR training set. - Requires: cifar_classes, a preprocessed class-indice dictionary. - Sample Method: take a uniformly sampled 10/100-dimension vector as parameters for - dirichlet distribution to sample number of images in each class. - """ - np.random.seed(666) - random.seed(666) - cifar_classes = {} - for ind, x in enumerate(dataset): - _, label = x - if label in cifar_classes: - cifar_classes[label].append(ind) - else: - cifar_classes[label] = [ind] +from torch.utils.data import DataLoader +from torchvision import datasets, transforms - per_participant_list = defaultdict(list) - no_classes = len(cifar_classes.keys()) - class_size = len(cifar_classes[0]) - datasize = {} - for n in range(no_classes): - random.shuffle(cifar_classes[n]) - sampled_probabilities = class_size * np.random.dirichlet( - np.array(no_participants * [alpha])) - for user in range(no_participants): - no_imgs = int(round(sampled_probabilities[user])) - datasize[user, n] = no_imgs - sampled_list = cifar_classes[n][:min(len(cifar_classes[n]), no_imgs)] - per_participant_list[user].extend(sampled_list) - cifar_classes[n] = cifar_classes[n][min(len(cifar_classes[n]), no_imgs):] - train_img_size = np.zeros(no_participants) - for i in range(no_participants): - train_img_size[i] = sum([datasize[i,j] for j in range(no_classes)]) - clas_weight = np.zeros((no_participants,no_classes)) - for i in range(no_participants): - for j in range(no_classes): - clas_weight[i,j] = float(datasize[i,j])/float((train_img_size[i])) - return per_participant_list, clas_weight +from baselines.fedpara.fedpara.dataset_preparation import iid, noniid, DatasetSplit def load_datasets( - config, num_clients, batch_size + config, num_clients, batch_size ) -> Tuple[List[DataLoader], DataLoader]: - """Load the dataset and return the dataloaders for the clients and the server.""" print("Loading data...") if config.name == "CIFAR10": @@ -94,39 +22,43 @@ def load_datasets( raise NotImplementedError data_directory = f"./data/{config.name.lower()}/" ds_path = f"{data_directory}train_{num_clients}_{config.alpha:.2f}.pkl" - transform_train = transforms.Compose([ + transform_train = transforms.Compose( + [ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), - ]) - transform_test = transforms.Compose([ + ] + ) + transform_test = transforms.Compose( + [ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), - ]) + ] + ) try: with open(ds_path, "rb") as file: train_datasets = pickle.load(file) except FileNotFoundError: dataset_train = Dataset( - data_directory, train=True, download=True, transform=transform_train) + data_directory, train=True, download=True, transform=transform_train + ) if config.partition == "iid": - train_datasets = iid( - dataset_train, - num_clients) + train_datasets = iid(dataset_train, num_clients) else: - train_datasets, _ = noniid( - dataset_train, - num_clients, - config.alpha) + train_datasets, _ = noniid(dataset_train, num_clients, config.alpha) dataset_test = Dataset( data_directory, train=False, download=True, transform=transform_test ) test_loader = DataLoader(dataset_test, batch_size=batch_size, num_workers=2) train_loaders = [ - DataLoader(DatasetSplit(dataset_train, ids), batch_size=batch_size, shuffle=True, num_workers=2) + DataLoader( + DatasetSplit(dataset_train, ids), + batch_size=batch_size, + shuffle=True, + num_workers=2, + ) for ids in train_datasets.values() ] return train_loaders, test_loader - diff --git a/baselines/fedpara/fedpara/dataset_preparation.py b/baselines/fedpara/fedpara/dataset_preparation.py new file mode 100644 index 000000000000..d957b1049531 --- /dev/null +++ b/baselines/fedpara/fedpara/dataset_preparation.py @@ -0,0 +1,101 @@ +"""Handle the dataset partitioning and (optionally) complex downloads. + +Please add here all the necessary logic to either download, uncompress, pre/post-process +your dataset (or all of the above). If the desired way of running your baseline is to +first download the dataset and partition it and then run the experiments, please +uncomment the lines below and tell us in the README.md (see the "Running the Experiment" +block) that this file should be executed first. +""" +import random +from collections import defaultdict + +import numpy as np +from torch.utils.data import Dataset + + +class DatasetSplit(Dataset): + """An abstract Dataset class wrapped around Pytorch Dataset class.""" + + def __init__(self, dataset, idxs): + self.dataset = dataset + self.targets = dataset.targets + self.idxs = list(idxs) + + def __len__(self): + """Return number of images.""" + return len(self.idxs) + + def __getitem__(self, item): + """Return a transformed example of the dataset.""" + image, label = self.dataset[self.idxs[item]] + return image, label + + +def iid(dataset, num_users): + """ + Sample I.I.D. clients data from a dataset. + + Args: + dataset: dataset object + num_users: number of users + Returns: + dict of image index + """ + num_items = int(len(dataset) / num_users) + dict_users, all_idxs = {}, list(range(len(dataset))) + for i in range(num_users): + dict_users[i] = set(np.random.choice(all_idxs, num_items, replace=False)) + all_idxs = list(set(all_idxs) - dict_users[i]) + return dict_users + + +def noniid(dataset, no_participants, alpha=0.5): + """ + Sample non-I.I.D client data from dataset. + + Args: + dataset: dataset object + no_participants: number of users + alpha: float parameter for dirichlet distribution + Returns: + dict of image index + Requires: + cifar_classes, a preprocessed class-indice dictionary. + Sample Method: take a uniformly sampled 10/100-dimension vector + as parameters for dirichlet distribution to sample number of + images in each class. + """ + np.random.seed(666) + random.seed(666) + cifar_classes = {} + for ind, x in enumerate(dataset): + _, label = x + if label in cifar_classes: + cifar_classes[label].append(ind) + else: + cifar_classes[label] = [ind] + + per_participant_list = defaultdict(list) + no_classes = len(cifar_classes.keys()) + class_size = len(cifar_classes[0]) + datasize = {} + for n in range(no_classes): + random.shuffle(cifar_classes[n]) + sampled_probabilities = class_size * np.random.dirichlet( + np.array(no_participants * [alpha]) + ) + for user in range(no_participants): + no_imgs = int(round(sampled_probabilities[user])) + datasize[user, n] = no_imgs + sampled_list = cifar_classes[n][: min(len(cifar_classes[n]), no_imgs)] + per_participant_list[user].extend(sampled_list) + cifar_classes[n] = cifar_classes[n][min(len(cifar_classes[n]), no_imgs):] + train_img_size = np.zeros(no_participants) + for i in range(no_participants): + train_img_size[i] = sum([datasize[i, j] for j in range(no_classes)]) + clas_weight = np.zeros((no_participants, no_classes)) + for i in range(no_participants): + for j in range(no_classes): + clas_weight[i, j] = float(datasize[i, j]) / float((train_img_size[i])) + return per_participant_list, clas_weight + diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index a4045a82f413..b9b6645cd0db 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -1,15 +1,16 @@ """Main script for running FedPara.""" -from comet_ml import Experiment import flwr as fl import hydra -import numpy as np +from comet_ml import Experiment from hydra.core.hydra_config import HydraConfig from hydra.utils import instantiate from omegaconf import DictConfig, OmegaConf + from fedpara import client, server, utils -from fedpara.dataset import load_datasets +from fedpara.dataset_preparation import load_datasets from fedpara.utils import get_parameters, seed_everything + @hydra.main(config_path="conf", config_name="cifar10", version_base=None) def main(cfg: DictConfig) -> None: """Run the baseline. @@ -25,16 +26,17 @@ def main(cfg: DictConfig) -> None: # # Comet ML tracking credentials = OmegaConf.load("fedpara/conf/credentials.yaml") experiment = Experiment( - api_key=credentials.api_key, - project_name=credentials.project_name, - workspace=credentials.workspace, + api_key=credentials.api_key, + project_name=credentials.project_name, + workspace=credentials.workspace, + ) + experiment.set_name( + f"flower | {cfg.strategy.algorithm} " + f"| {cfg.dataset_config.name} | Seed {cfg.seed}" ) - experiment.set_name(f"flower | {cfg.strategy.algorithm} | {cfg.dataset_config.name} | Seed {cfg.seed}") - hyper_params = { - "dataset": cfg.dataset_config.name, - "seed": cfg.seed, - } - # experiment.log_parameters(hyper_params) + hyper_params = OmegaConf.to_container(cfg, resolve=True) + experiment.log_parameters(hyper_params) + # 2. Prepare dataset train_loaders, test_loader = load_datasets( config=cfg.dataset_config, diff --git a/baselines/fedpara/fedpara/models.py b/baselines/fedpara/fedpara/models.py index b7df4295aba1..25f09e620f45 100644 --- a/baselines/fedpara/fedpara/models.py +++ b/baselines/fedpara/fedpara/models.py @@ -1,58 +1,67 @@ """Model definitions for FedPara.""" +import math from typing import Dict, Tuple -from torch.nn import init + +import numpy as np import torch import torch.nn.functional as F from flwr.common import Scalar from torch import nn -from torch.nn.utils import parameters_to_vector +from torch.nn import init from torch.utils.data import DataLoader -from torchvision import transforms from tqdm import tqdm -import torchvision.models as models -import numpy as np -import math + class LowRank(nn.Module): - def __init__(self, - in_channels: int, - out_channels: int, - low_rank: int, - kernel_size: int - ,activation: str = 'relu'): - super().__init__() - self.T = nn.Parameter( - torch.empty(size=(low_rank, low_rank, kernel_size, kernel_size)), - requires_grad=True - ) - self.X = nn.Parameter( - torch.empty(size=(low_rank, out_channels)), - requires_grad=True - ) - self.Y = nn.Parameter( - torch.empty(size=(low_rank, in_channels)), - requires_grad=True - ) - if activation == 'leakyrelu': activation = 'leaky_relu' - init.kaiming_normal_(self.T, mode='fan_out', nonlinearity=activation) - init.kaiming_normal_(self.X, mode='fan_out', nonlinearity=activation) - init.kaiming_normal_(self.Y, mode='fan_out', nonlinearity=activation) - - def forward(self): - # torch.einsum simplify the tensor produce (matrix multiplication) - return torch.einsum("xyzw,xo,yi->oizw", self.T, self.X, self.Y) + """Low-rank convolutional layer.""" + + def __init__( + self, + in_channels: int, + out_channels: int, + low_rank: int, + kernel_size: int, + activation: str = "relu", + ): + super().__init__() + self.T = nn.Parameter( + torch.empty(size=(low_rank, low_rank, kernel_size, kernel_size)), + requires_grad=True, + ) + self.X = nn.Parameter( + torch.empty(size=(low_rank, out_channels)), requires_grad=True + ) + self.Y = nn.Parameter( + torch.empty(size=(low_rank, in_channels)), requires_grad=True + ) + if activation == "leakyrelu": + activation = "leaky_relu" + init.kaiming_normal_(self.T, mode="fan_out", nonlinearity=activation) + init.kaiming_normal_(self.X, mode="fan_out", nonlinearity=activation) + init.kaiming_normal_(self.Y, mode="fan_out", nonlinearity=activation) + + def forward(self): + """Forward pass.""" + # torch.einsum simplify the tensor produce (matrix multiplication) + return torch.einsum("xyzw,xo,yi->oizw", self.T, self.X, self.Y) + + class Conv2d(nn.Module): - def __init__(self, - in_channels: int, - out_channels: int, - kernel_size: int = 3, - stride: int = 1, - padding: int = 0, - bias: bool = False, - ratio: float = 0.1, - add_nonlinear: bool = False, - activation: str = 'relu'): + """Convolutional layer with low-rank weights.""" + + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: int = 3, + stride: int = 1, + padding: int = 0, + bias: bool = False, + ratio: float = 0.1, + add_nonlinear: bool = False, + activation: str = "relu", + ): super().__init__() self.in_channels = in_channels self.out_channels = out_channels @@ -64,8 +73,12 @@ def __init__(self, self.low_rank = self._calc_from_ratio() self.add_nonlinear = add_nonlinear self.activation = activation - self.W1 = LowRank(in_channels, out_channels, self.low_rank, kernel_size,activation) - self.W2 = LowRank(in_channels, out_channels, self.low_rank, kernel_size,activation) + self.W1 = LowRank( + in_channels, out_channels, self.low_rank, kernel_size, activation + ) + self.W2 = LowRank( + in_channels, out_channels, self.low_rank, kernel_size, activation + ) self.bias = nn.Parameter(torch.zeros(out_channels)) if bias else None self.tanh = nn.Tanh() @@ -75,41 +88,76 @@ def _calc_from_ratio(self): r2 = int(np.ceil(np.sqrt(self.in_channels))) r = np.max((r1, r2)) - num_target_params = self.out_channels * self.in_channels * \ - (self.kernel_size ** 2) * self.ratio + num_target_params = ( + self.out_channels * self.in_channels * (self.kernel_size**2) * self.ratio + ) r3 = np.sqrt( - ((self.out_channels + self.in_channels) ** 2) / (4 * (self.kernel_size ** 4)) + \ - num_target_params / (2 * (self.kernel_size ** 2)) - ) - (self.out_channels + self.in_channels) / (2 * (self.kernel_size ** 2)) + ((self.out_channels + self.in_channels) ** 2) + / (4 * (self.kernel_size**4)) + + num_target_params / (2 * (self.kernel_size**2)) + ) - (self.out_channels + self.in_channels) / (2 * (self.kernel_size**2)) r3 = int(np.ceil(r3)) r = np.max((r, r3)) return r def forward(self, x): + """Forward pass.""" # Hadamard product of two submatrices if self.add_nonlinear: W = self.tanh(self.W1()) * self.tanh(self.W2()) else: W = self.W1() * self.W2() - out = F.conv2d(input=x, weight=W, bias=self.bias, - stride=self.stride, padding=self.padding) - return out + out = F.conv2d( + input=x, weight=W, bias=self.bias, stride=self.stride, padding=self.padding + ) + return out + + class VGG(nn.Module): - def __init__(self,num_classes, num_groups=2, ratio=0.1, activation='relu', - conv_type='lowrank', add_nonlinear=False): + """VGG16GN model.""" + + def __init__( + self, + num_classes, + num_groups=2, + ratio=0.1, + activation="relu", + conv_type="lowrank", + add_nonlinear=False, + ): super(VGG, self).__init__() - if activation == 'relu': + if activation == "relu": self.activation = nn.ReLU(inplace=True) - elif activation == 'leaky_relu': + elif activation == "leaky_relu": self.activation = nn.LeakyReLU(inplace=True) self.conv_type = conv_type self.num_groups = num_groups - self.num_classes = num_classes + self.num_classes = num_classes self.ratio = ratio - self.add_nonlinear = add_nonlinear - self.features = self.make_layers([64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M' - , 512, 512, 512, 'M', 512, 512, 512, 'M']) + self.add_nonlinear = add_nonlinear + self.features = self._make_layers( + [ + 64, + 64, + "M", + 128, + 128, + "M", + 256, + 256, + 256, + "M", + 512, + 512, + 512, + "M", + 512, + 512, + 512, + "M", + ] + ) self.classifier = nn.Sequential( nn.Dropout(), nn.Linear(512, 512), @@ -122,54 +170,68 @@ def __init__(self,num_classes, num_groups=2, ratio=0.1, activation='relu', self.init_weights() def init_weights(self): - for name, module in self.features.named_children(): + """Initialize the weights.""" + for name, module in self.features.named_children(): module = getattr(self.features, name) if isinstance(module, nn.Conv2d): - if self.conv_type == 'lowrank': + if self.conv_type == "lowrank": num_channels = module.in_channels - setattr(self.features, name, Conv2d( - num_channels, - module.out_channels, - module.kernel_size[0], - module.stride[0], - module.padding[0], - module.bias is not None, - ratio=self.ratio, - add_nonlinear=self.add_nonlinear, - # send the name of the activation function to the Conv2d class - activation=self.activation.__class__.__name__.lower() - )) - elif self.conv_type == 'standard': - n = module.kernel_size[0] * module.kernel_size[1] * module.out_channels - module.weight.data.normal_(0, math.sqrt(2. / n)) + setattr( + self.features, + name, + Conv2d( + num_channels, + module.out_channels, + module.kernel_size[0], + module.stride[0], + module.padding[0], + module.bias is not None, + ratio=self.ratio, + add_nonlinear=self.add_nonlinear, + # send the activation function to the Conv2d class + activation=self.activation.__class__.__name__.lower(), + ), + ) + elif self.conv_type == "standard": + n = ( + module.kernel_size[0] + * module.kernel_size[1] + * module.out_channels + ) + module.weight.data.normal_(0, math.sqrt(2.0 / n)) module.bias.data.zero_() - def make_layers(self,cfg, group_norm=True): + def _make_layers(self, cfg, group_norm=True): layers = [] in_channels = 3 for v in cfg: - if v == 'M': + if v == "M": layers += [nn.MaxPool2d(kernel_size=2, stride=2)] else: conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1) if group_norm: - layers += [conv2d, nn.GroupNorm(self.num_groups,v), self.activation] + layers += [ + conv2d, + nn.GroupNorm(self.num_groups, v), + self.activation, + ] else: layers += [conv2d, self.activation] in_channels = v return nn.Sequential(*layers) def forward(self, input): + """Forward pass.""" x = self.features(input) x = x.view(x.size(0), -1) x = self.classifier(x) return x -# Create an instance of the VGG16GN model with Group Normalization, custom Conv2d, and modified classifier - +# Create an instance of the VGG16GN model with Group Normalization, +# custom Conv2d, and modified classifier def test( - net: nn.Module, test_loader: DataLoader, device: torch.device + net: nn.Module, test_loader: DataLoader, device: torch.device ) -> Tuple[float, float]: """Evaluate the network on the entire test set. @@ -207,12 +269,12 @@ def test( def train( # pylint: disable=too-many-arguments - net: nn.Module, - trainloader: DataLoader, - device: torch.device, - epochs: int, - hyperparams: Dict[str, Scalar], - round: int, + net: nn.Module, + trainloader: DataLoader, + device: torch.device, + epochs: int, + hyperparams: Dict[str, Scalar], + round: int, ) -> None: """Train the network on the training set. @@ -229,7 +291,7 @@ def train( # pylint: disable=too-many-arguments hyperparams : Dict[str, Scalar] The hyperparameters to use for training. """ - lr=hyperparams["eta_l"]*hyperparams["learning_decay"]**(round-1) + lr = hyperparams["eta_l"] * hyperparams["learning_decay"] ** (round - 1) print(f"Learning rate: {lr}") criterion = torch.nn.CrossEntropyLoss() optimizer = torch.optim.SGD( @@ -251,12 +313,12 @@ def train( # pylint: disable=too-many-arguments def _train_one_epoch( # pylint: disable=too-many-arguments - net: nn.Module, - trainloader: DataLoader, - device: torch.device, - criterion, - optimizer, - hyperparams: Dict[str, Scalar], + net: nn.Module, + trainloader: DataLoader, + device: torch.device, + criterion, + optimizer, + hyperparams: Dict[str, Scalar], ) -> nn.Module: """Train for one epoch. @@ -291,11 +353,13 @@ def _train_one_epoch( # pylint: disable=too-many-arguments if __name__ == "__main__": - model = VGG(num_classes=10, num_groups=2, conv_type='standard') + model = VGG(num_classes=10, num_groups=2, conv_type="standard") model = torch.nn.Sequential(*list(model.features.children())) # Print the modified VGG16GN model architecture print(model) - total_trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) + total_trainable_params = sum( + p.numel() for p in model.parameters() if p.requires_grad + ) print(f"Total number of parameters: {total_trainable_params / 1e6}") param_size = 0 for param in model.parameters(): @@ -305,4 +369,4 @@ def _train_one_epoch( # pylint: disable=too-many-arguments buffer_size += buffer.nelement() * buffer.element_size() size_all_mb = (param_size + buffer_size) / 1024**2 - print('model size: {:.3f}MB'.format(size_all_mb)) + print("model size: {:.3f}MB".format(size_all_mb)) diff --git a/baselines/fedpara/fedpara/server.py b/baselines/fedpara/fedpara/server.py index f25732d22e18..368584a17d2b 100644 --- a/baselines/fedpara/fedpara/server.py +++ b/baselines/fedpara/fedpara/server.py @@ -49,10 +49,10 @@ def evaluate( net.to(device) loss, accuracy = test(net, test_loader, device=device) - + experiment.log_metric("loss", loss, epoch=server_round) - experiment.log_metric("accuracy", accuracy*100, epoch=server_round) - + experiment.log_metric("accuracy", accuracy * 100, epoch=server_round) + return loss, {"accuracy": accuracy} return evaluate diff --git a/baselines/fedpara/fedpara/strategy.py b/baselines/fedpara/fedpara/strategy.py index e4085f3f97fa..6122c86539ab 100644 --- a/baselines/fedpara/fedpara/strategy.py +++ b/baselines/fedpara/fedpara/strategy.py @@ -1,24 +1,17 @@ """FedPara strategy.""" -from typing import Dict, List, Optional, Tuple, Union -import numpy as np -import torch -from flwr.common import FitRes, Parameters, Scalar, ndarrays_to_parameters -from flwr.server.client_proxy import ClientProxy from flwr.server.strategy import FedAvg -from torch.nn.utils import parameters_to_vector, vector_to_parameters -from fedpara.utils import get_parameters class FedPara(FedAvg): """FedPara strategy.""" def __init__( - self, - algorithm: str, - **kwargs, + self, + algorithm: str, + **kwargs, ) -> None: super().__init__(**kwargs) self.algorithm = algorithm diff --git a/baselines/fedpara/fedpara/utils.py b/baselines/fedpara/fedpara/utils.py index 6c5cbd339123..8155a4f9f6ff 100644 --- a/baselines/fedpara/fedpara/utils.py +++ b/baselines/fedpara/fedpara/utils.py @@ -41,7 +41,9 @@ def plot_metric_from_history( rounds, values_accuracy = zip(*metric_dict["accuracy"]) _, axs = plt.subplots() # Set the title - axs.set_title(f"{cfg.strategy.algorithm} | {cfg.dataset_config.name} | Seed {cfg.seed}") + axs.set_title( + f"{cfg.strategy.algorithm} | {cfg.dataset_config.name} | Seed {cfg.seed}" + ) axs.plot(np.asarray(rounds), np.asarray(values_accuracy)) axs.set_ylabel("Accuracy") axs.set_xlabel("Rounds") From 5500a4caa8f80f1fefcb2358df992a3b6f6623fd Mon Sep 17 00:00:00 2001 From: Roeia Amr <60478505+Roeia99@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:36:02 +0200 Subject: [PATCH 07/39] added results --- baselines/fedpara/README.md | 16 ++++++++-------- baselines/fedpara/_static/Cifar100_iid.jpeg | Bin 0 -> 31160 bytes baselines/fedpara/_static/Cifar100_noniid.jpeg | Bin 0 -> 32803 bytes baselines/fedpara/_static/Cifar10_iid.jpeg | Bin 0 -> 29828 bytes baselines/fedpara/_static/Cifar10_noniid.jpeg | Bin 0 -> 32154 bytes 5 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 baselines/fedpara/_static/Cifar100_iid.jpeg create mode 100644 baselines/fedpara/_static/Cifar100_noniid.jpeg create mode 100644 baselines/fedpara/_static/Cifar10_iid.jpeg create mode 100644 baselines/fedpara/_static/Cifar10_noniid.jpeg diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 78bcde87b7a1..5aed70af0868 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -105,15 +105,15 @@ poetry run python -m .main ## Expected Results -:warning: _Your baseline implementation should replicate several of the experiments in the original paper. Please include here the exact command(s) needed to run each of those experiments followed by a figure (e.g. a line plot) or table showing the results you obtained when you ran the code. Below is an example of how you can present this. Please add command followed by results for all your experiments._ +### Cifar100 -```bash -# it is likely that for one experiment you need to sweep over different hyperparameters. You are encouraged to use Hydra's multirun functionality for this. This is an example of how you could achieve this for some typical FL hyperparameteres +| IID | Non-IID | +|:----:|:----:| +|![Cifar100 iid](_static/Cifar100_iid.jpeg) | ![Cifar100 non-iid](_static/Cifar100_noniid.jpeg) | -poetry run python -m .main --multirun num_client_per_round=5,10,50 dataset=femnist,cifar10 -# the above command will run a total of 6 individual experiments (because 3client_configs x 2datasets = 6 -- you can think of it as a grid). -[Now show a figure/table displaying the results of the above command] +### Cifar10 -# add more commands + plots for additional experiments. -``` +| IID | Non-IID | +|:----:|:----:| +|![CIFAR10 iid](_static/Cifar10_iid.jpeg) | ![CIFAR10 non-iid](_static/Cifar10_noniid.jpeg) | diff --git a/baselines/fedpara/_static/Cifar100_iid.jpeg b/baselines/fedpara/_static/Cifar100_iid.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ece077c6ce8fddc12d2ba383fea40010a72a225f GIT binary patch literal 31160 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 literal 0 HcmV?d00001 diff --git a/baselines/fedpara/_static/Cifar100_noniid.jpeg b/baselines/fedpara/_static/Cifar100_noniid.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4dbe7889f2994929aadcd4a8d65e894b07209df9 GIT binary patch literal 32803 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 Tzo#DOG8GcnkI>2-`~NoqoI2&o literal 0 HcmV?d00001 From 50eb8aadfbbd513a27732daec5b8e875caed40d6 Mon Sep 17 00:00:00 2001 From: Roeia Amr <60478505+Roeia99@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:37:20 +0200 Subject: [PATCH 08/39] added results --- baselines/fedpara/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 5aed70af0868..36799d57c9cf 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -105,14 +105,14 @@ poetry run python -m .main ## Expected Results -### Cifar100 +### Cifar100 (accuracy vs epochs) | IID | Non-IID | |:----:|:----:| |![Cifar100 iid](_static/Cifar100_iid.jpeg) | ![Cifar100 non-iid](_static/Cifar100_noniid.jpeg) | -### Cifar10 +### Cifar10 (accuracy vs epochs) | IID | Non-IID | |:----:|:----:| From 2828993be7407e8ee9f839dac25ec4247dc7ce88 Mon Sep 17 00:00:00 2001 From: Roeia Amr <60478505+Roeia99@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:10:04 +0200 Subject: [PATCH 09/39] readme --- baselines/fedpara/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 36799d57c9cf..b2ceb240dc4a 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -66,7 +66,13 @@ In non-IID settings: | Cifar100 | 100 | 50 | Dirichlet distribution | -****Training Hyperparameters:**** +****Training Hyperparameters:**** + +| Dataset | IID | Non-IID | Classes (K) | Tasks (T) | Epochs (E) | Batch Size (B) | Learning Rate (η) | Temperature (τ) | Regularization (λ) | +|------------|-------|---------|-------------|-----------|------------|-----------------|-------------------|------------------|---------------------| +| CIFAR-10 | 16 | 16 | 10 | 200 | 10 | 64 | 0.1 | 0.992 | 1 | +| CIFAR-100 | 16 | 16 | 16 | 200 | 5 | 64 | 0.1 | 0.992 | 1 | +| FEMNIST | 8 | 8 | 10 | 400 | 10 | 64 | 0.1 | 0.992 | 1 | For Dataset: Choice of alpha parameter for the Dirichlet distribution used to create heterogeneity in the client datasets for CIFAR From 8fd28086ed992786d67f6518874c7f7e677a2800 Mon Sep 17 00:00:00 2001 From: Roeia Amr <60478505+Roeia99@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:23:22 +0200 Subject: [PATCH 10/39] updated readme --- baselines/fedpara/README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index b2ceb240dc4a..9ce821f7797b 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -68,11 +68,17 @@ In non-IID settings: ****Training Hyperparameters:**** -| Dataset | IID | Non-IID | Classes (K) | Tasks (T) | Epochs (E) | Batch Size (B) | Learning Rate (η) | Temperature (τ) | Regularization (λ) | -|------------|-------|---------|-------------|-----------|------------|-----------------|-------------------|------------------|---------------------| -| CIFAR-10 | 16 | 16 | 10 | 200 | 10 | 64 | 0.1 | 0.992 | 1 | -| CIFAR-100 | 16 | 16 | 16 | 200 | 5 | 64 | 0.1 | 0.992 | 1 | -| FEMNIST | 8 | 8 | 10 | 400 | 10 | 64 | 0.1 | 0.992 | 1 | +| | Cifar10 IID | Cifar10 Non-IID | Cifar100 IID | Cifar100 Non-IID | FEMNIST | +|---|-------|-------|------|-------|----------| +| K | 16 | 16 | 8 | 8 | 10 | +| T | 200 | 200 | 400 | 400 | 100 | +| E | 10 | 5 | 10 | 5 | 5 | +| B | 64 | 64 | 64 | 64 | 10 | +| η | 0.1 | 0.1 | 0.1 | 0.1 | 0.1-0.01 | +| τ | 0.992 | 0.992 | 0.992| 0.992 | 0.999 | +| λ | 1 | 1 | 1 | 1 | 0 | + + For Dataset: Choice of alpha parameter for the Dirichlet distribution used to create heterogeneity in the client datasets for CIFAR From bbf554438b0a14cda5cb0074ff3b86dd8c4048c9 Mon Sep 17 00:00:00 2001 From: Roeia Amr <60478505+Roeia99@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:28:39 +0200 Subject: [PATCH 11/39] Updated readme --- baselines/fedpara/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 9ce821f7797b..9ed69a8ca82f 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -70,13 +70,13 @@ In non-IID settings: | | Cifar10 IID | Cifar10 Non-IID | Cifar100 IID | Cifar100 Non-IID | FEMNIST | |---|-------|-------|------|-------|----------| -| K | 16 | 16 | 8 | 8 | 10 | -| T | 200 | 200 | 400 | 400 | 100 | -| E | 10 | 5 | 10 | 5 | 5 | -| B | 64 | 64 | 64 | 64 | 10 | -| η | 0.1 | 0.1 | 0.1 | 0.1 | 0.1-0.01 | -| τ | 0.992 | 0.992 | 0.992| 0.992 | 0.999 | -| λ | 1 | 1 | 1 | 1 | 0 | +| Fraction of client (K) | 16 | 16 | 8 | 8 | 10 | +| Total rounds (T) | 200 | 200 | 400 | 400 | 100 | +| Number of SGD epochs (E) | 10 | 5 | 10 | 5 | 5 | +| Batch size (B) | 64 | 64 | 64 | 64 | 10 | +| Initial learning rate (η) | 0.1 | 0.1 | 0.1 | 0.1 | 0.1-0.01 | +| Learning rate decay (τ) | 0.992 | 0.992 | 0.992| 0.992 | 0.999 | +| Regularization coefficient (λ) | 1 | 1 | 1 | 1 | 0 | From 0cf7f7ca9c865055b224c3f24b3e46fafcdce856 Mon Sep 17 00:00:00 2001 From: "yahia.shaaban" Date: Wed, 20 Dec 2023 03:38:19 +0200 Subject: [PATCH 12/39] BUG_FIX: import typo, remove redundent conf file --- baselines/fedpara/fedpara/conf/base.yaml | 44 -------------------- baselines/fedpara/fedpara/conf/cifar100.yaml | 2 +- baselines/fedpara/fedpara/conf/femnist.yaml | 4 +- baselines/fedpara/fedpara/dataset.py | 2 +- baselines/fedpara/fedpara/main.py | 2 +- 5 files changed, 5 insertions(+), 49 deletions(-) delete mode 100644 baselines/fedpara/fedpara/conf/base.yaml diff --git a/baselines/fedpara/fedpara/conf/base.yaml b/baselines/fedpara/fedpara/conf/base.yaml deleted file mode 100644 index 12aaebf68483..000000000000 --- a/baselines/fedpara/fedpara/conf/base.yaml +++ /dev/null @@ -1,44 +0,0 @@ ---- -seed: 17 - -num_clients: 100 -num_rounds: 200 -clients_per_round: 16 -num_epochs: 5 -batch_size: 64 - -server_device: cuda -client_device: cuda - -client_resources: - num_cpus: 2 - num_gpus: 0.25 - -dataset_config: - name: CIFAR10 - partition: non-iid - num_classes: 10 - alpha: 0.5 - -model: - _target_: fedpara.models.VGG - num_classes: ${dataset_config.num_classes} - conv_type: lowrank # lowrank or standard - activation: relu # relu or leaky_relu - -hyperparams: - eta_l: 0.1 - learning_decay: 0.992 - momentum: 0.0 - weight_decay: 0 - -strategy: - _target_: fedpara.strategy.FedPara - algorithm: FedPara - fraction_fit: 0.00001 - fraction_evaluate: 0.0 - min_evaluate_clients: 0 - min_fit_clients: ${clients_per_round} - min_available_clients: ${clients_per_round} - accept_failures: false - diff --git a/baselines/fedpara/fedpara/conf/cifar100.yaml b/baselines/fedpara/fedpara/conf/cifar100.yaml index b30a93f3a8c9..c1fbb1073522 100644 --- a/baselines/fedpara/fedpara/conf/cifar100.yaml +++ b/baselines/fedpara/fedpara/conf/cifar100.yaml @@ -16,7 +16,7 @@ client_resources: dataset_config: name: CIFAR100 - partition: -iid + partition: non-iid num_classes: 100 alpha: 0.5 diff --git a/baselines/fedpara/fedpara/conf/femnist.yaml b/baselines/fedpara/fedpara/conf/femnist.yaml index 5ce343368b5b..5502f865cd38 100644 --- a/baselines/fedpara/fedpara/conf/femnist.yaml +++ b/baselines/fedpara/fedpara/conf/femnist.yaml @@ -34,8 +34,8 @@ hyperparams: weight_decay: 0 strategy: - _target_: fedpara.strategy.FedPara - algorithm: FedPara + _target_: fedpara.strategy.pFedPara + algorithm: pFedPara fraction_fit: 0.00001 fraction_evaluate: 0.0 min_evaluate_clients: 0 diff --git a/baselines/fedpara/fedpara/dataset.py b/baselines/fedpara/fedpara/dataset.py index 5cd7e06b31ae..81fb35746399 100644 --- a/baselines/fedpara/fedpara/dataset.py +++ b/baselines/fedpara/fedpara/dataset.py @@ -6,7 +6,7 @@ from torch.utils.data import DataLoader from torchvision import datasets, transforms -from baselines.fedpara.fedpara.dataset_preparation import iid, noniid, DatasetSplit +from fedpara.dataset_preparation import iid, noniid, DatasetSplit def load_datasets( diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index b9b6645cd0db..f1e70798ed71 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -7,7 +7,7 @@ from omegaconf import DictConfig, OmegaConf from fedpara import client, server, utils -from fedpara.dataset_preparation import load_datasets +from fedpara.dataset import load_datasets from fedpara.utils import get_parameters, seed_everything From 3b0248afa3ff9a945c0bdb880f7685785ec0271c Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 27 Dec 2023 00:06:43 +0000 Subject: [PATCH 13/39] fixed the ratio function and added model weight calculation --- baselines/fedpara/README.md | 29 ++++++++++-- baselines/fedpara/fedpara/main.py | 5 ++- baselines/fedpara/fedpara/models.py | 68 ++++++++++++++++------------- baselines/fedpara/fedpara/plot.py | 30 +++++++++++++ baselines/fedpara/fedpara/run.sh | 12 +++++ baselines/fedpara/fedpara/server.py | 3 +- baselines/fedpara/run.sh | 4 -- 7 files changed, 111 insertions(+), 40 deletions(-) create mode 100644 baselines/fedpara/fedpara/plot.py create mode 100644 baselines/fedpara/fedpara/run.sh delete mode 100644 baselines/fedpara/run.sh diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 9ed69a8ca82f..844f390339dd 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -27,7 +27,7 @@ page: https://github.com/South-hw/FedPara_ICLR22 ## About this baseline -****What’s implemented:**** The code in this directory replicates the experiments in FedPara paper implementing the Low-rank scheme for Convolution module inspired by the [author's code]( https://github.com/South-hw/FedPara_ICLR22). +****What’s implemented:**** The code in this directory replicates the experiments in FedPara paper implementing the Low-rank scheme for Convolution module. Specifically, it replicates the results for Cifar10 and Cifar100 in Figure 3 and the results for Feminist in Figure 5(a). @@ -116,15 +116,36 @@ poetry run python -m .main ## Expected Results - -### Cifar100 (accuracy vs epochs) +### From the [Fedpara](https://arxiv.org/pdf/2108.06098.pdf) paper: +#### Communication Cost: +*"FL evaluation typically measures the required rounds to achieve the target accuracy as communication costs, but we instead assess total transferred bit sizes, 2 × +(#participants)×(model size)×(#rounds)"* +#### Parameters ratio + +| Parameters ratio | Cifar10 | Cifar100 | +|----------|--------|--------| +| Original | 15.25M | 15.30M | +| 0.1 | 1.55M | 1.59M | +| 0.2 | 2.33M | 2.38M | +| 0.3 | 3.31M | 3.36M | +| 0.4 | 4.45M | 4.50M | +| 0.5 | 5.79M | 5.84M | +| 0.6 | 7.33M | 7.38M | +| 0.7 | 9.01M | 9.05M | +| 0.8 | 10.90M | 10.94M | +| 0.9 | 12.92M | 12.96M | + +** We are using parameter ratio of 0.1 for Cifar10 and 0.4 for Cifar100 +Parameters from + +### Cifar100 (Accuracy vs Communication Cost) | IID | Non-IID | |:----:|:----:| |![Cifar100 iid](_static/Cifar100_iid.jpeg) | ![Cifar100 non-iid](_static/Cifar100_noniid.jpeg) | -### Cifar10 (accuracy vs epochs) +### Cifar10 (Accuracy vs Communication Cost) | IID | Non-IID | |:----:|:----:| diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index b9b6645cd0db..c2edceb5308f 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -1,4 +1,5 @@ """Main script for running FedPara.""" +import logging import flwr as fl import hydra from comet_ml import Experiment @@ -36,7 +37,8 @@ def main(cfg: DictConfig) -> None: ) hyper_params = OmegaConf.to_container(cfg, resolve=True) experiment.log_parameters(hyper_params) - + # log the hyperparameters + logging.info(f"Hyperparameters: {hyper_params}") # 2. Prepare dataset train_loaders, test_loader = load_datasets( config=cfg.dataset_config, @@ -53,6 +55,7 @@ def main(cfg: DictConfig) -> None: ) evaluate_fn = server.gen_evaluate_fn( + num_clients=cfg.num_clients, test_loader=test_loader, model=cfg.model, device=cfg.server_device, diff --git a/baselines/fedpara/fedpara/models.py b/baselines/fedpara/fedpara/models.py index 25f09e620f45..08508908de37 100644 --- a/baselines/fedpara/fedpara/models.py +++ b/baselines/fedpara/fedpara/models.py @@ -84,22 +84,28 @@ def __init__( def _calc_from_ratio(self): # Return the low-rank of sub-matrices given the compression ratio + + # minimum possible parameter r1 = int(np.ceil(np.sqrt(self.out_channels))) r2 = int(np.ceil(np.sqrt(self.in_channels))) - r = np.max((r1, r2)) - + r = np.min((r1, r2)) + + # maximum possible rank, + """ + To solve it we need to know the roots of quadratic equation: ax^2+bx+c=0 + a = kernel**2 + b = out channel+ in channel + c = - num_target_params/2 + r3 is floored because we cannot take the ceil as it results a bigger number of parameters than the original problem + """ num_target_params = ( - self.out_channels * self.in_channels * (self.kernel_size**2) * self.ratio + self.out_channels * self.in_channels * (self.kernel_size**2) ) - r3 = np.sqrt( - ((self.out_channels + self.in_channels) ** 2) - / (4 * (self.kernel_size**4)) - + num_target_params / (2 * (self.kernel_size**2)) - ) - (self.out_channels + self.in_channels) / (2 * (self.kernel_size**2)) - r3 = int(np.ceil(r3)) - r = np.max((r, r3)) - - return r + a, b, c = self.kernel_size**2, self.out_channels + self.in_channels,- num_target_params/2 + discriminant = b**2 - 4 * a * c + r3 = math.floor((-b+math.sqrt(discriminant))/(2*a)) + ratio=math.ceil((1-self.ratio)*r+ self.ratio*r3) + return ratio def forward(self, x): """Forward pass.""" @@ -167,9 +173,9 @@ def __init__( self.activation, nn.Linear(512, num_classes), ) - self.init_weights() + self._init_weights() - def init_weights(self): + def _init_weights(self): """Initialize the weights.""" for name, module in self.features.named_children(): module = getattr(self.features, name) @@ -219,6 +225,22 @@ def _make_layers(self, cfg, group_norm=True): layers += [conv2d, self.activation] in_channels = v return nn.Sequential(*layers) + + @property + def model_size(self): + """ + Return the total number of trainable parameters (in million paramaters) and the size of the model in MB. + """ + total_trainable_params = sum( + p.numel() for p in model.parameters() if p.requires_grad)/1e6 + param_size = 0 + for param in model.parameters(): + param_size += param.nelement() * param.element_size() + buffer_size = 0 + for buffer in model.buffers(): + buffer_size += buffer.nelement() * buffer.element_size() + size_all_mb = (param_size + buffer_size) / 1024**2 + return total_trainable_params, size_all_mb def forward(self, input): """Forward pass.""" @@ -353,20 +375,6 @@ def _train_one_epoch( # pylint: disable=too-many-arguments if __name__ == "__main__": - model = VGG(num_classes=10, num_groups=2, conv_type="standard") - model = torch.nn.Sequential(*list(model.features.children())) + model = VGG(num_classes=10, num_groups=2, conv_type="standard", ratio=0.4) # Print the modified VGG16GN model architecture - print(model) - total_trainable_params = sum( - p.numel() for p in model.parameters() if p.requires_grad - ) - print(f"Total number of parameters: {total_trainable_params / 1e6}") - param_size = 0 - for param in model.parameters(): - param_size += param.nelement() * param.element_size() - buffer_size = 0 - for buffer in model.buffers(): - buffer_size += buffer.nelement() * buffer.element_size() - - size_all_mb = (param_size + buffer_size) / 1024**2 - print("model size: {:.3f}MB".format(size_all_mb)) + print(model.model_size) \ No newline at end of file diff --git a/baselines/fedpara/fedpara/plot.py b/baselines/fedpara/fedpara/plot.py new file mode 100644 index 000000000000..44c268ff6f70 --- /dev/null +++ b/baselines/fedpara/fedpara/plot.py @@ -0,0 +1,30 @@ +import matplotlib.pyplot as plt + +def plot_lines(list1, list2, save_path=None): + x1, y1 = zip(*list1) + x2, y2 = zip(*list2) + + plt.plot(x1, y1, label='Solid Line', linestyle='-', marker='o') + plt.plot(x2, y2, label='Dotted Line', linestyle='--', marker='x') + + plt.xlabel('X-axis') + plt.ylabel('Y-axis') + plt.title('Two Lines Plot') + plt.legend() + plt.grid(True) + + if save_path: + plt.savefig(save_path, format='png') + else: + plt.show() + +# Example lists of tuples +low_rank = [(0, 0.0103), (1, 0.0102), (2, 0.01), (3, 0.0103), (4, 0.01), (5, 0.0137), (6, 0.018), (7, 0.0242), (8, 0.0341), (9, 0.0373), (10, 0.0476), (11, 0.0547), (12, 0.0591), (13, 0.0635), (14, 0.07), (15, 0.0747), (16, 0.084), (17, 0.082), (18, 0.1034), (19, 0.1043), (20, 0.1109), (21, 0.1226), (22, 0.132), (23, 0.1353), (24, 0.1486), (25, 0.1529), (26, 0.1602), (27, 0.1594), (28, 0.1671), (29, 0.1729), (30, 0.1765), (31, 0.1862), (32, 0.1935), (33, 0.2041), (34, 0.2151), (35, 0.2148), (36, 0.2128), (37, 0.2237), (38, 0.2257), (39, 0.2361), (40, 0.2403), (41, 0.2454), (42, 0.2446), (43, 0.2517), (44, 0.2557), (45, 0.2664), (46, 0.266), (47, 0.2665), (48, 0.2692), (49, 0.2761), (50, 0.2808), (51, 0.2887), (52, 0.3007), (53, 0.2943), (54, 0.3066), (55, 0.2992), (56, 0.3066), (57, 0.3152), (58, 0.3187), (59, 0.3179), (60, 0.3163), (61, 0.3284), (62, 0.3306), (63, 0.3348), (64, 0.3399), (65, 0.3407), (66, 0.3427), (67, 0.3506), (68, 0.3527), (69, 0.3586), (70, 0.3668), (71, 0.3637), (72, 0.3659), (73, 0.3701), (74, 0.3759), (75, 0.3835), (76, 0.3823), (77, 0.3874), (78, 0.3851), (79, 0.386), (80, 0.393), (81, 0.3991), (82, 0.3897), (83, 0.4007), (84, 0.4057), (85, 0.3936), (86, 0.4062), (87, 0.4048), (88, 0.411), (89, 0.4127), (90, 0.4127), (91, 0.412), (92, 0.4174), (93, 0.4241), (94, 0.4135), (95, 0.4153), (96, 0.4307), (97, 0.4188), (98, 0.4278), (99, 0.4281), (100, 0.4297), (101, 0.4378), (102, 0.429), (103, 0.4408), (104, 0.4377), (105, 0.4368), (106, 0.4406), (107, 0.4456), (108, 0.4448), (109, 0.4469), (110, 0.4448), (111, 0.4513), (112, 0.4479), (113, 0.4527), (114, 0.4514), (115, 0.458), (116, 0.4546), (117, 0.4561), (118, 0.4533), (119, 0.4578), (120, 0.456), (121, 0.4622), (122, 0.4608), (123, 0.4657), (124, 0.4667), (125, 0.4625), (126, 0.461), (127, 0.4629), (128, 0.4599), (129, 0.4696), (130, 0.4757), (131, 0.47), (132, 0.4656), (133, 0.4694), (134, 0.4786), (135, 0.4709), (136, 0.4755), (137, 0.473), (138, 0.4762), (139, 0.472), (140, 0.4727), (141, 0.4789), (142, 0.4716), (143, 0.4749), (144, 0.4704), (145, 0.4733), (146, 0.4742), (147, 0.4799), (148, 0.4792), (149, 0.4858), (150, 0.4832), (151, 0.4788), (152, 0.4817), (153, 0.4815), (154, 0.4798), (155, 0.4826), (156, 0.485), (157, 0.4843), (158, 0.4863), (159, 0.4876), (160, 0.4819), (161, 0.4887), (162, 0.4906), (163, 0.4902), (164, 0.4864), (165, 0.4892), (166, 0.4871), (167, 0.4896), (168, 0.4876), (169, 0.4903), (170, 0.4889), (171, 0.4934), (172, 0.49), (173, 0.4927), (174, 0.4944), (175, 0.4925), (176, 0.4896), (177, 0.4932), (178, 0.4921), (179, 0.4913), (180, 0.4974), (181, 0.4938), (182, 0.4959), (183, 0.4941), (184, 0.4977), (185, 0.5006), (186, 0.4971), (187, 0.498), (188, 0.4965), (189, 0.4973), (190, 0.4986), (191, 0.4993), (192, 0.5005), (193, 0.499), (194, 0.4967), (195, 0.4949), (196, 0.4955), (197, 0.495), (198, 0.4952), (199, 0.494), (200, 0.496), (201, 0.497), (202, 0.4978), (203, 0.4948), (204, 0.4981), (205, 0.5009), (206, 0.5003), (207, 0.5033), (208, 0.4998), (209, 0.5016), (210, 0.5004), (211, 0.4931), (212, 0.5), (213, 0.4995), (214, 0.506), (215, 0.5046), (216, 0.5003), (217, 0.5025), (218, 0.5015), (219, 0.5027), (220, 0.4974), (221, 0.5021), (222, 0.4998), (223, 0.5017), (224, 0.5017), (225, 0.504), (226, 0.5004), (227, 0.5048), (228, 0.506), (229, 0.5001), (230, 0.4995), (231, 0.5037), (232, 0.5052), (233, 0.5073), (234, 0.5062), (235, 0.5006), (236, 0.506), (237, 0.5053), (238, 0.5061), (239, 0.5037), (240, 0.5017), (241, 0.5075), (242, 0.5035), (243, 0.5014), (244, 0.507), (245, 0.5001), (246, 0.5023), (247, 0.5032), (248, 0.5006), (249, 0.5005), (250, 0.5027), (251, 0.502), (252, 0.5049), (253, 0.5026), (254, 0.5019), (255, 0.4988), (256, 0.5031), (257, 0.503), (258, 0.5049), (259, 0.5059), (260, 0.5022), (261, 0.5008), (262, 0.5023), (263, 0.5002), (264, 0.5044), (265, 0.5104), (266, 0.5016), (267, 0.5041), (268, 0.5028), (269, 0.5045), (270, 0.5057), (271, 0.5013), (272, 0.5042), (273, 0.5034), (274, 0.5031), (275, 0.5051), (276, 0.502), (277, 0.5008), (278, 0.505), (279, 0.5005), (280, 0.5022), (281, 0.5046), (282, 0.5017), (283, 0.5043), (284, 0.5061), (285, 0.5017), (286, 0.5046), (287, 0.505), (288, 0.499), (289, 0.5033), (290, 0.503), (291, 0.5006), (292, 0.5046), (293, 0.5047), (294, 0.5068), (295, 0.504), (296, 0.5059), (297, 0.5071), (298, 0.5067), (299, 0.502), (300, 0.5016), (301, 0.5), (302, 0.5035), (303, 0.5041), (304, 0.5053), (305, 0.5052), (306, 0.5059), (307, 0.5031), (308, 0.5015), (309, 0.5014), (310, 0.5042), (311, 0.5045), (312, 0.5041), (313, 0.5003), (314, 0.504), (315, 0.4996), (316, 0.5035), (317, 0.5058), (318, 0.5057), (319, 0.4998), (320, 0.5025), (321, 0.5019), (322, 0.5068), (323, 0.5067), (324, 0.5041), (325, 0.5009), (326, 0.5049), (327, 0.5056), (328, 0.5021), (329, 0.5047), (330, 0.5015), (331, 0.5046), (332, 0.5017), (333, 0.5029), (334, 0.5016), (335, 0.5039), (336, 0.5012), (337, 0.5037), (338, 0.5033), (339, 0.5033), (340, 0.5022), (341, 0.5043), (342, 0.4999), (343, 0.4999), (344, 0.5026), (345, 0.4994), (346, 0.5014), (347, 0.5023), (348, 0.5027), (349, 0.5028), (350, 0.5005), (351, 0.5034), (352, 0.4999), (353, 0.5045), (354, 0.5036), (355, 0.5034), (356, 0.5049), (357, 0.5043), (358, 0.5049), (359, 0.5061), (360, 0.5024), (361, 0.5013), (362, 0.502), (363, 0.5024), (364, 0.5), (365, 0.5013), (366, 0.5025), (367, 0.503), (368, 0.5027), (369, 0.5033), (370, 0.5032), (371, 0.5033), (372, 0.501), (373, 0.5047), (374, 0.5006), (375, 0.5022), (376, 0.504), (377, 0.5027), (378, 0.5024), (379, 0.5044), (380, 0.5037), (381, 0.5033), (382, 0.5026), (383, 0.5016), (384, 0.5049), (385, 0.5018), (386, 0.4991), (387, 0.5013), (388, 0.5012), (389, 0.502), (390, 0.5022), (391, 0.5025), (392, 0.5003), (393, 0.5026), (394, 0.5023), (395, 0.5025), (396, 0.5019), (397, 0.5012), (398, 0.5006), (399, 0.5007), (400, 0.5025)] + +standard = [(0, 0.1), (1, 0.08), (2, 0.15)] + +# Specify the file path to save the plot as a PNG +save_path = 'two_lines_plot.png' + +# Call the function to plot the lines and save as PNG +plot_lines(low_rank, standard, save_path) diff --git a/baselines/fedpara/fedpara/run.sh b/baselines/fedpara/fedpara/run.sh new file mode 100644 index 000000000000..2365ae3f4e93 --- /dev/null +++ b/baselines/fedpara/fedpara/run.sh @@ -0,0 +1,12 @@ +comm= ("poetry run python -m fedpara.main" , +"poetry run python -m fedpara.main --config-name cifar100" , +"poetry run python -m fedpara.main num_epochs=10 model.conv_type=standard" , +"poetry run python -m fedpara.main --config-name cifar100 num_epochs=10 model.conv_type=standard") + +# for loop on comm and open tmux session for each command +for i in "${comm[@]}" +do + tmux new-session -d -s "fedpara" -n "fedpara" $i + tmux send-keys -t "fedpara" "C-m" +done +``` \ No newline at end of file diff --git a/baselines/fedpara/fedpara/server.py b/baselines/fedpara/fedpara/server.py index 368584a17d2b..a78eb26854bf 100644 --- a/baselines/fedpara/fedpara/server.py +++ b/baselines/fedpara/fedpara/server.py @@ -13,6 +13,7 @@ def gen_evaluate_fn( + num_clients: int, test_loader: DataLoader, model: DictConfig, device, @@ -51,7 +52,7 @@ def evaluate( loss, accuracy = test(net, test_loader, device=device) experiment.log_metric("loss", loss, epoch=server_round) - experiment.log_metric("accuracy", accuracy * 100, epoch=server_round) + experiment.log_metric("accuracy", accuracy * 100, epoch=server_round*2*net.model_size[1]*num_clients/1024) return loss, {"accuracy": accuracy} diff --git a/baselines/fedpara/run.sh b/baselines/fedpara/run.sh deleted file mode 100644 index 9ac265afdbed..000000000000 --- a/baselines/fedpara/run.sh +++ /dev/null @@ -1,4 +0,0 @@ - poetry run python -m fedpara.main > "cifar10noniidaug.txt" 2>&1 & - poetry run python -m fedpara.main --config-name cifar100 > "cifar100noniidaug.txt" 2>&1 & - -wait \ No newline at end of file From 7287f822bd6e9b81bd209eb8cd8213e3f3b1ec13 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 27 Dec 2023 15:18:59 +0000 Subject: [PATCH 14/39] edit --- baselines/fedpara/fedpara/conf/cifar100.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baselines/fedpara/fedpara/conf/cifar100.yaml b/baselines/fedpara/fedpara/conf/cifar100.yaml index c1fbb1073522..26a097b6cc94 100644 --- a/baselines/fedpara/fedpara/conf/cifar100.yaml +++ b/baselines/fedpara/fedpara/conf/cifar100.yaml @@ -1,5 +1,5 @@ --- -seed: 34213 +seed: 342130 num_clients: 50 num_rounds: 400 From 76644dcb052369a69de04b225d1d5adf90147c48 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 27 Dec 2023 21:18:51 +0200 Subject: [PATCH 15/39] cleaned --- baselines/fedpara/README.md | 51 +++++++++++++-------- baselines/fedpara/fedpara/conf/cifar10.yaml | 2 +- baselines/fedpara/fedpara/main.py | 18 +------- baselines/fedpara/fedpara/plot.py | 30 ------------ baselines/fedpara/fedpara/run.sh | 12 ----- baselines/fedpara/fedpara/server.py | 5 -- baselines/fedpara/fedpara/utils.py | 4 +- 7 files changed, 39 insertions(+), 83 deletions(-) delete mode 100644 baselines/fedpara/fedpara/plot.py delete mode 100644 baselines/fedpara/fedpara/run.sh diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 844f390339dd..43d0f293c2fa 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -36,7 +36,7 @@ Specifically, it replicates the results for Cifar10 and Cifar100 in Figure 3 and ****Hardware Setup:**** The experiment has been conducted on our server with the following specs: -- **GPU:** 1 Tesla V100 GPU 32GB VRAM +- **GPU:** 1 RTX A6000 GPU 50GB VRAM - **CPU:** 1x24 cores Intel Xeon(R) 6248R - **RAM:** 150 GB @@ -89,32 +89,45 @@ Choice of alpha parameter for the Dirichlet distribution used to create heteroge ## Environment Setup +To construct the Python environment follow these steps: -:warning: _The Python environment for all baselines should follow these guidelines in the `EXTENDED_README`. Specify the steps to create and activate your environment. If there are any external system-wide requirements, please include instructions for them too. These instructions should be comprehensive enough so anyone can run them (if non standard, describe them step-by-step)._ +```bash +# Set Python 3.10 +pyenv local 3.10.6 +# Tell poetry to use python 3.10 +poetry env use 3.10.6 +# Install the base Poetry environment +poetry install + +# Activate the environment +poetry shell +``` ## Running the Experiments -:warning: _Provide instructions on the steps to follow to run all the experiments._ ```bash -# The main experiment implemented in your baseline using default hyperparameters (that should be setup in the Hydra configs) should run (including dataset download and necessary partitioning) by executing the command: - -poetry run python -m .main # where is the name of this directory and that of the only sub-directory in this directory (i.e. where all your source code is) - -# If you are using a dataset that requires a complicated download (i.e. not using one natively supported by TF/PyTorch) + preprocessing logic, you might want to tell people to run one script first that will do all that. Please ensure the download + preprocessing can be configured to suit (at least!) a different download directory (and use as default the current directory). The expected command to run to do this is: -poetry run python -m .dataset_preparation +**Essential commands** +# To run fedpara +poetry run python -m fedpara.main +# Important configs +- model.conv_type --> choose parameterization scheme: lowrank or original(normal weights) +- dataset_config.partition --> choosing between non IID and IID scheme +- model.ratio --> choosing the ratio (lambda) of number of parameters + +**Multi-runs** + +# To run fedpara for non-iid cifar 10 on vgg16 for lowrank and original schemes +poetry run python -m fedpara.main --multirun model.conv_type=standard,lowrank +# To run fedpara for non-iid cifar 100 on vgg16 for lowrank and original schemes +poetry run python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standard,lowrank +# To run fedpara for iid cifar 10 on vgg16 for lowrank and original schemes +poetry run python -m fedpara.main --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid +# To run fedpara for iid cifar 100 on vgg16 for lowrank and original schemes +poetry run python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid -# It is expected that you baseline supports more than one dataset and different FL settings (e.g. different number of clients, dataset partitioning methods, etc). Please provide a list of commands showing how these experiments are run. Include also a short explanation of what each one does. Here it is expected you'll be using the Hydra syntax to override the default config. - -poetry run python -m .main -. -. -. -poetry run python -m .main ``` - - ## Expected Results ### From the [Fedpara](https://arxiv.org/pdf/2108.06098.pdf) paper: #### Communication Cost: @@ -138,6 +151,8 @@ poetry run python -m .main ** We are using parameter ratio of 0.1 for Cifar10 and 0.4 for Cifar100 Parameters from +The rank is calculated by the equation provided in paper R = r_max* lamda + (1-lambda)*r_min + ### Cifar100 (Accuracy vs Communication Cost) | IID | Non-IID | diff --git a/baselines/fedpara/fedpara/conf/cifar10.yaml b/baselines/fedpara/fedpara/conf/cifar10.yaml index 3dabc0f07ed1..deeac7797588 100644 --- a/baselines/fedpara/fedpara/conf/cifar10.yaml +++ b/baselines/fedpara/fedpara/conf/cifar10.yaml @@ -1,5 +1,5 @@ --- -seed: 17 +seed: 121 num_clients: 100 num_rounds: 200 diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index 705f28fbc1ce..76605b1f962a 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -2,7 +2,6 @@ import logging import flwr as fl import hydra -from comet_ml import Experiment from hydra.core.hydra_config import HydraConfig from hydra.utils import instantiate from omegaconf import DictConfig, OmegaConf @@ -24,19 +23,7 @@ def main(cfg: DictConfig) -> None: # 1. Print parsed config print(OmegaConf.to_yaml(cfg)) seed_everything(cfg.seed) - # # Comet ML tracking - credentials = OmegaConf.load("fedpara/conf/credentials.yaml") - experiment = Experiment( - api_key=credentials.api_key, - project_name=credentials.project_name, - workspace=credentials.workspace, - ) - experiment.set_name( - f"flower | {cfg.strategy.algorithm} " - f"| {cfg.dataset_config.name} | Seed {cfg.seed}" - ) hyper_params = OmegaConf.to_container(cfg, resolve=True) - experiment.log_parameters(hyper_params) # log the hyperparameters logging.info(f"Hyperparameters: {hyper_params}") # 2. Prepare dataset @@ -59,7 +46,6 @@ def main(cfg: DictConfig) -> None: test_loader=test_loader, model=cfg.model, device=cfg.server_device, - experiment=experiment, ) def get_on_fit_config(): @@ -110,9 +96,9 @@ def fit_config_fn(server_round: int): f"{cfg.clients_per_round}", ] ) - + utils.plot_metric_from_history( - hist=history, save_plot_path=save_path, suffix=file_suffix, cfg=cfg + hist=history, save_plot_path=save_path, suffix=file_suffix, cfg=cfg, model_size=net_glob.model_size()[1] ) diff --git a/baselines/fedpara/fedpara/plot.py b/baselines/fedpara/fedpara/plot.py deleted file mode 100644 index 44c268ff6f70..000000000000 --- a/baselines/fedpara/fedpara/plot.py +++ /dev/null @@ -1,30 +0,0 @@ -import matplotlib.pyplot as plt - -def plot_lines(list1, list2, save_path=None): - x1, y1 = zip(*list1) - x2, y2 = zip(*list2) - - plt.plot(x1, y1, label='Solid Line', linestyle='-', marker='o') - plt.plot(x2, y2, label='Dotted Line', linestyle='--', marker='x') - - plt.xlabel('X-axis') - plt.ylabel('Y-axis') - plt.title('Two Lines Plot') - plt.legend() - plt.grid(True) - - if save_path: - plt.savefig(save_path, format='png') - else: - plt.show() - -# Example lists of tuples -low_rank = [(0, 0.0103), (1, 0.0102), (2, 0.01), (3, 0.0103), (4, 0.01), (5, 0.0137), (6, 0.018), (7, 0.0242), (8, 0.0341), (9, 0.0373), (10, 0.0476), (11, 0.0547), (12, 0.0591), (13, 0.0635), (14, 0.07), (15, 0.0747), (16, 0.084), (17, 0.082), (18, 0.1034), (19, 0.1043), (20, 0.1109), (21, 0.1226), (22, 0.132), (23, 0.1353), (24, 0.1486), (25, 0.1529), (26, 0.1602), (27, 0.1594), (28, 0.1671), (29, 0.1729), (30, 0.1765), (31, 0.1862), (32, 0.1935), (33, 0.2041), (34, 0.2151), (35, 0.2148), (36, 0.2128), (37, 0.2237), (38, 0.2257), (39, 0.2361), (40, 0.2403), (41, 0.2454), (42, 0.2446), (43, 0.2517), (44, 0.2557), (45, 0.2664), (46, 0.266), (47, 0.2665), (48, 0.2692), (49, 0.2761), (50, 0.2808), (51, 0.2887), (52, 0.3007), (53, 0.2943), (54, 0.3066), (55, 0.2992), (56, 0.3066), (57, 0.3152), (58, 0.3187), (59, 0.3179), (60, 0.3163), (61, 0.3284), (62, 0.3306), (63, 0.3348), (64, 0.3399), (65, 0.3407), (66, 0.3427), (67, 0.3506), (68, 0.3527), (69, 0.3586), (70, 0.3668), (71, 0.3637), (72, 0.3659), (73, 0.3701), (74, 0.3759), (75, 0.3835), (76, 0.3823), (77, 0.3874), (78, 0.3851), (79, 0.386), (80, 0.393), (81, 0.3991), (82, 0.3897), (83, 0.4007), (84, 0.4057), (85, 0.3936), (86, 0.4062), (87, 0.4048), (88, 0.411), (89, 0.4127), (90, 0.4127), (91, 0.412), (92, 0.4174), (93, 0.4241), (94, 0.4135), (95, 0.4153), (96, 0.4307), (97, 0.4188), (98, 0.4278), (99, 0.4281), (100, 0.4297), (101, 0.4378), (102, 0.429), (103, 0.4408), (104, 0.4377), (105, 0.4368), (106, 0.4406), (107, 0.4456), (108, 0.4448), (109, 0.4469), (110, 0.4448), (111, 0.4513), (112, 0.4479), (113, 0.4527), (114, 0.4514), (115, 0.458), (116, 0.4546), (117, 0.4561), (118, 0.4533), (119, 0.4578), (120, 0.456), (121, 0.4622), (122, 0.4608), (123, 0.4657), (124, 0.4667), (125, 0.4625), (126, 0.461), (127, 0.4629), (128, 0.4599), (129, 0.4696), (130, 0.4757), (131, 0.47), (132, 0.4656), (133, 0.4694), (134, 0.4786), (135, 0.4709), (136, 0.4755), (137, 0.473), (138, 0.4762), (139, 0.472), (140, 0.4727), (141, 0.4789), (142, 0.4716), (143, 0.4749), (144, 0.4704), (145, 0.4733), (146, 0.4742), (147, 0.4799), (148, 0.4792), (149, 0.4858), (150, 0.4832), (151, 0.4788), (152, 0.4817), (153, 0.4815), (154, 0.4798), (155, 0.4826), (156, 0.485), (157, 0.4843), (158, 0.4863), (159, 0.4876), (160, 0.4819), (161, 0.4887), (162, 0.4906), (163, 0.4902), (164, 0.4864), (165, 0.4892), (166, 0.4871), (167, 0.4896), (168, 0.4876), (169, 0.4903), (170, 0.4889), (171, 0.4934), (172, 0.49), (173, 0.4927), (174, 0.4944), (175, 0.4925), (176, 0.4896), (177, 0.4932), (178, 0.4921), (179, 0.4913), (180, 0.4974), (181, 0.4938), (182, 0.4959), (183, 0.4941), (184, 0.4977), (185, 0.5006), (186, 0.4971), (187, 0.498), (188, 0.4965), (189, 0.4973), (190, 0.4986), (191, 0.4993), (192, 0.5005), (193, 0.499), (194, 0.4967), (195, 0.4949), (196, 0.4955), (197, 0.495), (198, 0.4952), (199, 0.494), (200, 0.496), (201, 0.497), (202, 0.4978), (203, 0.4948), (204, 0.4981), (205, 0.5009), (206, 0.5003), (207, 0.5033), (208, 0.4998), (209, 0.5016), (210, 0.5004), (211, 0.4931), (212, 0.5), (213, 0.4995), (214, 0.506), (215, 0.5046), (216, 0.5003), (217, 0.5025), (218, 0.5015), (219, 0.5027), (220, 0.4974), (221, 0.5021), (222, 0.4998), (223, 0.5017), (224, 0.5017), (225, 0.504), (226, 0.5004), (227, 0.5048), (228, 0.506), (229, 0.5001), (230, 0.4995), (231, 0.5037), (232, 0.5052), (233, 0.5073), (234, 0.5062), (235, 0.5006), (236, 0.506), (237, 0.5053), (238, 0.5061), (239, 0.5037), (240, 0.5017), (241, 0.5075), (242, 0.5035), (243, 0.5014), (244, 0.507), (245, 0.5001), (246, 0.5023), (247, 0.5032), (248, 0.5006), (249, 0.5005), (250, 0.5027), (251, 0.502), (252, 0.5049), (253, 0.5026), (254, 0.5019), (255, 0.4988), (256, 0.5031), (257, 0.503), (258, 0.5049), (259, 0.5059), (260, 0.5022), (261, 0.5008), (262, 0.5023), (263, 0.5002), (264, 0.5044), (265, 0.5104), (266, 0.5016), (267, 0.5041), (268, 0.5028), (269, 0.5045), (270, 0.5057), (271, 0.5013), (272, 0.5042), (273, 0.5034), (274, 0.5031), (275, 0.5051), (276, 0.502), (277, 0.5008), (278, 0.505), (279, 0.5005), (280, 0.5022), (281, 0.5046), (282, 0.5017), (283, 0.5043), (284, 0.5061), (285, 0.5017), (286, 0.5046), (287, 0.505), (288, 0.499), (289, 0.5033), (290, 0.503), (291, 0.5006), (292, 0.5046), (293, 0.5047), (294, 0.5068), (295, 0.504), (296, 0.5059), (297, 0.5071), (298, 0.5067), (299, 0.502), (300, 0.5016), (301, 0.5), (302, 0.5035), (303, 0.5041), (304, 0.5053), (305, 0.5052), (306, 0.5059), (307, 0.5031), (308, 0.5015), (309, 0.5014), (310, 0.5042), (311, 0.5045), (312, 0.5041), (313, 0.5003), (314, 0.504), (315, 0.4996), (316, 0.5035), (317, 0.5058), (318, 0.5057), (319, 0.4998), (320, 0.5025), (321, 0.5019), (322, 0.5068), (323, 0.5067), (324, 0.5041), (325, 0.5009), (326, 0.5049), (327, 0.5056), (328, 0.5021), (329, 0.5047), (330, 0.5015), (331, 0.5046), (332, 0.5017), (333, 0.5029), (334, 0.5016), (335, 0.5039), (336, 0.5012), (337, 0.5037), (338, 0.5033), (339, 0.5033), (340, 0.5022), (341, 0.5043), (342, 0.4999), (343, 0.4999), (344, 0.5026), (345, 0.4994), (346, 0.5014), (347, 0.5023), (348, 0.5027), (349, 0.5028), (350, 0.5005), (351, 0.5034), (352, 0.4999), (353, 0.5045), (354, 0.5036), (355, 0.5034), (356, 0.5049), (357, 0.5043), (358, 0.5049), (359, 0.5061), (360, 0.5024), (361, 0.5013), (362, 0.502), (363, 0.5024), (364, 0.5), (365, 0.5013), (366, 0.5025), (367, 0.503), (368, 0.5027), (369, 0.5033), (370, 0.5032), (371, 0.5033), (372, 0.501), (373, 0.5047), (374, 0.5006), (375, 0.5022), (376, 0.504), (377, 0.5027), (378, 0.5024), (379, 0.5044), (380, 0.5037), (381, 0.5033), (382, 0.5026), (383, 0.5016), (384, 0.5049), (385, 0.5018), (386, 0.4991), (387, 0.5013), (388, 0.5012), (389, 0.502), (390, 0.5022), (391, 0.5025), (392, 0.5003), (393, 0.5026), (394, 0.5023), (395, 0.5025), (396, 0.5019), (397, 0.5012), (398, 0.5006), (399, 0.5007), (400, 0.5025)] - -standard = [(0, 0.1), (1, 0.08), (2, 0.15)] - -# Specify the file path to save the plot as a PNG -save_path = 'two_lines_plot.png' - -# Call the function to plot the lines and save as PNG -plot_lines(low_rank, standard, save_path) diff --git a/baselines/fedpara/fedpara/run.sh b/baselines/fedpara/fedpara/run.sh deleted file mode 100644 index 2365ae3f4e93..000000000000 --- a/baselines/fedpara/fedpara/run.sh +++ /dev/null @@ -1,12 +0,0 @@ -comm= ("poetry run python -m fedpara.main" , -"poetry run python -m fedpara.main --config-name cifar100" , -"poetry run python -m fedpara.main num_epochs=10 model.conv_type=standard" , -"poetry run python -m fedpara.main --config-name cifar100 num_epochs=10 model.conv_type=standard") - -# for loop on comm and open tmux session for each command -for i in "${comm[@]}" -do - tmux new-session -d -s "fedpara" -n "fedpara" $i - tmux send-keys -t "fedpara" "C-m" -done -``` \ No newline at end of file diff --git a/baselines/fedpara/fedpara/server.py b/baselines/fedpara/fedpara/server.py index a78eb26854bf..641c60c35908 100644 --- a/baselines/fedpara/fedpara/server.py +++ b/baselines/fedpara/fedpara/server.py @@ -17,7 +17,6 @@ def gen_evaluate_fn( test_loader: DataLoader, model: DictConfig, device, - experiment=None, ) -> Callable[ [int, NDArrays, Dict[str, Scalar]], Optional[Tuple[float, Dict[str, Scalar]]] ]: @@ -50,10 +49,6 @@ def evaluate( net.to(device) loss, accuracy = test(net, test_loader, device=device) - - experiment.log_metric("loss", loss, epoch=server_round) - experiment.log_metric("accuracy", accuracy * 100, epoch=server_round*2*net.model_size[1]*num_clients/1024) - return loss, {"accuracy": accuracy} return evaluate diff --git a/baselines/fedpara/fedpara/utils.py b/baselines/fedpara/fedpara/utils.py index 8155a4f9f6ff..8d58740b0dbb 100644 --- a/baselines/fedpara/fedpara/utils.py +++ b/baselines/fedpara/fedpara/utils.py @@ -18,6 +18,7 @@ def plot_metric_from_history( save_plot_path: str, suffix: Optional[str] = "", cfg: Optional[DictConfig] = None, + model_size: float = None, ) -> None: """Plot the metrics from the history of the server. @@ -39,10 +40,11 @@ def plot_metric_from_history( else hist.metrics_distributed ) rounds, values_accuracy = zip(*metric_dict["accuracy"]) + rounds*=2*model_size*cfg.clients_per_round/1024 _, axs = plt.subplots() # Set the title axs.set_title( - f"{cfg.strategy.algorithm} | {cfg.dataset_config.name} | Seed {cfg.seed}" + f"{cfg.strategy.algorithm} | parameters: {cfg.model.conv_type} | {cfg.dataset_config.name} {cfg.dataset_config.partition} | Seed {cfg.seed}" ) axs.plot(np.asarray(rounds), np.asarray(values_accuracy)) axs.set_ylabel("Accuracy") From ba56335e305c9508878c240e3c37ce138eea0314 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 28 Dec 2023 01:40:09 +0200 Subject: [PATCH 16/39] project.toml and plots updated --- baselines/fedpara/README.md | 3 +++ baselines/fedpara/_static/Cifar100_iid.jpeg | Bin 31160 -> 31241 bytes .../fedpara/_static/Cifar100_noniid.jpeg | Bin 32803 -> 34720 bytes baselines/fedpara/_static/Cifar10_iid.jpeg | Bin 29828 -> 30338 bytes baselines/fedpara/_static/Cifar10_noniid.jpeg | Bin 32154 -> 33676 bytes baselines/fedpara/pyproject.toml | 4 ++++ 6 files changed, 7 insertions(+) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 43d0f293c2fa..2f746909dd21 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -91,6 +91,9 @@ Choice of alpha parameter for the Dirichlet distribution used to create heteroge ## Environment Setup To construct the Python environment follow these steps: +# Assumed that pyenv is installed, poetry is installed and python 3.10.6 is installed using pyenv +Refer to this [documentation](https://flower.dev/docs/baselines/how-to-usef-baselines.html#setting-up-your-machine) to ensure that your machine is ready + ```bash # Set Python 3.10 pyenv local 3.10.6 diff --git a/baselines/fedpara/_static/Cifar100_iid.jpeg b/baselines/fedpara/_static/Cifar100_iid.jpeg index ece077c6ce8fddc12d2ba383fea40010a72a225f..b3fe1a0886a2fe394879dc20dd3c3d9931fd1b7c 100644 GIT binary patch literal 31241 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 E05G`gJpcdz literal 31160 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 diff --git a/baselines/fedpara/_static/Cifar100_noniid.jpeg b/baselines/fedpara/_static/Cifar100_noniid.jpeg index 4dbe7889f2994929aadcd4a8d65e894b07209df9..0e123df1e036d50720e793d18b97af6fc98ac586 100644 GIT binary patch literal 34720 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 literal 32803 zcmeEv2V4|Ows#W;iK67JfFMyNXB5dI8I-6Z*#XHxU{oXs0s;aGl0h;ElA~l0$vH=f z0z)3s%-06g>w52ocX#)_{TA)tKzC17ovKq+C;ZPT)BtJ%IIbWgF9V>Vp#e9*e*kJ2 zkODB!(LenMe=x!SSU6Z%n3!0&5C}F7J}y2!9xfgp0pW?`1cbzdczDOjjuW3GAtfcn zCnBdHBcV7!LQ3+f5Ht+%JD6Cko8vOMK4IOMFHU#GwE*|(o!Epc`4Fdxm6XR2>!MEMP=Kv-#)`?T+FJhlm zy$LyOLvrCka6Asmu%E|OyaCwg6Z*rgYh1aaG6(#rE3R_hI%N$zQO{iK0?}Iq#1Z!#>5OJq~#O`)LXR=~VX~)5&NWpoiBynZS<( zVy7Snz8*{{;I(l(($B(w@?Z{mT!H|(sJx3k>`1@S?7nE|OTT^!5sIvikk~t$ih!Yj z)AZgb;3Fjz1tf&u=lAG<%{_@tL3D0=q5#p`hY}4b3HKTHFQR~$3m&o4vO`1%YoVJc zfGWlh@LYxHSw1QyC(v~QIt^)-WSG~J`@_$;*KhSzU(#$$t+5I#k zT1on8Z8mCU#FqbIr{uA3y%O}5Og=FZG|0D3IU=)XQw8&=Q_Q|quihzn{9CUi9#N$% z4P5*y#rloiI%PO2=OPAdiEr7)6`%mMD)oa_;7_dL?`V1I^4w`2uxr{z0p!|Fh66#) z-W%jT3YeGq_Y4(n`OGm1hf}-lk`fJBJ^bU2$~(im9q9j1PyZd=@aq9g@V-T5Jt}9r zJ1+*Q&`ae+f^%gHWaLwl|8y?-Rog9CU!A_*9&xILur}z~z20{Z>9>mLSBVZWRnYzl z&;6>jKY^7)1ja6CJ;>cPwxwVD&yzt z_|Be&R@K@v(Z2sdy88HnstyWh%$Hc9CYdQbq5dJkFEqV^)}MclFZpNjfaG#jQW-)484{~i$VX=A7QbjJ zuJ`atGjbm$92sA@p9iZPZ~MKvzcCe9I|^`x?ajlUeyu;LiFU#4`_3LJw9txQEhAqK za9$+Z8xGp>K|W-&@TT92_`(I4o2t?PiK>#8*8E@`=IR5*p%Qu;H>rHMWYKk~m9=TK zejl|lW_ldfi$G@#G4}xe@u?y&F0LyQ!(?s9db?bZu3devn&8A#dQqH8=vgJ9luw`4Ym zN?kb1W!%x*2}*|9++kbumYve19k)n6_kuKFhXRgE%t)=V9C~uWHq}+(C;)j`f&c{! zTo^_H(wy{v_!O`|D(A<~Q>&Y)S+MCL`W^CJjg+dc1aSEdGJ}m6evmw=D*b61{PsFx zyjQ7EiRnJ>9ce&GnnVTd=Su}-Mhb+A?AzT-MRt8vM>t)XeOs+1C^7{mJ)x%whjK!BX2?X3lOz?=yLRG z68PyFUd*imw>8ebOZ@P#Y+ay&HQTVhlHaG6-lWiPNJ&w%6k6DK?kVM=JWCu#8egD8 z4*ep|WjdPIU4z~j`UbnjJ2ixs;jj3gotX%QIw~PW7}H}2lP24S6kC%KR&9rYB7Qme zzA-i&an?*TYaQAr>1z*-G!AD=rm`-Mpnw(vzRFgrpsU!*J>BsLk;yV|I~T0p{fxW) z0=DPpg%1YOwg=KOYQie?tJuRn-tBaZ*4<-ALTPW0F?8PumLbQr8nfJRtr4`~ruJf%;E&GP zd42EjM2sKegs?t^UhBMcUko6P3gl0>P?Bo3KmqiKGk({El>}2H^vNeiQ2<7x8*~a3 zg-9$6$jaesCXwv&d^}c}9(QpcJ|axLn>b%lf z5%J<+cxtrc3sIAC7B$Ve7NgPb&0w8eXl5q0KXfqP z%{bf<4myeGR)7bjMyQtHEZ5ylY31u*)9N9$yx9wFI0!K_rC*oakH+wm*o*K(6 zA-`O-TeU7aZtfFTVlMk)=bl@{?qOqu5{s`W-V`mPO5aw0ul@cYRoev4Bb&Fao|L$P zi|4LQ2W9E@igV~%6t1Qq?ozipT6!0FlNpBLh`xsnV9d6|+G&Fxmy1BBvIv*1l#R}` z0UX8eo?gTks|I={@SUmU;hFqXZG#@2BgFkMnb zUWOJE!PJ!G?Gg|{Q(2iCV@d)(n9DPaT zjXXU(j9@afQ$6@Nx=osgg8q)-pq_vHs}*)7#lW^eo2}pr6fN#+=72a`QDl#skp3DQOvf1!9vpURsGPObNew&60;sw21 zgxQ(Vh5gnWyCbuEJ91rq%I*UD+q>5`q7|peA9F05Xu~!4lwW^bpSd34DA+dcW}jY3 zhp@~r7p9GF^{#MAu_Sz``#ibswP?V`x!YDgER-~BF$D`BY=uq2?8YK`gg&$yB!-1W zD7yC%<=D!G2k=oZ?v1aO%o$L^`^wy>mPVE9;JPPiLk5Qrql=^&OgLsRnncl>XsG7% z$ANiaj{vgpQfPUfP4uwo6Zvp4`4K|ptL_(4+Vu@`)Nza770?B? zD3SR~VIWCYy4+vBRoyK~r}UeYo(Bn^N|`-s#a5!=^yXPS$ML$B&Fo z8eJYb9UVx6rS5TeI;kzobySZU%*D7VbWB6$cZa^3|Zdbu`@<;9TDg((vF3@Sc<(dc+ zR1sqGd`s_Tawd7o+83)|L0(iSLBtLwi~^G05glwOPeOVQWs~hh-C1x-&6}I`nJX*y z-P&?ed(=asR8<|J%PCeUfH8hewvJPv_5x?lD)-iPL4V}}>JN7|n`j{=hnCdHldB}= zPY)9{;CH5mt=6}l&a$&E1$e%kD&@vSkSoAnntpt`S{ix?9cisJSGK*jfglvih#xM? zlFhBx+?i7k-c)FHTdkVVF057CIi>u<0h*80a&=oVG8aGF%{NM6Q5Yf5`CM&fl>f$U z&k^Dgs%eQL(Ti)$mt9XQ#?LG5YzcLrydvToAxgAe#XM`CICo;-Aa61r_`0>TyND6%H7;N z+8p9^Y+2L_%D0j2>4UrW>!Mv7waf6pby`z}ed)Hq3EA|TmD_Hnj~nhiylT@RLMS)V zE6%8=l+G1B*~+@qPp67A|w^ z@@^hnX+GIFRO22Z@l0l`GIuxo6c0) z^~m|O^dsxeuf_4N`d!*0p5Yezmc;vxuR7Bwz3KZ!`rQfw3huDp*qw0{aETX<5A29z z^(>=v1$Hs4Ykg6Za-CdE=Bu}-s6}kRxpO03;>FwSdvro;3YbrI5GFMSiZWx+p`0C; z-HVJ>)>$rF=bt`_7Gyc=XqYib`waf(rN(TJ(J0d2X+EM zsvurr4ue`56`#-(pS#I)W2VS%y|+TDaYwkfXUO_dmbONzdZFSDlaB7;HTR8=RULk9 z6!XFgNxe~}dA*}%=AtCM+BhP~rtV>p6>U*<24}n7*l$@Cus?e!?nlHi*C{y@n{4x> zk{R)|*DEo$z3HYpdQ- z1cs*mG2<2r2w17z)j|ORd)(z!mWp`Eq^;bupeVP9oN*z4RBjMMgs|krq>jGSwha{? z8qZMVU)Q`VF^0KMltujbNT2$pBP8q4Vak0@Vb@P>XBtO z!tGYwyxZY#X=E86R5XbpC>l5FU5N_pZrm-n6LU&8R4!|Cb!pEvnqowWeEl8w=Mhx1`#Nt?sUOO-R=v~h*t{Dp$|O=kyr3RWR; zXfM&E@@uBTlvuKo7^C0ANn6B$^^L&FB{qqFt>yhx30nJse6v0z$#?tX$7mft75G_A_4!eIRf|CZ3V!`av3L|<0}DIS6MtGGhG31EhAV!N z@%f(7wju7 zzh}i;f6Im~VISNkE1115A5JkOb4pWYN*N=tpyq%rU`uW*=*0V~^4^ufX{H;#>Srk= zf^!5&M&)Y>V^&s1BX-MZ5xVM+)(D^pOBpDD+0oM3XmTZJ@_NkF5K9cG$@CfM3%uaj z`w(%ZJsJ1>hgX?_!eOR5dcCxR)y|%i51oDLt<#iJ70IG)aAh5gj zg;Qf&s&h?8hc&zQ`4{Vxbs5Y4^1-6Tk!cBt^ka=f|M`k5+V2LnF{uMDkFBAkNAfeYd(+3)1dPnyV{!$GN1uD8OL z5zFM1{2hRh6{8gK;FIIyvBXD8(BzX48RUZkK(~lBvO0Wx@2uHRPZJQZyAqQa@PYLi z6z~*U1QOrP97h=L=eaDg*=D#u8w4_Uw-9VK66}RP_ulg#%$I56UFTp!47UAsUA4(<+5N(- zlZ#W#EeZm;9b?Wd^4;aq&;&hw4jr!=m*B*^Nw@FwbhAJ(D$lxUPPK63EI^$c>t6&m z-3)rcY}cvbbf#Dt7h%#pg%y~aZOI?NyVsl-xym8aiQ5U2#f;$Bz3oo49Omlx@?MM_piopUb4FjDFWJkm^l^Rc{hzNdE5>|J+gxs(2Q=vL?9 ze04bjmWilUF6(zNpDF6!e#|?p;l>=(ho9Jy%Wyl^&|(=$j|gy6oy@pbQ>ov?Nn6K~ zX^ZZ3mbOARm$YgdoFJkgyH{Ga_8Cn`_|iF2^1_bGsUmi7m10hyfVVz$bK=h`{8|=e zGIh-t&g@sjj;9=)JIv4R`JfTfIlG2myNG9w>&BlYm&<~QbsWd?i zWvlst5}(*#{~ByuJ54uXN4o{qxGt1|T=WtaGb~5m^T0&`7wSnNL$HOHRXeTp-&l2# z&PMDnZ}X!49XG$(Ls`C-E^U36M1Eb)!`4{`D3QMSmF;D}J_VHj%v;|VvEAN74I z>k#Ft81ey?KY{!Io|)qM&55M?m1@=6yO?jHLu_jiutAc4&4To8(Mt+cK5yr3FmTfk zaQ~`w(Dd$r(hkvL1l4Ad>uo2_99>!SG>-Qx5$72kG}*3p-d}!q-(@cD6@q&b`52BI zTjV@Vows8}H?^eN!^ORl%*LN;zQW`}sp*WXi_3HWf+gqz{|_*3m|it&=L?<6auDU2 z++S9j-q0H2VTmZSnjSEPPzq8ux_;PF_6==~pXW>*n+y;Q zmVSj70RRwglR7tEg$F#3+%p_IKTJ?=WFJzL zkfR2>7-Jbyf=IS=fN$?#FUZ%`*TT!ma}U2$*tl_NB9QRXQ@QEL0eDU=VzaC^V~HH1 z-Ji7IuSOIuv2A2Hi6Pi-P+6QKyyjIN39EyBZ-n}()o7i~MA}V54k;9XlJtniUMB>n5ScOz_lcWOc9`9ZIw3Z=@ z(Na)5&XdNyC!vN`5sd zP3bDPX1ZXz7m~V{H%jah3eX2EsU8i_&d{*kPZeZxjj32FrigPt3k#E>dvv#_m(?P| zc8xUIH+`}7dgZWNxsR$eY1fV4YN)^K+xbam|D_e+*J_6bxO`_7qX1{v{OOd`A7n#t z0sMv2>Zf}Ui8Tz+zjgvPaA6K~ukq0np@11F`1qkGXVs=!=N~=nfL#2kuumQ`bh_W@ zBnm%R1T=oFcu0Yp1YIty6m*JqBgH@k)&6&^1z0(IVSBN&7OoHb4;2oTH+a!FRr(6< zxf5L}t}KIb4PlNgKY@!u?3VR>{KOgQ2@(4;ojSby5sEK9+;BQ|OY@xFgAGDkVJiQI z02FX6$j`nVPewk^pL{4#m_&WQxV`ta+|qsacNu{`9Mke+=Ig_)F=3)4yYX!49hQ+u zMc7hD+8T8oStSRYKZ4dXKYXwEBiW*jcf-NUz=5-#*J3d@?`34&nw{F1^}np$o%QB$ znvo|qm0GsL&r`P3io=6l$JrsmF~Z13JO$hJj{UCWChji1y1__prpr*AX=vcq^yJ<3 z##afFW# zWc^G~#6SUi#J}f_|KFm(e9@W+VZTLKiPNKiCZEZweQaIWd~)I+J&YrK_#2JrOM#eT z$S~5JgL_iQDHOo=><4K-^>;7=U%6DHm*9)}>en5ooWchhq`W>c?9J;Zm-?vm!VCMg zZ7$l_6id+rqJ=_Unx@ToIGlKPqa^90-xIy)glw0F#K||)IVTY7e2r1~{M&tjSI{EZykGx-ngR3chSZhGFYVq;)@P5-`E!pq8v>p<5jXe+X=DWzA1 zmAlbsBxaHS04P@AST{O0^s>WiGrSvnb=&rv4WPB5JG`7B3Vt$SD_@r(Bc+DXhU`;+#%KO-QATa7x z;v@SJ$9*P4Y6zg}cbC?iz)(Tefk#RvU|Md;66d_wLMx?~cabgpcv$um!LnI)?#)-?+Q zSXfG$DKMVIln1iA1USpqN4;>!IboomL7$5-)14bLH&%1mR^eGSkTLcTC-e~|1yK(Z zRW);I;gd%G*=enV4hIp_Mx%|c^=mQ3q5_ldifV!c)_5eN9l4XTQ*!hHcN#Ysc1YhI zD6r`E9tch53+cAxQj_vM;{Yh*)+(xY3rAsZV&F&u~k7)mN$A0eqbl zy{StY4$Ngvpn;6tj^RiC`taOA;j6 zcRUympDVtp4X>Uk4mCUVia0`0u$=+}*n;$ELb{luCSuA`N4fV*GIdoW=5OQ2-GgF~ z7P!ML#H6YnT}2}X$`o@7^$c9x-Y*vmP_7U{_7h51$;@8uSC@vrFg?z-ZH<3E4X_%x z^dtr%xMV1T&&8Ee5YD18HKhMCe4M*yC)uw?ZCjBbQBA&bky{o8SR2A+V!~{>?8~kX ztT8DJ?xph8BXq5OE18^qu0b3sW71v?zn(E&RYZ6`K=gTpfo90Mp@tXc&?#DI=Es`> zr>Q)m*26Vb#9f3#Y?^3XZ1iIhRlD$Tq*-&hSek?{`pHPw#gx*zaH$ zzwXSS)9s67H~(uf=i&IOGN|(qt&@sfwBAWU0ogvgxWBeBe|J5C?;c?se`iBK+T;^6 zbVv2go$g*6gPD#s<1q=CoaYXiIiB!nmh0vJ9JXM6pD5t0$dhkz{01M7|CZpekfsZ$uiNJe9?d$DX>f!bLSY zcr%dtCTHHkle~{Ztrc1_fY+w7k8oj8@`2=wZT0^hm3CgvZP0fQ7 zY^+ICpHktc;Skb1yo#2_SqT|Xw#*J9cJ|Rob*(R_Z%bnWiRMWZ(epr za>A1?cZ_{CF|6N?6W)aD_a(!`-=rZTbbBt#J7g@;Nk&imp7T5RTAIWnqoQf(e#l38 zrrG=+QVnGtp*vtxXto&H-tu~}S_T&f48f7KJTS)1fBrop0i zI3o?S0t5v=@dxl`Ihd}r~OZQ>&Z&8kF0+J9yE*{JV%UDF_DQSagxYcBv1@n3<{l6b!& z^bdyod#CW*Y^8D)`9kKZ=B!QBoO*o$ksWas^)uC!@s{> z-qc9UCjZ9D@Cz-FH4gI=+I&c2M_OrVL2ukoR3?TL zo;l>twG((QS9VOE2)EodgqZGtTw!pcR*X?}Gjd9LVjlsKnA3jEsMX;Pr9*oY*+umSWJO;*81!r;NAkN6Gs2lVr zAv^j%s5r@Cx2VXD1i0dxQZBk|@eNN0S!`%hJ#Y7@e7Zo20*YV}v#x{%qAVp+*Bv52 z02}h&cJ2mOO1hpiwMM9Xccj@=Zr|;)btAj?7gY;mTqxQNUH8Q)%hd126ofql#75q|w9%N~Cw0*gSQ**s?;3xc0nc%m8Dr^bYA)?H z*09!)BvszXMwV0o2#0dcNnL76UH2x#3#Ke0Nw=<-tTqd7qnBPS*hH`@#L(d?v^yxxSbBix1eqMh9|uLwu53~BXm zZjdK)l4!d$9eE5$|I*(5OI+U%CR|Xa{}$}@J7}O`_d$tq>p9Sr_ccoI*T}r1O@+<% zn;!5xu)=QwKltjm(6(Pr6!sw48%c@+k85bDf-K4F?0P?(e_$hi^R zOhL=D*(kKBNR8*t)g@vnr#3cr{!t(wZrnXI1NON_`G&q%(XlSPD1iNNF0 zzGFcD)bb7;^V%(~f;E!@WAYOg!p5a6NK7n=SC)AQ>xt{-JX6-hhVOhiS1Ac{)kE`F zOq!OP>BH=*NO%j&5e1XSg%z}&isc1@ceP)GCdP8uir>b$YjQn}S{Zzq%qb(|$PriP z_Zn$8%+)**(>Y@yD4`?z*>;44}1WP&s;WycQ{;X%hN3-L}&)1ol zxmA}lAWxUH@r+Yseqbhb5h0~05n-~WC%9&S2x@RTbXt@!X+N}8sRHf!Th#~DUQyLk zhuya$1A6-JR7Sm=-URwx^n_edfXS1x(ZiKFE0Mz~)#a3ttUU^y6fY>BGO@Yv`>fL= z3zat>Yupbghl)I)1cP#9r*-gsT%2%HQI%Cz+QwW~MU7a^6pe$;&gd*ykHu z-c*$~ll&QlsPR8X`Cv)D3r*t%W$>Ykpq6~Tj8pqw3 zON-&Fo|(^N3~FqcS7txX@IDu7n{3Q-i?OCEuK)PDZH21LsZ?t}+nryX?V9%Tc@bsh zu&C1Aq{@xXa+YM+ErjWn*NoU>ql^+NaVHO!j4DnC#1UO(-OK^^&a1e13F5Z4cA=q* z!bgxaQobV}F1y;VLwdggw|lm}?r9(}?8p%GGwOuP*BL zv&QFy)JV4l2s}OW;Wn*+VM3*2@mjyXD7EPtHoGB-o^y+Gw{$>Qcv;4<4*Rf$dh1Lp z+P>&r+0%7&8 zeymLd*cy)@gcn(W4v66&*@%8}<4eJji{~dK2o*e48qD>*tL%vHVGIU<=1Qi}9P*nSb19^D<`3Yg*!D?W7p)y&byS zi92^M5w&sz)THjkcAPYDfM?W4M2ubNGp-C#ezW*GYFN`&?pl}D6F|gfp$D)ul7H%U zc`elPf)yws*pWUAV>XRNB=2Cv9W)R)tG*X6kiJK1-kM(?vEe=x)AO2MEA#M8`K?gL z)q~2S?p2d^5c~v*ChIK_Q@V^iRVfbbS#H)_XN2TEc<`W+?!HmpPQCPYDu|B-UGGE) z6)U5!{q5Sr+VF@}kQ*Ux8m5z<(b^MhxLCKTA#p^yd%A?y=dWmAeEeYlWP#mB z4^M@15ksdTjv!XWrmE%>CiL*=3#vzh3ofkxq`OBOL-P0bWUZ*)RNZipcsrIdbf2^H zpH%U$3HrfTE#6C?0UL)LfQGO~Ad&)vV-p-sFH4fMrh0sR^1Tm1hxI?{?l*h;_x^#d z96u($!=*f84UfZn_KR@_kud+HyS+ye{@J5GV58tV&O3f@b#6rUJ@HQTW#Qn7PUN=rVFR zr8K#Y`dKz&AOn~d!t{KIR&SvyNvA(K{=jjM`;oO9Cv2+qUL75MOpi{zkqcq$oxKC%2Yiyq8P-bVq+V_|O|nNpe)b_cng+|@Q2>wW9o zPSqb&&XQzgf4BKe;yL6>wQu|3re#~Bd^+KIN85al9iuV~lBG0xbLy5A?L!>5i}1`v zDy=ZGFpoXs-hiTkgL=vnm)=6ugy$$3_F~-vhow80^J%$;mES0{jg6w8Mgi)J@{9LE zwfAnQ6l0Jk(=b!1}21rmtFk~JUGhX@JA2~5DD~OL(iX% zo|3v|z`uqWLk=&DPru~PRJgXH;kL}efIl6xRiVe-@M;h~t~I<9^-e`RFD{$ISYEAG zBaPf7VR_MEVxi&HnU_r2FGD;ccn|QVDGqKX;Jq?@GbS^p*_+#RUQYN4-CfrpYu5&N zX@KRF>Ei+^Z@+hf`SZF98%+|cnVQ_>2Q1dd7dw?qo%_fld~+>qWMF;i_ziFEIdCvy z2Ec{ELR{kgb{tuv-4g*H@A5P=!67>9Y3^19I`&Joauy%lnO{Y^Ave0x zwiN7B)Shg``0*n&xB5R=%Q!q!m3Akd{HDtsiNOB=jPbu_9fEK9UH*m)E?Gaqu;L(F zOCU2zMAe+HMgd~0(CNm*e8`@LI0GUnhITD)L7Ts7jXmJxGI=davlENeO11OM{{D@e z`n1b^;OS(Gp!pD)=1(>gI3O-m=725t2^Oh+ST9Uo5J+ z#;sOVqP88r$EG89fBMjzXEplVk`>mmKME%}VAtEQ*?A5&w6nY*7AEr;F z32Q`_+V*#{>9W<9apFT;y!VmuW5%1sjrK2`bEGL2^fni!SPb|wm_n48qPHB^^#lFFSTA`d zOBW7bU1lyg7`HtnQ8h@L^*S1#qtE~EArZHsU0IP?1T(S-U-2*#l*q=Pi0ei(WdyTh}{em4o=WFMQp0>6u!bT{M_C%uUFr+DRwuV6QD@O5a+Qn7J<}(*@lr zkfDaPrkX@GJ$r}I0$nkeDKrx3_z8UwkAOLlA24`OgW>D5)X)JCCB&Ro`Nx3u!F>(G z@k0W73;%sE&)nyPuf618)P;5b5l%H6YD@=-9eh60_jD8>uZ4C7yJf=&iOGaSxJpvDBeufIoWj zp2!2+ShFeJ24Gp_-kGZ|O9vGK3kJrJt;lv_(|2Ks9uu>fW=4=@q z$cWnar~-#(`HFnWXV^Tee?piU_j4-PFa46ZtO|o| ze#sd7xm(P>blc|)u%CP7FA7O0C<%a0W5I3aEj}F0O;)t8ul-TydVa2p*Zp%-KBw^g zn@6ZeYe$uX4R0fKU@x-dJMe($=kfa=RPmh&z7)boTF|3Ni3O$yDWhMdD)}K(=D&be z)==)==1=|mE#)E07q0#5!}E`fu^;vGTBV4^T^jm}+TTJ^z;vv?W`KXBhQH(@zr|A= zyRa$DyI`Q(hi^V}R^T6*wtw@mtI3{jDp3zs{%um$KQ)kc0?8}zV&d_g>RJQ6YV_&s zEB;9AveJ!JKl%mNh({b~v-&zhesk$@95BML*;)FP?jU#ZL&5{>=g$=js~l6@aUVS< z!3ThPc{xx#?oXsWA&d9>31eTK{&m*Y|Egf0LX`eA6)di}IIxc;L{>u@>#@=O^Ub_b zs@g{KMYoL*Z|cKih`Rzt+ir&iMPQY?HH-Qrq^>C4au%qG2r(3REGl_9a%7sg23xI- zr;BfAF8n}DK7OK18KLB`6hv;w$d^~*so-~Ik`dGU>Dl>fb1`v5SF{uNT(!ewxXJkp zQr=dQ?p1YcrWbhm#}hq100HPw!<^?}6j6oI``>4a-w87NnpEolY98UA;@Q}~r5{rN z4XpB)IbuKn;`a>1eyo22z7g&01(FlWeJ_~OaCyquPCp3;8KWn10}S1Dj(5lsxY}h; zdr8Qok4tM{M5ig+$e%L!0VLJ1F>UjySG&$F9gc`QzJX9N8H=e9ps$c9}-Xx&wR?gRds@p*9?!2g6BE&M%=gp({ z?3|6NdS=P1E$F6)K{R3HKuO`GbaCzN@JN>(>r~JaR3X*=nGZmp(0>=Fues6w>5Eew zTiy-+~?_2ll&lzZHG85AoH_%~P9s^#8v z-UGo#EFB*?Zgk(dkzXY3hjUz(Do0x79OQ$+6~lh^vpJKEnzJaNneJIc&w`*tpzbc$ zSkB%^ku-^Rw#$PbY<&OHlnDPpz69;FYXS`?oljN^2fl&cDT)YovC-9SdY{JB)f6m4 zk;=riqc?H#$#i5r!hLG>cqXvd$A3|LGK#j0bSTM|?q)&Q6!M7NjlwuW66;{L z{Is1oBIyJ$wc~HzGy4n91O67SjU$r$mlySek3^6AH@w#aLapdMs5wl(qWd-((W@^Gc9VgxH4#i9358>Cff3a&wlQ*_|PswjgM?v4e3;B0U#(%6;mdWrc|9oXC^L2xHs)AR!rZlg}V%`}ClSyKbGSS?|1`~y= zV2>VUrke|LqqUx1J!TuodD|HxOax9$-w+D%bAK zzL}yYUg9rZeat79wVYhsy}6TH7>MKayz{jAs~b-8j6CE*9OnQ)+v&e2*ZjfBHUF;c zW@ys45E=E6h7awp7eH@)$jY2HRvxX{kY{{@bvcl=H%8Wkf$elS(|t7F_G5@Dg|&vD z!{o#fdA_RW9ZeBr>%+?Ues?=t@YC}yPnVXaQ9?k`ar7tC$`>6 zs&_UA?a|dpYo7wVfg2k#MjL0mApsYJcAww4{Sr-G|3D935b*@!eWvG(zIv;oWAGg- zsxF~XCnj!t#&b-WxWOj{?Mm*Z*v??&|7Bfisf%Q#VgdDATkrWDwbt^xM@Hlr1DP;5 zKE9V!!Io#5K$9e!&z8N4NSRd9eY=i$*Qi%m1~0!Qb(lgp_qfigss7xBU1K(W&*I=B zM;QxUHxqbRztf~YvPEXUPfgXEt4ALuVoMO2>6zk19E;?cnek%JEI?b*Z zF8XZ@$TNJRJ3X$34cr74L)a-<;k}y`+*hSTT(+`s_mwED%RXe);TSVb01pBcp4`4K zDH6#Z1vd@iwWb@?8=2qGr_XDh(Hk}#Xg9XQjFQ3%Io8Q^Ni)p*1Em(`?cdj)U z*>zg$tEFAD-emY%MzlRPMb8`yY0yiwD4Q5$dAwQgWp0tkKh16n3xo>XG*T zz=>67emaBul;5QH-Kzxlo1gQ7e~;3UvazAfJfX~v>BqJ~BGIBOQuKJ8<(U;gsKu=*czN+A3nEQh(`^QkUgmW@qdC{iw54 zEN#ufrU6uPQI=T27>uM0=c}_Te`+G=IbnYc->F~4caE)tT+S`2RGgl5IkTfsmj7_I zMbOS#w3EXL%{BC-0L|uXJlv@PhVQ(-o3&}UoR+A{=w3QkJLFXmiZhTARvM1w_4*=) z<;#g_!8pQoziBt>HPc%9*d2A>9Se5Or4Y3LtA zwpLgq9+qU-&NpM$)nJlaT;#XN2hC#6&r4{$>nDdbX^LSx6HqIb{Otq=m&OKv#e(hw zp3LDP{YA1px)SR&1D%1lJ16teZp++$e4*=t%Y(vHj!Q4uX)tsrvZ6=&J%rZq3k6)= zeSG=a3kRxUmdshzUeGw7_s+L9om+UXTq^Z$IEKNu`0Ff|koNHt`AbBIYoU4wgW85Z zdScUxTvZoix>u26J#x(@DL13Ar60XZfvuupvX17zY$slz%FJ6DgD6pJg=+VTnp$QL zTO3Qd+9z>KH_By5s|@$1jI>vGi-|U~w#CzT%3aTXyK8+i=xA)=&F2r}MKy<;`r!;| z={nQ7;HZX>@oJe^32I`7zQyc)$BHowANs3^2iGJ#qR4A%&7pKZq+pwU%qi)e&I8Pyc7qJ z#sY`bm5Eu_#+;3kEA8c8;ksibDv@?wPyhPJ`D!mZ)|p_xnJ|AY!D$;{g4<_HOtGf& zj5t-{n!~u~#{vR+)z`1rE{qo(wzma+y3 z5_OoG2QzDn$n+8f6of1mp1jsB^pNF*!i^UvF6+0EkZW?j4h|bqPVfAF%ExCERcw}p zW&C{qkf*M`0cR4CP+{JqR?#wz!ln+}_cr$v(-^e8NYFMo*qzoQY9xrQWumfl5W~71 zLv6!Xdw2Wmb4_P*#`?6qJgIiOH2yn`;{#0i{aHVpTJo75os|h4omL@vCgMc# zre|d@@h3HGUvi<3g-ymzuP-SiKW$@iQ&PKex0 z4Hixb^MWQxheWv$1*Oi%5z!%F_XGkeImevSyXE_ME9izLedLWY%|jQ~747DDGc#!B zGvE0?sJM7Z&&#B`@%7JxLX&9}@citpxThIUVy*7=JX3+RX`dTAP(wuAOEu?5vY92p zXX8_<4w!~GhV(1_Ia;XFb>7q;i%z=DJj~-ooTe>(gC0)}K324*F8ik2J{nJ}i)&LE z!-9%nqBno{qsrL(@UiiU$%g@dzIDCadabP1H7g*< zpV?oLT;3&RX^5$iU-;U&JWA-SS)%S^{hFK>j-`SZ5oWtVB9pQB!v>2xL&~)+%2e|@ zoNWQAQt5-vVSIv_H;SJTG3aLPyrYrpJ^@vZC=z-6&U68E!@aGf`hQ#dy>n0b1MeAM zY~$U#5ABoMacQgbs?5tbSL~W_Tcb^Es?!~f-!|=MI_Da0d1u{L`wev>k73ycoh8g$ zHy%7z=m$(1IT!wk08d^!6fd<$w7hzKX4T)x^;t1lQcL%@>m|;;=h9L4FS%N&Jz>?# z*3i)YtYsINu6{VzxF%cRkL8D~7r(Z)t$xV8`lf00@4a>vxlFUOI|LW*O#Sryb5u!! zQkjz5iSr|;Cy(qbu6kIzy4g873h|2Z3i4ci z_~ePRn>Ziea`sT<-T`EUPjyV)z0iw zBb{G=arVbssCa!oDq_;)afAnx7}!Tz#MGGEBuCoFUB~CHRvS>^>b)m48k)b-H}=lS zo~!VthUo07srH-J1t>2zAy$#1RLUY+ZC8tM1tie}tuQD?; ze<9bZ!ua<;tZbt4^6~|GsWT*`r12)^u5$2K?~r~%Qc}_l%Ky`kV;l?&45?x+dEY*$ zrtq5YFSJ*_dlxLv++NXo!KLy@e(Z)o15=&>D=X__H5qNC02>>_Ua{V|(- zXU)OQ-L%#~&ZgFuCS<#R(95_K#^^8Y>|A-*f{T~8r`hX}->Q#fVroj3Io@ox(fz69 zZ6+o(xln?jhK8Gu9zQ;qaT{imkT7Jgn(6QC#OMqo9-m^5SLXNo^JH*qdD%+&8rfjy z=g%Y_9vyNYxD&L1?#tX^WJ}y-bRHNul!z$mE!mQ`H-51#{BH$VEtP;Ed?GH z5yP!;(Rp9;wV}+;ank@ol(L%Ib8YX_`8(Fu*5T37-A#KR%6=`q#lLy;W~x-cZSg1T zfxEL!mh&I{o=P75`L(q^UB~z4&6`TIc616VD$9{P1u?f(g74qIUriZszhY<{O)qg- zE{rHdrLbc#L-b&;^{>UomqPvbk7#r&Orl?B`x_+IhyT2kK+F8a_1C7(t@EFw&QG8A zng*O>+1lC;`ivH-cdiViz={PtJffkYvCA|y7P5H!xV$=5MMcHT#s(WMD|r37v%mjR zZw$R$g-Oe<_fRkvzJ``o@KSGVK0F*17#BxEa77q*>sxszK3{WA^~q+w?QoXo(WGtd zk#Um;;WHfKp68dyKNqS}J=vQxPMiq;iIq_pt~_9y?HBs}yTu!m76b08lGUA^Xc{4# zhbHrrYpdtS^I=U*5~zhi(fQ}{%t2vcIH+I5Cu1e|7q_@0nHw1ZYm&dJXHT0F^eAo)7=ZQpvfDzw1;edi~eW0$1< zp1B!I#n>$F?#8%peUr)6Sg0PpO)f!~ryDCGDqpa2JRcAScIJJ&YrN;i7;xsIZ)mvm zYut2PxHUB+0|Rw&er!s2+n4&D)Vc6fol`7W$s3~vGMGW{@5427d;3!N@{;?nJLcvX z+}4H^?%cW4@?BX~RZdq|x7^Q9R#vvsvNy&%rMx60B&2;{0N>|mn^b}Vo(@OGj^grV z=Ihr9S(IWPeg2JxhE`cq)3sI6YLfrkdZNZ49G;1!hM_N2T?^6-4;B`dNX*_u zmF2TNolIpNZS677isojct<)(iTU)sjZF`f9j0^{|*dlEsBZ3^em#TU1pCh{fv%Ew_ zMfFP3M|W-9e8a&#mgK|nxAfXvI?+peNxn1p+}*U@563dU zjE(tg(%Y9(0kP%>TUz8ynLe{E(VTZPe^Z3ikbBt-|o z`qh!K{=r&@aekNY@_j3=&A(SO)V=xg-UlKp)eR=JHD1x1Dqx8mvvQh9V#eE1;1kC6 zRZDq8i5X)U{ZG)q`1TJEOPIR1I} z?j1U+`EW>LqS;G`!T-ekQ>p+)cXzjby))C<`8g>K&4axY_m!>)@)K|#xmqQ0+1arH zf6u85-#-ch$4xAC78n?a8nIycLZ#*yUL2~#=tuGTUUpAM2>!veQ>*#P$0UA1L0M^O z)MY-CD9-yuW2fnGOrNQ-b;6N){^N)1&y7(WpW|Hx2qZ0Us~?Uq8#Q^TA9{nAnP_z9 zMlDR*W`DgV>geb=(HbC?mzTGHH0>nEn_>wk1lhV`xi-;96M=-ZH(;Ys$;rw42M6sA z6)mADCf;`(I%Jsb#Ul3Sy)7V&<>^NGIfjRaD_8J0Fu~tjI+jD>uRrm?Z$gmch8OrB ze*Ch!+MrpYUGK7l&BMcUHD0-kC=7nzlqSKXprmZ??#9yEXxXxvGIDi;HLY)Gh@i)Y zC@3OrZ_fpuB4F4OezG(-N0UBZbY3xr$A8O(0|+mYK7Zh2=hm<8em}9WkQI4&o^BDZ zvC2el>i_?A{KZ169V?%h zXE-><=~@rwwO`%gZcAD6?r6z;;6_oXO9pOXWS2L%qeE8z!GqkK9Atwm!T>ZpR(4UK zyj!@+QjmY0z`9m6>skTT_t?>m^^x`~<|aF4T;p%UG_*_hd(&veUM7YS?Wm1(PokS= zdGN;%po%lXf2dF>4_#OAmZ2$4MAr^R6{<)VYPDUwco2T4Pc}(KT0-citmI>+BxQ{v z4P?ezA!I9c@5h~zW!u_499i;07}+ zU;f_6k6{d!JM6MmQhO#7qL`V%5Hrix~v#}W$cnx38n*) zNq0z8-f54$)l~(@2y}@dtC_?4+jBenYT**E%A=VQl;iFj1=XAhQhzMi_uyAzSHjm% zqee#sOFrWFJHK;_X3XSugt}kVDp>MQ#U!mv3hJ}hx&>M#9g{WaCrt)lJuB35<$djM zlYPN=;Vh%R*-cshATLmxE2yxFDT=T#PWc)~h4yzve;Pr2ZZkUzloGz>NrQf#DZ5Xi z9@oL>2t^m$imVHFsD$LQWmr_OtIb_@*Sdjfn{KB=+bI{QFJp-!7!zTk)KNL}yoP1Eb1yhoE%Ly8 z*RxR%M?dY#X0w`wg{vDe;)}XSv(sbWbhv^Qh18bNsU!tn@2yyBnIE$EKXb6Wnv}E2 zKrdybbqRgSaM4P|Kl8ii#5e=I)rrVOs7-c4p}^U+VQzH-)~h#But?+A=)dL%5Lju| z>(g|Oe4!3mNm3r>>m4a;I9anGN(>I{^=bUxpS81SW=B9SN3)E*c_oi~vfFd7uDb8K zuG!aWnf=PL$&vRtZ^)>=;F~|@3ady^$ox3+jlZD*2SNUWWqB$QSOM1-nkF1js0jH? zH7hEehcA=E{B{HiGRS#tDf8=PFWqk-m{g#gNJ$Op_`#LhZ;gBS?wK~a(ZKZ(HLjr6 zGRk)^xcA3Hcuvh_PA0l!I zMsYlsR{TwBiSE4BE_J?MM?wEcT||WGuluqJ@B8e=Aa?KfN^wntk^`O#S~pDoEQ-A< zSFhafNBvxP-dieJG5fcz7y0|x&c9Hfy{OHscs+#WuLXDfQ1SIy;CfHj`u1zk*RNBU zqIe%Q&bdb>`_Y#=Z^dcq$B>UZes|WoTi*Gc@kdpsMu082mgd%Tx=5~4GfVsM?tAXN zfkQEUJUmKMMnO+kT$v{2942UH8Wes=k;Y? z>E;7-M60O+pK%tA>CBl^ax0t3*x$}Rf1P<#yhcdDzXYB8;zQKvIR3)O5W(%3RqE}v z=R`Y>#N=5MuUTHYsqCMY&`$U;;jcMSEc_0;MJiG}YRvwI#U3wjeCCaQ6*@i@ue_on zcH`5%7|4uRu3cMx+skL%NWrc5R$fH~Z+m-te>veEv$*(`o}M1}M$?Rn2F|8S+Q}HI z;tqQO9^4C(8n05re@5l0U=^x7{Cv$Ol8chp=5l-c7hIytn|~{GICYdie2vfr*Edog zRx*Q1QlZTHy^4FcV7YVf8>ZyUBWJd9l%AW)=9AXIBi&Sz)gVntBlo#m*KpA@{FQAD z)CUKP)H6sZDMNJ1beV*ODIjGAkc3G_PQJLXps{rFYioRIr*6I{UBuyY(^Dq|NRqSP zg(Rx|C9A4DhYWWP2ip4&IgTFU)ilZx<>z9Zf7HLo%iHUyP+C+hcg%mq`&Q&?)X%H9 z+!zqAqaU=!xEW6M$GfAW{%AE=mtf60M&}5& zVvEMa(=xQtQ$G@F7-?duT4MN8(V*Y+aRZR9|CED9_1NTjZV&}|$E|%U4Igm+iCm>| z>3X3M%P<=s3G>d!)>h_ig5RXlR~Zg~`eH7knrbR}vn8Bu^v_0H`-E=KtsC zhO)A8$E>!HXA!1ICnlRdpgxK!YfF#7VBx;?dMjIdBA#uQMEhCz+Rsx ziaGM6;B6h0}kSoxLr0_9ng2`4~`dd(nve)gFa!uFHQ^2d5}g*>wGbq%&4)w$?pN zJ#CYaq|=uGf4rGbJ4zsZ zLUIuMbs3u@GYau;ean{&#S!bA(8w$(cKrH-B3X~QLLSYH<(a|jK2afyIu-j2`O|^n z{{A4XlIQvP`QQANl$4C>opZmrWqGaAEH5uFwBs;7zlvL2TH2w?*Yf=7T!YV`{F5ib zw@(%@GJEE}muRq!l(HDUP-|v zBb5kd2X#%=Yog0p`Ss_2}-C)!nChGhU*CMrXpulV(S9ZNNs}>#oV>bR&2rDs5RqUSL0Vdx!6{^mi_6Q}TQ^}c z3|T(AH<8>Cg$f7=NK8yz2%$1*1JVe;q-Iq^j)jE<_@<2ysOxv^vKmbX8%%>rr}W~w zU;O2-Vk%Wgla2W0!U-s@V_=TXMelHbbi^UOHb#6Rotd?QLSonkh05Ap35{hu49imq z$ta8zADsQZV-`cK=;83im1N{`9;>Kp1D%YFOn5{@d$DFds#t=ex2wwx&RuQReFFmn zmZ13lF{z2ik9cJtXJlc$r7t~iDaH>hxo(!Tc8Ouc4>8o0a-G*@#zR~^HQ9Hs3RD;O z9LVh6Ku~T=%hY0D(ycdB+0S=(%J&UC*_8{)Qv0m=i`bO6n*>Wk(xlTkb zCqo2`ii!8Z>{_9Z4>w|#7K%(44XMxGsjxya@VU2%{^h$D=GGSBRHxpHOpFqP8ms&j z9=M10>^3eD#SHT5jy+K{&$hQ8eae)e0!j^+fS~KGag%Un)O{_j8hiWIPiYuHRo!%3 zy$Tf1(a{k#5IMImPAsb;uYQ=|t1*>o!Yby#{_!#pC*NKwteEnqCnpZ4SK+?nrAElX@m4@|s?h+t(+0qm<`sBJZ3PlX49NnX=jK(H@d7oQ8`XkbHfn#cI}@_T)tT zLEH2k-QnL96v`9c^VasKN+w>cHAf1I&3o*?qkB$$59`n6PrQS`>pdd&rM5!4=5uA+ z@-A!?;C?zAb$K;MgF-+2K<)sCy)8kbSNh?b8`V;L3uWhekdEwER$xHp+NsUkiNn&= z(cVEF-y+HHFkp9zDWNjuX1(X6EHFKUd%{C6tn$7>P*cITYog7>!WkWUyK!1 zNh8!qFyF+gwzujX@_(qq5yby^@b!WlH6|kFLq2wBq`tFPxbb<*%q~iV+-Gs3He=s~ zAAp)s%}GSDyM^oM)T6XF=jrs}C0Lwqi#tZR8!ClFgDVoSZl9dry3tjLLitCxqCGRD zfBO2?7^%$e`_#XuH}x|<3jCJ2SH7iu?d28qyYlkx-OEKH_;log}V}mM|t+#{8Ie#0R&OTDt(ANzKey5W9(fZ_Ug8}#0P2TnH zB_gAt_C9GU>X}VBmU!i><|cCT;yo~*2R^+IIox`i7?lbve;49GW@c}*jTHzEPAvW< zu}}lSxr9Vc!ZR#v4;N`YDpg!`XHHeg>57D;M!sk`>9E;dT>@m2K?@vxSEndmV33e zhAcCaho86RS)V33shy?k=#?|kssmciyL1J05CFkOXds);(JINR@%0Q8x@agiDYKJJ zmGEwyHuE_nJWEOq*%BQ8C$S55XhI&$L{J@}rZ zTMo3vMc1QiHkX-L>nPAM!3iS4Oyg49dd`vOb2^HOt26# z+%&1|F9b{PA&ne3Z{9abl~7wb8_o=$!oHY)Td2k5D8)#2-Gpw;9^mf&cj8``1uY^1 zYce(5!!~XVUo&fa4v&YBD@Vd=7HFZf2WUv87@}ITs6-j;4?c1yrB<>L{biefUSbsL z#EzL?my$VQPD&aP43}#s`k(%uAPm%IGvOKI`gFQOccK2_5AfnxZF|;KES2 z@X6uS&Kf-<86T0oiy+>|Dp4l#Fy6I=KZAk<8>^U*+uxv91wQ5t9aUFXX!z7EFb>U& z^5r5+DVL?3%Pb-l6sEgPQIH#s^E|qQf;3oo*p_JXIgZ~1g@Sz7auI83)_hTBO1+E5 z3ul39+eKI|qf({bIYh&|huinU>okBfHTbA7Jsp|-SH0V?PIvvH z!W?bLv!+Q^NJ(CP%v+$r?hfzS-q9gV?cdMw{`IePp4ZjAk_q!D+J7h7`+YV775F-M zIzP6K{*C$y1QxSL9CD&m+{)Ls@5#w)>wzzNnWy&aeI~Kl60alASi$A(`any_>A|t5 z``yE;>wSF>lRut`+5Q@A#ht>&04($@GEpLXE!$B%gbV?iqh8}FT}%3lz@u;a`>9go zz!}rSg1+Q%WEBl9`rad>zWJh+7X!txvl~pT3Z?(i6RN+nF||zYnEE4REjI$ zUb{IT2&2Aq*X5&P!j%JR!>f9@b>9R+r4H&tq*ty)*4NiJF-*4x1R#+m*1R)R#Ij3$ zVd2a4uwPi@W$X>bLamTV!(p>Oe-b7#;;Z&;I)zCLM!_D ztIJZ-XmRUdJ5eW`Iq9G28LXr7V%b`-z-c`1`nFX+m{4(Ac=`+n z1s6ci5`N=ixwsmVFwzsb)^v9*8mDEZJ36bBud-wQOT|ogRt*B$y+3&+WuUGN$q)|~(SLhuQ zxcar67^Bb&1YIn1u^S%bwS@(|QUdq}WG(5)uepF$I8#pmKrg%A3=R$^At!&PnXib3 z(&nPHu&{t){7;{(Ca*&qZEbC&`13eaIYZy^rMJTplRdSzXo**0cvE`V{qe(iQ-e!j zOE|pN`SQ7#sAh(_OIn-`kAXL3{-d$~f0I*`A0itB56^}2wZ-#t!<$rJ4Sk0EzI>sp zPmDhMq7t>fi;kM`{w_W@;fu$WyIKV+ntHRI9NWX(6po-kMAl3cjS%+aaLNwRrx)E*1ddLNQ=*+|KW+xERMVL?sDq@qNn=Hm~3J;8KMqhQ5jB{KR5e4Ou@z9`!b@Tt@Zv{lxxG@Z_~DMX3B zXccK#%q#rjc+c1S>v?nu6Uxjt6$d!z7t}Q1u4C8z9H*Ltw>Hal@Lw5K5}vMU-T26@ z<6L{Ay^WXX#9xa8vWxHC{#h1bit_Tu?aPX4I+S&5_2#ZD;z=rP{bRl-V(Cl6hU!|R z#+gucTbQ52yVxn0kBNiYTX27LYUx^?06(_cB?{f)QyC!5*a{DN_LjVb4Gj<2F( z!;5QSs3M23F+=u%K)G-Qgq{6x+ZhW5v^{KQ8ZA@btm3CiC~~2t&V+9)$8MHe>bWdE zT#Ee2ebBRdp9FKEWZ}qj7mavZy-(h$!h?=dL4FKRpn>$hce8iK&$?ps8f#Wc9Z$de zx8Bb4$obHcH;c{?U+xLrHkFefi-cftNp0dMQ)uFhR`Y(RfKeX@_MJ=^tDiOIc29q&?Ai?wD=7(G79 z(I42z+x8LG|G_1Vzir{la+yt3{cS-W9``YOt_o}0Odl$fZct#y+=*cO`#(H0_8fV( zuwIL`>!{U*SV9bVABG*ZgJatB$ul_aij^iNJSj)TbwtKp(Q>ipu4*kD8|!BO%RR`( znj&bf089$(kESkFQb}_gi{a&&a6xnPmwfU1L!^SP`Aw5~>ex>mc=ZMuW@c14Rh=x% zc{V*GS{53*#dX+<-+OX#TqC(FSDrT}ue$0AVDnDK=KbL6BP`JW^@t>jz=~N(8A468 zh^R%emT0y#8@VA<{Nl1_d&vh3`I^T z#DziaBv23gBqyVca&FcGoA~#{QpDAmIA&)1GFal=(#8h>9WBo;XNS&^VL=6UUs;r$ zy01WAY6Y#T6EJmr)dLlRkDE4}M-6txKU7#hK-PmwotVbJ7QEmjCyl=nK-#6zP1GG- z`)5H1DZ4xiA?*qEAd#ShN65e>r_oVP}$EAv3YKrd7 z@)@-mF@tz0TOb$|PECBWX$;UN!LyI~xjyS`e}?S}fioET&iB{134>1@21(XFdy-7>(p9vDt6ETBuI;h##Vyih8D}3bP z={@=CQvbjp0*EU9Q_^o!B&3?N;aUIc1)yBxzi~tH;X@9PW$k+T`upDodLGeD0XLU> z|Mgvvy(kGOs1B~wMjt2-@W}zq`6g`w-{#c-;LJ_sG1DmZj2lc`TzF-=<+3nLGOuxD zcsTAgHnxuTTpL@%5ZNG^XV}1jp|zg$MQpxDr_wjrGJKi#>~ zTk)V8+sn&K%=d(!fRGRk1@ALR z!wItj{TIuvTW22pTAUr7oust1hFdqM>ztJI^{J5+0E+kt2#)YC^8{*@H2;U-*4Hoo zMG}2=#^2-tn+e@@eHBMek1TZ@l-kC$=JG?Z4-vBK4gLQ!&unF7HRe5(#I3gv!W`)m zZ3_^hAxv$h=YfUq&ym>-Pf#0~TU!@;f!1Q9hVGYEiy2$n@+vQ|+vrIsgrXR_`yX*Z zdE7dE#>Q--PNYgdI!PTf zaKK!(p&;OY+k+mpFKc=)J|FDnn@Y`XQIG`2^;?0qXKX4vE$tHM%?yTJJUl{kbD69J z(`-yrc#Y%Y19D=h|xQMu9ik=xgm4MN|T8p@LDHi(H@_gMbzx zPT>o(puVA@o|y)>`1tsswl?YQsan<1elb>7P{Hh+4Gazv0wQHM2{?Dh#Kdg-`jt}1 z=2IKs>MkzXPGzfOsiNrL8|xydHsE~puh2%)fnc=OhD@g3&G+2}HxTeiNtCc3W_FXG z@?9>wqelt_t^Gxx!V%ePF|2%(=X&d;e;%q(6SrH45u`y+2DkJ7`hD)GlV4~cl7z?6 zuPasegj6*7asi|OR3D^>LbO7ltOli&@Zyow8R3+VWYYCtNv9@MX7mkk0XFiTdjg61 zyHZe~>-)%TH&F!WCr}9)t6G``b1GJWxe@hMH6+;(7Nw}F%`G@O(<53ImL4*BfOjvz zK?dAh?&(8c;J2kn)U}vhv_iGn-ow7ym+9~rgt3nN4R*Yg;3`Q|C{Tg!VF{W>nju4X zFH^7X@gR#@poEMNX^8J}rD~%P0;>aYnchX#pGwgJt#4K^z%DXCx(%nB%MwZ2RooN% z05XJs9T|$M1wdBdmUs92c+t${T2b^A$P`8-$efwv&L1?-4C4zTUMoHZ947DA{^;h% z-)11^Tl~Fup-`w5m}V5(Pwe%*Kj+9I3~Gtq;r;4voGN-wgY@0ea@PPf!kLjiKGHpL z)WUtEZrCpS8VrD|Y8bAne>-PT+cJeGLsGe8bp^GM_L=D4;@&}?gjKRxUYk+x621VL zIgp5*ebSLi6G0$nN~cEFH0NzYpw6_a0yr)B7W&q>= z>F@b1)YW&fI0V*bZV=;x3-&k2$Ss*@Z1JNfzh^))sglT3;6XC=0giW@RUZ46%x=U~}44%EDm5`8V zzWB2h9vj;`lCLyUa-Ur-Q=F2Qm)G;xS3Q@_F;3&&$;KqBvbv)8fY&TJ8w$ldcG#|w zEsRM2g96OxInRi?C4G*%AlDZF^*18A9!wX(#l!26BbIy)O5P3To^O0o3N5v+*P#?xyI+iTZcuYumNPUPXtE_X$9FsK(nR*2|(PqwRv zKbISd4xZZB*npOm)W~Q;JLscejrl^na0Tm58uBm$b^6=Abd zQ&Ue(C)!Zwe||ait^88;3i5WaAXL5v5L;ECF1-NT!;bbvFa(&bk(}G2BO@aKDb%CS z9lpJ3M|9_VTje08_@BX%_jaTE7Nl-93_U&9>xCS=K6pDkx^6=ut^-W%e0jtv_PaX( zeGF1z=B9qXD8--d8d(O6zcsG1|61y8>;P(YD9m<%oZtC-*CmjW(}UUeef-$Cbra-+ z%J=S#--1e;?w+7<#&^RN{>N|{#QE38g*?}*TlY765&-T8Ltg6r4G?w`6RJ2u zZoaU+ZDiTn(jp0>;Vt*_{}mH7YTyuBdgmIM*v*@E3^6g+>+K3OB>RmW_SBl;;3ERa zv1Y+-u>>>}NUhKWOD=?KVHy1&YPc?~u7-k0{_8}*`sZBl=r}D73NimPF#t?XIMuil7U;BV83s!m<*L$*59jp4BDqWXotpj# zF;K1tgWY!wPm(1tZgRL0?tcr#=9-2O%cQe9U@SPBzOb_7}q#zlr((PnC8((Ut~_nVLBd_eXQ7!h&F*VlJfd~I-cVscW3ErCf$=)OaM zjSbtYSDQDs$^n6S|6C6mNZ}j*^C3Q7vt{WEr}|)YuBU)-N7iAQ#wF>vCc;1N@BT%N zyi@zJDXrDj)%CAsRA8mwaxi6iQ&>1SlpW^il2k%KkX=5N=A+4#1PvH!<@B!%F(s89 zQD&8<_`5ag^72o@u)ofr+CUt>567K_l~rL>Sm!Zlm0{tvwJF%tQUezj%$`4gZU+4s z9GsjoR#w+gW`Mq&-Q5*HCxJ4%IQ#9n5QMV;O%P{izC1?tBw+ZRFxdR(JmKrsTaS~u zILhQ7Y4M`Wob}!^LX<9-gO!h_6}5n25cde3BLbfLW{BPglmwpJRlS?;<)Cf2Dj`9O z^dNw$2^|BY%3+*WoqZ`WkwUq!jyu(Ht~mlp4p>+)Zrr$mIJ(8ou*J2t@whzUmd$ip zyREW@Nf706C7~E`+(0)1Jrck}ipS#yUmZIx0F8GusB=AXlHP-PqrsW`97R5 zzsk()rq6uB2>lJv%p)r!gT{0Hdd5X)MivIydki@$G^YFM7yi-OCE85?sSqDb6?vYy zh|}Lc^;s-u5@COQ4I(Hng5@*@)j>8NN=S<>J?m5i$|&+_t^QGsP;+(TKC_^7_&VI@1e zlHI&fcD=u(62YfjP%s_t)j~r-MV&*3V-W%@N{>69g@u{9vx3Xo%@M1{=1?oHT;|QmPUA} zg$)wX=Fj8!6$C#?5nz$`r|hZgeI1>Uzvp`au=S`M6Z}n(e0}pW^UnRDOP)D~;(s_P z*FHSY(FHO9s66b;4}WXvV~3OU_}8wRrI}eEB7?v0CU=}upEN_A9!aZ2|L9E5*g&?PBRd0n zU6>(<1=bjBC^#+c2-67~SHv&9{aVIz-^tK(a4<$_1GpDipv8V3y!($8DcWhc9{wD? zhk-Kj`=v8@J3<~^h6Dmcjy9J&xv_)ZqTyplx~UrOek6{ zTDhT$2umP0VM;>fKbyRU90~v$v`?QtHSW#LE_HBRzFxO#Gg@wtftE?XU}eqYDh$3d zdOT-&^>nYbb?p>#SSJmB@7E!ss!4iWRV!WPFa~~Wczyx2Uryp!u&|E0_yDLGkZA!q;T1gp!&UWiB^MU~kmDUJJR`Q~ z3?*!gsrqH#E+Xp1isyT9Y>kp0Gc%>`GwAKeg2^1`SDOkmGBWx?SC5Fj`&7+kBez4Z zPplM4dhOug!G0)_6UUr6vLQ82NI>8WO$Ej#@7x`76HZvy5*K<|#-T7ww1rL+763F3 zgB@(f&UBqa%VkWojg1XNI`{eYJ;qGTA}WLRYyKIruk%z`bDY?HejcnA_`$4A!5*Wo zh^TDULYoytK{!=`-7l!Nn0e3RTy$F=n~R7x4Ix^g_5M{3h?-Kc3EEsqZW+S2I8>AiFvd>pjScg}D=k;mgYC_HL>^-NK<1>%z@a4gUX z^c)QX8-;=lzS6uC`_WwUgV$*94I&{S_=Jk^Ojw3oy?_4vh3bQDAgV1C>xp|@o}pOrRnm7faV^$js6tq92O#sJ`@fmR2--}be7zRlL z%fA4aUA8Bx6mfw1&nT4NRcrp6C*H6z-fuD{hP;|8ETi3M5tE2WZO=tbjj&qAjdEm7 zR1VhaId!xb*OP9cqytQc6xAR{Ln71R+Az`vC~|bXK7s*2)CqPrm&de4H+v0NA&7OKvLtDtxh&o9_wV1HYne~^AAY`?i8WrDv|3unc4N@nP=wAlip8lYdSZZCkO_zJA#@k`K z>VZ?!`557+3F->3>nV3Y=VV;nBP}m~tq340G=z!y{So&6v%&sK+#QdNjSZ@YT|oDl zKkQ2?LPIHRVsV+TjXtWQz(kv;t4Bv6eH!5Y2o9fyrjcgg0X?_#P@|dxm712e2>MA- zt+9Nrb20^P&k{ODCqDT3v{ckubmMt?dbUkWQ0M06f^xKM)C+2Bh?*A^d7FO2YAs>wX zDNt8I(4FmfL_ktOC^Vi?v z{~<6%Y$S$0eR`gda6ft};pNM?SFfT-Sri{0l|UqxAs~oLO~vKo<12Tw8P1A^3hLk% zw4N5-b58f#2$5X-nmvz^wPR-YDfi^xGdUW7Klyy?_~@u71fMD{B?X6;Hp5%}kS}F` zl!D?pVCg|;$Nzw8wd~t1JqMW*PkW$qyXI&xO$Y~?RiT0TLHV5*|EU~r(k0wYoKJ6< z>}q`ASa;go$a1joR)ky@sfH`sRyR#f-FQS;_brF!h?Mzp`5Tpjq=!XB-85J$VxaFtk&GlEo=F~<#v)B)x6!Uat*xao^5YQCiH_;q z-^pIkWpCdPbW+IXh?cs(TYj;YIkXpn5O(fWJ!JdESKvmC2c>gYZp?0XAq{1(>(OJ9 z6~oN}kRC=tsMF=XGinie)HtF#Pk$FX$HcK+qo*Psxif$=L-uR=a!*^q9o_(@d=hr8 zA5cYD*od>yzZ(n}P=eTrLbzXj^ixzbLh+fI*(!{f4mv=eIp^olUGBkN-y5K80}Hu$(I5S`B=IQ$R^8a| zm4Fv|qIU>)RtYTS(*FDHBelWW(j_P>#lXU{yL`r)g-(kqy4oAg-e(~YkZ$;%rMQ!HsuU-d6zHJ zioGOv%=ticoN1g5%uP0b!$>XHESo4-QchasUPRW;ftJ+06ERnM*s{DPbJu)_k9fay zmHG#v7HvhT)7H_48i_rXb8K@&cJ8>tRK@tdZfPBM;~mF~TPbKL>d)OlNb;&lzdx=u zUiw7OjRiNDtzq$dDqQZ~xvV%YOdmgfLjdPke~ab&GY$w ztxc+-q)izmQfaDu00nCrBNN?RE{cC)Q>u-AA(n3t_kk@;uGwbO@>4lfi;sZgJ z`NX}d>pA~ZdMv2MkKI5XS=!^yarYx{&$k4-IORY@HXamZiV%l>`ziOwuT1i|?`sjE za$-~H(P!VdQy^2FhBE$mV)T{s_plSOp;+oCaIkb74gfO8x006W*z!P$oyK|1~zLWW(ft0yDqU+^tt)F6hwl4gmubnzQXC=e~Wr zy!YXh7XqQnpPzQnv&0MqKETJeLQ4Kr=|tV`rvACmlXtUwv!E{lzA8YfnGYpPo(JzR z?-!{d{KMJV84~eR)6*@0q5%F@OF|tNd^9lnV6V3&EmsF2iyyBBOLlIzIm(t zH#$qb6iu$ZETiB*8*%{UNmo}_$oCf4*NbcCM@FEtoCfGH;G=+yi5pm9TVw2R~n4^pbTcEVQxnDctbYM+H=X3|>;(g+F( z)gIZ}*mU&wZ@gW*Q>f+XLflcE)Y5ne(ue;_Ei0=zK>3-0*1b^b<e1p96Vd%jQvgBPgPIcR&P=_{gk4!$=`iC?W{rW&-q|x+hRjeFB>43q6}?qxLM1 z&RV=yoei6ftpzd(INPdUtI5Y*(4_{0(~LZV;Gwqg(3dtI-y45r!?sUUJpcj((bueN zNv0Q4=x{-A3A)*T=72oPTUfPlIZ3*N3@S$`9Q=GXA|yB40Z*q2yL3v7(fUe9QmBgN znmhFaSj~S?Aw_sl>-e6d#SbpM;4vHcwndOhM)0*T&lYd{+LZ`eCt4mF9}yCS0fb&4tt zQ>oI9ZLvtW9zWSCRdr9zO+5!?UwHauWbC_+*Ph;U6w^XjDLYty)*$#c1|%lPwB1V@ z`puD@(Y%i==y6E{DJI&uYgvkk8S=5BF$!d7fmAw*6{)U7w@y+4?+^K@T~8YfKScy{ zyjDCm-841>y>-~A`pMKbjhhEL0M&jKaDHT1tjz_vkg(fw?aA=Nl4Zkc(!JKyXe|ZGFF&ALA8oj7f?zwPnhQqEv(sN@OThirPYm3~ee&Tjq*NJ1IkDo01`O zAtHI!Rp-5*_wzo_{k+fp$9+EMbPjv}e%JNA)_1M-UDuJr%+Gg~9#2=&@CxhI=I(FZ z@?Tpnbz7&#JA02EKVG-};*dFNVPqul@8;wj*D0;pFP?im)-X0D+lA*J;-pa4`Tv0j zE9w%`#X4hmX;xH4iQhZYyb+B-li!a2JX{Jh_Rjx}|XJ}EeuohhTDVzKgGQ?>!q zO3`1JHzb~YQdv3V*Glq`011R1dk^XyJ2s**?D+TJ z9LC1Rbi>#myO<_l=(L2*$8Ql}^-iRqmY<@hukVPY8g2mr1I&{Wu^mdWME`C`2?N!)VBM34=KsiWh|(u8OLQ7?yfoIlA}sBIVL~BN3saYfSL2Ls z`k#i_q^`E+I#3xNE0A!Nbn2TCz5i8$svr*AK+kh_v3-`xSIp17ec4>F5zz}MiI_>p zK3ce1jg+0O|FlNYU*6qrnra0$cwmQ4S~WnMe+6sEa}PBo&|T`P6w(?nut<)X^;vpP zDFndV! z9I|`;(P9~A(!(`J%;5qje+(d#QjY8* zMTc??ryQlFrPnqb64{xwCCe)bL(d5JsmpiS1wO0d?E45lYlpGQOg{SI-T)1n>F44|GAn!LGZM^{M;uEx3 zBD~){{9%3ZR9_Cwi6pdP({cyj{4fzTI}xM?!Y5i{^EpWkzk?FZdG=a#C%3fSStE6Z zOFNL*WdI7H=){-cU7A1i_B_4vI$^*lM6*4_R2n1y5d=R&1ZZJ~ zbPn7Ma8vO?e2Mz;oVuj(_xWUyoyAa6`Wr`orkO>B~b-x$h+IS zAa$|FXilwKYg=^7i-nXXqH;+gDUX~Exub}>>Hrq3g1jI3PxhNN!=p=MxP^MJ+aTW6 zW+5wlsgkf+0nBVayMKKcn9$JDO0{d@kTs!&;A!rk{Wa6uR;ESqimeCl&xQ#FqE+IB zDk?q-U=rPZh{d2a9XfP~=9H6XHh8aE@(J`c1Sqnx!Jjx{>$Dff2WKWN6dys1GV%f+Ofu-_*T;4)KfZNc+v2-^nT!k@1>)y3RA#e%`}P$IrZ044 zMy+i@#9ZUS)$_eq$u4vCg3;gt)M;Y%pqz$a+*y8R4*u(J@B?CblUAa^%0A`RtIF)LKG$Ne!1ofED84= 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 literal 32154 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 Tzo#DOG8GcnkI>2-`~NoqoI2&o diff --git a/baselines/fedpara/pyproject.toml b/baselines/fedpara/pyproject.toml index e07a100940f3..5e14f722fc05 100644 --- a/baselines/fedpara/pyproject.toml +++ b/baselines/fedpara/pyproject.toml @@ -40,6 +40,10 @@ classifiers = [ python = ">=3.8.15, <3.12.0" # don't change this flwr = { extras = ["simulation"], version = "1.5.0" } hydra-core = "1.3.2" # don't change this +matplotlib = "^3.7.2" +tqdm = "^4.66.1" +torch = {url = "https://download.pytorch.org/whl/cu117/torch-2.0.1%2Bcu117-cp310-cp310-linux_x86_64.whl"} +torchvision = { url = "https://download.pytorch.org/whl/cu117/torchvision-0.15.2%2Bcu117-cp310-cp310-linux_x86_64.whl"} [tool.poetry.dev-dependencies] isort = "==5.11.5" From 040ee11c2038dd2790a1869777c99266574c4fb5 Mon Sep 17 00:00:00 2001 From: Yahia Salaheldin Shaaban <62369984+yehias21@users.noreply.github.com> Date: Thu, 28 Dec 2023 05:05:19 +0200 Subject: [PATCH 17/39] Update README.md --- baselines/fedpara/README.md | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 2f746909dd21..1d8574063903 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -141,19 +141,10 @@ poetry run python -m fedpara.main --config-name cifar100 --multirun model.conv_t | Parameters ratio | Cifar10 | Cifar100 | |----------|--------|--------| | Original | 15.25M | 15.30M | -| 0.1 | 1.55M | 1.59M | -| 0.2 | 2.33M | 2.38M | -| 0.3 | 3.31M | 3.36M | -| 0.4 | 4.45M | 4.50M | -| 0.5 | 5.79M | 5.84M | -| 0.6 | 7.33M | 7.38M | -| 0.7 | 9.01M | 9.05M | -| 0.8 | 10.90M | 10.94M | -| 0.9 | 12.92M | 12.96M | - -** We are using parameter ratio of 0.1 for Cifar10 and 0.4 for Cifar100 -Parameters from +| 0.1 | 1.55M | - | +| 0.4 | - | 4.53M | +** We are using a parameter ratio of 0.1 for Cifar10 and 0.4 for Cifar100 The rank is calculated by the equation provided in paper R = r_max* lamda + (1-lambda)*r_min ### Cifar100 (Accuracy vs Communication Cost) From daff4711b807f0ab66da3d889e2e0a61e35ea52b Mon Sep 17 00:00:00 2001 From: Yahia Salaheldin Shaaban <62369984+yehias21@users.noreply.github.com> Date: Thu, 28 Dec 2023 05:05:59 +0200 Subject: [PATCH 18/39] Update README.md --- baselines/fedpara/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 1d8574063903..c450d8c95460 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -145,7 +145,6 @@ poetry run python -m fedpara.main --config-name cifar100 --multirun model.conv_t | 0.4 | - | 4.53M | ** We are using a parameter ratio of 0.1 for Cifar10 and 0.4 for Cifar100 -The rank is calculated by the equation provided in paper R = r_max* lamda + (1-lambda)*r_min ### Cifar100 (Accuracy vs Communication Cost) From 8c3c5af847848d18842ed39cfc01414b133167fd Mon Sep 17 00:00:00 2001 From: jafermarq Date: Sun, 31 Dec 2023 08:20:53 +0000 Subject: [PATCH 19/39] formatting and fixing mypy (one issue remains) --- baselines/fedpara/README.md | 22 +++++------ baselines/fedpara/fedpara/client.py | 7 ++-- baselines/fedpara/fedpara/dataset.py | 6 +-- .../fedpara/fedpara/dataset_preparation.py | 9 ++--- baselines/fedpara/fedpara/main.py | 13 +++++-- baselines/fedpara/fedpara/models.py | 37 ++++++++++++------- baselines/fedpara/fedpara/strategy.py | 1 - baselines/fedpara/fedpara/utils.py | 23 +++++++----- 8 files changed, 66 insertions(+), 52 deletions(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index c450d8c95460..f6edde270d96 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -1,5 +1,5 @@ --- -title: FedPara Low-rank Hadamard Product for Communication-Efficient Federated Learning +title: "FedPara: Low-rank Hadamard Product for Communication-Efficient Federated Learning" url: https://openreview.net/forum?id=d71n4ftoCBy labels: [image classification, personalization, low-rank training, tensor decomposition] dataset: [CIFAR10, CIFAR100, FEMNIST] @@ -11,9 +11,9 @@ dataset: [CIFAR10, CIFAR100, FEMNIST] **Paper:** [openreview.net/forum?id=d71n4ftoCBy](https://openreview.net/forum?id=d71n4ftoCBy) -****Authors:**** Nam Hyeon-Woo, Moon Ye-Bin, Tae-Hyun Oh +**Authors:** Nam Hyeon-Woo, Moon Ye-Bin, Tae-Hyun Oh -****Abstract:**** In this work, we propose a communication-efficient parameterization, FedPara, +**Abstract:** In this work, we propose a communication-efficient parameterization, FedPara, for federated learning (FL) to overcome the burdens on frequent model uploads and downloads. Our method re-parameterizes weight parameters of layers using low-rank weights followed by the Hadamard product. Compared to the conventional low-rank parameterization, our FedPara method is not restricted to lowrank constraints, and thereby it has a far larger capacity. This property enables to @@ -27,29 +27,29 @@ page: https://github.com/South-hw/FedPara_ICLR22 ## About this baseline -****What’s implemented:**** The code in this directory replicates the experiments in FedPara paper implementing the Low-rank scheme for Convolution module. +**What’s implemented:** The code in this directory replicates the experiments in FedPara paper implementing the Low-rank scheme for Convolution module. Specifically, it replicates the results for Cifar10 and Cifar100 in Figure 3 and the results for Feminist in Figure 5(a). -****Datasets:**** CIFAR10, CIFAR100, FEMNIST from PyTorch's Torchvision +**Datasets:** CIFAR10, CIFAR100, FEMNIST from PyTorch's Torchvision -****Hardware Setup:**** The experiment has been conducted on our server with the following specs: +**Hardware Setup:** The experiment has been conducted on our server with the following specs: - **GPU:** 1 RTX A6000 GPU 50GB VRAM - **CPU:** 1x24 cores Intel Xeon(R) 6248R - **RAM:** 150 GB -****Contributors:**** Yahia Salaheldin Shaaban, Omar Mokhtar and Roeia Amr +**Contributors:** Yahia Salaheldin Shaaban, Omar Mokhtar and Roeia Amr ## Experimental Setup -****Task:**** Image classification +**Task:** Image classification -****Model:**** This directory implements Vgg16 with group normalization. +**Model:** This directory implements Vgg16 with group normalization. -****Dataset:**** +**Dataset:** In IID settings: @@ -66,7 +66,7 @@ In non-IID settings: | Cifar100 | 100 | 50 | Dirichlet distribution | -****Training Hyperparameters:**** +**Training Hyperparameters:** | | Cifar10 IID | Cifar10 Non-IID | Cifar100 IID | Cifar100 Non-IID | FEMNIST | |---|-------|-------|------|-------|----------| diff --git a/baselines/fedpara/fedpara/client.py b/baselines/fedpara/fedpara/client.py index 21e62eeaed08..18e09a6fae65 100644 --- a/baselines/fedpara/fedpara/client.py +++ b/baselines/fedpara/fedpara/client.py @@ -53,7 +53,7 @@ def fit( self.device, epochs=self.num_epochs, hyperparams=config, - round=config["curr_round"], + round=int(config["curr_round"]), ) return ( @@ -73,11 +73,10 @@ def gen_client_fn( def client_fn(cid: str) -> FlowerClient: """Create a new FlowerClient for a given cid.""" - cid = int(cid) return FlowerClient( - cid=cid, + cid=int(cid), net=instantiate(model).to(args["device"]), - train_loader=train_loaders[cid], + train_loader=train_loaders[int(cid)], device=args["device"], num_epochs=num_epochs, ) diff --git a/baselines/fedpara/fedpara/dataset.py b/baselines/fedpara/fedpara/dataset.py index 1f951ad27018..6d207aec9524 100644 --- a/baselines/fedpara/fedpara/dataset.py +++ b/baselines/fedpara/fedpara/dataset.py @@ -6,11 +6,11 @@ from torch.utils.data import DataLoader from torchvision import datasets, transforms -from fedpara.dataset_preparation import iid, noniid, DatasetSplit +from fedpara.dataset_preparation import DatasetSplit, iid, noniid def load_datasets( - config, num_clients, batch_size + config, num_clients, batch_size ) -> Tuple[List[DataLoader], DataLoader]: """Load the dataset and return the dataloaders for the clients and the server.""" print("Loading data...") @@ -61,4 +61,4 @@ def load_datasets( for ids in train_datasets.values() ] - return train_loaders, test_loader \ No newline at end of file + return train_loaders, test_loader diff --git a/baselines/fedpara/fedpara/dataset_preparation.py b/baselines/fedpara/fedpara/dataset_preparation.py index d957b1049531..d4b2cd1ccbbc 100644 --- a/baselines/fedpara/fedpara/dataset_preparation.py +++ b/baselines/fedpara/fedpara/dataset_preparation.py @@ -32,8 +32,7 @@ def __getitem__(self, item): def iid(dataset, num_users): - """ - Sample I.I.D. clients data from a dataset. + """Sample I.I.D. clients data from a dataset. Args: dataset: dataset object @@ -50,8 +49,7 @@ def iid(dataset, num_users): def noniid(dataset, no_participants, alpha=0.5): - """ - Sample non-I.I.D client data from dataset. + """Sample non-I.I.D client data from dataset. Args: dataset: dataset object @@ -89,7 +87,7 @@ def noniid(dataset, no_participants, alpha=0.5): datasize[user, n] = no_imgs sampled_list = cifar_classes[n][: min(len(cifar_classes[n]), no_imgs)] per_participant_list[user].extend(sampled_list) - cifar_classes[n] = cifar_classes[n][min(len(cifar_classes[n]), no_imgs):] + cifar_classes[n] = cifar_classes[n][min(len(cifar_classes[n]), no_imgs) :] train_img_size = np.zeros(no_participants) for i in range(no_participants): train_img_size[i] = sum([datasize[i, j] for j in range(no_classes)]) @@ -98,4 +96,3 @@ def noniid(dataset, no_participants, alpha=0.5): for j in range(no_classes): clas_weight[i, j] = float(datasize[i, j]) / float((train_img_size[i])) return per_participant_list, clas_weight - diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index 76605b1f962a..326c65507cc1 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -1,5 +1,6 @@ """Main script for running FedPara.""" import logging + import flwr as fl import hydra from hydra.core.hydra_config import HydraConfig @@ -50,7 +51,9 @@ def main(cfg: DictConfig) -> None: def get_on_fit_config(): def fit_config_fn(server_round: int): - fit_config = OmegaConf.to_container(cfg.hyperparams, resolve=True) + fit_config = OmegaConf.to_container( # type: ignore + cfg.hyperparams, resolve=True + ) fit_config["curr_round"] = server_round return fit_config @@ -96,9 +99,13 @@ def fit_config_fn(server_round: int): f"{cfg.clients_per_round}", ] ) - + utils.plot_metric_from_history( - hist=history, save_plot_path=save_path, suffix=file_suffix, cfg=cfg, model_size=net_glob.model_size()[1] + hist=history, + save_plot_path=save_path, + suffix=file_suffix, + cfg=cfg, + model_size=net_glob.model_size()[1], ) diff --git a/baselines/fedpara/fedpara/models.py b/baselines/fedpara/fedpara/models.py index 8d6c0f0b3a02..407409909811 100644 --- a/baselines/fedpara/fedpara/models.py +++ b/baselines/fedpara/fedpara/models.py @@ -84,27 +84,32 @@ def __init__( def _calc_from_ratio(self): # Return the low-rank of sub-matrices given the compression ratio - + # minimum possible parameter r1 = int(np.ceil(np.sqrt(self.out_channels))) r2 = int(np.ceil(np.sqrt(self.in_channels))) r = np.min((r1, r2)) - - # maximum possible rank, + + # maximum possible rank, """ To solve it we need to know the roots of quadratic equation: ax^2+bx+c=0 a = kernel**2 b = out channel+ in channel c = - num_target_params/2 - r3 is floored because we cannot take the ceil as it results a bigger number of parameters than the original problem + r3 is floored because we cannot take the ceil as it results a bigger number + of parameters than the original problem """ num_target_params = ( self.out_channels * self.in_channels * (self.kernel_size**2) ) - a, b, c = self.kernel_size**2, self.out_channels + self.in_channels,- num_target_params/2 + a, b, c = ( + self.kernel_size**2, + self.out_channels + self.in_channels, + -num_target_params / 2, + ) discriminant = b**2 - 4 * a * c - r3 = math.floor((-b+math.sqrt(discriminant))/(2*a)) - ratio=math.ceil((1-self.ratio)*r+ self.ratio*r3) + r3 = math.floor((-b + math.sqrt(discriminant)) / (2 * a)) + ratio = math.ceil((1 - self.ratio) * r + self.ratio * r3) return ratio def forward(self, x): @@ -225,14 +230,16 @@ def _make_layers(self, cfg, group_norm=True): layers += [conv2d, self.activation] in_channels = v return nn.Sequential(*layers) - + @property def model_size(self): + """Return the total number of trainable parameters (in million paramaters) and. + + the size of the model in MB. """ - Return the total number of trainable parameters (in million paramaters) and the size of the model in MB. - """ - total_trainable_params = sum( - p.numel() for p in self.parameters() if p.requires_grad)/1e6 + total_trainable_params = ( + sum(p.numel() for p in self.parameters() if p.requires_grad) / 1e6 + ) param_size = 0 for param in self.parameters(): param_size += param.nelement() * param.element_size() @@ -313,7 +320,9 @@ def train( # pylint: disable=too-many-arguments hyperparams : Dict[str, Scalar] The hyperparameters to use for training. """ - lr = hyperparams["eta_l"] * hyperparams["learning_decay"] ** (round - 1) + lr = float(hyperparams["eta_l"]) * float(hyperparams["learning_decay"]) ** ( + round - 1 + ) print(f"Learning rate: {lr}") criterion = torch.nn.CrossEntropyLoss() optimizer = torch.optim.SGD( @@ -377,4 +386,4 @@ def _train_one_epoch( # pylint: disable=too-many-arguments if __name__ == "__main__": model = VGG(num_classes=10, num_groups=2, conv_type="standard", ratio=0.4) # Print the modified VGG16GN model architecture - print(model.model_size) \ No newline at end of file + print(model.model_size) diff --git a/baselines/fedpara/fedpara/strategy.py b/baselines/fedpara/fedpara/strategy.py index 6122c86539ab..0a7cd788d189 100644 --- a/baselines/fedpara/fedpara/strategy.py +++ b/baselines/fedpara/fedpara/strategy.py @@ -4,7 +4,6 @@ from flwr.server.strategy import FedAvg - class FedPara(FedAvg): """FedPara strategy.""" diff --git a/baselines/fedpara/fedpara/utils.py b/baselines/fedpara/fedpara/utils.py index 8d58740b0dbb..78cdaa0e0f68 100644 --- a/baselines/fedpara/fedpara/utils.py +++ b/baselines/fedpara/fedpara/utils.py @@ -2,7 +2,6 @@ import random from pathlib import Path -from typing import Optional import matplotlib.pyplot as plt import numpy as np @@ -16,9 +15,9 @@ def plot_metric_from_history( hist: History, save_plot_path: str, - suffix: Optional[str] = "", - cfg: Optional[DictConfig] = None, - model_size: float = None, + model_size: float, + cfg: DictConfig, + suffix: str = "", ) -> None: """Plot the metrics from the history of the server. @@ -28,10 +27,12 @@ def plot_metric_from_history( Object containing evaluation for all rounds. save_plot_path : str Folder to save the plot to. - suffix: Optional[str] - Optional string to add at the end of the filename for the plot. - cfg : Optional[DictConfig] + model_size : float + Size of the model. + cfg : Optional Optional dictionary containing the configuration of the experiment. + suffix: Optional + Optional string to add at the end of the filename for the plot. """ metric_type = "centralized" metric_dict = ( @@ -40,12 +41,14 @@ def plot_metric_from_history( else hist.metrics_distributed ) rounds, values_accuracy = zip(*metric_dict["accuracy"]) - rounds*=2*model_size*cfg.clients_per_round/1024 + rounds *= 2 * model_size * cfg.clients_per_round / 1024 _, axs = plt.subplots() # Set the title - axs.set_title( - f"{cfg.strategy.algorithm} | parameters: {cfg.model.conv_type} | {cfg.dataset_config.name} {cfg.dataset_config.partition} | Seed {cfg.seed}" + title = f"{cfg.strategy.algorithm} | parameters: {cfg.model.conv_type} | " + title += ( + f"{cfg.dataset_config.name} {cfg.dataset_config.partition} | Seed {cfg.seed}" ) + axs.set_title(title) axs.plot(np.asarray(rounds), np.asarray(values_accuracy)) axs.set_ylabel("Accuracy") axs.set_xlabel("Rounds") From c20314b66320a841680cf3a7943e54e3521bbacc Mon Sep 17 00:00:00 2001 From: jafermarq Date: Sun, 31 Dec 2023 10:54:08 +0000 Subject: [PATCH 20/39] formatting; passing most tests; other small changes --- baselines/fedpara/fedpara/client.py | 7 ++++--- baselines/fedpara/fedpara/conf/cifar10.yaml | 1 - baselines/fedpara/fedpara/conf/cifar100.yaml | 1 - baselines/fedpara/fedpara/conf/femnist.yaml | 1 - .../fedpara/fedpara/dataset_preparation.py | 1 + baselines/fedpara/fedpara/main.py | 18 ++--------------- baselines/fedpara/fedpara/models.py | 10 ++++++---- baselines/fedpara/fedpara/server.py | 20 +++++++++++++++++-- baselines/fedpara/pyproject.toml | 2 +- 9 files changed, 32 insertions(+), 29 deletions(-) diff --git a/baselines/fedpara/fedpara/client.py b/baselines/fedpara/fedpara/client.py index 18e09a6fae65..50156df1dc48 100644 --- a/baselines/fedpara/fedpara/client.py +++ b/baselines/fedpara/fedpara/client.py @@ -67,17 +67,18 @@ def gen_client_fn( train_loaders: List[DataLoader], model: DictConfig, num_epochs: int, - args: Dict, ) -> Callable[[str], FlowerClient]: """Return a function which creates a new FlowerClient for a given cid.""" def client_fn(cid: str) -> FlowerClient: """Create a new FlowerClient for a given cid.""" + device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + return FlowerClient( cid=int(cid), - net=instantiate(model).to(args["device"]), + net=instantiate(model).to(device), train_loader=train_loaders[int(cid)], - device=args["device"], + device=device, num_epochs=num_epochs, ) diff --git a/baselines/fedpara/fedpara/conf/cifar10.yaml b/baselines/fedpara/fedpara/conf/cifar10.yaml index deeac7797588..8b14a6162f5f 100644 --- a/baselines/fedpara/fedpara/conf/cifar10.yaml +++ b/baselines/fedpara/fedpara/conf/cifar10.yaml @@ -8,7 +8,6 @@ num_epochs: 5 batch_size: 64 server_device: cuda -client_device: cuda client_resources: num_cpus: 2 diff --git a/baselines/fedpara/fedpara/conf/cifar100.yaml b/baselines/fedpara/fedpara/conf/cifar100.yaml index 26a097b6cc94..130688d15f87 100644 --- a/baselines/fedpara/fedpara/conf/cifar100.yaml +++ b/baselines/fedpara/fedpara/conf/cifar100.yaml @@ -8,7 +8,6 @@ num_epochs: 5 batch_size: 64 server_device: cuda -client_device: cuda client_resources: num_cpus: 2 diff --git a/baselines/fedpara/fedpara/conf/femnist.yaml b/baselines/fedpara/fedpara/conf/femnist.yaml index 5502f865cd38..dfd67c0ab935 100644 --- a/baselines/fedpara/fedpara/conf/femnist.yaml +++ b/baselines/fedpara/fedpara/conf/femnist.yaml @@ -8,7 +8,6 @@ num_epochs: 5 batch_size: 10 server_device: cuda -client_device: cuda client_resources: num_cpus: 2 diff --git a/baselines/fedpara/fedpara/dataset_preparation.py b/baselines/fedpara/fedpara/dataset_preparation.py index d4b2cd1ccbbc..ccdbea1edbe8 100644 --- a/baselines/fedpara/fedpara/dataset_preparation.py +++ b/baselines/fedpara/fedpara/dataset_preparation.py @@ -48,6 +48,7 @@ def iid(dataset, num_users): return dict_users +# pylint: disable=too-many-locals def noniid(dataset, no_participants, alpha=0.5): """Sample non-I.I.D client data from dataset. diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index 326c65507cc1..bfdeda8bebd8 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -39,7 +39,6 @@ def main(cfg: DictConfig) -> None: train_loaders=train_loaders, model=cfg.model, num_epochs=cfg.num_epochs, - args={"device": cfg.client_device}, ) evaluate_fn = server.gen_evaluate_fn( @@ -49,23 +48,13 @@ def main(cfg: DictConfig) -> None: device=cfg.server_device, ) - def get_on_fit_config(): - def fit_config_fn(server_round: int): - fit_config = OmegaConf.to_container( # type: ignore - cfg.hyperparams, resolve=True - ) - fit_config["curr_round"] = server_round - return fit_config - - return fit_config_fn - net_glob = instantiate(cfg.model) # 4. Define strategy strategy = instantiate( cfg.strategy, evaluate_fn=evaluate_fn, - on_fit_config_fn=get_on_fit_config(), + on_fit_config_fn=server.get_on_fit_config(dict(cfg.hyperparams)), initial_parameters=fl.common.ndarrays_to_parameters(get_parameters(net_glob)), ) @@ -75,10 +64,7 @@ def fit_config_fn(server_round: int): num_clients=cfg.num_clients, config=fl.server.ServerConfig(num_rounds=cfg.num_rounds), strategy=strategy, - client_resources={ - "num_cpus": cfg.client_resources.num_cpus, - "num_gpus": cfg.client_resources.num_gpus, - }, + client_resources=cfg.client_resources, ray_init_args={ "num_cpus": 40, "num_gpus": 1, diff --git a/baselines/fedpara/fedpara/models.py b/baselines/fedpara/fedpara/models.py index 407409909811..a083ee9aaf3f 100644 --- a/baselines/fedpara/fedpara/models.py +++ b/baselines/fedpara/fedpara/models.py @@ -16,7 +16,7 @@ class LowRank(nn.Module): """Low-rank convolutional layer.""" - def __init__( + def __init__( # pylint: disable=too-many-arguments self, in_channels: int, out_channels: int, @@ -47,10 +47,11 @@ def forward(self): return torch.einsum("xyzw,xo,yi->oizw", self.T, self.X, self.Y) +# pylint: disable=too-many-instance-attributes class Conv2d(nn.Module): """Convolutional layer with low-rank weights.""" - def __init__( + def __init__( # pylint: disable=too-many-arguments self, in_channels: int, out_channels: int, @@ -125,10 +126,11 @@ def forward(self, x): return out +# pylint: disable=too-many-instance-attributes class VGG(nn.Module): """VGG16GN model.""" - def __init__( + def __init__( # pylint: disable=too-many-arguments self, num_classes, num_groups=2, @@ -137,7 +139,7 @@ def __init__( conv_type="lowrank", add_nonlinear=False, ): - super(VGG, self).__init__() + super().__init__() if activation == "relu": self.activation = nn.ReLU(inplace=True) elif activation == "leaky_relu": diff --git a/baselines/fedpara/fedpara/server.py b/baselines/fedpara/fedpara/server.py index 641c60c35908..e8230294f897 100644 --- a/baselines/fedpara/fedpara/server.py +++ b/baselines/fedpara/fedpara/server.py @@ -12,6 +12,19 @@ from fedpara.models import test +def get_on_fit_config(hypearparams: Dict): + """Generate fit config function.""" + + def fit_config_fn(server_round: int): + print(server_round) + print(hypearparams) + hypearparams["curr_round"] = server_round + print(f"{hypearparams = }") + return hypearparams + + return fit_config_fn + + def gen_evaluate_fn( num_clients: int, test_loader: DataLoader, @@ -38,9 +51,12 @@ def gen_evaluate_fn( The centralized evaluation function. """ + # pylint: disable=unused-argument def evaluate( - server_round, parameters_ndarrays: NDArrays, __ - ) -> Optional[Tuple[float, Dict[str, Scalar]]]: # pylint: disable=unused-argument + server_round, + parameters_ndarrays: NDArrays, + __, + ) -> Optional[Tuple[float, Dict[str, Scalar]]]: """Use the entire CIFAR-10/100 test set for evaluation.""" net = instantiate(model) params_dict = zip(net.state_dict().keys(), parameters_ndarrays) diff --git a/baselines/fedpara/pyproject.toml b/baselines/fedpara/pyproject.toml index 5e14f722fc05..8080ae5de9e0 100644 --- a/baselines/fedpara/pyproject.toml +++ b/baselines/fedpara/pyproject.toml @@ -83,7 +83,7 @@ plugins = "numpy.typing.mypy_plugin" [tool.pylint."MESSAGES CONTROL"] disable = "bad-continuation,duplicate-code,too-few-public-methods,useless-import-alias" -good-names = "i,j,k,_,x,y,X,Y" +good-names = "i,j,k,_,x,y,X,Y,n,T,W1,W2,r1,r2,r,a,b,c,r3,W,W,n,v,lr" signature-mutators="hydra.main.main" [tool.pylint.typecheck] From 1069e110a43c3f5e44591c27ebe5d38010a33479 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Sun, 31 Dec 2023 11:04:05 +0000 Subject: [PATCH 21/39] remove prints --- baselines/fedpara/fedpara/server.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/baselines/fedpara/fedpara/server.py b/baselines/fedpara/fedpara/server.py index e8230294f897..275076b900d3 100644 --- a/baselines/fedpara/fedpara/server.py +++ b/baselines/fedpara/fedpara/server.py @@ -16,10 +16,7 @@ def get_on_fit_config(hypearparams: Dict): """Generate fit config function.""" def fit_config_fn(server_round: int): - print(server_round) - print(hypearparams) hypearparams["curr_round"] = server_round - print(f"{hypearparams = }") return hypearparams return fit_config_fn From 017c8b11c45d80daacc81b56f3a57b4d3fdcb57b Mon Sep 17 00:00:00 2001 From: yehias21 Date: Mon, 1 Jan 2024 20:04:39 +0000 Subject: [PATCH 22/39] review comments fixed --- baselines/fedpara/README.md | 10 +++++----- baselines/fedpara/_static/Cifar100_iid.jpeg | Bin 31241 -> 38597 bytes .../fedpara/_static/Cifar100_noniid.jpeg | Bin 34720 -> 39653 bytes baselines/fedpara/_static/Cifar10_iid.jpeg | Bin 30338 -> 40057 bytes baselines/fedpara/_static/Cifar10_noniid.jpeg | Bin 33676 -> 40919 bytes baselines/fedpara/fedpara/models.py | 5 ++--- baselines/fedpara/pyproject.toml | 2 +- 7 files changed, 8 insertions(+), 9 deletions(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index f6edde270d96..8405c5179792 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -2,7 +2,7 @@ title: "FedPara: Low-rank Hadamard Product for Communication-Efficient Federated Learning" url: https://openreview.net/forum?id=d71n4ftoCBy labels: [image classification, personalization, low-rank training, tensor decomposition] -dataset: [CIFAR10, CIFAR100, FEMNIST] +dataset: [CIFAR-10, CIFAR- 100, FEMNIST] --- # FedPara: Low-rank Hadamard Product for Communication-Efficient Federated Learning @@ -122,13 +122,13 @@ poetry run python -m fedpara.main **Multi-runs** # To run fedpara for non-iid cifar 10 on vgg16 for lowrank and original schemes -poetry run python -m fedpara.main --multirun model.conv_type=standard,lowrank +python -m fedpara.main --multirun model.conv_type=standard,lowrank # To run fedpara for non-iid cifar 100 on vgg16 for lowrank and original schemes -poetry run python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standard,lowrank +python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standard,lowrank # To run fedpara for iid cifar 10 on vgg16 for lowrank and original schemes -poetry run python -m fedpara.main --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid +python -m fedpara.main --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid # To run fedpara for iid cifar 100 on vgg16 for lowrank and original schemes -poetry run python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid +python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid ``` ## Expected Results diff --git a/baselines/fedpara/_static/Cifar100_iid.jpeg b/baselines/fedpara/_static/Cifar100_iid.jpeg index b3fe1a0886a2fe394879dc20dd3c3d9931fd1b7c..c8b94f94670dbb3b8e0b58411639a8b34366230d 100644 GIT binary patch literal 38597 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 E05G`gJpcdz diff --git a/baselines/fedpara/_static/Cifar100_noniid.jpeg b/baselines/fedpara/_static/Cifar100_noniid.jpeg index 0e123df1e036d50720e793d18b97af6fc98ac586..f76de3c46b0208ed0801fc1b39c1914a121bc08b 100644 GIT binary patch literal 39653 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 diff --git a/baselines/fedpara/_static/Cifar10_iid.jpeg b/baselines/fedpara/_static/Cifar10_iid.jpeg index 6d8c76b021519c761297f1a61ef4f5de71326125..0cf6675ac52e1f1e65846cacfe45317512a2b7d3 100644 GIT binary patch literal 40057 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 literal 30338 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%0A`RtIF)LKG$Ne!1ofED84= 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 Q#O<%~JO7_s=;6@+065^=0ssI2 literal 33676 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 diff --git a/baselines/fedpara/fedpara/models.py b/baselines/fedpara/fedpara/models.py index a083ee9aaf3f..41e978b69799 100644 --- a/baselines/fedpara/fedpara/models.py +++ b/baselines/fedpara/fedpara/models.py @@ -10,7 +10,6 @@ from torch import nn from torch.nn import init from torch.utils.data import DataLoader -from tqdm import tqdm class LowRank(nn.Module): @@ -287,7 +286,7 @@ def test( correct, total, loss = 0, 0, 0.0 net.eval() with torch.no_grad(): - for images, labels in tqdm(test_loader, "Testing ..."): + for images, labels in test_loader: images, labels = images.to(device), labels.to(device) outputs = net(images) loss += criterion(outputs, labels).item() @@ -334,7 +333,7 @@ def train( # pylint: disable=too-many-arguments weight_decay=hyperparams["weight_decay"], ) net.train() - for _ in tqdm(range(epochs), desc="Local Training ..."): + for _ in range(epochs): net = _train_one_epoch( net=net, trainloader=trainloader, diff --git a/baselines/fedpara/pyproject.toml b/baselines/fedpara/pyproject.toml index 8080ae5de9e0..2c93b5f43cad 100644 --- a/baselines/fedpara/pyproject.toml +++ b/baselines/fedpara/pyproject.toml @@ -7,7 +7,7 @@ name = "fedpara" # <----- Ensure it matches the name of your baseline directory version = "1.0.0" description = "Flower Baselines" license = "Apache-2.0" -authors = ["The Flower Authors "] +authors = ["Yahia Salaheldin Shaaban, Omar Mokhtar, Roeia Amr"] readme = "README.md" homepage = "https://flower.dev" repository = "https://github.com/adap/flower" From 1916d31bc9958b6ba255227065c655cacdc589ef Mon Sep 17 00:00:00 2001 From: yehias21 Date: Tue, 2 Jan 2024 15:25:58 +0000 Subject: [PATCH 23/39] Test passed! --- baselines/fedpara/fedpara/client.py | 2 +- baselines/fedpara/fedpara/main.py | 7 ++----- baselines/fedpara/fedpara/models.py | 26 +++++++++++--------------- baselines/fedpara/fedpara/utils.py | 10 ++++++---- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/baselines/fedpara/fedpara/client.py b/baselines/fedpara/fedpara/client.py index 50156df1dc48..aa5641199700 100644 --- a/baselines/fedpara/fedpara/client.py +++ b/baselines/fedpara/fedpara/client.py @@ -53,7 +53,7 @@ def fit( self.device, epochs=self.num_epochs, hyperparams=config, - round=int(config["curr_round"]), + epoch=int(config["curr_round"]), ) return ( diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index bfdeda8bebd8..3f87a896eebf 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -1,5 +1,4 @@ """Main script for running FedPara.""" -import logging import flwr as fl import hydra @@ -24,9 +23,7 @@ def main(cfg: DictConfig) -> None: # 1. Print parsed config print(OmegaConf.to_yaml(cfg)) seed_everything(cfg.seed) - hyper_params = OmegaConf.to_container(cfg, resolve=True) - # log the hyperparameters - logging.info(f"Hyperparameters: {hyper_params}") + OmegaConf.to_container(cfg, resolve=True) # 2. Prepare dataset train_loaders, test_loader = load_datasets( config=cfg.dataset_config, @@ -91,7 +88,7 @@ def main(cfg: DictConfig) -> None: save_plot_path=save_path, suffix=file_suffix, cfg=cfg, - model_size=net_glob.model_size()[1], + model_size=net_glob.model_size[1], ) diff --git a/baselines/fedpara/fedpara/models.py b/baselines/fedpara/fedpara/models.py index 41e978b69799..6670e1a6260e 100644 --- a/baselines/fedpara/fedpara/models.py +++ b/baselines/fedpara/fedpara/models.py @@ -91,14 +91,13 @@ def _calc_from_ratio(self): r = np.min((r1, r2)) # maximum possible rank, - """ - To solve it we need to know the roots of quadratic equation: ax^2+bx+c=0 - a = kernel**2 - b = out channel+ in channel - c = - num_target_params/2 - r3 is floored because we cannot take the ceil as it results a bigger number - of parameters than the original problem - """ + # To solve it we need to know the roots of quadratic equation: ax^2+bx+c=0 + # a = kernel**2 + # b = out channel+ in channel + # c = - num_target_params/2 + # r3 is floored because we cannot take the ceil as it results a bigger number + # of parameters than the original problem + num_target_params = ( self.out_channels * self.in_channels * (self.kernel_size**2) ) @@ -250,9 +249,9 @@ def model_size(self): size_all_mb = (param_size + buffer_size) / 1024**2 return total_trainable_params, size_all_mb - def forward(self, input): + def forward(self, x): """Forward pass.""" - x = self.features(input) + x = self.features(x) x = x.view(x.size(0), -1) x = self.classifier(x) return x @@ -304,7 +303,7 @@ def train( # pylint: disable=too-many-arguments device: torch.device, epochs: int, hyperparams: Dict[str, Scalar], - round: int, + epoch: int, ) -> None: """Train the network on the training set. @@ -322,9 +321,8 @@ def train( # pylint: disable=too-many-arguments The hyperparameters to use for training. """ lr = float(hyperparams["eta_l"]) * float(hyperparams["learning_decay"]) ** ( - round - 1 + epoch - 1 ) - print(f"Learning rate: {lr}") criterion = torch.nn.CrossEntropyLoss() optimizer = torch.optim.SGD( net.parameters(), @@ -340,7 +338,6 @@ def train( # pylint: disable=too-many-arguments device=device, criterion=criterion, optimizer=optimizer, - hyperparams=hyperparams, ) @@ -350,7 +347,6 @@ def _train_one_epoch( # pylint: disable=too-many-arguments device: torch.device, criterion, optimizer, - hyperparams: Dict[str, Scalar], ) -> nn.Module: """Train for one epoch. diff --git a/baselines/fedpara/fedpara/utils.py b/baselines/fedpara/fedpara/utils.py index 78cdaa0e0f68..5a79f12f4ae2 100644 --- a/baselines/fedpara/fedpara/utils.py +++ b/baselines/fedpara/fedpara/utils.py @@ -40,18 +40,20 @@ def plot_metric_from_history( if metric_type == "centralized" else hist.metrics_distributed ) - rounds, values_accuracy = zip(*metric_dict["accuracy"]) - rounds *= 2 * model_size * cfg.clients_per_round / 1024 _, axs = plt.subplots() + rounds, values_accuracy = zip(*metric_dict["accuracy"]) + r_cc = (i * 2 * model_size * int(cfg.clients_per_round) / 1024 for i in rounds) + # Set the title title = f"{cfg.strategy.algorithm} | parameters: {cfg.model.conv_type} | " title += ( f"{cfg.dataset_config.name} {cfg.dataset_config.partition} | Seed {cfg.seed}" ) axs.set_title(title) - axs.plot(np.asarray(rounds), np.asarray(values_accuracy)) + axs.grid(True) + axs.plot(np.asarray([*r_cc]), np.asarray(values_accuracy)) axs.set_ylabel("Accuracy") - axs.set_xlabel("Rounds") + axs.set_xlabel("Communication Cost (GB)") fig_name = "_".join([metric_type, "metrics", suffix]) + ".png" plt.savefig(Path(save_plot_path) / Path(fig_name)) plt.close() From 4eb59bba557e2c0f3d649b18b1fc26df3f774407 Mon Sep 17 00:00:00 2001 From: Yahia Salaheldin Shaaban <62369984+yehias21@users.noreply.github.com> Date: Tue, 2 Jan 2024 17:53:30 +0200 Subject: [PATCH 24/39] Update README.md --- baselines/fedpara/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 8405c5179792..4e5e70e627fb 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -91,7 +91,7 @@ Choice of alpha parameter for the Dirichlet distribution used to create heteroge ## Environment Setup To construct the Python environment follow these steps: -# Assumed that pyenv is installed, poetry is installed and python 3.10.6 is installed using pyenv +It is assumed that `pyenv` is installed, `poetry` is installed and python 3.10.6 is installed using `pyenv`. Refer to this [documentation](https://flower.dev/docs/baselines/how-to-usef-baselines.html#setting-up-your-machine) to ensure that your machine is ready. Refer to this [documentation](https://flower.dev/docs/baselines/how-to-usef-baselines.html#setting-up-your-machine) to ensure that your machine is ready ```bash From d517240156f99ac08d6def056f71740ee1154dc8 Mon Sep 17 00:00:00 2001 From: Yahia Salaheldin Shaaban <62369984+yehias21@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:55:23 +0200 Subject: [PATCH 25/39] Update README.md --- baselines/fedpara/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 4e5e70e627fb..70a8c26dfe90 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -92,7 +92,6 @@ Choice of alpha parameter for the Dirichlet distribution used to create heteroge To construct the Python environment follow these steps: It is assumed that `pyenv` is installed, `poetry` is installed and python 3.10.6 is installed using `pyenv`. Refer to this [documentation](https://flower.dev/docs/baselines/how-to-usef-baselines.html#setting-up-your-machine) to ensure that your machine is ready. -Refer to this [documentation](https://flower.dev/docs/baselines/how-to-usef-baselines.html#setting-up-your-machine) to ensure that your machine is ready ```bash # Set Python 3.10 From a78a51e85f13d9a4b65d9b29c45ec446cd560ce5 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Wed, 3 Jan 2024 22:06:27 +0000 Subject: [PATCH 26/39] tweaks to README; configs and pyproject.toml --- baselines/fedpara/.gitignore | 2 + baselines/fedpara/README.md | 102 +++++++++---------- baselines/fedpara/fedpara/client.py | 2 - baselines/fedpara/fedpara/conf/cifar10.yaml | 2 +- baselines/fedpara/fedpara/conf/cifar100.yaml | 1 - baselines/fedpara/pyproject.toml | 2 +- 6 files changed, 54 insertions(+), 57 deletions(-) create mode 100644 baselines/fedpara/.gitignore diff --git a/baselines/fedpara/.gitignore b/baselines/fedpara/.gitignore new file mode 100644 index 000000000000..de1e160448e5 --- /dev/null +++ b/baselines/fedpara/.gitignore @@ -0,0 +1,2 @@ +outputs/ +multirun/ diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 70a8c26dfe90..999fb1b9e4c2 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -2,7 +2,7 @@ title: "FedPara: Low-rank Hadamard Product for Communication-Efficient Federated Learning" url: https://openreview.net/forum?id=d71n4ftoCBy labels: [image classification, personalization, low-rank training, tensor decomposition] -dataset: [CIFAR-10, CIFAR- 100, FEMNIST] +dataset: [CIFAR-10, CIFAR-100, FEMNIST] --- # FedPara: Low-rank Hadamard Product for Communication-Efficient Federated Learning @@ -29,17 +29,20 @@ page: https://github.com/South-hw/FedPara_ICLR22 **What’s implemented:** The code in this directory replicates the experiments in FedPara paper implementing the Low-rank scheme for Convolution module. -Specifically, it replicates the results for Cifar10 and Cifar100 in Figure 3 and the results for Feminist in Figure 5(a). +Specifically, it replicates the results for CIFAR-10 and CIFAR-100 in Figure 3 and the results for Feminist in Figure 5(a). -**Datasets:** CIFAR10, CIFAR100, FEMNIST from PyTorch's Torchvision +**Datasets:** CIFAR-10, CIFAR-100, FEMNIST from PyTorch's Torchvision -**Hardware Setup:** The experiment has been conducted on our server with the following specs: +**Hardware Setup:** The experiments have been conducted on our server with the following specs: -- **GPU:** 1 RTX A6000 GPU 50GB VRAM +- **GPU:** 1x RTX A6000 GPU with 48GB VRAM - **CPU:** 1x24 cores Intel Xeon(R) 6248R - **RAM:** 150 GB +On a machine with RTX 3090Ti (24GB VRAM) it takes approximately 1h to run each CIFAR-10/100 experiment while using < 12GB of VRAM. You can lower the VRAM footprint my reducing the number of clients allowed to run in parallel in your GPU (do this by raising `client_resources.num_gpus`). + + **Contributors:** Yahia Salaheldin Shaaban, Omar Mokhtar and Roeia Amr @@ -47,23 +50,14 @@ Specifically, it replicates the results for Cifar10 and Cifar100 in Figure 3 and **Task:** Image classification -**Model:** This directory implements Vgg16 with group normalization. +**Model:** This baseline implements VGG16 with group normalization. **Dataset:** -In IID settings: - -| Dataset | #classes | #partitions | partitioning method | -|:---------|:--------:|:-----------:|:----------------------:| -| Cifar10 | 10 | 100 | random split | -| Cifar100 | 100 | 50 | random split| - -In non-IID settings: - -| Dataset | #classes | #partitions | partitioning method | -|:---------|:--------:|:-----------:|:----------------------:| -| Cifar10 | 10 | 100 | Dirichlet distribution | -| Cifar100 | 100 | 50 | Dirichlet distribution | +| Dataset | #classes | #partitions | partitioning method IID | partitioning method non-IID | +|:---------|:--------:|:-----------:|:----------------------:| :----------------------:| +| CIFAR-10 | 10 | 100 | random split | Dirichlet distribution ($\alpha=0.5$)| +| CIFAR-100 | 100 | 50 | random split| Dirichlet distribution ($\alpha=0.5$)| **Training Hyperparameters:** @@ -78,15 +72,13 @@ In non-IID settings: | Learning rate decay (τ) | 0.992 | 0.992 | 0.992| 0.992 | 0.999 | | Regularization coefficient (λ) | 1 | 1 | 1 | 1 | 0 | +As for the parameters ratio ($\gamma$) we use the following model sizes. As in the paper, $\gamma=0.1$ is used for CIFAR-10 and $\gamma=0.4$ for CIFAR-100: - -For Dataset: -Choice of alpha parameter for the Dirichlet distribution used to create heterogeneity in the client datasets for CIFAR - -| Description | Default Value | -|-------------|---------------| -| alpha | 0.5 | - +| Parameters ratio ($\gamma$) | CIFAR-10 | CIFAR-100 | +|----------|--------|--------| +| 1.0 (original) | 15.25M | 15.30M | +| 0.1 | 1.55M | - | +| 0.4 | - | 4.53M | ## Environment Setup To construct the Python environment follow these steps: @@ -108,51 +100,57 @@ poetry shell ## Running the Experiments +Running `FedPara` is easy. You can run it with default parameters directly or by tweaking them directly on the command line. Some command examples are shown below. + ```bash +# To run fedpara with default parameters +python -m fedpara.main + +# Run for more rounds and a different number of local epochs +python -m fedpara.main num_rounds=2024 num_epochs=1 + +# Choose parameterization scheme: lowrank or original (normal weights) +python -m fedpara.main model.conv_type=standard # or lowrank (default) -**Essential commands** -# To run fedpara -poetry run python -m fedpara.main -# Important configs -- model.conv_type --> choose parameterization scheme: lowrank or original(normal weights) -- dataset_config.partition --> choosing between non IID and IID scheme -- model.ratio --> choosing the ratio (lambda) of number of parameters +# Choosing between non IID and IID scheme +python -m fedpara.main dataset_config.partition=iid # or non-iid (default) -**Multi-runs** +# Choosing the ratio (lambda) of number of parameters to communicate +python -m fedpara.main model.ratio=0.1 -# To run fedpara for non-iid cifar 10 on vgg16 for lowrank and original schemes +# Choosing the CIFAR-100 config +python -m fedpara.main --config-name cifar100 # change settings as shown above if desired +``` + +## Expected Results + +To reproduce the curves shown below (which correspond to those in Figure 3 in the paper), run the following commands. Experiments running with `model.conv_type=lowrank` correspond to those with `-FedPara` in the legend of the figures below. Those with `model.conv_type=standard` are labelled with the `-orig` (as original) tag. + +```bash +# To run fedpara for non-iid CIFAR-10 on vgg16 for lowrank and original schemes python -m fedpara.main --multirun model.conv_type=standard,lowrank -# To run fedpara for non-iid cifar 100 on vgg16 for lowrank and original schemes +# To run fedpara for non-iid CIFAR-100 on vgg16 for lowrank and original schemes python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standard,lowrank -# To run fedpara for iid cifar 10 on vgg16 for lowrank and original schemes +# To run fedpara for iid CIFAR-10 on vgg16 for lowrank and original schemes python -m fedpara.main --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid -# To run fedpara for iid cifar 100 on vgg16 for lowrank and original schemes +# To run fedpara for iid CIFAR-100 on vgg16 for lowrank and original schemes python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid - ``` -## Expected Results -### From the [Fedpara](https://arxiv.org/pdf/2108.06098.pdf) paper: + #### Communication Cost: +Communication costs as measured as described in the paper: *"FL evaluation typically measures the required rounds to achieve the target accuracy as communication costs, but we instead assess total transferred bit sizes, 2 × (#participants)×(model size)×(#rounds)"* -#### Parameters ratio - -| Parameters ratio | Cifar10 | Cifar100 | -|----------|--------|--------| -| Original | 15.25M | 15.30M | -| 0.1 | 1.55M | - | -| 0.4 | - | 4.53M | -** We are using a parameter ratio of 0.1 for Cifar10 and 0.4 for Cifar100 -### Cifar100 (Accuracy vs Communication Cost) +### CIFAR-100 (Accuracy vs Communication Cost) | IID | Non-IID | |:----:|:----:| |![Cifar100 iid](_static/Cifar100_iid.jpeg) | ![Cifar100 non-iid](_static/Cifar100_noniid.jpeg) | -### Cifar10 (Accuracy vs Communication Cost) +### CIFAR-100 (Accuracy vs Communication Cost) | IID | Non-IID | |:----:|:----:| diff --git a/baselines/fedpara/fedpara/client.py b/baselines/fedpara/fedpara/client.py index aa5641199700..818312a57f2d 100644 --- a/baselines/fedpara/fedpara/client.py +++ b/baselines/fedpara/fedpara/client.py @@ -24,7 +24,6 @@ def __init__( device: str, num_epochs: int, ): # pylint: disable=too-many-arguments - print(f"Initializing Client {cid}") self.cid = cid self.net = net self.train_loader = train_loader @@ -45,7 +44,6 @@ def fit( ) -> Tuple[NDArrays, int, Dict]: """Train the network on the training set.""" self._set_parameters(parameters) - print(f"Client {self.cid} Training...") train( self.net, diff --git a/baselines/fedpara/fedpara/conf/cifar10.yaml b/baselines/fedpara/fedpara/conf/cifar10.yaml index 8b14a6162f5f..b8b0c25c6fdb 100644 --- a/baselines/fedpara/fedpara/conf/cifar10.yaml +++ b/baselines/fedpara/fedpara/conf/cifar10.yaml @@ -11,7 +11,7 @@ server_device: cuda client_resources: num_cpus: 2 - num_gpus: 0.0625 + num_gpus: 0.125 dataset_config: name: CIFAR10 diff --git a/baselines/fedpara/fedpara/conf/cifar100.yaml b/baselines/fedpara/fedpara/conf/cifar100.yaml index 130688d15f87..cb7eb73283c4 100644 --- a/baselines/fedpara/fedpara/conf/cifar100.yaml +++ b/baselines/fedpara/fedpara/conf/cifar100.yaml @@ -19,7 +19,6 @@ dataset_config: num_classes: 100 alpha: 0.5 - model: _target_: fedpara.models.VGG num_classes: ${dataset_config.num_classes} diff --git a/baselines/fedpara/pyproject.toml b/baselines/fedpara/pyproject.toml index 2c93b5f43cad..abeb5c20c43d 100644 --- a/baselines/fedpara/pyproject.toml +++ b/baselines/fedpara/pyproject.toml @@ -7,7 +7,7 @@ name = "fedpara" # <----- Ensure it matches the name of your baseline directory version = "1.0.0" description = "Flower Baselines" license = "Apache-2.0" -authors = ["Yahia Salaheldin Shaaban, Omar Mokhtar, Roeia Amr"] +authors = ["Yahia Salaheldin Shaaban <> ", "Omar Mokhtar <>", "Roeia Amr <>"] readme = "README.md" homepage = "https://flower.dev" repository = "https://github.com/adap/flower" From fcf09cea4c51d2556d771eb4725a3fa1baed05d8 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Wed, 3 Jan 2024 22:33:12 +0000 Subject: [PATCH 27/39] fix and tweak --- baselines/fedpara/README.md | 2 +- baselines/fedpara/fedpara/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 999fb1b9e4c2..9397bc28be9a 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -150,7 +150,7 @@ Communication costs as measured as described in the paper: |![Cifar100 iid](_static/Cifar100_iid.jpeg) | ![Cifar100 non-iid](_static/Cifar100_noniid.jpeg) | -### CIFAR-100 (Accuracy vs Communication Cost) +### CIFAR-10 (Accuracy vs Communication Cost) | IID | Non-IID | |:----:|:----:| diff --git a/baselines/fedpara/fedpara/utils.py b/baselines/fedpara/fedpara/utils.py index 5a79f12f4ae2..cbb6a1a75b66 100644 --- a/baselines/fedpara/fedpara/utils.py +++ b/baselines/fedpara/fedpara/utils.py @@ -28,7 +28,7 @@ def plot_metric_from_history( save_plot_path : str Folder to save the plot to. model_size : float - Size of the model. + Size of the model in MB. cfg : Optional Optional dictionary containing the configuration of the experiment. suffix: Optional From c742900573300ac4584f37af59de9c39fc925b05 Mon Sep 17 00:00:00 2001 From: yehias21 Date: Thu, 4 Jan 2024 15:02:19 +0200 Subject: [PATCH 28/39] fix chart Cifar10 typo --- baselines/fedpara/_static/Cifar10_iid.jpeg | Bin 40057 -> 39653 bytes baselines/fedpara/_static/Cifar10_noniid.jpeg | Bin 40919 -> 40363 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/baselines/fedpara/_static/Cifar10_iid.jpeg b/baselines/fedpara/_static/Cifar10_iid.jpeg index 0cf6675ac52e1f1e65846cacfe45317512a2b7d3..bc12f28ea4daa0cba1ef6f1d633f219b42f410bf 100644 GIT binary patch literal 39653 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 X2M{uX{m*Uae?w>g|1`4vW9&Zw55TC& literal 40057 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 diff --git a/baselines/fedpara/_static/Cifar10_noniid.jpeg b/baselines/fedpara/_static/Cifar10_noniid.jpeg index 1da36798d095edbb7ce22ee74f6f25a744a7a779..2f510f04731803fc64500f7ac6aab54666347f35 100644 GIT binary patch literal 40363 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 delta 23688 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

cZydjfP&)_VX${=2VEW#0+qWNI zlwy7E-cfkUv@ZfekPB=eAbW#@RYGA(x?)GB4}0NvmSr-RsytKV(kDL>V;PJ7l(Pz+ zwhvDHF{F3)@hMW@sv{tWOHZ!Eg(*Fx#RylcE4~k1{5}Og!xYcZG(

k*?HKvgp+h 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 GmD&fHBps#z From e427d490a8cbc35a5d960c23c38220b80472f830 Mon Sep 17 00:00:00 2001 From: Yahia Salaheldin Shaaban <62369984+yehias21@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:23:57 +0200 Subject: [PATCH 29/39] Update pyproject.toml added email --- baselines/fedpara/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baselines/fedpara/pyproject.toml b/baselines/fedpara/pyproject.toml index abeb5c20c43d..d0e404c69fd0 100644 --- a/baselines/fedpara/pyproject.toml +++ b/baselines/fedpara/pyproject.toml @@ -7,7 +7,7 @@ name = "fedpara" # <----- Ensure it matches the name of your baseline directory version = "1.0.0" description = "Flower Baselines" license = "Apache-2.0" -authors = ["Yahia Salaheldin Shaaban <> ", "Omar Mokhtar <>", "Roeia Amr <>"] +authors = ["Yahia Salaheldin Shaaban ", "Omar Mokhtar", "Roeia Amr"] readme = "README.md" homepage = "https://flower.dev" repository = "https://github.com/adap/flower" From 4455932183804d5b94d54bf1fd2c1bf27ed2e972 Mon Sep 17 00:00:00 2001 From: Yahia Salaheldin Shaaban <62369984+yehias21@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:51:40 +0200 Subject: [PATCH 30/39] Update README.md --- baselines/fedpara/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 9397bc28be9a..576dabc7671d 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -80,6 +80,14 @@ As for the parameters ratio ($\gamma$) we use the following model sizes. As in t | 0.1 | 1.55M | - | | 0.4 | - | 4.53M | + +### Notes: +- Notably, Fedpara's low-rank training technique heavily relies on initialization, with our experiments revealing that employing a 'Fan-in' He initialization (or Kaiming) renders the model incapable of convergence, resulting in a performance akin to that of a random classifier. We found that only Fan-out initialization yielded the anticipated results, and we postulated that this is attributed to the variance conservation during backward propagation. + +- The paper lacks explicit guidance on calculating the rank, aside from the "Rank_min - Rank_max" equation. To address this, we devised an equation aligning with the literature's explanation and constraint, solving a quadratic equation to determine max_rank and utilizing proposition 2 from the paper to establish min_rank. + +- The Jacobian correction was not incorporated into our implementation, primarily due to the lack of explicit instructions in the paper regarding the specific implementation of the dual update principle mentioned in the Jacobian correction section. + ## Environment Setup To construct the Python environment follow these steps: From 3f5f54ca18dc8f6c34093ee479c9aa430ddad368 Mon Sep 17 00:00:00 2001 From: yehias21 Date: Sun, 7 Jan 2024 18:18:16 +0000 Subject: [PATCH 31/39] pfedpara: pre-release --- baselines/fedpara/fedpara/client.py | 102 ++++++++++++-- .../fedpara/conf/{femnist.yaml => mnist.yaml} | 27 ++-- baselines/fedpara/fedpara/dataset.py | 97 ++++++++----- .../fedpara/fedpara/dataset_preparation.py | 15 +- baselines/fedpara/fedpara/main.py | 64 ++++++--- baselines/fedpara/fedpara/models.py | 131 +++++++++++++++++- baselines/fedpara/fedpara/test.ipynb | 85 ++++++++++++ 7 files changed, 438 insertions(+), 83 deletions(-) rename baselines/fedpara/fedpara/conf/{femnist.yaml => mnist.yaml} (58%) create mode 100644 baselines/fedpara/fedpara/test.ipynb diff --git a/baselines/fedpara/fedpara/client.py b/baselines/fedpara/fedpara/client.py index 818312a57f2d..92c0b0484458 100644 --- a/baselines/fedpara/fedpara/client.py +++ b/baselines/fedpara/fedpara/client.py @@ -1,8 +1,8 @@ """Client for FedPara.""" from collections import OrderedDict -from typing import Callable, Dict, List, Tuple - +from typing import Callable, Dict, List, Tuple, Optional +import copy import flwr as fl import torch from flwr.common import NDArrays, Scalar @@ -60,24 +60,98 @@ def fit( {}, ) +class PFedParaClient(fl.client.NumPyClient): + """personalized FedPara Client""" + def __init__( + self, + cid: int, + net: torch.nn.Module, + train_loader: DataLoader, + test_dataset: List[DataLoader], + device: str, + num_epochs: int, + state_path: str, + ): + + self.cid = cid + self.net = net + self.train_loader = train_loader + self.test_dataset = test_dataset + self.device = torch.device(device) + self.num_epochs = num_epochs + self.state_path = state_path + + def get_parameters(self, config: Dict[str, Scalar]) -> NDArrays: + """Return the parameters of the current net.""" + return [val.cpu().numpy() for _, val in self.net.state_dict().items()] + + def _set_parameters(self, parameters: NDArrays) -> None: + params_dict = zip(self.net.state_dict().keys(), parameters) + state_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) + self.net.load_state_dict(state_dict, strict=True) + + def fit( + self, parameters: NDArrays, config: Dict[str, Scalar] + ) -> Tuple[NDArrays, int, Dict]: + """Train the network on the training set.""" + self._set_parameters(parameters) + print(f"Client {self.cid} Training...") + + train( + self.net, + self.train_loader, + self.device, + epochs=self.num_epochs, + hyperparams=config, + round=config["curr_round"], + ) + + return ( + self.get_parameters({}), + len(self.train_loader), + {}, + ) + def evaluate(self, parameters: NDArrays, config: Dict[str, Scalar]) -> Tuple[int, float, Dict]: + """Evaluate the network on the test set.""" + self._set_parameters(parameters) + print(f"Client {self.cid} Evaluating...") + + return ( + len(self.test_dataset[self.cid]), + train.test(self.net, self.test_dataset[self.cid], self.device), + {}, + ) def gen_client_fn( train_loaders: List[DataLoader], model: DictConfig, num_epochs: int, -) -> Callable[[str], FlowerClient]: + args: Dict, + test_loader: Optional[List[DataLoader]]=None, + state_path: Optional[str]=None, +) -> Callable[[str], fl.client.NumPyClient]: """Return a function which creates a new FlowerClient for a given cid.""" - def client_fn(cid: str) -> FlowerClient: + def client_fn(cid: str) -> fl.client.NumPyClient: """Create a new FlowerClient for a given cid.""" - device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") - - return FlowerClient( - cid=int(cid), - net=instantiate(model).to(device), - train_loader=train_loaders[int(cid)], - device=device, - num_epochs=num_epochs, - ) - + cid = int(cid) + if args['algorithm'] == "pfedpara" or args['algorithm'] == "fedper": + return PFedParaClient( + cid=cid, + net=instantiate(model).to(args["device"]), + train_loader=train_loaders[cid], + test_dataset=copy.deepcopy(test_loader), + device=args["device"], + num_epochs=num_epochs, + state_path=state_path, + ) + else: + return FlowerClient( + cid=cid, + net=instantiate(model).to(args["device"]), + train_loader=train_loaders[cid], + device=args["device"], + num_epochs=num_epochs, + ) + return client_fn diff --git a/baselines/fedpara/fedpara/conf/femnist.yaml b/baselines/fedpara/fedpara/conf/mnist.yaml similarity index 58% rename from baselines/fedpara/fedpara/conf/femnist.yaml rename to baselines/fedpara/fedpara/conf/mnist.yaml index dfd67c0ab935..8115cc3e11ab 100644 --- a/baselines/fedpara/fedpara/conf/femnist.yaml +++ b/baselines/fedpara/fedpara/conf/mnist.yaml @@ -6,7 +6,7 @@ num_rounds: 100 clients_per_round: 10 num_epochs: 5 batch_size: 10 - +state_pah: ./state/ server_device: cuda client_resources: @@ -14,31 +14,26 @@ client_resources: num_gpus: 0.0625 dataset_config: - name: FEMNIST - partition: non-iid #redundent - num_classes: 62 - alpha: 0 # redundant - + name: MNIST + num_classes: 10 + shard_size: 300 + data_seed: ${seed} model: - _target_: fedpara.models.VGG + _target_: fedpara.models.FC num_classes: ${dataset_config.num_classes} - conv_type: lowrank # lowrank or standard + weights: lowrank # lowrank or standard activation: relu # relu or leaky_relu - ratio: 0.1 # lowrank ratio + ratio: 0.5 # lowrank ratio hyperparams: - eta_l: 0.1 + eta_l: 0.01 learning_decay: 0.999 - momentum: 0.0 - weight_decay: 0 strategy: _target_: fedpara.strategy.pFedPara algorithm: pFedPara fraction_fit: 0.00001 fraction_evaluate: 0.0 - min_evaluate_clients: 0 + min_evaluate_clients: ${clients_per_round} min_fit_clients: ${clients_per_round} - min_available_clients: ${clients_per_round} - accept_failures: false - + min_available_clients: ${clients_per_round} \ No newline at end of file diff --git a/baselines/fedpara/fedpara/dataset.py b/baselines/fedpara/fedpara/dataset.py index 6d207aec9524..8bc41f89608c 100644 --- a/baselines/fedpara/fedpara/dataset.py +++ b/baselines/fedpara/fedpara/dataset.py @@ -6,7 +6,7 @@ from torch.utils.data import DataLoader from torchvision import datasets, transforms -from fedpara.dataset_preparation import DatasetSplit, iid, noniid +from fedpara.dataset_preparation import DatasetSplit, iid, noniid, mnist_niid def load_datasets( @@ -14,39 +14,67 @@ def load_datasets( ) -> Tuple[List[DataLoader], DataLoader]: """Load the dataset and return the dataloaders for the clients and the server.""" print("Loading data...") - if config.name == "CIFAR10": - Dataset = datasets.CIFAR10 - elif config.name == "CIFAR100": - Dataset = datasets.CIFAR100 - else: - raise NotImplementedError + match config.name: + case "CIFAR10": + Dataset = datasets.CIFAR10 + case "CIFAR100": + Dataset = datasets.CIFAR100 + case "MNIST": + Dataset = datasets.MNIST + case _: + raise NotImplementedError data_directory = f"./data/{config.name.lower()}/" - ds_path = f"{data_directory}train_{num_clients}_{config.alpha:.2f}.pkl" - transform_train = transforms.Compose( - [ - transforms.RandomCrop(32, padding=4), - transforms.RandomHorizontalFlip(), - transforms.ToTensor(), - transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), - ] - ) - transform_test = transforms.Compose( - [ - transforms.ToTensor(), - transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), - ] - ) - try: - with open(ds_path, "rb") as file: - train_datasets = pickle.load(file) - except FileNotFoundError: - dataset_train = Dataset( - data_directory, train=True, download=True, transform=transform_train - ) - if config.partition == "iid": - train_datasets = iid(dataset_train, num_clients) - else: - train_datasets, _ = noniid(dataset_train, num_clients, config.alpha) + match config.name: + case "CIFAR10" | "CIFAR100": + ds_path = f"{data_directory}train_{num_clients}_{config.alpha:.2f}.pkl" + transform_train = transforms.Compose( + [ + transforms.RandomCrop(32, padding=4), + transforms.RandomHorizontalFlip(), + transforms.ToTensor(), + transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), + ] + ) + transform_test = transforms.Compose( + [ + transforms.ToTensor(), + transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), + ] + ) + try: + with open(ds_path, "rb") as file: + train_datasets = pickle.load(file) + except FileNotFoundError: + dataset_train = Dataset( + data_directory, train=True, download=True, transform=transform_train + ) + if config.partition == "iid": + train_datasets = iid(dataset_train, num_clients) + else: + train_datasets, _ = noniid(dataset_train, num_clients, config.alpha) + pickle.dump(train_datasets, open(ds_path, "wb")) + train_datasets = train_datasets.values() + + case "MNIST": + ds_path = f"{data_directory}train_{num_clients}.pkl" + transform_train = transforms.Compose( + [ + transforms.ToTensor(), + transforms.Normalize((0.1307,), (0.3081,)), + ] + ) + transform_test = transforms.Compose( + [transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))] + ) + try: + train_datasets = pickle.load(open(ds_path, "rb")) + except FileNotFoundError: + dataset_train = Dataset( + data_directory, train=True, download=True, transform=transform_train + ) + train_datasets = mnist_niid(dataset_train, num_clients, config.shard_size, config.data_seed) + pickle.dump(train_datasets, open(ds_path, "wb")) + dataset_test = Dataset( data_directory, train=False, download=True, transform=transform_test ) @@ -58,7 +86,8 @@ def load_datasets( shuffle=True, num_workers=2, ) - for ids in train_datasets.values() + for ids in train_datasets ] return train_loaders, test_loader + diff --git a/baselines/fedpara/fedpara/dataset_preparation.py b/baselines/fedpara/fedpara/dataset_preparation.py index ccdbea1edbe8..b76dcf576f22 100644 --- a/baselines/fedpara/fedpara/dataset_preparation.py +++ b/baselines/fedpara/fedpara/dataset_preparation.py @@ -11,7 +11,8 @@ import numpy as np from torch.utils.data import Dataset - +import logging +from collections import Counter class DatasetSplit(Dataset): """An abstract Dataset class wrapped around Pytorch Dataset class.""" @@ -97,3 +98,15 @@ def noniid(dataset, no_participants, alpha=0.5): for j in range(no_classes): clas_weight[i, j] = float(datasize[i, j]) / float((train_img_size[i])) return per_participant_list, clas_weight + +def mnist_niid(dataset: Dataset, num_clients: int, shard_size: int, seed: int) -> np.ndarray: + """ Partitioning technique as mentioned in https://arxiv.org/pdf/1602.05629.pdf""" + indices = dataset.targets[np.argsort(dataset.targets)].numpy() + logging.debug(Counter(dataset.targets[indices].numpy())) + silos = np.array_split(indices, len(dataset) // shard_size)# randomly assign silos to clients + np.random.seed(seed+17) + np.random.shuffle(silos) + clients = np.array(np.array_split(silos, num_clients)).reshape(num_clients, -1) + logging.debug(clients.shape) + logging.debug(Counter([len(Counter(dataset.targets[client].numpy())) for client in clients])) + return clients diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index 3f87a896eebf..dca22b7ef2da 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -32,28 +32,58 @@ def main(cfg: DictConfig) -> None: ) # 3. Define clients - client_fn = client.gen_client_fn( - train_loaders=train_loaders, - model=cfg.model, - num_epochs=cfg.num_epochs, - ) + # In this scheme the responsability of choosing the client is on the client manager + if cfg.strategy.min_evaluate_clients: + client_fn = client.gen_client_fn( + train_loaders=train_loaders, + test_loader=test_loader, + model=cfg.model, + num_epochs=cfg.num_epochs, + args={"device": cfg.client_device, "algorithm": cfg.strategy.algorithm}, + state_path=cfg.state_path, + ) + else : + client_fn = client.gen_client_fn( + train_loaders=train_loaders, + model=cfg.model, + num_epochs=cfg.num_epochs, + args={"device": cfg.client_device, "algorithm": cfg.strategy.algorithm}, + ) - evaluate_fn = server.gen_evaluate_fn( - num_clients=cfg.num_clients, - test_loader=test_loader, - model=cfg.model, - device=cfg.server_device, - ) + if not cfg.strategy.min_evaluate_clients : + evaluate_fn = server.gen_evaluate_fn( + num_clients=cfg.num_clients, + test_loader=test_loader, + model=cfg.model, + device=cfg.server_device, + state_path=cfg.state_path, + ) + + def get_on_fit_config(): + def fit_config_fn(server_round: int): + fit_config = OmegaConf.to_container(cfg.hyperparams, resolve=True) + fit_config["curr_round"] = server_round + return fit_config + + return fit_config_fn net_glob = instantiate(cfg.model) # 4. Define strategy - strategy = instantiate( - cfg.strategy, - evaluate_fn=evaluate_fn, - on_fit_config_fn=server.get_on_fit_config(dict(cfg.hyperparams)), - initial_parameters=fl.common.ndarrays_to_parameters(get_parameters(net_glob)), - ) + if cfg.strategy.min_evaluate_clients: + strategy = instantiate( + cfg.strategy, + on_fit_config_fn=get_on_fit_config(), + initial_parameters=fl.common.ndarrays_to_parameters(get_parameters(net_glob)), + ) + else : + strategy = instantiate( + cfg.strategy, + evaluate_fn=evaluate_fn, + on_fit_config_fn=get_on_fit_config(), + initial_parameters=fl.common.ndarrays_to_parameters(get_parameters(net_glob)), + ) + # 5. Start Simulation history = fl.simulation.start_simulation( diff --git a/baselines/fedpara/fedpara/models.py b/baselines/fedpara/fedpara/models.py index 6670e1a6260e..233f76f935ec 100644 --- a/baselines/fedpara/fedpara/models.py +++ b/baselines/fedpara/fedpara/models.py @@ -2,7 +2,6 @@ import math from typing import Dict, Tuple - import numpy as np import torch import torch.nn.functional as F @@ -10,7 +9,137 @@ from torch import nn from torch.nn import init from torch.utils.data import DataLoader +class LowRankNN(nn.Module): + def __init__(self,input, output, rank,activation: str = "relu",) -> None: + super(LowRankNN, self).__init__() + + self.X = nn.Parameter( + torch.empty(size=(input, rank)), + requires_grad=True, + ) + self.Y = nn.Parameter( + torch.empty(size=(output,rank)), requires_grad=True + ) + + if activation == "leakyrelu": + activation = "leaky_relu" + init.kaiming_normal_(self.X, mode="fan_out", nonlinearity=activation) + init.kaiming_normal_(self.Y, mode="fan_out", nonlinearity=activation) + + def forward(self,x): + out = torch.einsum("xr,yr->xy", self.X, self.Y) + return out + +class Linear(nn.Module): + def __init__(self, input, output, ratio, activation: str = "relu",bias= True, pfedpara=True) -> None: + super(Linear, self).__init__() + rank = self._calc_from_ratio(ratio,input, output) + self.w1 = LowRankNN(input, output, rank, activation) + self.w2 = LowRankNN(input, output, rank, activation) + # make the bias for each layer + if bias: + self.bias = nn.Parameter(torch.zeros(output)) + self.pfedpara = pfedpara + + def _calc_from_ratio(self, ratio,input, output): + # Return the low-rank of sub-matrices given the compression ratio + # minimum possible parameter + r1 = int(np.ceil(np.sqrt(output))) + r2 = int(np.ceil(np.sqrt(input))) + r = np.min((r1, r2)) + # maximum possible rank, + """ + To solve it we need to know the roots of quadratic equation: ax^2+bx+c=0 + a = kernel**2 + b = out channel+ in channel + c = - num_target_params/2 + r3 is floored because we cannot take the ceil as it results a bigger number of parameters than the original problem + """ + num_target_params = ( + output * input + ) + a, b, c = input, output,- num_target_params/2 + discriminant = b**2 - 4 * a * c + r3 = math.floor((-b+math.sqrt(discriminant))/(2*a)) + rank=math.ceil((1-ratio)*r+ ratio*r3) + return rank + + def forward(self,x): + # personalized + if self.pfedpara: + w = self.w1() * self.w2() + self.w1() + else: + w = self.w1() * self.w2() + out = F.linear(x, w,self.bias) + return out + +class FC(nn.Module): + def __init__(self, input_size=28**2, hidden_size=256, num_classes=10, ratio=0.1, param_type="standard",activation: str = "relu",): + super(FC, self).__init__() + + if param_type == "standard": + self.fc1 = nn.Linear(input_size, hidden_size) + self.relu = nn.ReLU() + self.fc2 = nn.Linear(hidden_size, num_classes) + self.softmax = nn.Softmax(dim=1) + elif param_type == "lowrank": + self.fc1 = Linear(input_size, hidden_size, ratio, activation) + self.relu = nn.ReLU() + self.fc2 = Linear(hidden_size, num_classes, ratio, activation) + self.softmax = nn.Softmax(dim=1) + else: + raise ValueError("param_type must be either standard or lowrank") + @property + def per_param(self): + """ + Return the personalized parameters of the model + """ + if self.method == "pfedpara": + params = {"fc1.X":self.fc1.w1.X, "fc1.Y":self.fc1.w1.Y, "fc2.X":self.fc2.w1.X, "fc2.Y":self.fc2.w1.Y} + # return the w1 only of each layer, same format as the state_dict + elif self.method == "fedper": + params = {"fc2.w1":self.fc2.w1, "fc2.w2":self.fc2.w2} + else: + raise ValueError("method must be either pfedpara, fedper") + return params + @property + def load_per_param(self,state_dict): + """ + Load the personalized parameters of the model + """ + if self.method == "pfedpara": + self.fc1.w1.X = state_dict["fc1.X"] + self.fc1.w1.Y = state_dict["fc1.Y"] + self.fc2.w1.X = state_dict["fc2.X"] + self.fc2.w1.Y = state_dict["fc2.Y"] + elif self.method == "fedper": + self.fc2.w1 = state_dict["fc2.w1"] + self.fc2.w2 = state_dict["fc2.w2"] + else: + raise ValueError("method must be either pfedpara, fedper") + @property + def model_size(self): + """ + Return the total number of trainable parameters (in million paramaters) and the size of the model in MB. + """ + total_trainable_params = sum( + p.numel() for p in self.parameters() if p.requires_grad)/1e6 + param_size = 0 + for param in self.parameters(): + param_size += param.nelement() * param.element_size() + buffer_size = 0 + for buffer in self.buffers(): + buffer_size += buffer.nelement() * buffer.element_size() + size_all_mb = (param_size + buffer_size) / 1024**2 + return total_trainable_params, size_all_mb + + def forward(self,x): + out = self.fc1(x) + out = self.relu(out) + out = self.fc2(out) + out = self.softmax(out) + return out class LowRank(nn.Module): """Low-rank convolutional layer.""" diff --git a/baselines/fedpara/fedpara/test.ipynb b/baselines/fedpara/fedpara/test.ipynb new file mode 100644 index 000000000000..a25ae10e7d6b --- /dev/null +++ b/baselines/fedpara/fedpara/test.ipynb @@ -0,0 +1,85 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import torchvision.datasets\n", + "import torchvision.transforms as transforms\n", + "import numpy as np\n", + "from collections import Counter\n", + "import logging" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "data = torchvision.datasets.MNIST\n", + "transform= transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])\n", + "trainset = data(root='./data', train = True, download = True, transform = transform)" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "metadata": {}, + "outputs": [], + "source": [ + "def mnist_niid(dataset: Dataset, num_clients: int, silo_size: int, seed: int) -> list:\n", + " indices = trainset.targets[np.argsort(trainset.targets)].numpy()\n", + " logging.debug(Counter(trainset.targets[indices].numpy()))\n", + " silos = np.array_split(indices, len(trainset) // 300)# randomly assign silos to clients\n", + " np.random.seed(seed+17)\n", + " np.random.shuffle(silos)\n", + " clients = np.array(np.array_split(silos, 100)).reshape(100, -1)\n", + " logging.debug(clients.shape)\n", + " logging.debug(Counter([len(Counter(trainset.targets[client].numpy())) for client in clients]))\n", + " return clients" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Counter({2: 82, 1: 11, 3: 7})" + ] + }, + "execution_count": 98, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "flower", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 4bc609cd0a28da388f17837b81d0b013c661a753 Mon Sep 17 00:00:00 2001 From: Yahia Salaheldin Shaaban <62369984+yehias21@users.noreply.github.com> Date: Sun, 7 Jan 2024 20:22:39 +0200 Subject: [PATCH 32/39] - Bug fixes --- baselines/fedpara/.gitignore | 2 + baselines/fedpara/fedpara/client.py | 91 +++++++++++++------- baselines/fedpara/fedpara/conf/cifar10.yaml | 3 +- baselines/fedpara/fedpara/conf/cifar100.yaml | 2 - baselines/fedpara/fedpara/conf/mnist.yaml | 17 ++-- baselines/fedpara/fedpara/dataset.py | 28 ++++-- baselines/fedpara/fedpara/main.py | 51 +++++------ baselines/fedpara/fedpara/models.py | 52 ++++------- baselines/fedpara/fedpara/server.py | 3 - baselines/fedpara/fedpara/test.ipynb | 85 ------------------ baselines/fedpara/fedpara/utils.py | 74 +++++++++++++++- baselines/fedper/fedper/server.py | 1 + 12 files changed, 208 insertions(+), 201 deletions(-) delete mode 100644 baselines/fedpara/fedpara/test.ipynb diff --git a/baselines/fedpara/.gitignore b/baselines/fedpara/.gitignore index de1e160448e5..6244dfada6ee 100644 --- a/baselines/fedpara/.gitignore +++ b/baselines/fedpara/.gitignore @@ -1,2 +1,4 @@ outputs/ multirun/ +client_states/ +data/ \ No newline at end of file diff --git a/baselines/fedpara/fedpara/client.py b/baselines/fedpara/fedpara/client.py index 92c0b0484458..f366c1e7b3b6 100644 --- a/baselines/fedpara/fedpara/client.py +++ b/baselines/fedpara/fedpara/client.py @@ -2,16 +2,15 @@ from collections import OrderedDict from typing import Callable, Dict, List, Tuple, Optional -import copy +import copy,os import flwr as fl import torch from flwr.common import NDArrays, Scalar from hydra.utils import instantiate from omegaconf import DictConfig from torch.utils.data import DataLoader - -from fedpara.models import train - +from fedpara.models import train,test +import logging class FlowerClient(fl.client.NumPyClient): """Standard Flower client for CNN training.""" @@ -34,7 +33,7 @@ def get_parameters(self, config: Dict[str, Scalar]) -> NDArrays: """Return the parameters of the current net.""" return [val.cpu().numpy() for _, val in self.net.state_dict().items()] - def _set_parameters(self, parameters: NDArrays) -> None: + def set_parameters(self, parameters: NDArrays) -> None: params_dict = zip(self.net.state_dict().keys(), parameters) state_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) self.net.load_state_dict(state_dict, strict=True) @@ -43,7 +42,7 @@ def fit( self, parameters: NDArrays, config: Dict[str, Scalar] ) -> Tuple[NDArrays, int, Dict]: """Train the network on the training set.""" - self._set_parameters(parameters) + self.set_parameters(parameters) train( self.net, @@ -60,41 +59,72 @@ def fit( {}, ) -class PFedParaClient(fl.client.NumPyClient): - """personalized FedPara Client""" +class PFlowerClient(fl.client.NumPyClient): + """personalized Flower Client""" def __init__( self, cid: int, net: torch.nn.Module, train_loader: DataLoader, - test_dataset: List[DataLoader], + test_loader: DataLoader, device: str, num_epochs: int, state_path: str, + algorithm: str, ): self.cid = cid self.net = net self.train_loader = train_loader - self.test_dataset = test_dataset + self.test_loader = test_loader self.device = torch.device(device) self.num_epochs = num_epochs self.state_path = state_path - + self.algorithm = algorithm + + def get_keys_state_dict(self, mode:str="local")->list[str]: + match self.algorithm: + case "fedper": + if mode == "local": + return list(filter(lambda x: 'fc2' in x,self.net.state_dict().keys())) + elif mode == "global": + return list(filter(lambda x: 'fc1' in x,self.net.state_dict().keys())) + case "pfedpara": + if mode == "local": + return list(filter(lambda x: 'w2' in x,self.net.state_dict().keys())) + elif mode == "global": + return list(filter(lambda x: 'w1' in x,self.net.state_dict().keys())) + case _: + raise NotImplementedError(f"algorithm {self.algorithm} not implemented") + + def get_parameters(self, config: Dict[str, Scalar]) -> NDArrays: """Return the parameters of the current net.""" + model_dict = self.net.state_dict() + #TODO: overwrite the server private parameters + for k in self.private_server_param.keys(): + model_dict[k] = self.private_server_param[k] return [val.cpu().numpy() for _, val in self.net.state_dict().items()] - - def _set_parameters(self, parameters: NDArrays) -> None: + + def set_parameters(self, parameters: NDArrays) -> None: + self.private_server_param: Dict[str, torch.Tensor] = {} params_dict = zip(self.net.state_dict().keys(), parameters) - state_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) + state_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) + self.private_server_param = {k:state_dict[k] for k in self.get_keys_state_dict(mode="local")} self.net.load_state_dict(state_dict, strict=True) - + if os.path.isfile(self.state_path): + # only overwrite global parameters + with open(self.state_path, 'rb') as f: + model_dict = self.net.state_dict() + state_dict = torch.load(f) + for k in self.get_keys_state_dict(mode="global"): + model_dict[k] = state_dict[k] + def fit( self, parameters: NDArrays, config: Dict[str, Scalar] ) -> Tuple[NDArrays, int, Dict]: """Train the network on the training set.""" - self._set_parameters(parameters) + self.set_parameters(parameters) print(f"Client {self.cid} Training...") train( @@ -103,24 +133,25 @@ def fit( self.device, epochs=self.num_epochs, hyperparams=config, - round=config["curr_round"], + epoch=config["curr_round"], ) + if self.state_path is not None: + with open(self.state_path, 'wb') as f: + torch.save(self.net.state_dict(), f) return ( self.get_parameters({}), len(self.train_loader), {}, ) - def evaluate(self, parameters: NDArrays, config: Dict[str, Scalar]) -> Tuple[int, float, Dict]: + def evaluate(self, parameters: NDArrays, config: Dict[str, Scalar]) -> Tuple[float, int, Dict]: """Evaluate the network on the test set.""" - self._set_parameters(parameters) + self.set_parameters(parameters) print(f"Client {self.cid} Evaluating...") - - return ( - len(self.test_dataset[self.cid]), - train.test(self.net, self.test_dataset[self.cid], self.device), - {}, - ) + self.net.to(self.device) + loss, accuracy = test(self.net, self.test_loader, device=self.device) + return loss, len(self.test_loader), {"accuracy": accuracy} + def gen_client_fn( train_loaders: List[DataLoader], @@ -135,15 +166,17 @@ def gen_client_fn( def client_fn(cid: str) -> fl.client.NumPyClient: """Create a new FlowerClient for a given cid.""" cid = int(cid) - if args['algorithm'] == "pfedpara" or args['algorithm'] == "fedper": - return PFedParaClient( + if args['algorithm'].lower() == "pfedpara" or args['algorithm'] == "fedper": + cl_path = f"{state_path}/client_{cid}.pth" + return PFlowerClient( cid=cid, net=instantiate(model).to(args["device"]), train_loader=train_loaders[cid], - test_dataset=copy.deepcopy(test_loader), + test_loader=copy.deepcopy(test_loader), device=args["device"], num_epochs=num_epochs, - state_path=state_path, + state_path=cl_path, + algorithm=args['algorithm'].lower(), ) else: return FlowerClient( diff --git a/baselines/fedpara/fedpara/conf/cifar10.yaml b/baselines/fedpara/fedpara/conf/cifar10.yaml index b8b0c25c6fdb..1c05e0ec1ca9 100644 --- a/baselines/fedpara/fedpara/conf/cifar10.yaml +++ b/baselines/fedpara/fedpara/conf/cifar10.yaml @@ -29,8 +29,7 @@ model: hyperparams: eta_l: 0.1 learning_decay: 0.992 - momentum: 0.0 - weight_decay: 0 + strategy: _target_: fedpara.strategy.FedPara diff --git a/baselines/fedpara/fedpara/conf/cifar100.yaml b/baselines/fedpara/fedpara/conf/cifar100.yaml index cb7eb73283c4..ee8402324519 100644 --- a/baselines/fedpara/fedpara/conf/cifar100.yaml +++ b/baselines/fedpara/fedpara/conf/cifar100.yaml @@ -29,8 +29,6 @@ model: hyperparams: eta_l: 0.1 learning_decay: 0.992 - momentum: 0.0 - weight_decay: 0 strategy: _target_: fedpara.strategy.FedPara diff --git a/baselines/fedpara/fedpara/conf/mnist.yaml b/baselines/fedpara/fedpara/conf/mnist.yaml index 8115cc3e11ab..05155542fb6c 100644 --- a/baselines/fedpara/fedpara/conf/mnist.yaml +++ b/baselines/fedpara/fedpara/conf/mnist.yaml @@ -6,8 +6,10 @@ num_rounds: 100 clients_per_round: 10 num_epochs: 5 batch_size: 10 -state_pah: ./state/ -server_device: cuda +state_path: ./client_states/ +client_device: cuda +algorithm: pFedPara + client_resources: num_cpus: 2 @@ -17,23 +19,24 @@ dataset_config: name: MNIST num_classes: 10 shard_size: 300 + data_seed: ${seed} + model: _target_: fedpara.models.FC num_classes: ${dataset_config.num_classes} - weights: lowrank # lowrank or standard + param_type: lowrank # lowrank or standard activation: relu # relu or leaky_relu ratio: 0.5 # lowrank ratio - + algorithm: ${algorithm} hyperparams: eta_l: 0.01 learning_decay: 0.999 strategy: - _target_: fedpara.strategy.pFedPara - algorithm: pFedPara + _target_: fedpara.strategy.FedAvg fraction_fit: 0.00001 - fraction_evaluate: 0.0 + fraction_evaluate: 0.00001 min_evaluate_clients: ${clients_per_round} min_fit_clients: ${clients_per_round} min_available_clients: ${clients_per_round} \ No newline at end of file diff --git a/baselines/fedpara/fedpara/dataset.py b/baselines/fedpara/fedpara/dataset.py index 8bc41f89608c..b4737d597ac2 100644 --- a/baselines/fedpara/fedpara/dataset.py +++ b/baselines/fedpara/fedpara/dataset.py @@ -14,7 +14,7 @@ def load_datasets( ) -> Tuple[List[DataLoader], DataLoader]: """Load the dataset and return the dataloaders for the clients and the server.""" print("Loading data...") - match config.name: + match config['name']: case "CIFAR10": Dataset = datasets.CIFAR10 case "CIFAR100": @@ -23,8 +23,8 @@ def load_datasets( Dataset = datasets.MNIST case _: raise NotImplementedError - data_directory = f"./data/{config.name.lower()}/" - match config.name: + data_directory = f"./data/{config['name'].lower()}/" + match config['name']: case "CIFAR10" | "CIFAR100": ds_path = f"{data_directory}train_{num_clients}_{config.alpha:.2f}.pkl" transform_train = transforms.Compose( @@ -44,6 +44,12 @@ def load_datasets( try: with open(ds_path, "rb") as file: train_datasets = pickle.load(file) + dataset_train = Dataset( + data_directory, train=True, download=False, transform=transform_train + ) + dataset_test = Dataset( + data_directory, train=False, download=False, transform=transform_test + ) except FileNotFoundError: dataset_train = Dataset( data_directory, train=True, download=True, transform=transform_train @@ -54,6 +60,9 @@ def load_datasets( train_datasets, _ = noniid(dataset_train, num_clients, config.alpha) pickle.dump(train_datasets, open(ds_path, "wb")) train_datasets = train_datasets.values() + dataset_test = Dataset( + data_directory, train=False, download=True, transform=transform_test + ) case "MNIST": ds_path = f"{data_directory}train_{num_clients}.pkl" @@ -68,16 +77,23 @@ def load_datasets( ) try: train_datasets = pickle.load(open(ds_path, "rb")) + dataset_train = Dataset( + data_directory, train=True, download=False, transform=transform_train + ) + dataset_test = Dataset( + data_directory, train=False, download=False, transform=transform_test + ) except FileNotFoundError: dataset_train = Dataset( data_directory, train=True, download=True, transform=transform_train ) train_datasets = mnist_niid(dataset_train, num_clients, config.shard_size, config.data_seed) pickle.dump(train_datasets, open(ds_path, "wb")) + dataset_test = Dataset( + data_directory, train=False, download=True, transform=transform_test + ) + - dataset_test = Dataset( - data_directory, train=False, download=True, transform=transform_test - ) test_loader = DataLoader(dataset_test, batch_size=batch_size, num_workers=2) train_loaders = [ DataLoader( diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index dca22b7ef2da..4da028738f5e 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -5,13 +5,12 @@ from hydra.core.hydra_config import HydraConfig from hydra.utils import instantiate from omegaconf import DictConfig, OmegaConf - from fedpara import client, server, utils from fedpara.dataset import load_datasets -from fedpara.utils import get_parameters, seed_everything +from fedpara.utils import get_parameters, save_results_as_pickle, seed_everything, set_client_state_save_path -@hydra.main(config_path="conf", config_name="cifar10", version_base=None) +@hydra.main(config_path="conf", config_name="mnist", version_base=None) def main(cfg: DictConfig) -> None: """Run the baseline. @@ -24,6 +23,9 @@ def main(cfg: DictConfig) -> None: print(OmegaConf.to_yaml(cfg)) seed_everything(cfg.seed) OmegaConf.to_container(cfg, resolve=True) + if 'state_path' in cfg: state_path=set_client_state_save_path(cfg.state_path) + else: state_path = None + # 2. Prepare dataset train_loaders, test_loader = load_datasets( config=cfg.dataset_config, @@ -33,31 +35,15 @@ def main(cfg: DictConfig) -> None: # 3. Define clients # In this scheme the responsability of choosing the client is on the client manager - if cfg.strategy.min_evaluate_clients: - client_fn = client.gen_client_fn( - train_loaders=train_loaders, - test_loader=test_loader, - model=cfg.model, - num_epochs=cfg.num_epochs, - args={"device": cfg.client_device, "algorithm": cfg.strategy.algorithm}, - state_path=cfg.state_path, - ) - else : - client_fn = client.gen_client_fn( - train_loaders=train_loaders, - model=cfg.model, - num_epochs=cfg.num_epochs, - args={"device": cfg.client_device, "algorithm": cfg.strategy.algorithm}, - ) - if not cfg.strategy.min_evaluate_clients : - evaluate_fn = server.gen_evaluate_fn( - num_clients=cfg.num_clients, - test_loader=test_loader, - model=cfg.model, - device=cfg.server_device, - state_path=cfg.state_path, - ) + client_fn = client.gen_client_fn( + train_loaders=train_loaders, + test_loader=test_loader, + model=cfg.model, + num_epochs=cfg.num_epochs, + args={"device": cfg.client_device, "algorithm": cfg.algorithm}, + state_path=state_path, + ) def get_on_fit_config(): def fit_config_fn(server_round: int): @@ -76,7 +62,15 @@ def fit_config_fn(server_round: int): on_fit_config_fn=get_on_fit_config(), initial_parameters=fl.common.ndarrays_to_parameters(get_parameters(net_glob)), ) + else : + evaluate_fn = server.gen_evaluate_fn( + num_clients=cfg.num_clients, + test_loader=test_loader, + model=cfg.model, + device=cfg.server_device, + state_path=cfg.state_path, + ) strategy = instantiate( cfg.strategy, evaluate_fn=evaluate_fn, @@ -98,7 +92,8 @@ def fit_config_fn(server_round: int): "_memory": 30 * 1024 * 1024 * 1024, }, ) - + save_results_as_pickle(history) + # 6. Save results save_path = HydraConfig.get().runtime.output_dir file_suffix = "_".join( diff --git a/baselines/fedpara/fedpara/models.py b/baselines/fedpara/fedpara/models.py index 233f76f935ec..bb785e5b893c 100644 --- a/baselines/fedpara/fedpara/models.py +++ b/baselines/fedpara/fedpara/models.py @@ -9,6 +9,7 @@ from torch import nn from torch.nn import init from torch.utils.data import DataLoader + class LowRankNN(nn.Module): def __init__(self,input, output, rank,activation: str = "relu",) -> None: super(LowRankNN, self).__init__() @@ -26,8 +27,8 @@ def __init__(self,input, output, rank,activation: str = "relu",) -> None: init.kaiming_normal_(self.X, mode="fan_out", nonlinearity=activation) init.kaiming_normal_(self.Y, mode="fan_out", nonlinearity=activation) - def forward(self,x): - out = torch.einsum("xr,yr->xy", self.X, self.Y) + def forward(self): + out = torch.einsum("yr,xr->yx",self.Y, self.X) return out class Linear(nn.Module): @@ -75,49 +76,27 @@ def forward(self,x): class FC(nn.Module): - def __init__(self, input_size=28**2, hidden_size=256, num_classes=10, ratio=0.1, param_type="standard",activation: str = "relu",): + def __init__(self, input_size=28**2, hidden_size=256, num_classes=10, ratio=0.5, param_type="lowrank",activation: str = "relu",algorithm="pfedpara"): super(FC, self).__init__() - + self.input_size = input_size + self.method = algorithm.lower() if param_type == "standard": self.fc1 = nn.Linear(input_size, hidden_size) self.relu = nn.ReLU() self.fc2 = nn.Linear(hidden_size, num_classes) self.softmax = nn.Softmax(dim=1) elif param_type == "lowrank": - self.fc1 = Linear(input_size, hidden_size, ratio, activation) + pfedpara = False + if self.method == "pfedpara": + pfedpara = True + + self.fc1 = Linear(input_size, hidden_size, ratio, activation, pfedpara=pfedpara) self.relu = nn.ReLU() - self.fc2 = Linear(hidden_size, num_classes, ratio, activation) + self.fc2 = Linear(hidden_size, num_classes, ratio, activation, pfedpara=pfedpara) self.softmax = nn.Softmax(dim=1) else: raise ValueError("param_type must be either standard or lowrank") - @property - def per_param(self): - """ - Return the personalized parameters of the model - """ - if self.method == "pfedpara": - params = {"fc1.X":self.fc1.w1.X, "fc1.Y":self.fc1.w1.Y, "fc2.X":self.fc2.w1.X, "fc2.Y":self.fc2.w1.Y} - # return the w1 only of each layer, same format as the state_dict - elif self.method == "fedper": - params = {"fc2.w1":self.fc2.w1, "fc2.w2":self.fc2.w2} - else: - raise ValueError("method must be either pfedpara, fedper") - return params - @property - def load_per_param(self,state_dict): - """ - Load the personalized parameters of the model - """ - if self.method == "pfedpara": - self.fc1.w1.X = state_dict["fc1.X"] - self.fc1.w1.Y = state_dict["fc1.Y"] - self.fc2.w1.X = state_dict["fc2.X"] - self.fc2.w1.Y = state_dict["fc2.Y"] - elif self.method == "fedper": - self.fc2.w1 = state_dict["fc2.w1"] - self.fc2.w2 = state_dict["fc2.w2"] - else: - raise ValueError("method must be either pfedpara, fedper") + @property def model_size(self): """ @@ -135,6 +114,7 @@ def model_size(self): return total_trainable_params, size_all_mb def forward(self,x): + x = x.view(-1, self.input_size) out = self.fc1(x) out = self.relu(out) out = self.fc2(out) @@ -456,8 +436,8 @@ def train( # pylint: disable=too-many-arguments optimizer = torch.optim.SGD( net.parameters(), lr=lr, - momentum=hyperparams["momentum"], - weight_decay=hyperparams["weight_decay"], + momentum=0, + weight_decay=0, ) net.train() for _ in range(epochs): diff --git a/baselines/fedpara/fedpara/server.py b/baselines/fedpara/fedpara/server.py index 275076b900d3..f73618ab8569 100644 --- a/baselines/fedpara/fedpara/server.py +++ b/baselines/fedpara/fedpara/server.py @@ -2,13 +2,11 @@ from collections import OrderedDict from typing import Callable, Dict, Optional, Tuple - import torch from flwr.common import NDArrays, Scalar from hydra.utils import instantiate from omegaconf import DictConfig from torch.utils.data import DataLoader - from fedpara.models import test @@ -18,7 +16,6 @@ def get_on_fit_config(hypearparams: Dict): def fit_config_fn(server_round: int): hypearparams["curr_round"] = server_round return hypearparams - return fit_config_fn diff --git a/baselines/fedpara/fedpara/test.ipynb b/baselines/fedpara/fedpara/test.ipynb deleted file mode 100644 index a25ae10e7d6b..000000000000 --- a/baselines/fedpara/fedpara/test.ipynb +++ /dev/null @@ -1,85 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import torchvision.datasets\n", - "import torchvision.transforms as transforms\n", - "import numpy as np\n", - "from collections import Counter\n", - "import logging" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "data = torchvision.datasets.MNIST\n", - "transform= transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])\n", - "trainset = data(root='./data', train = True, download = True, transform = transform)" - ] - }, - { - "cell_type": "code", - "execution_count": 89, - "metadata": {}, - "outputs": [], - "source": [ - "def mnist_niid(dataset: Dataset, num_clients: int, silo_size: int, seed: int) -> list:\n", - " indices = trainset.targets[np.argsort(trainset.targets)].numpy()\n", - " logging.debug(Counter(trainset.targets[indices].numpy()))\n", - " silos = np.array_split(indices, len(trainset) // 300)# randomly assign silos to clients\n", - " np.random.seed(seed+17)\n", - " np.random.shuffle(silos)\n", - " clients = np.array(np.array_split(silos, 100)).reshape(100, -1)\n", - " logging.debug(clients.shape)\n", - " logging.debug(Counter([len(Counter(trainset.targets[client].numpy())) for client in clients]))\n", - " return clients" - ] - }, - { - "cell_type": "code", - "execution_count": 98, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Counter({2: 82, 1: 11, 3: 7})" - ] - }, - "execution_count": 98, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "flower", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.13" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/baselines/fedpara/fedpara/utils.py b/baselines/fedpara/fedpara/utils.py index cbb6a1a75b66..77d70cb6ce38 100644 --- a/baselines/fedpara/fedpara/utils.py +++ b/baselines/fedpara/fedpara/utils.py @@ -1,8 +1,8 @@ """Utility functions for FedPara.""" - import random from pathlib import Path - +from secrets import token_hex +from typing import Optional, Union import matplotlib.pyplot as plt import numpy as np import torch @@ -10,7 +10,7 @@ from flwr.server import History from omegaconf import DictConfig from torch.nn import Module - +import time, os, pickle def plot_metric_from_history( hist: History, @@ -70,3 +70,71 @@ def seed_everything(seed): def get_parameters(net: Module) -> NDArrays: """Get the parameters of the network.""" return [val.cpu().numpy() for _, val in net.state_dict().items()] + +def save_results_as_pickle( + history: History, + default_filename: Optional[str] = "results.pkl", +) -> None: + """Save results from simulation to pickle. + + Parameters + ---------- + history: History + History returned by start_simulation. + file_path: Union[str, Path] + Path to file to create and store both history and extra_results. + If path is a directory, the default_filename will be used. + path doesn't exist, it will be created. If file exists, a + randomly generated suffix will be added to the file name. This + is done to avoid overwritting results. + extra_results : Optional[Dict] + A dictionary containing additional results you would like + to be saved to disk. Default: {} (an empty dictionary) + default_filename: Optional[str] + File used by default if file_path points to a directory instead + to a file. Default: "results.pkl" + """ + file_path = set_client_state_save_path("./outputs/") + path = Path(file_path) + + # ensure path exists + path.mkdir(exist_ok=True, parents=True) + + def _add_random_suffix(path_: Path): + """Add a random suffix to the file name.""" + print(f"File `{path_}` exists! ") + suffix = token_hex(4) + print(f"New results to be saved with suffix: {suffix}") + return path_.parent / (path_.stem + "_" + suffix + ".pkl") + + def _complete_path_with_default_name(path_: Path): + """Append the default file name to the path.""" + print("Using default filename") + if default_filename is None: + return path_ + return path_ / default_filename + + if path.is_dir(): + path = _complete_path_with_default_name(path) + + if path.is_file(): + path = _add_random_suffix(path) + + print(f"Results will be saved into: {path}") + # data = {"history": history, **extra_results} + data = {"history": history} + # save results to pickle + with open(str(path), "wb") as handle: + pickle.dump(data, handle, protocol=pickle.HIGHEST_PROTOCOL) + + +def set_client_state_save_path(path: str) -> str: + """Set the client state save path.""" + client_state_save_path = time.strftime("%Y-%m-%d") + client_state_sub_path = time.strftime("%H-%M-%S") + client_state_save_path = ( + f"{path}{client_state_save_path}/{client_state_sub_path}" + ) + if not os.path.exists(client_state_save_path): + os.makedirs(client_state_save_path) + return client_state_save_path diff --git a/baselines/fedper/fedper/server.py b/baselines/fedper/fedper/server.py index 93616f50f45a..50a4f8c5d8a8 100644 --- a/baselines/fedper/fedper/server.py +++ b/baselines/fedper/fedper/server.py @@ -1,4 +1,5 @@ """Server strategies pipelines for FedPer.""" + from flwr.server.strategy.fedavg import FedAvg from fedper.strategy import ( From 63a1ebf89b966959725e96f6615e82f132d04cdb Mon Sep 17 00:00:00 2001 From: = <=> Date: Sat, 27 Jan 2024 12:24:18 +0000 Subject: [PATCH 33/39] Minor changes --- baselines/fedpara/README.md | 18 +- .../_static/non-iid_mnist_personalization.png | Bin 0 -> 17118 bytes baselines/fedpara/fedpara/client.py | 108 +++++------ baselines/fedpara/fedpara/conf/cifar10.yaml | 8 +- baselines/fedpara/fedpara/conf/cifar100.yaml | 8 +- .../fedpara/fedpara/conf/mnist_fedavg.yaml | 38 ++++ .../fedpara/fedpara/conf/mnist_fedper.yaml | 42 +++++ .../conf/{mnist.yaml => mnist_pfedpara.yaml} | 17 +- baselines/fedpara/fedpara/dataset.py | 58 ++++-- .../fedpara/fedpara/dataset_preparation.py | 73 ++++++-- baselines/fedpara/fedpara/main.py | 51 +++--- baselines/fedpara/fedpara/models.py | 172 ++++++++---------- baselines/fedpara/fedpara/server.py | 16 +- baselines/fedpara/fedpara/strategy.py | 19 +- baselines/fedpara/fedpara/utils.py | 45 +++-- 15 files changed, 421 insertions(+), 252 deletions(-) create mode 100644 baselines/fedpara/_static/non-iid_mnist_personalization.png create mode 100644 baselines/fedpara/fedpara/conf/mnist_fedavg.yaml create mode 100644 baselines/fedpara/fedpara/conf/mnist_fedper.yaml rename baselines/fedpara/fedpara/conf/{mnist.yaml => mnist_pfedpara.yaml} (75%) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 576dabc7671d..0c46567ac492 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -2,7 +2,7 @@ title: "FedPara: Low-rank Hadamard Product for Communication-Efficient Federated Learning" url: https://openreview.net/forum?id=d71n4ftoCBy labels: [image classification, personalization, low-rank training, tensor decomposition] -dataset: [CIFAR-10, CIFAR-100, FEMNIST] +dataset: [CIFAR-10, CIFAR-100, MNIST] --- # FedPara: Low-rank Hadamard Product for Communication-Efficient Federated Learning @@ -29,10 +29,10 @@ page: https://github.com/South-hw/FedPara_ICLR22 **What’s implemented:** The code in this directory replicates the experiments in FedPara paper implementing the Low-rank scheme for Convolution module. -Specifically, it replicates the results for CIFAR-10 and CIFAR-100 in Figure 3 and the results for Feminist in Figure 5(a). +Specifically, it replicates the results for CIFAR-10 and CIFAR-100 in Figure 3 and the results for MNIST in Figure 5(c). -**Datasets:** CIFAR-10, CIFAR-100, FEMNIST from PyTorch's Torchvision +**Datasets:** CIFAR-10, CIFAR-100, MNIST from PyTorch's Torchvision **Hardware Setup:** The experiments have been conducted on our server with the following specs: @@ -62,7 +62,7 @@ On a machine with RTX 3090Ti (24GB VRAM) it takes approximately 1h to run each C **Training Hyperparameters:** -| | Cifar10 IID | Cifar10 Non-IID | Cifar100 IID | Cifar100 Non-IID | FEMNIST | +| | Cifar10 IID | Cifar10 Non-IID | Cifar100 IID | Cifar100 Non-IID | MNIST | |---|-------|-------|------|-------|----------| | Fraction of client (K) | 16 | 16 | 8 | 8 | 10 | | Total rounds (T) | 200 | 200 | 400 | 400 | 100 | @@ -143,6 +143,12 @@ python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standar python -m fedpara.main --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid # To run fedpara for iid CIFAR-100 on vgg16 for lowrank and original schemes python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid +# To run fedavg for non-iid MINST on FC +python -m fedpara.main --config-name mnist_fedavg +# To run fedper for non-iid MINST on FC +python -m fedpara.main --config-name mnist_fedper +# To run pfedpara for non-iid MINST on FC +python -m fedpara.main --config-name mnist_pfedpara ``` #### Communication Cost: @@ -163,3 +169,7 @@ Communication costs as measured as described in the paper: | IID | Non-IID | |:----:|:----:| |![CIFAR10 iid](_static/Cifar10_iid.jpeg) | ![CIFAR10 non-iid](_static/Cifar10_noniid.jpeg) | + +### NON-IID MINST (FedAvg vs FedPer vs pFedPara) +**Important Note: The only federated averaging (FedAvg) implementation replicates the results outlined in the paper. However, challenges with convergence were encountered when applying pFedPara and FedPer methods.** +![Personalization algorithms](_static/non-iid_mnist_personalization.png) \ No newline at end of file diff --git a/baselines/fedpara/_static/non-iid_mnist_personalization.png b/baselines/fedpara/_static/non-iid_mnist_personalization.png new file mode 100644 index 0000000000000000000000000000000000000000..4b09de2c28366790aa575e78ba029fe5d5b9d178 GIT binary patch literal 17118 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[str]: - match self.algorithm: - case "fedper": - if mode == "local": - return list(filter(lambda x: 'fc2' in x,self.net.state_dict().keys())) - elif mode == "global": - return list(filter(lambda x: 'fc1' in x,self.net.state_dict().keys())) - case "pfedpara": - if mode == "local": - return list(filter(lambda x: 'w2' in x,self.net.state_dict().keys())) - elif mode == "global": - return list(filter(lambda x: 'w1' in x,self.net.state_dict().keys())) - case _: - raise NotImplementedError(f"algorithm {self.algorithm} not implemented") - - def get_parameters(self, config: Dict[str, Scalar]) -> NDArrays: """Return the parameters of the current net.""" - model_dict = self.net.state_dict() - #TODO: overwrite the server private parameters + model_dict = self.net.state_dict().copy() + # overwrite the server private parameters for k in self.private_server_param.keys(): model_dict[k] = self.private_server_param[k] - return [val.cpu().numpy() for _, val in self.net.state_dict().items()] + return [val.cpu().numpy() for _, val in model_dict.items()] - def set_parameters(self, parameters: NDArrays) -> None: + def set_parameters(self, parameters: NDArrays, evaluate: False) -> None: self.private_server_param: Dict[str, torch.Tensor] = {} params_dict = zip(self.net.state_dict().keys(), parameters) - state_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) - self.private_server_param = {k:state_dict[k] for k in self.get_keys_state_dict(mode="local")} - self.net.load_state_dict(state_dict, strict=True) + server_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) + self.private_server_param = { + k: server_dict[k] + for k in get_keys_state_dict( + model=self.net, algorithm=self.algorithm, mode="local" + ) + } + + if evaluate: + client_dict = self.net.state_dict().copy() + else: + client_dict = copy.deepcopy(server_dict) + if os.path.isfile(self.state_path): - # only overwrite global parameters - with open(self.state_path, 'rb') as f: - model_dict = self.net.state_dict() - state_dict = torch.load(f) - for k in self.get_keys_state_dict(mode="global"): - model_dict[k] = state_dict[k] + with open(self.state_path, "rb") as f: + client_dict = torch.load(f) + + for k in get_keys_state_dict( + model=self.net, algorithm=self.algorithm, mode="global" + ): + client_dict[k] = server_dict[k] + + self.net.load_state_dict(client_dict, strict=False) def fit( self, parameters: NDArrays, config: Dict[str, Scalar] ) -> Tuple[NDArrays, int, Dict]: """Train the network on the training set.""" - self.set_parameters(parameters) + self.set_parameters(parameters, evaluate=False) print(f"Client {self.cid} Training...") train( @@ -136,55 +138,59 @@ def fit( epoch=config["curr_round"], ) if self.state_path is not None: - with open(self.state_path, 'wb') as f: + with open(self.state_path, "wb") as f: torch.save(self.net.state_dict(), f) return ( self.get_parameters({}), len(self.train_loader), - {}, + {}, ) - def evaluate(self, parameters: NDArrays, config: Dict[str, Scalar]) -> Tuple[float, int, Dict]: + + def evaluate( + self, parameters: NDArrays, config: Dict[str, Scalar] + ) -> Tuple[float, int, Dict]: """Evaluate the network on the test set.""" - self.set_parameters(parameters) + self.set_parameters(parameters, evaluate=True) print(f"Client {self.cid} Evaluating...") self.net.to(self.device) loss, accuracy = test(self.net, self.test_loader, device=self.device) return loss, len(self.test_loader), {"accuracy": accuracy} - + def gen_client_fn( train_loaders: List[DataLoader], model: DictConfig, num_epochs: int, args: Dict, - test_loader: Optional[List[DataLoader]]=None, - state_path: Optional[str]=None, + test_loader: Optional[List[DataLoader]] = None, + state_path: Optional[str] = None, ) -> Callable[[str], fl.client.NumPyClient]: """Return a function which creates a new FlowerClient for a given cid.""" def client_fn(cid: str) -> fl.client.NumPyClient: """Create a new FlowerClient for a given cid.""" cid = int(cid) - if args['algorithm'].lower() == "pfedpara" or args['algorithm'] == "fedper": + device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + if args["algorithm"].lower() == "pfedpara" or args["algorithm"] == "fedper": cl_path = f"{state_path}/client_{cid}.pth" return PFlowerClient( cid=cid, - net=instantiate(model).to(args["device"]), + net=instantiate(model).to(device), train_loader=train_loaders[cid], test_loader=copy.deepcopy(test_loader), - device=args["device"], num_epochs=num_epochs, state_path=cl_path, - algorithm=args['algorithm'].lower(), + algorithm=args["algorithm"].lower(), + device=device, ) else: return FlowerClient( cid=cid, - net=instantiate(model).to(args["device"]), + net=instantiate(model).to(device), train_loader=train_loaders[cid], - device=args["device"], num_epochs=num_epochs, + device=device, ) - + return client_fn diff --git a/baselines/fedpara/fedpara/conf/cifar10.yaml b/baselines/fedpara/fedpara/conf/cifar10.yaml index 1c05e0ec1ca9..13f5a07b5f93 100644 --- a/baselines/fedpara/fedpara/conf/cifar10.yaml +++ b/baselines/fedpara/fedpara/conf/cifar10.yaml @@ -6,6 +6,7 @@ num_rounds: 200 clients_per_round: 16 num_epochs: 5 batch_size: 64 +algorithm: FedPara server_device: cuda @@ -22,8 +23,7 @@ dataset_config: model: _target_: fedpara.models.VGG num_classes: ${dataset_config.num_classes} - conv_type: lowrank # lowrank or standard - activation: relu # relu or leaky_relu + param_type: lowrank # lowrank or standard ratio: 0.1 # lowrank ratio hyperparams: @@ -32,8 +32,7 @@ hyperparams: strategy: - _target_: fedpara.strategy.FedPara - algorithm: FedPara + _target_: fedpara.strategy.FedAvg fraction_fit: 0.00001 fraction_evaluate: 0.0 min_evaluate_clients: 0 @@ -41,3 +40,4 @@ strategy: min_available_clients: ${clients_per_round} accept_failures: false +exp_id: ${algorithm}_${dataset_config.name}_${seed}_${dataset_config.partition}_${dataset_config.alpha}_${model.param_type}_${model.ratio} diff --git a/baselines/fedpara/fedpara/conf/cifar100.yaml b/baselines/fedpara/fedpara/conf/cifar100.yaml index ee8402324519..4563ee949c31 100644 --- a/baselines/fedpara/fedpara/conf/cifar100.yaml +++ b/baselines/fedpara/fedpara/conf/cifar100.yaml @@ -6,6 +6,7 @@ num_rounds: 400 clients_per_round: 8 num_epochs: 5 batch_size: 64 +algorithm: FedPara server_device: cuda @@ -22,8 +23,7 @@ dataset_config: model: _target_: fedpara.models.VGG num_classes: ${dataset_config.num_classes} - conv_type: lowrank # lowrank or standard - activation: relu # relu or leaky_relu + param_type: lowrank # lowrank or standard ratio: 0.4 # lowrank ratio hyperparams: @@ -31,8 +31,7 @@ hyperparams: learning_decay: 0.992 strategy: - _target_: fedpara.strategy.FedPara - algorithm: FedPara + _target_: fedpara.strategy.FedAvg fraction_fit: 0.00001 fraction_evaluate: 0.0 min_evaluate_clients: 0 @@ -40,3 +39,4 @@ strategy: min_available_clients: ${clients_per_round} accept_failures: false +exp_id: ${algorithm}_${dataset_config.name}_${seed}_${dataset_config.partition}_${dataset_config.alpha}_${model.param_type}_${model.ratio} \ No newline at end of file diff --git a/baselines/fedpara/fedpara/conf/mnist_fedavg.yaml b/baselines/fedpara/fedpara/conf/mnist_fedavg.yaml new file mode 100644 index 000000000000..2429f9b20d6e --- /dev/null +++ b/baselines/fedpara/fedpara/conf/mnist_fedavg.yaml @@ -0,0 +1,38 @@ +--- +seed: 424 + +num_clients: 100 +num_rounds: 100 +clients_per_round: 10 +num_epochs: 1 +batch_size: 10 +server_device: cuda +algorithm: fedavg + +client_resources: + num_cpus: 2 + num_gpus: 0.1 + +dataset_config: + name: MNIST + num_classes: 10 + shard_size: 300 + +model: + _target_: fedpara.models.FC + num_classes: ${dataset_config.num_classes} + hidden_size: 200 + +hyperparams: + eta_l: 0.05 + learning_decay: 1 + +strategy: + _target_: fedpara.strategy.FedAvg + fraction_fit: 0.00001 + fraction_evaluate: 0 + min_evaluate_clients: 0 + min_fit_clients: ${clients_per_round} + min_available_clients: ${clients_per_round} + +exp_id: ${algorithm}_${dataset_config.name}_${seed} diff --git a/baselines/fedpara/fedpara/conf/mnist_fedper.yaml b/baselines/fedpara/fedpara/conf/mnist_fedper.yaml new file mode 100644 index 000000000000..13d2dedfc0ad --- /dev/null +++ b/baselines/fedpara/fedpara/conf/mnist_fedper.yaml @@ -0,0 +1,42 @@ +--- +seed: 17 + +num_clients: 100 +num_rounds: 100 +clients_per_round: 10 +num_epochs: 1 +batch_size: 10 +state_path: ./client_states/ +server_device: cuda +client_device: cuda + +algorithm: fedper +# fedavg in future + +client_resources: + num_cpus: 2 + num_gpus: 0.1 + +dataset_config: + name: MNIST + num_classes: 10 + shard_size: 300 + + data_seed: ${seed} + +model: + _target_: fedpara.models.FC + num_classes: ${dataset_config.num_classes} + hidden_size: 200 + +hyperparams: + eta_l: 0.05 + learning_decay: 0 + +strategy: + _target_: fedpara.strategy.FedAvg + fraction_fit: 0.00001 + fraction_evaluate: 0.00001 + min_evaluate_clients: ${clients_per_round} + min_fit_clients: ${clients_per_round} + min_available_clients: ${clients_per_round} \ No newline at end of file diff --git a/baselines/fedpara/fedpara/conf/mnist.yaml b/baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml similarity index 75% rename from baselines/fedpara/fedpara/conf/mnist.yaml rename to baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml index 05155542fb6c..7bf8b3979e06 100644 --- a/baselines/fedpara/fedpara/conf/mnist.yaml +++ b/baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml @@ -7,28 +7,25 @@ clients_per_round: 10 num_epochs: 5 batch_size: 10 state_path: ./client_states/ -client_device: cuda -algorithm: pFedPara - +server_device: cuda +algorithm: pFedpara client_resources: num_cpus: 2 - num_gpus: 0.0625 + num_gpus: 0.1 dataset_config: name: MNIST num_classes: 10 shard_size: 300 - - data_seed: ${seed} model: _target_: fedpara.models.FC num_classes: ${dataset_config.num_classes} param_type: lowrank # lowrank or standard - activation: relu # relu or leaky_relu ratio: 0.5 # lowrank ratio - algorithm: ${algorithm} + hidden_size: 200 + hyperparams: eta_l: 0.01 learning_decay: 0.999 @@ -39,4 +36,6 @@ strategy: fraction_evaluate: 0.00001 min_evaluate_clients: ${clients_per_round} min_fit_clients: ${clients_per_round} - min_available_clients: ${clients_per_round} \ No newline at end of file + min_available_clients: ${clients_per_round} + +exp_id: ${algorithm}_${dataset_config.name}_${seed}_${model.param_type}_${model.ratio} diff --git a/baselines/fedpara/fedpara/dataset.py b/baselines/fedpara/fedpara/dataset.py index b4737d597ac2..c76a75d982f6 100644 --- a/baselines/fedpara/fedpara/dataset.py +++ b/baselines/fedpara/fedpara/dataset.py @@ -6,7 +6,12 @@ from torch.utils.data import DataLoader from torchvision import datasets, transforms -from fedpara.dataset_preparation import DatasetSplit, iid, noniid, mnist_niid +from fedpara.dataset_preparation import ( + DatasetSplit, + iid, + noniid, + noniid_partition_loader, +) def load_datasets( @@ -14,7 +19,7 @@ def load_datasets( ) -> Tuple[List[DataLoader], DataLoader]: """Load the dataset and return the dataloaders for the clients and the server.""" print("Loading data...") - match config['name']: + match config["name"]: case "CIFAR10": Dataset = datasets.CIFAR10 case "CIFAR100": @@ -24,7 +29,7 @@ def load_datasets( case _: raise NotImplementedError data_directory = f"./data/{config['name'].lower()}/" - match config['name']: + match config["name"]: case "CIFAR10" | "CIFAR100": ds_path = f"{data_directory}train_{num_clients}_{config.alpha:.2f}.pkl" transform_train = transforms.Compose( @@ -32,23 +37,33 @@ def load_datasets( transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), - transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), + transforms.Normalize( + (0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010) + ), ] ) transform_test = transforms.Compose( [ transforms.ToTensor(), - transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), + transforms.Normalize( + (0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010) + ), ] ) try: with open(ds_path, "rb") as file: - train_datasets = pickle.load(file) + train_datasets = pickle.load(file).values() dataset_train = Dataset( - data_directory, train=True, download=False, transform=transform_train + data_directory, + train=True, + download=False, + transform=transform_train, ) dataset_test = Dataset( - data_directory, train=False, download=False, transform=transform_test + data_directory, + train=False, + download=False, + transform=transform_test, ) except FileNotFoundError: dataset_train = Dataset( @@ -63,7 +78,7 @@ def load_datasets( dataset_test = Dataset( data_directory, train=False, download=True, transform=transform_test ) - + case "MNIST": ds_path = f"{data_directory}train_{num_clients}.pkl" transform_train = transforms.Compose( @@ -78,21 +93,37 @@ def load_datasets( try: train_datasets = pickle.load(open(ds_path, "rb")) dataset_train = Dataset( - data_directory, train=True, download=False, transform=transform_train + data_directory, + train=True, + download=False, + transform=transform_train, ) dataset_test = Dataset( - data_directory, train=False, download=False, transform=transform_test + data_directory, + train=False, + download=False, + transform=transform_test, ) + except FileNotFoundError: dataset_train = Dataset( data_directory, train=True, download=True, transform=transform_train ) - train_datasets = mnist_niid(dataset_train, num_clients, config.shard_size, config.data_seed) + train_datasets = noniid_partition_loader( + dataset_train, + m_per_shard=config.shard_size, + n_shards_per_client=len(dataset_train) // (config.shard_size * 100), + ) pickle.dump(train_datasets, open(ds_path, "wb")) dataset_test = Dataset( data_directory, train=False, download=True, transform=transform_test ) - + train_loaders = [ + DataLoader(x, batch_size=batch_size, shuffle=True) + for x in train_datasets + ] + test_loader = DataLoader(dataset_test, batch_size=batch_size, num_workers=2) + return train_loaders, test_loader test_loader = DataLoader(dataset_test, batch_size=batch_size, num_workers=2) train_loaders = [ @@ -106,4 +137,3 @@ def load_datasets( ] return train_loaders, test_loader - diff --git a/baselines/fedpara/fedpara/dataset_preparation.py b/baselines/fedpara/fedpara/dataset_preparation.py index b76dcf576f22..2de43be318ce 100644 --- a/baselines/fedpara/fedpara/dataset_preparation.py +++ b/baselines/fedpara/fedpara/dataset_preparation.py @@ -6,13 +6,14 @@ uncomment the lines below and tell us in the README.md (see the "Running the Experiment" block) that this file should be executed first. """ + import random from collections import defaultdict import numpy as np +import torch from torch.utils.data import Dataset -import logging -from collections import Counter + class DatasetSplit(Dataset): """An abstract Dataset class wrapped around Pytorch Dataset class.""" @@ -99,14 +100,60 @@ def noniid(dataset, no_participants, alpha=0.5): clas_weight[i, j] = float(datasize[i, j]) / float((train_img_size[i])) return per_participant_list, clas_weight -def mnist_niid(dataset: Dataset, num_clients: int, shard_size: int, seed: int) -> np.ndarray: - """ Partitioning technique as mentioned in https://arxiv.org/pdf/1602.05629.pdf""" - indices = dataset.targets[np.argsort(dataset.targets)].numpy() - logging.debug(Counter(dataset.targets[indices].numpy())) - silos = np.array_split(indices, len(dataset) // shard_size)# randomly assign silos to clients - np.random.seed(seed+17) - np.random.shuffle(silos) - clients = np.array(np.array_split(silos, num_clients)).reshape(num_clients, -1) - logging.debug(clients.shape) - logging.debug(Counter([len(Counter(dataset.targets[client].numpy())) for client in clients])) - return clients + +def data_to_tensor(data): + """Loads dataset to memory, applies transform.""" + loader = torch.utils.data.DataLoader(data, batch_size=len(data)) + img, label = next(iter(loader)) + return img, label + + +def noniid_partition_loader(data, m_per_shard=300, n_shards_per_client=2): + """semi-pathological client sample partition + 1. sort examples by label, form shards of size 300 by grouping points + successively + 2. each client is 2 random shards + most clients will have 2 digits, at most 4 + """ + # load data into memory + img, label = data_to_tensor(data) + + # sort + idx = torch.argsort(label) + img = img[idx] + label = label[idx] + + # split into n_shards of size m_per_shard + m = len(data) + assert m % m_per_shard == 0 + n_shards = m // m_per_shard + shards_idx = [ + torch.arange(m_per_shard * i, m_per_shard * (i + 1)) for i in range(n_shards) + ] + random.shuffle(shards_idx) # shuffle shards + + # pick shards to create a dataset for each client + assert n_shards % n_shards_per_client == 0 + n_clients = n_shards // n_shards_per_client + client_data = [ + torch.utils.data.TensorDataset( + torch.cat( + [ + img[shards_idx[j]] + for j in range( + i * n_shards_per_client, (i + 1) * n_shards_per_client + ) + ] + ), + torch.cat( + [ + label[shards_idx[j]] + for j in range( + i * n_shards_per_client, (i + 1) * n_shards_per_client + ) + ] + ), + ) + for i in range(n_clients) + ] + return client_data diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index 4da028738f5e..b18ae70f101f 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -5,12 +5,19 @@ from hydra.core.hydra_config import HydraConfig from hydra.utils import instantiate from omegaconf import DictConfig, OmegaConf + from fedpara import client, server, utils from fedpara.dataset import load_datasets -from fedpara.utils import get_parameters, save_results_as_pickle, seed_everything, set_client_state_save_path +from fedpara.server import weighted_average +from fedpara.utils import ( + get_parameters, + save_results_as_pickle, + seed_everything, + set_client_state_save_path, +) -@hydra.main(config_path="conf", config_name="mnist", version_base=None) +@hydra.main(config_path="conf", config_name="cifar100", version_base=None) def main(cfg: DictConfig) -> None: """Run the baseline. @@ -23,8 +30,10 @@ def main(cfg: DictConfig) -> None: print(OmegaConf.to_yaml(cfg)) seed_everything(cfg.seed) OmegaConf.to_container(cfg, resolve=True) - if 'state_path' in cfg: state_path=set_client_state_save_path(cfg.state_path) - else: state_path = None + if "state_path" in cfg: + state_path = set_client_state_save_path(cfg.state_path) + else: + state_path = None # 2. Prepare dataset train_loaders, test_loader = load_datasets( @@ -41,7 +50,7 @@ def main(cfg: DictConfig) -> None: test_loader=test_loader, model=cfg.model, num_epochs=cfg.num_epochs, - args={"device": cfg.client_device, "algorithm": cfg.algorithm}, + args={"algorithm": cfg.algorithm}, state_path=state_path, ) @@ -60,25 +69,28 @@ def fit_config_fn(server_round: int): strategy = instantiate( cfg.strategy, on_fit_config_fn=get_on_fit_config(), - initial_parameters=fl.common.ndarrays_to_parameters(get_parameters(net_glob)), + initial_parameters=fl.common.ndarrays_to_parameters( + get_parameters(net_glob) + ), + evaluate_metrics_aggregation_fn=weighted_average, ) - else : + else: evaluate_fn = server.gen_evaluate_fn( num_clients=cfg.num_clients, test_loader=test_loader, model=cfg.model, device=cfg.server_device, - state_path=cfg.state_path, ) strategy = instantiate( cfg.strategy, evaluate_fn=evaluate_fn, on_fit_config_fn=get_on_fit_config(), - initial_parameters=fl.common.ndarrays_to_parameters(get_parameters(net_glob)), + initial_parameters=fl.common.ndarrays_to_parameters( + get_parameters(net_glob) + ), ) - # 5. Start Simulation history = fl.simulation.start_simulation( client_fn=client_fn, @@ -92,21 +104,12 @@ def fit_config_fn(server_round: int): "_memory": 30 * 1024 * 1024 * 1024, }, ) - save_results_as_pickle(history) - - # 6. Save results save_path = HydraConfig.get().runtime.output_dir - file_suffix = "_".join( - [ - repr(strategy), - cfg.dataset_config.name, - f"{cfg.seed}", - f"{cfg.dataset_config.alpha}", - f"{cfg.num_clients}", - f"{cfg.num_rounds}", - f"{cfg.clients_per_round}", - ] - ) + + save_results_as_pickle(history, file_path=save_path) + + # 6. Save results + file_suffix = "_".join([(net_glob).__class__.__name__, f"{cfg.exp_id}"]) utils.plot_metric_from_history( hist=history, diff --git a/baselines/fedpara/fedpara/models.py b/baselines/fedpara/fedpara/models.py index bb785e5b893c..458b3748b732 100644 --- a/baselines/fedpara/fedpara/models.py +++ b/baselines/fedpara/fedpara/models.py @@ -2,6 +2,7 @@ import math from typing import Dict, Tuple + import numpy as np import torch import torch.nn.functional as F @@ -10,100 +11,94 @@ from torch.nn import init from torch.utils.data import DataLoader + class LowRankNN(nn.Module): - def __init__(self,input, output, rank,activation: str = "relu",) -> None: + """Fedpara Low-rank weight systhesis for fully connected layer.""" + + def __init__(self, input, output, rank) -> None: super(LowRankNN, self).__init__() - + self.X = nn.Parameter( torch.empty(size=(input, rank)), requires_grad=True, ) - self.Y = nn.Parameter( - torch.empty(size=(output,rank)), requires_grad=True - ) + self.Y = nn.Parameter(torch.empty(size=(output, rank)), requires_grad=True) + + init.kaiming_normal_(self.X, mode="fan_out", nonlinearity="relu") + init.kaiming_normal_(self.Y, mode="fan_out", nonlinearity="relu") - if activation == "leakyrelu": - activation = "leaky_relu" - init.kaiming_normal_(self.X, mode="fan_out", nonlinearity=activation) - init.kaiming_normal_(self.Y, mode="fan_out", nonlinearity=activation) - def forward(self): - out = torch.einsum("yr,xr->yx",self.Y, self.X) + out = torch.einsum("yr,xr->yx", self.Y, self.X) return out - + + class Linear(nn.Module): - def __init__(self, input, output, ratio, activation: str = "relu",bias= True, pfedpara=True) -> None: + """Low-rank fully connected layer module for personalized scheme.""" + + def __init__(self, input, output, ratio, bias=True) -> None: super(Linear, self).__init__() - rank = self._calc_from_ratio(ratio,input, output) - self.w1 = LowRankNN(input, output, rank, activation) - self.w2 = LowRankNN(input, output, rank, activation) + rank = self._calc_from_ratio(ratio, input, output) + self.w1 = LowRankNN(input, output, rank) + self.w2 = LowRankNN(input, output, rank) # make the bias for each layer if bias: self.bias = nn.Parameter(torch.zeros(output)) - self.pfedpara = pfedpara - def _calc_from_ratio(self, ratio,input, output): + def _calc_from_ratio(self, ratio, input, output): # Return the low-rank of sub-matrices given the compression ratio # minimum possible parameter r1 = int(np.ceil(np.sqrt(output))) r2 = int(np.ceil(np.sqrt(input))) r = np.min((r1, r2)) - # maximum possible rank, + # maximum possible rank, """ - To solve it we need to know the roots of quadratic equation: ax^2+bx+c=0 - a = kernel**2 - b = out channel+ in channel - c = - num_target_params/2 - r3 is floored because we cannot take the ceil as it results a bigger number of parameters than the original problem + To solve it we need to know the roots of quadratic equation: 2*r*(m+n)=m*n """ - num_target_params = ( - output * input - ) - a, b, c = input, output,- num_target_params/2 - discriminant = b**2 - 4 * a * c - r3 = math.floor((-b+math.sqrt(discriminant))/(2*a)) - rank=math.ceil((1-ratio)*r+ ratio*r3) + r3 = math.floor((output * input) / (2 * (output + input))) + rank = math.ceil((1 - ratio) * r + ratio * r3) return rank - - def forward(self,x): + + def forward(self, x): # personalized - if self.pfedpara: - w = self.w1() * self.w2() + self.w1() - else: - w = self.w1() * self.w2() - out = F.linear(x, w,self.bias) + w = self.w1() * self.w2() + self.w1() + out = F.linear(x, w, self.bias) return out - + class FC(nn.Module): - def __init__(self, input_size=28**2, hidden_size=256, num_classes=10, ratio=0.5, param_type="lowrank",activation: str = "relu",algorithm="pfedpara"): + """2NN Fully connected layer as in the paper: https://arxiv.org/abs/1602.05629""" + + def __init__( + self, + input_size=28**2, + hidden_size=200, + num_classes=10, + ratio=0.5, + param_type="standard", + ): super(FC, self).__init__() self.input_size = input_size - self.method = algorithm.lower() if param_type == "standard": - self.fc1 = nn.Linear(input_size, hidden_size) - self.relu = nn.ReLU() - self.fc2 = nn.Linear(hidden_size, num_classes) - self.softmax = nn.Softmax(dim=1) + self.fc1 = nn.Linear(input_size, hidden_size) + self.fc2 = nn.Linear(hidden_size, 256) + self.out = nn.Linear(256, num_classes) + elif param_type == "lowrank": - pfedpara = False - if self.method == "pfedpara": - pfedpara = True - - self.fc1 = Linear(input_size, hidden_size, ratio, activation, pfedpara=pfedpara) - self.relu = nn.ReLU() - self.fc2 = Linear(hidden_size, num_classes, ratio, activation, pfedpara=pfedpara) - self.softmax = nn.Softmax(dim=1) + self.fc1 = Linear(input_size, hidden_size, ratio) + self.fc2 = Linear(hidden_size, 256, ratio) + self.out = Linear(256, num_classes, ratio) + else: raise ValueError("param_type must be either standard or lowrank") @property def model_size(self): + """Return the total number of trainable parameters (in million paramaters) and + the size of the model in MB. """ - Return the total number of trainable parameters (in million paramaters) and the size of the model in MB. - """ - total_trainable_params = sum( - p.numel() for p in self.parameters() if p.requires_grad)/1e6 + total_trainable_params = ( + sum(p.numel() for p in self.parameters() if p.requires_grad) / 1e6 + ) param_size = 0 for param in self.parameters(): param_size += param.nelement() * param.element_size() @@ -112,17 +107,17 @@ def model_size(self): buffer_size += buffer.nelement() * buffer.element_size() size_all_mb = (param_size + buffer_size) / 1024**2 return total_trainable_params, size_all_mb - - def forward(self,x): + + def forward(self, x): x = x.view(-1, self.input_size) - out = self.fc1(x) - out = self.relu(out) - out = self.fc2(out) - out = self.softmax(out) - return out + x = F.relu(self.fc1(x)) + x = F.relu(self.fc2(x)) + x = self.out(x) + return x + class LowRank(nn.Module): - """Low-rank convolutional layer.""" + """Fedpara Low-rank weight systhesis for Convolution layer.""" def __init__( # pylint: disable=too-many-arguments self, @@ -130,7 +125,6 @@ def __init__( # pylint: disable=too-many-arguments out_channels: int, low_rank: int, kernel_size: int, - activation: str = "relu", ): super().__init__() self.T = nn.Parameter( @@ -143,11 +137,9 @@ def __init__( # pylint: disable=too-many-arguments self.Y = nn.Parameter( torch.empty(size=(low_rank, in_channels)), requires_grad=True ) - if activation == "leakyrelu": - activation = "leaky_relu" - init.kaiming_normal_(self.T, mode="fan_out", nonlinearity=activation) - init.kaiming_normal_(self.X, mode="fan_out", nonlinearity=activation) - init.kaiming_normal_(self.Y, mode="fan_out", nonlinearity=activation) + init.kaiming_normal_(self.T, mode="fan_out", nonlinearity="relu") + init.kaiming_normal_(self.X, mode="fan_out", nonlinearity="relu") + init.kaiming_normal_(self.Y, mode="fan_out", nonlinearity="relu") def forward(self): """Forward pass.""" @@ -169,7 +161,6 @@ def __init__( # pylint: disable=too-many-arguments bias: bool = False, ratio: float = 0.1, add_nonlinear: bool = False, - activation: str = "relu", ): super().__init__() self.in_channels = in_channels @@ -181,13 +172,8 @@ def __init__( # pylint: disable=too-many-arguments self.ratio = ratio self.low_rank = self._calc_from_ratio() self.add_nonlinear = add_nonlinear - self.activation = activation - self.W1 = LowRank( - in_channels, out_channels, self.low_rank, kernel_size, activation - ) - self.W2 = LowRank( - in_channels, out_channels, self.low_rank, kernel_size, activation - ) + self.W1 = LowRank(in_channels, out_channels, self.low_rank, kernel_size) + self.W2 = LowRank(in_channels, out_channels, self.low_rank, kernel_size) self.bias = nn.Parameter(torch.zeros(out_channels)) if bias else None self.tanh = nn.Tanh() @@ -207,9 +193,7 @@ def _calc_from_ratio(self): # r3 is floored because we cannot take the ceil as it results a bigger number # of parameters than the original problem - num_target_params = ( - self.out_channels * self.in_channels * (self.kernel_size**2) - ) + num_target_params = self.out_channels * self.in_channels * (self.kernel_size**2) a, b, c = ( self.kernel_size**2, self.out_channels + self.in_channels, @@ -242,16 +226,11 @@ def __init__( # pylint: disable=too-many-arguments num_classes, num_groups=2, ratio=0.1, - activation="relu", - conv_type="lowrank", + param_type="lowrank", add_nonlinear=False, ): super().__init__() - if activation == "relu": - self.activation = nn.ReLU(inplace=True) - elif activation == "leaky_relu": - self.activation = nn.LeakyReLU(inplace=True) - self.conv_type = conv_type + self.param_type = param_type self.num_groups = num_groups self.num_classes = num_classes self.ratio = ratio @@ -281,10 +260,10 @@ def __init__( # pylint: disable=too-many-arguments self.classifier = nn.Sequential( nn.Dropout(), nn.Linear(512, 512), - self.activation, + nn.ReLU(inplace=True), nn.Dropout(), nn.Linear(512, 512), - self.activation, + nn.ReLU(inplace=True), nn.Linear(512, num_classes), ) self._init_weights() @@ -294,7 +273,7 @@ def _init_weights(self): for name, module in self.features.named_children(): module = getattr(self.features, name) if isinstance(module, nn.Conv2d): - if self.conv_type == "lowrank": + if self.param_type == "lowrank": num_channels = module.in_channels setattr( self.features, @@ -309,10 +288,9 @@ def _init_weights(self): ratio=self.ratio, add_nonlinear=self.add_nonlinear, # send the activation function to the Conv2d class - activation=self.activation.__class__.__name__.lower(), ), ) - elif self.conv_type == "standard": + elif self.param_type == "standard": n = ( module.kernel_size[0] * module.kernel_size[1] @@ -333,10 +311,10 @@ def _make_layers(self, cfg, group_norm=True): layers += [ conv2d, nn.GroupNorm(self.num_groups, v), - self.activation, + nn.ReLU(inplace=True), ] else: - layers += [conv2d, self.activation] + layers += [conv2d, nn.ReLU(inplace=True)] in_channels = v return nn.Sequential(*layers) @@ -490,6 +468,6 @@ def _train_one_epoch( # pylint: disable=too-many-arguments if __name__ == "__main__": - model = VGG(num_classes=10, num_groups=2, conv_type="standard", ratio=0.4) + model = VGG(num_classes=10, num_groups=2, param_type="standard", ratio=0.4) # Print the modified VGG16GN model architecture print(model.model_size) diff --git a/baselines/fedpara/fedpara/server.py b/baselines/fedpara/fedpara/server.py index f73618ab8569..cd370822e1f7 100644 --- a/baselines/fedpara/fedpara/server.py +++ b/baselines/fedpara/fedpara/server.py @@ -1,12 +1,14 @@ """Global evaluation function.""" from collections import OrderedDict -from typing import Callable, Dict, Optional, Tuple +from typing import Callable, Dict, List, Optional, Tuple + import torch -from flwr.common import NDArrays, Scalar +from flwr.common import Metrics, NDArrays, Scalar from hydra.utils import instantiate from omegaconf import DictConfig from torch.utils.data import DataLoader + from fedpara.models import test @@ -16,6 +18,7 @@ def get_on_fit_config(hypearparams: Dict): def fit_config_fn(server_round: int): hypearparams["curr_round"] = server_round return hypearparams + return fit_config_fn @@ -62,3 +65,12 @@ def evaluate( return loss, {"accuracy": accuracy} return evaluate + + +def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: + # Multiply accuracy of each client by number of examples used + accuracies = [num_examples * m["accuracy"] for num_examples, m in metrics] + examples = [num_examples for num_examples, _ in metrics] + print(f"accuracies: {sum(accuracies) / sum(examples)}") + # Aggregate and return custom metric (weighted average) + return {"accuracy": sum(accuracies) / sum(examples)} diff --git a/baselines/fedpara/fedpara/strategy.py b/baselines/fedpara/fedpara/strategy.py index 0a7cd788d189..87b4f2c46dbc 100644 --- a/baselines/fedpara/fedpara/strategy.py +++ b/baselines/fedpara/fedpara/strategy.py @@ -1,20 +1,7 @@ -"""FedPara strategy.""" - - +# FedPara uses FedAvg as the default strategy from flwr.server.strategy import FedAvg -class FedPara(FedAvg): - """FedPara strategy.""" - - def __init__( - self, - algorithm: str, - **kwargs, - ) -> None: +class FedAvg(FedAvg): + def __init__(self, **kwargs): super().__init__(**kwargs) - self.algorithm = algorithm - - def __repr__(self) -> str: - """Return the name of the strategy.""" - return self.algorithm diff --git a/baselines/fedpara/fedpara/utils.py b/baselines/fedpara/fedpara/utils.py index 77d70cb6ce38..da433012329d 100644 --- a/baselines/fedpara/fedpara/utils.py +++ b/baselines/fedpara/fedpara/utils.py @@ -1,8 +1,13 @@ """Utility functions for FedPara.""" + +import os +import pickle import random +import time from pathlib import Path from secrets import token_hex -from typing import Optional, Union +from typing import Optional + import matplotlib.pyplot as plt import numpy as np import torch @@ -10,7 +15,7 @@ from flwr.server import History from omegaconf import DictConfig from torch.nn import Module -import time, os, pickle + def plot_metric_from_history( hist: History, @@ -34,10 +39,9 @@ def plot_metric_from_history( suffix: Optional Optional string to add at the end of the filename for the plot. """ - metric_type = "centralized" metric_dict = ( hist.metrics_centralized - if metric_type == "centralized" + if hist.metrics_centralized else hist.metrics_distributed ) _, axs = plt.subplots() @@ -45,16 +49,14 @@ def plot_metric_from_history( r_cc = (i * 2 * model_size * int(cfg.clients_per_round) / 1024 for i in rounds) # Set the title - title = f"{cfg.strategy.algorithm} | parameters: {cfg.model.conv_type} | " - title += ( - f"{cfg.dataset_config.name} {cfg.dataset_config.partition} | Seed {cfg.seed}" - ) + # make the suffix space seperated not underscore seperated + title = " ".join(suffix.split("_")) axs.set_title(title) axs.grid(True) axs.plot(np.asarray([*r_cc]), np.asarray(values_accuracy)) axs.set_ylabel("Accuracy") axs.set_xlabel("Communication Cost (GB)") - fig_name = "_".join([metric_type, "metrics", suffix]) + ".png" + fig_name = suffix + ".png" plt.savefig(Path(save_plot_path) / Path(fig_name)) plt.close() @@ -71,9 +73,11 @@ def get_parameters(net: Module) -> NDArrays: """Get the parameters of the network.""" return [val.cpu().numpy() for _, val in net.state_dict().items()] + def save_results_as_pickle( history: History, - default_filename: Optional[str] = "results.pkl", + file_path: str, + default_filename: Optional[str] = "history.pkl", ) -> None: """Save results from simulation to pickle. @@ -94,7 +98,6 @@ def save_results_as_pickle( File used by default if file_path points to a directory instead to a file. Default: "results.pkl" """ - file_path = set_client_state_save_path("./outputs/") path = Path(file_path) # ensure path exists @@ -132,9 +135,23 @@ def set_client_state_save_path(path: str) -> str: """Set the client state save path.""" client_state_save_path = time.strftime("%Y-%m-%d") client_state_sub_path = time.strftime("%H-%M-%S") - client_state_save_path = ( - f"{path}{client_state_save_path}/{client_state_sub_path}" - ) + client_state_save_path = f"{path}{client_state_save_path}/{client_state_sub_path}" if not os.path.exists(client_state_save_path): os.makedirs(client_state_save_path) return client_state_save_path + + +def get_keys_state_dict(model, algorithm, mode: str = "local") -> list[str]: + match algorithm: + case "fedper": + if mode == "local": + return list(filter(lambda x: "fc1" not in x, model.state_dict().keys())) + elif mode == "global": + return list(filter(lambda x: "fc1" in x, model.state_dict().keys())) + case "pfedpara": + if mode == "local": + return list(filter(lambda x: "w2" in x, model.state_dict().keys())) + elif mode == "global": + return list(filter(lambda x: "w1" in x, model.state_dict().keys())) + case _: + raise NotImplementedError(f"algorithm {algorithm} not implemented") From 2fa2fff41e087dba1556e6f0c338215444222c0f Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 30 Jan 2024 13:21:02 +0000 Subject: [PATCH 34/39] fixes --- baselines/fedpara/README.md | 12 ++--- baselines/fedpara/fedpara/client.py | 33 +++++++------- baselines/fedpara/fedpara/conf/cifar10.yaml | 2 +- baselines/fedpara/fedpara/conf/cifar100.yaml | 2 +- .../fedpara/fedpara/conf/mnist_fedavg.yaml | 2 +- .../fedpara/fedpara/conf/mnist_fedper.yaml | 2 +- .../fedpara/fedpara/conf/mnist_pfedpara.yaml | 2 +- .../fedpara/fedpara/dataset_preparation.py | 7 +-- baselines/fedpara/fedpara/main.py | 12 ++--- baselines/fedpara/fedpara/models.py | 45 ++++++++++--------- baselines/fedpara/fedpara/server.py | 3 +- baselines/fedpara/fedpara/strategy.py | 10 ++--- baselines/fedpara/fedpara/utils.py | 16 ++++--- baselines/fedpara/pyproject.toml | 4 +- 14 files changed, 82 insertions(+), 70 deletions(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 0c46567ac492..5e14eba62274 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -118,7 +118,7 @@ python -m fedpara.main python -m fedpara.main num_rounds=2024 num_epochs=1 # Choose parameterization scheme: lowrank or original (normal weights) -python -m fedpara.main model.conv_type=standard # or lowrank (default) +python -m fedpara.main model.param_type=standard # or lowrank (default) # Choosing between non IID and IID scheme python -m fedpara.main dataset_config.partition=iid # or non-iid (default) @@ -132,17 +132,17 @@ python -m fedpara.main --config-name cifar100 # change settings as shown above i ## Expected Results -To reproduce the curves shown below (which correspond to those in Figure 3 in the paper), run the following commands. Experiments running with `model.conv_type=lowrank` correspond to those with `-FedPara` in the legend of the figures below. Those with `model.conv_type=standard` are labelled with the `-orig` (as original) tag. +To reproduce the curves shown below (which correspond to those in Figure 3 in the paper), run the following commands. Experiments running with `model.param_type=lowrank` correspond to those with `-FedPara` in the legend of the figures below. Those with `model.param_type=standard` are labelled with the `-orig` (as original) tag. ```bash # To run fedpara for non-iid CIFAR-10 on vgg16 for lowrank and original schemes -python -m fedpara.main --multirun model.conv_type=standard,lowrank +python -m fedpara.main --multirun model.param_type=standard,lowrank # To run fedpara for non-iid CIFAR-100 on vgg16 for lowrank and original schemes -python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standard,lowrank +python -m fedpara.main --config-name cifar100 --multirun model.param_type=standard,lowrank # To run fedpara for iid CIFAR-10 on vgg16 for lowrank and original schemes -python -m fedpara.main --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid +python -m fedpara.main --multirun model.param_type=standard,lowrank num_epochs=10 dataset_config.partition=iid # To run fedpara for iid CIFAR-100 on vgg16 for lowrank and original schemes -python -m fedpara.main --config-name cifar100 --multirun model.conv_type=standard,lowrank num_epochs=10 dataset_config.partition=iid +python -m fedpara.main --config-name cifar100 --multirun model.param_type=standard,lowrank num_epochs=10 dataset_config.partition=iid # To run fedavg for non-iid MINST on FC python -m fedpara.main --config-name mnist_fedavg # To run fedper for non-iid MINST on FC diff --git a/baselines/fedpara/fedpara/client.py b/baselines/fedpara/fedpara/client.py index 030c38666c82..24f4e87ebd34 100644 --- a/baselines/fedpara/fedpara/client.py +++ b/baselines/fedpara/fedpara/client.py @@ -38,6 +38,7 @@ def get_parameters(self, config: Dict[str, Scalar]) -> NDArrays: return [val.cpu().numpy() for _, val in self.net.state_dict().items()] def set_parameters(self, parameters: NDArrays) -> None: + """Apply parameters to model state dict.""" params_dict = zip(self.net.state_dict().keys(), parameters) state_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) self.net.load_state_dict(state_dict, strict=True) @@ -64,10 +65,11 @@ def fit( ) +# pylint: disable=too-many-instance-attributes class PFlowerClient(fl.client.NumPyClient): """Personalized Flower Client.""" - def __init__( + def __init__( # pylint: disable=too-many-arguments self, cid: int, net: torch.nn.Module, @@ -95,7 +97,8 @@ def get_parameters(self, config: Dict[str, Scalar]) -> NDArrays: model_dict[k] = self.private_server_param[k] return [val.cpu().numpy() for _, val in model_dict.items()] - def set_parameters(self, parameters: NDArrays, evaluate: False) -> None: + def set_parameters(self, parameters: NDArrays, evaluate: bool) -> None: + """Apply parameters to model state dict.""" self.private_server_param: Dict[str, torch.Tensor] = {} params_dict = zip(self.net.state_dict().keys(), parameters) server_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) @@ -135,7 +138,7 @@ def fit( self.device, epochs=self.num_epochs, hyperparams=config, - epoch=config["curr_round"], + epoch=int(config["curr_round"]), ) if self.state_path is not None: with open(self.state_path, "wb") as f: @@ -158,6 +161,7 @@ def evaluate( return loss, len(self.test_loader), {"accuracy": accuracy} +# pylint: disable=too-many-arguments def gen_client_fn( train_loaders: List[DataLoader], model: DictConfig, @@ -170,27 +174,26 @@ def gen_client_fn( def client_fn(cid: str) -> fl.client.NumPyClient: """Create a new FlowerClient for a given cid.""" - cid = int(cid) + cid_ = int(cid) device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") if args["algorithm"].lower() == "pfedpara" or args["algorithm"] == "fedper": - cl_path = f"{state_path}/client_{cid}.pth" + cl_path = f"{state_path}/client_{cid_}.pth" return PFlowerClient( - cid=cid, + cid=cid_, net=instantiate(model).to(device), - train_loader=train_loaders[cid], + train_loader=train_loaders[cid_], test_loader=copy.deepcopy(test_loader), num_epochs=num_epochs, state_path=cl_path, algorithm=args["algorithm"].lower(), device=device, ) - else: - return FlowerClient( - cid=cid, - net=instantiate(model).to(device), - train_loader=train_loaders[cid], - num_epochs=num_epochs, - device=device, - ) + return FlowerClient( + cid=cid_, + net=instantiate(model).to(device), + train_loader=train_loaders[cid_], + num_epochs=num_epochs, + device=device, + ) return client_fn diff --git a/baselines/fedpara/fedpara/conf/cifar10.yaml b/baselines/fedpara/fedpara/conf/cifar10.yaml index 13f5a07b5f93..b6288196b431 100644 --- a/baselines/fedpara/fedpara/conf/cifar10.yaml +++ b/baselines/fedpara/fedpara/conf/cifar10.yaml @@ -32,7 +32,7 @@ hyperparams: strategy: - _target_: fedpara.strategy.FedAvg + _target_: flwr.server.strategy.FedAvg fraction_fit: 0.00001 fraction_evaluate: 0.0 min_evaluate_clients: 0 diff --git a/baselines/fedpara/fedpara/conf/cifar100.yaml b/baselines/fedpara/fedpara/conf/cifar100.yaml index 4563ee949c31..2431de389840 100644 --- a/baselines/fedpara/fedpara/conf/cifar100.yaml +++ b/baselines/fedpara/fedpara/conf/cifar100.yaml @@ -31,7 +31,7 @@ hyperparams: learning_decay: 0.992 strategy: - _target_: fedpara.strategy.FedAvg + _target_: flwr.server.strategy.FedAvg fraction_fit: 0.00001 fraction_evaluate: 0.0 min_evaluate_clients: 0 diff --git a/baselines/fedpara/fedpara/conf/mnist_fedavg.yaml b/baselines/fedpara/fedpara/conf/mnist_fedavg.yaml index 2429f9b20d6e..e1088df236eb 100644 --- a/baselines/fedpara/fedpara/conf/mnist_fedavg.yaml +++ b/baselines/fedpara/fedpara/conf/mnist_fedavg.yaml @@ -28,7 +28,7 @@ hyperparams: learning_decay: 1 strategy: - _target_: fedpara.strategy.FedAvg + _target_: flwr.server.strategy.FedAvg fraction_fit: 0.00001 fraction_evaluate: 0 min_evaluate_clients: 0 diff --git a/baselines/fedpara/fedpara/conf/mnist_fedper.yaml b/baselines/fedpara/fedpara/conf/mnist_fedper.yaml index 13d2dedfc0ad..9a23b3cfb434 100644 --- a/baselines/fedpara/fedpara/conf/mnist_fedper.yaml +++ b/baselines/fedpara/fedpara/conf/mnist_fedper.yaml @@ -34,7 +34,7 @@ hyperparams: learning_decay: 0 strategy: - _target_: fedpara.strategy.FedAvg + _target_: flwr.server.strategy.FedAvg fraction_fit: 0.00001 fraction_evaluate: 0.00001 min_evaluate_clients: ${clients_per_round} diff --git a/baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml b/baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml index 7bf8b3979e06..483e340d672a 100644 --- a/baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml +++ b/baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml @@ -31,7 +31,7 @@ hyperparams: learning_decay: 0.999 strategy: - _target_: fedpara.strategy.FedAvg + _target_: flwr.server.strategy.FedAvg fraction_fit: 0.00001 fraction_evaluate: 0.00001 min_evaluate_clients: ${clients_per_round} diff --git a/baselines/fedpara/fedpara/dataset_preparation.py b/baselines/fedpara/fedpara/dataset_preparation.py index 2de43be318ce..2f3922e97cef 100644 --- a/baselines/fedpara/fedpara/dataset_preparation.py +++ b/baselines/fedpara/fedpara/dataset_preparation.py @@ -102,18 +102,19 @@ def noniid(dataset, no_participants, alpha=0.5): def data_to_tensor(data): - """Loads dataset to memory, applies transform.""" + """Load dataset to memory, applies transform.""" loader = torch.utils.data.DataLoader(data, batch_size=len(data)) img, label = next(iter(loader)) return img, label def noniid_partition_loader(data, m_per_shard=300, n_shards_per_client=2): - """semi-pathological client sample partition + """Partition in semi-pathological fashion. + 1. sort examples by label, form shards of size 300 by grouping points successively 2. each client is 2 random shards - most clients will have 2 digits, at most 4 + most clients will have 2 digits, at most 4. """ # load data into memory img, label = data_to_tensor(data) diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index b18ae70f101f..55084f262802 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -1,7 +1,10 @@ """Main script for running FedPara.""" +from typing import Dict + import flwr as fl import hydra +from flwr.common import Scalar from hydra.core.hydra_config import HydraConfig from hydra.utils import instantiate from omegaconf import DictConfig, OmegaConf @@ -56,7 +59,9 @@ def main(cfg: DictConfig) -> None: def get_on_fit_config(): def fit_config_fn(server_round: int): - fit_config = OmegaConf.to_container(cfg.hyperparams, resolve=True) + fit_config: Dict[str, Scalar] = OmegaConf.to_container( + cfg.hyperparams, resolve=True + ) # type: ignore fit_config["curr_round"] = server_round return fit_config @@ -98,11 +103,6 @@ def fit_config_fn(server_round: int): config=fl.server.ServerConfig(num_rounds=cfg.num_rounds), strategy=strategy, client_resources=cfg.client_resources, - ray_init_args={ - "num_cpus": 40, - "num_gpus": 1, - "_memory": 30 * 1024 * 1024 * 1024, - }, ) save_path = HydraConfig.get().runtime.output_dir diff --git a/baselines/fedpara/fedpara/models.py b/baselines/fedpara/fedpara/models.py index 458b3748b732..370720ff58ce 100644 --- a/baselines/fedpara/fedpara/models.py +++ b/baselines/fedpara/fedpara/models.py @@ -15,11 +15,11 @@ class LowRankNN(nn.Module): """Fedpara Low-rank weight systhesis for fully connected layer.""" - def __init__(self, input, output, rank) -> None: - super(LowRankNN, self).__init__() + def __init__(self, input_, output, rank) -> None: + super().__init__() self.X = nn.Parameter( - torch.empty(size=(input, rank)), + torch.empty(size=(input_, rank)), requires_grad=True, ) self.Y = nn.Parameter(torch.empty(size=(output, rank)), requires_grad=True) @@ -28,6 +28,7 @@ def __init__(self, input, output, rank) -> None: init.kaiming_normal_(self.Y, mode="fan_out", nonlinearity="relu") def forward(self): + """Forward pass.""" out = torch.einsum("yr,xr->yx", self.Y, self.X) return out @@ -35,30 +36,30 @@ def forward(self): class Linear(nn.Module): """Low-rank fully connected layer module for personalized scheme.""" - def __init__(self, input, output, ratio, bias=True) -> None: - super(Linear, self).__init__() - rank = self._calc_from_ratio(ratio, input, output) - self.w1 = LowRankNN(input, output, rank) - self.w2 = LowRankNN(input, output, rank) + def __init__(self, input_, output, ratio, bias=True) -> None: + super().__init__() + rank = self._calc_from_ratio(ratio, input_, output) + self.w1 = LowRankNN(input_, output, rank) + self.w2 = LowRankNN(input_, output, rank) # make the bias for each layer if bias: self.bias = nn.Parameter(torch.zeros(output)) - def _calc_from_ratio(self, ratio, input, output): + @staticmethod + def _calc_from_ratio(ratio, input_, output): # Return the low-rank of sub-matrices given the compression ratio # minimum possible parameter r1 = int(np.ceil(np.sqrt(output))) - r2 = int(np.ceil(np.sqrt(input))) + r2 = int(np.ceil(np.sqrt(input_))) r = np.min((r1, r2)) - # maximum possible rank, - """ - To solve it we need to know the roots of quadratic equation: 2*r*(m+n)=m*n - """ - r3 = math.floor((output * input) / (2 * (output + input))) + # maximum possible rank + # To solve it we need to know the roots of quadratic equation: 2*r*(m+n)=m*n + r3 = math.floor((output * input_) / (2 * (output + input_))) rank = math.ceil((1 - ratio) * r + ratio * r3) return rank def forward(self, x): + """Forward pass.""" # personalized w = self.w1() * self.w2() + self.w1() out = F.linear(x, w, self.bias) @@ -66,9 +67,9 @@ def forward(self, x): class FC(nn.Module): - """2NN Fully connected layer as in the paper: https://arxiv.org/abs/1602.05629""" + """2NN Fully connected layer as in the paper: https://arxiv.org/abs/1602.05629.""" - def __init__( + def __init__( # pylint: disable=too-many-arguments self, input_size=28**2, hidden_size=200, @@ -76,7 +77,7 @@ def __init__( ratio=0.5, param_type="standard", ): - super(FC, self).__init__() + super().__init__() self.input_size = input_size if param_type == "standard": self.fc1 = nn.Linear(input_size, hidden_size) @@ -93,7 +94,8 @@ def __init__( @property def model_size(self): - """Return the total number of trainable parameters (in million paramaters) and + """Return the total number of trainable parameters (in million paramaters) and. + the size of the model in MB. """ total_trainable_params = ( @@ -109,6 +111,7 @@ def model_size(self): return total_trainable_params, size_all_mb def forward(self, x): + """Forward pass.""" x = x.view(-1, self.input_size) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) @@ -193,7 +196,9 @@ def _calc_from_ratio(self): # r3 is floored because we cannot take the ceil as it results a bigger number # of parameters than the original problem - num_target_params = self.out_channels * self.in_channels * (self.kernel_size**2) + num_target_params = ( + self.out_channels * self.in_channels * (self.kernel_size**2) + ) a, b, c = ( self.kernel_size**2, self.out_channels + self.in_channels, diff --git a/baselines/fedpara/fedpara/server.py b/baselines/fedpara/fedpara/server.py index cd370822e1f7..036efa090c52 100644 --- a/baselines/fedpara/fedpara/server.py +++ b/baselines/fedpara/fedpara/server.py @@ -68,8 +68,9 @@ def evaluate( def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: + """Do weighted average of metrics.""" # Multiply accuracy of each client by number of examples used - accuracies = [num_examples * m["accuracy"] for num_examples, m in metrics] + accuracies = [float(num_examples * m["accuracy"]) for num_examples, m in metrics] examples = [num_examples for num_examples, _ in metrics] print(f"accuracies: {sum(accuracies) / sum(examples)}") # Aggregate and return custom metric (weighted average) diff --git a/baselines/fedpara/fedpara/strategy.py b/baselines/fedpara/fedpara/strategy.py index 87b4f2c46dbc..17436c401c30 100644 --- a/baselines/fedpara/fedpara/strategy.py +++ b/baselines/fedpara/fedpara/strategy.py @@ -1,7 +1,5 @@ -# FedPara uses FedAvg as the default strategy -from flwr.server.strategy import FedAvg +"""Optionally define a custom strategy. - -class FedAvg(FedAvg): - def __init__(self, **kwargs): - super().__init__(**kwargs) +Needed only when the strategy is not yet implemented in Flower or because you want to +extend or modify the functionality of an existing strategy. +""" diff --git a/baselines/fedpara/fedpara/utils.py b/baselines/fedpara/fedpara/utils.py index da433012329d..fcaf95f50c59 100644 --- a/baselines/fedpara/fedpara/utils.py +++ b/baselines/fedpara/fedpara/utils.py @@ -6,7 +6,7 @@ import time from pathlib import Path from secrets import token_hex -from typing import Optional +from typing import List, Optional import matplotlib.pyplot as plt import numpy as np @@ -141,17 +141,21 @@ def set_client_state_save_path(path: str) -> str: return client_state_save_path -def get_keys_state_dict(model, algorithm, mode: str = "local") -> list[str]: +def get_keys_state_dict(model, algorithm, mode: str = "local") -> List[str]: + """.""" + keys: List[str] = [] match algorithm: case "fedper": if mode == "local": - return list(filter(lambda x: "fc1" not in x, model.state_dict().keys())) + keys = list(filter(lambda x: "fc1" not in x, model.state_dict().keys())) elif mode == "global": - return list(filter(lambda x: "fc1" in x, model.state_dict().keys())) + keys = list(filter(lambda x: "fc1" in x, model.state_dict().keys())) case "pfedpara": if mode == "local": - return list(filter(lambda x: "w2" in x, model.state_dict().keys())) + keys = list(filter(lambda x: "w2" in x, model.state_dict().keys())) elif mode == "global": - return list(filter(lambda x: "w1" in x, model.state_dict().keys())) + keys = list(filter(lambda x: "w1" in x, model.state_dict().keys())) case _: raise NotImplementedError(f"algorithm {algorithm} not implemented") + + return keys diff --git a/baselines/fedpara/pyproject.toml b/baselines/fedpara/pyproject.toml index d0e404c69fd0..3582841b8df2 100644 --- a/baselines/fedpara/pyproject.toml +++ b/baselines/fedpara/pyproject.toml @@ -37,7 +37,7 @@ classifiers = [ ] [tool.poetry.dependencies] -python = ">=3.8.15, <3.12.0" # don't change this +python = ">=3.10, <3.12.0" # don't change this flwr = { extras = ["simulation"], version = "1.5.0" } hydra-core = "1.3.2" # don't change this matplotlib = "^3.7.2" @@ -83,7 +83,7 @@ plugins = "numpy.typing.mypy_plugin" [tool.pylint."MESSAGES CONTROL"] disable = "bad-continuation,duplicate-code,too-few-public-methods,useless-import-alias" -good-names = "i,j,k,_,x,y,X,Y,n,T,W1,W2,r1,r2,r,a,b,c,r3,W,W,n,v,lr" +good-names = "i,j,k,_,x,y,X,Y,m,w1,w2,W1,W2,n,r1,r2,r3,W,w,v,lr,f,r,T,a,b,c" signature-mutators="hydra.main.main" [tool.pylint.typecheck] From 288d54e83602f1cfb3a91c470bc23fcd01d657c7 Mon Sep 17 00:00:00 2001 From: yehias21 Date: Tue, 30 Jan 2024 17:20:32 +0200 Subject: [PATCH 35/39] - readme and client bug fixed --- baselines/fedpara/README.md | 10 +++++++++- baselines/fedpara/fedpara/client.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 5e14eba62274..1b80c385ead8 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -88,6 +88,8 @@ As for the parameters ratio ($\gamma$) we use the following model sizes. As in t - The Jacobian correction was not incorporated into our implementation, primarily due to the lack of explicit instructions in the paper regarding the specific implementation of the dual update principle mentioned in the Jacobian correction section. +- It was observed that data generation is crutial for model convergence + ## Environment Setup To construct the Python environment follow these steps: @@ -172,4 +174,10 @@ Communication costs as measured as described in the paper: ### NON-IID MINST (FedAvg vs FedPer vs pFedPara) **Important Note: The only federated averaging (FedAvg) implementation replicates the results outlined in the paper. However, challenges with convergence were encountered when applying pFedPara and FedPer methods.** -![Personalization algorithms](_static/non-iid_mnist_personalization.png) \ No newline at end of file +![Personalization algorithms](_static/non-iid_mnist_personalization.png) + +## Code Acknowledgments +Our code is inspired from these repos: +- [Fedpara low rank tensor CNN class structure](https://github.com/South-hw/FedPara_ICLR22) +- [Non-IID mnist data preparation](https://github.com/nimeshagrawal/FedAvg-Pytorch) +- [Cifar non IID data generation](https://github.com/guobbin/PFL-MoE) diff --git a/baselines/fedpara/fedpara/client.py b/baselines/fedpara/fedpara/client.py index 24f4e87ebd34..c6645091e7e2 100644 --- a/baselines/fedpara/fedpara/client.py +++ b/baselines/fedpara/fedpara/client.py @@ -88,6 +88,7 @@ def __init__( # pylint: disable=too-many-arguments self.num_epochs = num_epochs self.state_path = state_path self.algorithm = algorithm + self.private_server_param: Dict[str, torch.Tensor] = {} def get_parameters(self, config: Dict[str, Scalar]) -> NDArrays: """Return the parameters of the current net.""" @@ -99,7 +100,6 @@ def get_parameters(self, config: Dict[str, Scalar]) -> NDArrays: def set_parameters(self, parameters: NDArrays, evaluate: bool) -> None: """Apply parameters to model state dict.""" - self.private_server_param: Dict[str, torch.Tensor] = {} params_dict = zip(self.net.state_dict().keys(), parameters) server_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) self.private_server_param = { From 3bf12c5125045b46fda4f5f27ee2e42eb10bd4b0 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 30 Jan 2024 21:51:31 +0000 Subject: [PATCH 36/39] updates --- baselines/fedpara/fedpara/client.py | 2 -- baselines/fedpara/fedpara/conf/cifar10.yaml | 2 +- baselines/fedpara/fedpara/conf/cifar100.yaml | 2 +- baselines/fedpara/fedpara/conf/mnist_fedavg.yaml | 2 +- baselines/fedpara/fedpara/conf/mnist_fedper.yaml | 5 ++++- baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml | 2 +- baselines/fedpara/fedpara/main.py | 2 +- baselines/fedpara/pyproject.toml | 2 +- 8 files changed, 10 insertions(+), 9 deletions(-) diff --git a/baselines/fedpara/fedpara/client.py b/baselines/fedpara/fedpara/client.py index c6645091e7e2..a435e82c6bf8 100644 --- a/baselines/fedpara/fedpara/client.py +++ b/baselines/fedpara/fedpara/client.py @@ -130,7 +130,6 @@ def fit( ) -> Tuple[NDArrays, int, Dict]: """Train the network on the training set.""" self.set_parameters(parameters, evaluate=False) - print(f"Client {self.cid} Training...") train( self.net, @@ -155,7 +154,6 @@ def evaluate( ) -> Tuple[float, int, Dict]: """Evaluate the network on the test set.""" self.set_parameters(parameters, evaluate=True) - print(f"Client {self.cid} Evaluating...") self.net.to(self.device) loss, accuracy = test(self.net, self.test_loader, device=self.device) return loss, len(self.test_loader), {"accuracy": accuracy} diff --git a/baselines/fedpara/fedpara/conf/cifar10.yaml b/baselines/fedpara/fedpara/conf/cifar10.yaml index b6288196b431..97ec2520c3c1 100644 --- a/baselines/fedpara/fedpara/conf/cifar10.yaml +++ b/baselines/fedpara/fedpara/conf/cifar10.yaml @@ -40,4 +40,4 @@ strategy: min_available_clients: ${clients_per_round} accept_failures: false -exp_id: ${algorithm}_${dataset_config.name}_${seed}_${dataset_config.partition}_${dataset_config.alpha}_${model.param_type}_${model.ratio} +exp_id: ${model.param_type}_${dataset_config.name}_${dataset_config.partition}_alpha${dataset_config.alpha} diff --git a/baselines/fedpara/fedpara/conf/cifar100.yaml b/baselines/fedpara/fedpara/conf/cifar100.yaml index 2431de389840..d1023014605b 100644 --- a/baselines/fedpara/fedpara/conf/cifar100.yaml +++ b/baselines/fedpara/fedpara/conf/cifar100.yaml @@ -39,4 +39,4 @@ strategy: min_available_clients: ${clients_per_round} accept_failures: false -exp_id: ${algorithm}_${dataset_config.name}_${seed}_${dataset_config.partition}_${dataset_config.alpha}_${model.param_type}_${model.ratio} \ No newline at end of file +exp_id: ${model.param_type}_${dataset_config.name}_${dataset_config.partition}_alpha${dataset_config.alpha} \ No newline at end of file diff --git a/baselines/fedpara/fedpara/conf/mnist_fedavg.yaml b/baselines/fedpara/fedpara/conf/mnist_fedavg.yaml index e1088df236eb..d0cda9a62e76 100644 --- a/baselines/fedpara/fedpara/conf/mnist_fedavg.yaml +++ b/baselines/fedpara/fedpara/conf/mnist_fedavg.yaml @@ -35,4 +35,4 @@ strategy: min_fit_clients: ${clients_per_round} min_available_clients: ${clients_per_round} -exp_id: ${algorithm}_${dataset_config.name}_${seed} +exp_id: ${algorithm}_${dataset_config.name} diff --git a/baselines/fedpara/fedpara/conf/mnist_fedper.yaml b/baselines/fedpara/fedpara/conf/mnist_fedper.yaml index 9a23b3cfb434..dd9034284459 100644 --- a/baselines/fedpara/fedpara/conf/mnist_fedper.yaml +++ b/baselines/fedpara/fedpara/conf/mnist_fedper.yaml @@ -39,4 +39,7 @@ strategy: fraction_evaluate: 0.00001 min_evaluate_clients: ${clients_per_round} min_fit_clients: ${clients_per_round} - min_available_clients: ${clients_per_round} \ No newline at end of file + min_available_clients: ${clients_per_round} + + +exp_id: ${algorithm}_${dataset_config.name} diff --git a/baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml b/baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml index 483e340d672a..5733397def0f 100644 --- a/baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml +++ b/baselines/fedpara/fedpara/conf/mnist_pfedpara.yaml @@ -38,4 +38,4 @@ strategy: min_fit_clients: ${clients_per_round} min_available_clients: ${clients_per_round} -exp_id: ${algorithm}_${dataset_config.name}_${seed}_${model.param_type}_${model.ratio} +exp_id: ${algorithm}_${dataset_config.name}_${model.param_type}_${model.ratio} diff --git a/baselines/fedpara/fedpara/main.py b/baselines/fedpara/fedpara/main.py index 55084f262802..2397a20e17ef 100644 --- a/baselines/fedpara/fedpara/main.py +++ b/baselines/fedpara/fedpara/main.py @@ -20,7 +20,7 @@ ) -@hydra.main(config_path="conf", config_name="cifar100", version_base=None) +@hydra.main(config_path="conf", config_name="cifar10", version_base=None) def main(cfg: DictConfig) -> None: """Run the baseline. diff --git a/baselines/fedpara/pyproject.toml b/baselines/fedpara/pyproject.toml index 3582841b8df2..4d7f5261c71b 100644 --- a/baselines/fedpara/pyproject.toml +++ b/baselines/fedpara/pyproject.toml @@ -7,7 +7,7 @@ name = "fedpara" # <----- Ensure it matches the name of your baseline directory version = "1.0.0" description = "Flower Baselines" license = "Apache-2.0" -authors = ["Yahia Salaheldin Shaaban ", "Omar Mokhtar", "Roeia Amr"] +authors = ["Yahia Salaheldin Shaaban ", "Omar Mokhtar < >", "Roeia Amr < >"] readme = "README.md" homepage = "https://flower.dev" repository = "https://github.com/adap/flower" From fac5854d2c67db036e869e3f895e863690c23c58 Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 30 Jan 2024 21:54:23 +0000 Subject: [PATCH 37/39] w/ previous --- baselines/fedpara/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/baselines/fedpara/README.md b/baselines/fedpara/README.md index 1b80c385ead8..068366aa261c 100644 --- a/baselines/fedpara/README.md +++ b/baselines/fedpara/README.md @@ -173,7 +173,9 @@ Communication costs as measured as described in the paper: |![CIFAR10 iid](_static/Cifar10_iid.jpeg) | ![CIFAR10 non-iid](_static/Cifar10_noniid.jpeg) | ### NON-IID MINST (FedAvg vs FedPer vs pFedPara) -**Important Note: The only federated averaging (FedAvg) implementation replicates the results outlined in the paper. However, challenges with convergence were encountered when applying pFedPara and FedPer methods.** + +The only federated averaging (FedAvg) implementation replicates the results outlined in the paper. However, challenges with convergence were encountered when applying `pFedPara` and `FedPer` methods. + ![Personalization algorithms](_static/non-iid_mnist_personalization.png) ## Code Acknowledgments From e399f618a031220521cbb0e6f16fd547525610ea Mon Sep 17 00:00:00 2001 From: jafermarq Date: Tue, 30 Jan 2024 23:55:06 +0000 Subject: [PATCH 38/39] done? --- README.md | 1 + baselines/fedpara/pyproject.toml | 1 + baselines/fedper/fedper/server.py | 1 - doc/source/ref-changelog.md | 2 ++ 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 750b5cdb4b93..4c6119f258bf 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ Flower Baselines is a collection of community-contributed projects that reproduc - [MOON](https://github.com/adap/flower/tree/main/baselines/moon) - [niid-Bench](https://github.com/adap/flower/tree/main/baselines/niid_bench) - [TAMUNA](https://github.com/adap/flower/tree/main/baselines/tamuna) +- [FedPara](https://github.com/adap/flower/tree/main/baselines/fedpara) - [FedAvg](https://github.com/adap/flower/tree/main/baselines/flwr_baselines/flwr_baselines/publications/fedavg_mnist) - [FedOpt](https://github.com/adap/flower/tree/main/baselines/flwr_baselines/flwr_baselines/publications/adaptive_federated_optimization) diff --git a/baselines/fedpara/pyproject.toml b/baselines/fedpara/pyproject.toml index 4d7f5261c71b..73414aa66b84 100644 --- a/baselines/fedpara/pyproject.toml +++ b/baselines/fedpara/pyproject.toml @@ -56,6 +56,7 @@ pytest = "==6.2.4" pytest-watch = "==4.2.0" ruff = "==0.0.272" types-requests = "==2.27.7" +virtualenv = "20.21.0" [tool.isort] line_length = 88 diff --git a/baselines/fedper/fedper/server.py b/baselines/fedper/fedper/server.py index 50a4f8c5d8a8..93616f50f45a 100644 --- a/baselines/fedper/fedper/server.py +++ b/baselines/fedper/fedper/server.py @@ -1,5 +1,4 @@ """Server strategies pipelines for FedPer.""" - from flwr.server.strategy.fedavg import FedAvg from fedper.strategy import ( diff --git a/doc/source/ref-changelog.md b/doc/source/ref-changelog.md index c4aad511a4a5..d3f663d69720 100644 --- a/doc/source/ref-changelog.md +++ b/doc/source/ref-changelog.md @@ -18,6 +18,8 @@ - HeteroFL [#2439](https://github.com/adap/flower/pull/2439) + - FedPara [#2722](https://github.com/adap/flower/pull/2722) + ## v1.6.0 (2023-11-28) ### Thanks to our contributors From 012ee6689f23906db1275926d7d5f57ca65044ce Mon Sep 17 00:00:00 2001 From: jafermarq Date: Wed, 31 Jan 2024 00:03:06 +0000 Subject: [PATCH 39/39] fix --- baselines/fedpara/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baselines/fedpara/pyproject.toml b/baselines/fedpara/pyproject.toml index 73414aa66b84..c5751c536043 100644 --- a/baselines/fedpara/pyproject.toml +++ b/baselines/fedpara/pyproject.toml @@ -7,7 +7,7 @@ name = "fedpara" # <----- Ensure it matches the name of your baseline directory version = "1.0.0" description = "Flower Baselines" license = "Apache-2.0" -authors = ["Yahia Salaheldin Shaaban ", "Omar Mokhtar < >", "Roeia Amr < >"] +authors = ["Yahia Salaheldin Shaaban ", "Omar Mokhtar < >", "Roeia Amr < >"] readme = "README.md" homepage = "https://flower.dev" repository = "https://github.com/adap/flower"