From d8305fbd6b8f880000e79b318faa214b07f7d257 Mon Sep 17 00:00:00 2001 From: salmantoor Date: Mon, 6 May 2024 21:49:14 +0000 Subject: [PATCH] Adding updates that are compatible with fedn v0.9.0 --- Power-consumption-keras/.dockerignore | 4 + Power-consumption-keras/.gitignore | 6 + Power-consumption-keras/Dockerfile | 16 +++ Power-consumption-keras/client/data.py | 68 +++++++++++ Power-consumption-keras/client/fedn.yaml | 10 ++ Power-consumption-keras/client/model.py | 36 ++++++ .../client/python_env.yaml | 9 ++ .../client/python_env_macosx.yaml | 10 ++ Power-consumption-keras/client/train.py | 64 ++++++++++ Power-consumption-keras/client/validate.py | 59 +++++++++ .../requirements-macos.txt | 4 + Power-consumption-keras/requirements.txt | 4 + Power-consumption-pytorch/.dockerignore | 4 + Power-consumption-pytorch/.gitignore | 6 + Power-consumption-pytorch/Dockerfile | 16 +++ Power-consumption-pytorch/client/data.py | 114 ++++++++++++++++++ Power-consumption-pytorch/client/fedn.yaml | 10 ++ Power-consumption-pytorch/client/model.py | 104 ++++++++++++++++ .../client/python_env.yaml | 9 ++ Power-consumption-pytorch/client/train.py | 87 +++++++++++++ Power-consumption-pytorch/client/validate.py | 69 +++++++++++ Power-consumption-pytorch/requirements.txt | 4 + 22 files changed, 713 insertions(+) create mode 100644 Power-consumption-keras/.dockerignore create mode 100644 Power-consumption-keras/.gitignore create mode 100644 Power-consumption-keras/Dockerfile create mode 100644 Power-consumption-keras/client/data.py create mode 100644 Power-consumption-keras/client/fedn.yaml create mode 100644 Power-consumption-keras/client/model.py create mode 100644 Power-consumption-keras/client/python_env.yaml create mode 100644 Power-consumption-keras/client/python_env_macosx.yaml create mode 100644 Power-consumption-keras/client/train.py create mode 100644 Power-consumption-keras/client/validate.py create mode 100644 Power-consumption-keras/requirements-macos.txt create mode 100644 Power-consumption-keras/requirements.txt create mode 100644 Power-consumption-pytorch/.dockerignore create mode 100644 Power-consumption-pytorch/.gitignore create mode 100644 Power-consumption-pytorch/Dockerfile create mode 100644 Power-consumption-pytorch/client/data.py create mode 100644 Power-consumption-pytorch/client/fedn.yaml create mode 100644 Power-consumption-pytorch/client/model.py create mode 100644 Power-consumption-pytorch/client/python_env.yaml create mode 100644 Power-consumption-pytorch/client/train.py create mode 100644 Power-consumption-pytorch/client/validate.py create mode 100644 Power-consumption-pytorch/requirements.txt diff --git a/Power-consumption-keras/.dockerignore b/Power-consumption-keras/.dockerignore new file mode 100644 index 0000000..8ba9024 --- /dev/null +++ b/Power-consumption-keras/.dockerignore @@ -0,0 +1,4 @@ +data +seed.npz +*.tgz +*.tar.gz \ No newline at end of file diff --git a/Power-consumption-keras/.gitignore b/Power-consumption-keras/.gitignore new file mode 100644 index 0000000..4e531b1 --- /dev/null +++ b/Power-consumption-keras/.gitignore @@ -0,0 +1,6 @@ +data +*.npz +*.tgz +*.tar.gz +.mnist-keras +client.yaml \ No newline at end of file diff --git a/Power-consumption-keras/Dockerfile b/Power-consumption-keras/Dockerfile new file mode 100644 index 0000000..e953af6 --- /dev/null +++ b/Power-consumption-keras/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.10.6-slim as base +LABEL maintainer="salman@scaleoutsystems.com" +WORKDIR /app +COPY requirements.txt . +RUN apt-get update \ + && apt-get install --no-install-recommends -y git \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && pip install git+https://github.com/scaleoutsystems/fedn.git@master#egg=fedn\&subdirectory=fedn \ + && pip install --no-cache-dir -r requirements.txt + + +FROM python:3.10.6-slim as build +COPY --from=base /usr/local/lib/python3.10/site-packages/ /usr/local/lib/python3.10/site-packages/ +COPY --from=base /usr/local/bin/fedn /usr/local/bin/ +WORKDIR /app diff --git a/Power-consumption-keras/client/data.py b/Power-consumption-keras/client/data.py new file mode 100644 index 0000000..8566130 --- /dev/null +++ b/Power-consumption-keras/client/data.py @@ -0,0 +1,68 @@ +import os +import shutil +from math import floor + +import numpy as np +dir_path = os.path.dirname(os.path.realpath(__file__)) +abs_path = os.path.abspath(dir_path) + +def copy_files(source_dir, destination_dir): + # Get a list of all files in the source directory + files = os.listdir(source_dir) + + # Iterate through the files and copy them to the destination directory + for file_name in files: + source_file = os.path.join(source_dir, file_name) + destination_file = os.path.join(destination_dir, file_name) + shutil.copy2(source_file, destination_file) + print(f"Copied {file_name} to {destination_file}") + +def get_data(out_dir='data'): + # Make dir if necessary + if not os.path.exists(out_dir): + os.mkdir(out_dir) + + print('dir_path: ', dir_path) + parent_dir = os.path.abspath(os.path.join(dir_path, os.pardir)) + print('parent_dir: ', parent_dir) + + source_dir = parent_dir+'/data' + destination_dir = dir_path+'/data' + + copy_files(source_dir, destination_dir) + +def load_data(data_path=None, is_train=True): + """ Load data from disk. + + :param data_path: Path to data file. + :type data_path: str + :param is_train: Whether to load training or test data. + :type is_train: bool + :return: Tuple of data and labels. + :rtype: tuple + """ + if data_path is None: + data_path = os.environ.get("FEDN_DATA_PATH", abs_path+'/data/power.npz') + + data = np.load(data_path) + + if is_train: + X = data['x_train'] + y = data['y_train'] + else: + X = data['x_test'] + y = data['y_test'] + + # Normalize + X = X / 255 + + return X, y + +if __name__ == '__main__': + # Prepare data if not already done + if not os.path.exists(abs_path+'/data'): + print('Note: The data directory does not exist. Loading the data..') + get_data() + else: + print('Good to go, The data directory exist.') + diff --git a/Power-consumption-keras/client/fedn.yaml b/Power-consumption-keras/client/fedn.yaml new file mode 100644 index 0000000..4d08c58 --- /dev/null +++ b/Power-consumption-keras/client/fedn.yaml @@ -0,0 +1,10 @@ +python_env: python_env.yaml +entry_points: + build: + command: python model.py + startup: + command: python data.py + train: + command: python train.py + validate: + command: python validate.py diff --git a/Power-consumption-keras/client/model.py b/Power-consumption-keras/client/model.py new file mode 100644 index 0000000..ac188e0 --- /dev/null +++ b/Power-consumption-keras/client/model.py @@ -0,0 +1,36 @@ + +import json +import os + +import numpy as np +import tensorflow as tf + + +from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics + +HELPER_MODULE = 'numpyhelper' +helper = get_helper(HELPER_MODULE) + + +def compile_model(img_rows=28, img_cols=28): + # Set input shape + #input_shape = (img_rows, img_cols, 1) + + # Define model + opt = tf.keras.optimizers.SGD(lr=0.0001) + model = tf.keras.models.Sequential() + model.add(tf.keras.layers.Dense(64, input_dim=4, activation="relu")) + model.add(tf.keras.layers.Dense(32, activation="relu")) + model.add(tf.keras.layers.Dense(1, activation="linear")) + #model.summary() + model.compile(loss = "mse", optimizer = opt,metrics=['mae']) + + return model + +def init_seed(out_path='seed.npz'): + + weights = compile_model().get_weights() + helper.save(weights, out_path) + +if __name__ == "__main__": + init_seed('../seed.npz') diff --git a/Power-consumption-keras/client/python_env.yaml b/Power-consumption-keras/client/python_env.yaml new file mode 100644 index 0000000..cb2ca00 --- /dev/null +++ b/Power-consumption-keras/client/python_env.yaml @@ -0,0 +1,9 @@ +name: power-consumption-keras +build_dependencies: + - pip + - setuptools + - wheel==0.37.1 +dependencies: + - tensorflow==2.13.1 + - fire==0.3.1 + - fedn==0.9.0 diff --git a/Power-consumption-keras/client/python_env_macosx.yaml b/Power-consumption-keras/client/python_env_macosx.yaml new file mode 100644 index 0000000..d13fc46 --- /dev/null +++ b/Power-consumption-keras/client/python_env_macosx.yaml @@ -0,0 +1,10 @@ +name: mnist-keras +build_dependencies: + - pip + - setuptools + - wheel==0.37.1 +dependencies: + - tensorflow-macos + - tensorflow-metal + - fire==0.3.1 + - fedn==0.9.0b2 diff --git a/Power-consumption-keras/client/train.py b/Power-consumption-keras/client/train.py new file mode 100644 index 0000000..bd04942 --- /dev/null +++ b/Power-consumption-keras/client/train.py @@ -0,0 +1,64 @@ +import json +import os +import sys +import fire +import numpy as np +import tensorflow as tf + +from data import load_data +from model import compile_model + +from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics + +HELPER_MODULE = 'numpyhelper' +helper = get_helper(HELPER_MODULE) + +def train(in_model_path, out_model_path, data_path=None, batch_size=32, epochs=1): + """ Complete a model update. + + Load model paramters from in_model_path (managed by the FEDn client), + perform a model update, and write updated paramters + to out_model_path (picked up by the FEDn client). + + :param in_model_path: The path to the input model. + :type in_model_path: str + :param out_model_path: The path to save the output model to. + :type out_model_path: str + :param data_path: The path to the data file. + :type data_path: str + :param batch_size: The batch size to use. + :type batch_size: int + :param epochs: The number of epochs to train. + :type epochs: int + """ + # Load data + x_train, y_train = load_data(data_path) + + # Load model + model = compile_model() + #weights = load_parameters(in_model_path) + weights = helper.load(in_model_path) + model.set_weights(weights) + + # Train + model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs) + + # Metadata needed for aggregation server side + metadata = { + # num_examples are mandatory + 'num_examples': len(x_train), + 'batch_size': batch_size, + 'epochs': epochs, + } + + # Save JSON metadata file (mandatory) + save_metadata(metadata, out_model_path) + + # Save model update (mandatory) + weights = model.get_weights() + helper.save(weights, out_model_path) + + +if __name__ == "__main__": + train(sys.argv[1], sys.argv[2]) + diff --git a/Power-consumption-keras/client/validate.py b/Power-consumption-keras/client/validate.py new file mode 100644 index 0000000..ec1ed98 --- /dev/null +++ b/Power-consumption-keras/client/validate.py @@ -0,0 +1,59 @@ +import json +import os +import sys +import fire +import numpy as np +import tensorflow as tf + +from data import load_data +from model import compile_model + +from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics + +HELPER_MODULE = 'numpyhelper' +helper = get_helper(HELPER_MODULE) + +dir_path = os.path.dirname(os.path.realpath(__file__)) +abs_path = os.path.abspath(dir_path) + + +def validate(in_model_path, out_json_path, data_path=None): + """ Validate model. + + :param in_model_path: The path to the input model. + :type in_model_path: str + :param out_json_path: The path to save the output JSON to. + :type out_json_path: str + :param data_path: The path to the data file. + :type data_path: str + """ + + # Load data + x_train, y_train = load_data(data_path) + x_test, y_test = load_data(data_path, is_train=False) + + # Load model + model = compile_model() + helper = get_helper(HELPER_MODULE) + weights = helper.load(in_model_path) + model.set_weights(weights) + + # Evaluate + model_score = model.evaluate(x_train, y_train) + model_score_test = model.evaluate(x_test, y_test) + y_pred = model.predict(x_test) + y_pred = np.argmax(y_pred, axis=1) + + # JSON schema + report = { + "training_loss": model_score[0], + "training_accuracy": model_score[1], + "test_loss": model_score_test[0], + "test_accuracy": model_score_test[1], + } + + # Save JSON + save_metrics(report, out_json_path) + +if __name__ == "__main__": + validate(sys.argv[1], sys.argv[2]) diff --git a/Power-consumption-keras/requirements-macos.txt b/Power-consumption-keras/requirements-macos.txt new file mode 100644 index 0000000..4770f97 --- /dev/null +++ b/Power-consumption-keras/requirements-macos.txt @@ -0,0 +1,4 @@ +tensorflow-macos +tensorflow-metal +fire==0.3.1 +docker==5.0.2 diff --git a/Power-consumption-keras/requirements.txt b/Power-consumption-keras/requirements.txt new file mode 100644 index 0000000..0572615 --- /dev/null +++ b/Power-consumption-keras/requirements.txt @@ -0,0 +1,4 @@ +tensorflow==2.13.1 +fire==0.3.1 +docker==6.1.1 +fedn==0.9.0 diff --git a/Power-consumption-pytorch/.dockerignore b/Power-consumption-pytorch/.dockerignore new file mode 100644 index 0000000..8ba9024 --- /dev/null +++ b/Power-consumption-pytorch/.dockerignore @@ -0,0 +1,4 @@ +data +seed.npz +*.tgz +*.tar.gz \ No newline at end of file diff --git a/Power-consumption-pytorch/.gitignore b/Power-consumption-pytorch/.gitignore new file mode 100644 index 0000000..8bc2232 --- /dev/null +++ b/Power-consumption-pytorch/.gitignore @@ -0,0 +1,6 @@ +data +*.npz +*.tgz +*.tar.gz +.power-consumption-pytorch +client.yaml diff --git a/Power-consumption-pytorch/Dockerfile b/Power-consumption-pytorch/Dockerfile new file mode 100644 index 0000000..e953af6 --- /dev/null +++ b/Power-consumption-pytorch/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.10.6-slim as base +LABEL maintainer="salman@scaleoutsystems.com" +WORKDIR /app +COPY requirements.txt . +RUN apt-get update \ + && apt-get install --no-install-recommends -y git \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && pip install git+https://github.com/scaleoutsystems/fedn.git@master#egg=fedn\&subdirectory=fedn \ + && pip install --no-cache-dir -r requirements.txt + + +FROM python:3.10.6-slim as build +COPY --from=base /usr/local/lib/python3.10/site-packages/ /usr/local/lib/python3.10/site-packages/ +COPY --from=base /usr/local/bin/fedn /usr/local/bin/ +WORKDIR /app diff --git a/Power-consumption-pytorch/client/data.py b/Power-consumption-pytorch/client/data.py new file mode 100644 index 0000000..e89b80a --- /dev/null +++ b/Power-consumption-pytorch/client/data.py @@ -0,0 +1,114 @@ +import os +import shutil +from math import floor + +import torch +import torchvision +import numpy as np +dir_path = os.path.dirname(os.path.realpath(__file__)) +abs_path = os.path.abspath(dir_path) + +def copy_files(source_dir, destination_dir): + # Get a list of all files in the source directory + files = os.listdir(source_dir) + + # Iterate through the files and copy them to the destination directory + for file_name in files: + source_file = os.path.join(source_dir, file_name) + destination_file = os.path.join(destination_dir, file_name) + shutil.copy2(source_file, destination_file) + print(f"Copied {file_name} to {destination_file}") + + +def get_data(out_dir='data'): + # Make dir if necessary + if not os.path.exists(out_dir): + os.mkdir(out_dir) + + print('dir_path: ', dir_path) + parent_dir = os.path.abspath(os.path.join(dir_path, os.pardir)) + print('parent_dir: ', parent_dir) + + source_dir = parent_dir+'/data' + destination_dir = dir_path+'/data' + + copy_files(source_dir, destination_dir) + +def load_data(data_path=None, is_train=True): + """ Load data from disk. + + :param data_path: Path to data file. + :type data_path: str + :param is_train: Whether to load training or test data. + :type is_train: bool + :return: Tuple of data and labels. + :rtype: tuple + """ + if data_path is None: + data_path = os.environ.get("FEDN_DATA_PATH", abs_path+'/data/power.npz') + + data = np.load(data_path) + + if is_train: + X = data['x_train'] + y = data['y_train'] + else: + X = data['x_test'] + y = data['y_test'] + + # Normalize + X = X / 255 + + return X, y + + +def splitset(dataset, parts): + n = dataset.shape[0] + local_n = floor(n/parts) + result = [] + for i in range(parts): + result.append(dataset[i*local_n: (i+1)*local_n]) + return result + + +def split(out_dir='data'): + + n_splits = int(os.environ.get("FEDN_NUM_DATA_SPLITS", 2)) + + # Make dir + if not os.path.exists(f'{out_dir}/clients'): + os.mkdir(f'{out_dir}/clients') + + # Load and convert to dict + train_data = torchvision.datasets.MNIST( + root=f'{out_dir}/train', transform=torchvision.transforms.ToTensor, train=True) + test_data = torchvision.datasets.MNIST( + root=f'{out_dir}/test', transform=torchvision.transforms.ToTensor, train=False) + data = { + 'x_train': splitset(train_data.data, n_splits), + 'y_train': splitset(train_data.targets, n_splits), + 'x_test': splitset(test_data.data, n_splits), + 'y_test': splitset(test_data.targets, n_splits), + } + + # Make splits + for i in range(n_splits): + subdir = f'{out_dir}/clients/{str(i+1)}' + if not os.path.exists(subdir): + os.mkdir(subdir) + torch.save({ + 'x_train': data['x_train'][i], + 'y_train': data['y_train'][i], + 'x_test': data['x_test'][i], + 'y_test': data['y_test'][i], + }, + f'{subdir}/mnist.pt') + + +if __name__ == '__main__': + # Prepare data if not already done + if not os.path.exists(abs_path+'/data'): + print('Note: The data directory does not exist. Loading the data..') + get_data() + else: + print('Good to go, The data directory exist.') diff --git a/Power-consumption-pytorch/client/fedn.yaml b/Power-consumption-pytorch/client/fedn.yaml new file mode 100644 index 0000000..b055041 --- /dev/null +++ b/Power-consumption-pytorch/client/fedn.yaml @@ -0,0 +1,10 @@ +python_env: python_env.yaml +entry_points: + build: + command: python model.py + startup: + command: python data.py + train: + command: python train.py + validate: + command: python validate.py \ No newline at end of file diff --git a/Power-consumption-pytorch/client/model.py b/Power-consumption-pytorch/client/model.py new file mode 100644 index 0000000..3bfcf9d --- /dev/null +++ b/Power-consumption-pytorch/client/model.py @@ -0,0 +1,104 @@ +import collections + +import torch + +from fedn.utils.helpers.helpers import get_helper + +HELPER_MODULE = 'numpyhelper' +helper = get_helper(HELPER_MODULE) + +from torch.nn import Linear +from torch.nn import ReLU +from torch.nn import Sigmoid +from torch.nn import Module +from torch.optim import SGD + +from torch.nn.init import kaiming_uniform_ +from torch.nn.init import xavier_uniform_ + + +def compile_model(): + """ Compile the pytorch model. + + :return: The compiled model. + :rtype: torch.nn.Module + """ + class Net(torch.nn.Module): + def __init__(self): + super(Net, self).__init__() + + self.hidden1 = torch.nn.Linear(4, 64) + kaiming_uniform_(self.hidden1.weight, nonlinearity='relu') + self.act1 = ReLU() + + self.hidden2 = Linear(64, 32) + kaiming_uniform_(self.hidden2.weight, nonlinearity='relu') + self.act2 = ReLU() + + self.hidden3 = Linear(32, 1) + xavier_uniform_(self.hidden3.weight) + + + def forward(self, x): + + # input to first hidden layer + x = self.hidden1(x) + x = self.act1(x) + + # second hidden layer + x = self.hidden2(x) + x = self.act2(x) + + # third hidden layer and output + x = self.hidden3(x) + #x = self.act3(x) + + return x + + # Return model + return Net() + + + +def save_parameters(model, out_path): + """ Save model paramters to file. + + :param model: The model to serialize. + :type model: torch.nn.Module + :param out_path: The path to save to. + :type out_path: str + """ + parameters_np = [val.cpu().numpy() for _, val in model.state_dict().items()] + helper.save(parameters_np, out_path) + + +def load_parameters(model_path): + """ Load model parameters from file and populate model. + + param model_path: The path to load from. + :type model_path: str + :return: The loaded model. + :rtype: torch.nn.Module + """ + model = compile_model() + parameters_np = helper.load(model_path) + + params_dict = zip(model.state_dict().keys(), parameters_np) + state_dict = collections.OrderedDict({key: torch.tensor(x) for key, x in params_dict}) + model.load_state_dict(state_dict, strict=True) + return model + + +def init_seed(out_path='seed.npz'): + """ Initialize seed model and save it to file. + + :param out_path: The path to save the seed model to. + :type out_path: str + """ + # Init and save + model = compile_model() + save_parameters(model, out_path) + + +if __name__ == "__main__": + init_seed('../seed.npz') diff --git a/Power-consumption-pytorch/client/python_env.yaml b/Power-consumption-pytorch/client/python_env.yaml new file mode 100644 index 0000000..5b0f872 --- /dev/null +++ b/Power-consumption-pytorch/client/python_env.yaml @@ -0,0 +1,9 @@ +name: power-consumption-pytorch +build_dependencies: + - pip + - setuptools + - wheel==0.37.1 +dependencies: + - torch==2.2.1 + - torchvision==0.17.1 + - fedn==0.9.0 diff --git a/Power-consumption-pytorch/client/train.py b/Power-consumption-pytorch/client/train.py new file mode 100644 index 0000000..f69e9bc --- /dev/null +++ b/Power-consumption-pytorch/client/train.py @@ -0,0 +1,87 @@ +import math +import os +import sys + +import torch +from data import load_data +from model import load_parameters, save_parameters + +import numpy as np +from fedn.utils.helpers.helpers import save_metadata + +dir_path = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.abspath(dir_path)) + + +def train(in_model_path, out_model_path, data_path=None, batch_size=32, epochs=1, lr=0.01): + """ Complete a model update. + + Load model paramters from in_model_path (managed by the FEDn client), + perform a model update, and write updated paramters + to out_model_path (picked up by the FEDn client). + + :param in_model_path: The path to the input model. + :type in_model_path: str + :param out_model_path: The path to save the output model to. + :type out_model_path: str + :param data_path: The path to the data file. + :type data_path: str + :param batch_size: The batch size to use. + :type batch_size: int + :param epochs: The number of epochs to train. + :type epochs: int + :param lr: The learning rate to use. + :type lr: float + """ + # Load data + x_train, y_train = load_data(data_path) + + # Load parmeters and initialize model + model = load_parameters(in_model_path) + + # Train + optimizer = torch.optim.SGD(model.parameters(), lr=lr) + n_batches = int(math.ceil(len(x_train) / batch_size)) + #criterion = torch.nn.NLLLoss() + criterion = torch.nn.L1Loss() + + for e in range(epochs): # epoch loop + for b in range(n_batches): # batch loop + # Retrieve current batch + #batch_x = x_train[b * batch_size:(b + 1) * batch_size] + batch_x_tmp = torch.from_numpy(x_train[b * batch_size:(b + 1) * batch_size]) + batch_x = torch.tensor(batch_x_tmp, dtype=torch.float32) + + #batch_y = y_train[b * batch_size:(b + 1) * batch_size] + batch_y_tmp = torch.from_numpy(np.expand_dims(y_train[b * batch_size:(b + 1) * batch_size],-1)) + batch_y = torch.tensor(batch_y_tmp, dtype=torch.float32) + + # Train on batch + optimizer.zero_grad() + outputs = model(batch_x) + loss = criterion(outputs, batch_y) + loss.backward() + optimizer.step() + # Log + if b % 100 == 0: + print( + f"Epoch {e}/{epochs-1} | Batch: {b}/{n_batches-1} | Loss: {loss.item()}") + + # Metadata needed for aggregation server side + metadata = { + # num_examples are mandatory + 'num_examples': len(x_train), + 'batch_size': batch_size, + 'epochs': epochs, + 'lr': lr + } + + # Save JSON metadata file (mandatory) + save_metadata(metadata, out_model_path) + + # Save model update (mandatory) + save_parameters(model, out_model_path) + + +if __name__ == "__main__": + train(sys.argv[1], sys.argv[2]) diff --git a/Power-consumption-pytorch/client/validate.py b/Power-consumption-pytorch/client/validate.py new file mode 100644 index 0000000..aabdba4 --- /dev/null +++ b/Power-consumption-pytorch/client/validate.py @@ -0,0 +1,69 @@ +import os +import sys + +import torch +from data import load_data +from model import load_parameters + +import numpy as np + +from fedn.utils.helpers.helpers import save_metrics + +dir_path = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.abspath(dir_path)) + + +def validate(in_model_path, out_json_path, data_path=None): + """ Validate model. + + :param in_model_path: The path to the input model. + :type in_model_path: str + :param out_json_path: The path to save the output JSON to. + :type out_json_path: str + :param data_path: The path to the data file. + :type data_path: str + """ + # Load data + x_train, y_train = load_data(data_path) + x_test, y_test = load_data(data_path, is_train=False) + + # Load model + model = load_parameters(in_model_path) + model.eval() + + # Evaluate + criterion_mae = torch.nn.L1Loss() + criterion_mse = torch.nn.MSELoss() + with torch.no_grad(): + x_train_t = torch.tensor(x_train, dtype=torch.float32) + train_out = model(x_train_t) + + y_train = torch.from_numpy(np.expand_dims(y_train,-1)) + y_train_t = torch.tensor(y_train, dtype=torch.float32) + + training_loss_mae = criterion_mae(train_out, y_train_t) + training_loss_mse = criterion_mse(train_out, y_train_t) + + x_test_t = torch.tensor(x_test, dtype=torch.float32) + test_out = model(x_test_t) + + y_test = torch.from_numpy(np.expand_dims(y_test,-1)) + y_test_t = torch.tensor(y_test, dtype=torch.float32) + + test_loss_mae = criterion_mae(test_out, y_test_t) + test_loss_mse = criterion_mse(test_out, y_test_t) + + # JSON schema + report = { + "test_mae": str(test_loss_mae.item()), + "test_mse": str(test_loss_mse.item()), + "training_mae": str(training_loss_mae.item()), + "training_mse": str(training_loss_mse.item()), + + } + + # Save JSON + save_metrics(report, out_json_path) + +if __name__ == "__main__": + validate(sys.argv[1], sys.argv[2]) diff --git a/Power-consumption-pytorch/requirements.txt b/Power-consumption-pytorch/requirements.txt new file mode 100644 index 0000000..09253d8 --- /dev/null +++ b/Power-consumption-pytorch/requirements.txt @@ -0,0 +1,4 @@ +torch==2.2.1 +torchvision==0.17.1 +fedn==0.9.0 +