diff --git a/Dockerfile b/Dockerfile
index 88cface8..7d9d039f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -30,10 +30,13 @@ ADD configs configs
ADD fltk fltk
-# Update relevant runtime configuration for experiment
-COPY cloud_configs/cloud_experiment.yaml configs/cloud_config.yaml
+
# Install newest version of library
RUN python3 -m setup install
# Expose the container's port to the host OS
EXPOSE 5000
+
+
+# Update relevant runtime configuration for experiment
+COPY cloud_configs/cloud_experiment.yaml configs/cloud_config.yaml
\ No newline at end of file
diff --git a/charts/federator/templates/fl-server-pod.yaml b/charts/federator/templates/fl-server-pod.yaml
index e8c0f3e6..3b88db45 100644
--- a/charts/federator/templates/fl-server-pod.yaml
+++ b/charts/federator/templates/fl-server-pod.yaml
@@ -15,7 +15,7 @@ spec:
- -m
- fltk
- poison
- - configs/cloud_experiment.yaml
+ - configs/cloud_config.yaml
- --rank=0
env:
- name: MASTER_PORT
diff --git a/cloud_configs/cloud_experiment.yaml b/cloud_configs/cloud_experiment.yaml
index 566ab044..ec24c498 100644
--- a/cloud_configs/cloud_experiment.yaml
+++ b/cloud_configs/cloud_experiment.yaml
@@ -29,3 +29,7 @@ poison:
type: "flip"
config:
- 5: 3
+antidote:
+ type: "clustering"
+ f: 0
+ k: 1
\ No newline at end of file
diff --git a/configs/local_experiment.yaml b/configs/local_experiment.yaml
index 7a660b87..4786295a 100644
--- a/configs/local_experiment.yaml
+++ b/configs/local_experiment.yaml
@@ -1,5 +1,5 @@
# Experiment configuration
-total_epochs: 1
+total_epochs: 35
epochs_per_cycle: 1
wait_for_clients: true
net: Cifar10CNN
@@ -13,7 +13,7 @@ cuda: false
experiment_prefix: 'experiment_single_machine'
output_location: 'output'
tensor_board_active: true
-clients_per_round: 2
+clients_per_round: 3
system:
federator:
# Use the SERVICE provided by the fl-server to connect
@@ -21,7 +21,7 @@ system:
# Default NIC is eth0
nic: 'eth0'
clients:
- amount: 2
+ amount: 3
# For a simple config is provided in configs/poison.example.yaml
poison:
seed: 420
@@ -30,5 +30,9 @@ poison:
type: "flip"
config:
- 5: 3
+antidote:
+ type: "clustering"
+ f: 0
+ k: 1
diff --git a/fltk/datasets/distributed/cifar10.py b/fltk/datasets/distributed/cifar10.py
index 58fb5d63..f4b5376b 100644
--- a/fltk/datasets/distributed/cifar10.py
+++ b/fltk/datasets/distributed/cifar10.py
@@ -2,7 +2,6 @@
from torch.utils.data import DataLoader
import logging
-from memory_profiler import profile
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision import transforms
diff --git a/fltk/federator.py b/fltk/federator.py
index 4ee4cef3..cbd6307f 100644
--- a/fltk/federator.py
+++ b/fltk/federator.py
@@ -85,7 +85,7 @@ def __init__(self, client_id_triple, num_epochs=3, config: BareConfig = None, at
self.attack = attack
logging.info(f'Federator with attack {attack}')
self.antidote = antidote
- logging.info(f'Fedetrator with antidote {antidote}')
+ logging.info(f'Federator with antidote {antidote}')
self.log_rref = log_rref
self.num_epoch = num_epochs
@@ -236,7 +236,10 @@ def remote_run_epoch(self, epochs, ratio=None, store_grad=False):
self.epoch_counter)
client_weights.append(weights)
- updated_model = self.antidote.process_gradients(client_weights, epoch = self.epoch_counter)
+ # TODO: Make sure that we keep track of whose gradient we are dealing with
+ updated_model = self.antidote.process_gradients(client_weights, epoch=self.epoch_counter,
+ clients=selected_clients,
+ model=self.test_data.net.state_dict())
self.test_data.net.load_state_dict(updated_model)
# test global model
logging.info("Testing on global test set")
@@ -277,7 +280,7 @@ def save_epoch_data(self, ratio=None):
def ensure_path_exists(self, path):
Path(path).mkdir(parents=True, exist_ok=True)
- def run(self, ratios=[0.12, 0.18, 0.0]):
+ def run(self, ratios=[0.12, 0.18, 0.6, 0.0]):
"""
Main loop of the Federator
:return:
@@ -324,6 +327,7 @@ def run(self, ratios=[0.12, 0.18, 0.0]):
self.client_reset_model()
# Reset dataloader, etc. for next experiment
self.set_data()
+ self.antidote.save_data_and_reset(rat)
logging.info(f'Federator is stopping')
diff --git a/fltk/strategy/antidote.py b/fltk/strategy/antidote.py
index 96fd7a37..9f16a6ca 100644
--- a/fltk/strategy/antidote.py
+++ b/fltk/strategy/antidote.py
@@ -1,16 +1,15 @@
+import logging
from abc import abstractmethod, ABC
+
import numpy as np
-import torch
+from sklearn.cluster import KMeans
+from sklearn.decomposition import PCA
+from sklearn.preprocessing import StandardScaler
-from fltk.client import Client
-from fltk.nets.util.utils import flatten_params
from fltk.strategy.util.antidote import calc_krum_scores
from fltk.util.base_config import BareConfig
from fltk.util.fed_avg import average_nn_parameters
-from sklearn.cluster import KMeans
-from sklearn.decomposition import PCA
-from sklearn.preprocessing import StandardScaler
class Antidote(ABC):
@@ -21,6 +20,10 @@ def __init__(self):
def process_gradients(self, gradients, **kwargs):
pass
+ def save_data_and_reset(self, ratio, iteration=0):
+ pass
+
+
class DummyAntidote(Antidote):
def __init__(self, cfg: BareConfig):
@@ -30,6 +33,7 @@ def __init__(self, cfg: BareConfig):
def process_gradients(self, gradients, **kwargs):
return average_nn_parameters(gradients)
+
class MultiKrumAntidote(Antidote):
def __init__(self, cfg: BareConfig, **kwargs):
@@ -50,81 +54,151 @@ def process_gradients(self, gradients, **kwargs):
class ClusterAntidote(Antidote):
- @staticmethod
- def ema(s_t_prev, value, t, rho, bias_correction = True):
- s_t = rho * s_t_prev + (1 - rho) * value
+ def ema(self, s_t_prev, value, t, bias_correction=True):
+ """
+ Exponential Moving Average, with bias correction by default.
+ @param s_t_prev:
+ @type s_t_prev:
+ @param value:
+ @type value:
+ @param t:
+ @type t:
+ @param bias_correction:
+ @type bias_correction:
+ @return:
+ @rtype:
+ """
+ s_t = self.rho_ * s_t_prev + (1 - self.rho_) * value
s_t_hat = None
if bias_correction:
- s_t_hat = s_t / (1.0 - rho**(t + 1))
+ s_t_hat = s_t / (1.0 - self.rho_ ** (t + 1))
return s_t_hat if bias_correction else s_t
def __init__(self, cfg: BareConfig, **kwargs):
- Antidote.__init__(self)
+ super(ClusterAntidote, self).__init__()
+ # Needed for KRUM/Multi-KRUM for testing purposes.
self.f = cfg.get_antidote_f_value()
self.k = cfg.get_antidote_k_value()
self.past_gradients = np.array([])
- # TODO: Not hardcode this for cifar10
- self.class_targeted = np.zeros((10, cfg.epochs))
- # Rho for this round poisoned
- self.rho_1 = 0.5
+ self.logger = logging.getLogger()
# Rho for this class poisoned
- self.rho_1 = 0.75
+ self.rho_ = 0.75
self.max_epoch = 130
self.num_classes = 10
+ self.offset = 20
+
+ # Logging information for testing only
+ self.krum_proposed = list()
+ self.selected_updates = list()
+ self.cheating_client = dict()
+ self.class_targeted = np.zeros((10, cfg.epochs + 2))
+
def process_gradients(self, gradients, **kwargs):
"""
Function which returns the average of the k gradient with the lowest score.
"""
epoch_indx = kwargs['epoch']
+ clients_round = kwargs['clients']
+ model = kwargs['model']
+ cur_last = list(model.values())[-2].numpy()
# First 10 epochs we effectively don't do much
if epoch_indx > 10:
- new_connected_grads = [next(reversed(gradient.values())).numpy() for gradient in gradients]
- self.past_gradients = np.stack([self.past_gradients] + new_connected_grads)
+ # Store gradients
+ new_connected_grads = [list(gradient.values())[-2].numpy() - cur_last for gradient in gradients]
+ # The array may be empty
+ if self.past_gradients.size:
+ self.past_gradients = np.vstack([self.past_gradients] + new_connected_grads)
+ else:
+ self.past_gradients = np.vstack(new_connected_grads)
# If collected enough data, we continue to the next round
- if epoch_indx > 20:
- trusty_indices = self.target_malicious(gradients, epoch_indx)
+ if epoch_indx > self.offset:
+ trusty_indices = self.target_malicious(gradients, epoch_indx, clients_round)
return average_nn_parameters([gradients[indx] for indx in trusty_indices])
return average_nn_parameters(gradients)
- def target_malicious(self, gradients, epoch_indx):
+ def target_malicious(self, gradients, epoch_indx, clients_round):
truthy_gradient = np.zeros((self.num_classes, len(gradients)), dtype=bool)
+ krum_scores = calc_krum_scores(gradients, int(np.ceil(1/3 * len(gradients))))
+ # Take the index of smallest client.
+ most_likely_good_index = np.argmin(krum_scores)
+ most_likely_good = clients_round[np.argmin(krum_scores)].name
for cls in range(self.num_classes):
# Slice to get only the rows corresponding the the output node.
sub_sample = self.past_gradients[cls::self.num_classes]
- clf = KMeans(2)
- scaler = StandardScaler()
- fitter = PCA(n_components=2)
- scaled_param_diff = scaler.fit_transform(sub_sample)
- dim_reduced_gradients = fitter.fit_transform(scaled_param_diff)
- classified = clf.fit_transform(dim_reduced_gradients)
+ classified = self.unsupervised_classification(sub_sample)
# If total is roughly 50/50 then unlikely to be poisoned. Else likely to be poisoned
cluster_split = np.average(classified)
- if 0.4 * epoch_indx * len(gradients) < cluster_split < 0.6 * len(gradients):
+ self.logger.info(f"Cluster division: {cluster_split}")
+ if 1 / 3 < cluster_split < 2 / 3:
# Roughly 50/50 divided, so we assume valid updates.
# As such, we don't need to perform KRUM, as the distribution over the two clusters
# is arbitrary. Hence, we cannot distill much information from the assignment to one of the
# two clusters.
+
+ # Use 0 as estimate, because we suspect that the class is _not_ targeted.
+ self.class_targeted[cls, epoch_indx + 1] = self.ema(self.class_targeted[cls, epoch_indx], 0,
+ epoch_indx - self.offset)
+ # Broadcast True ot the truthy_gradient matrix
truthy_gradient[cls] = True
else:
- krum_scores = calc_krum_scores(gradients)
- most_likely_good = np.argmax(krum_scores)
- # Get the label assigned to the 'krum' vector, either 1/0
- estimated_cluster = classified[-(len(gradients) - most_likely_good)]
+ # Use 0 as estimate, because we suspect that the class _is_ targeted.
+ self.class_targeted[cls, epoch_indx + 1] = self.ema(self.class_targeted[cls, epoch_indx], 1,
+ epoch_indx - self.offset)
+ biggest_cluster = 0 if cluster_split < 0.5 else 1
# Boolean array to indicate which belong to the same cluster.
- truthy_gradient[cls] = classified[-len(gradients):] == estimated_cluster
+ truthy_gradient[cls] = (classified[-len(gradients):] == classified[-len(gradients) + most_likely_good_index])
+ self.logger.info(f"Biggest: {biggest_cluster}, KRUM cluster: {classified[-len(gradients) + most_likely_good_index]}")
# Only select the gradients that we suspect that are unaffected
# Take row-wise and, as such only a column that has only 'TRUE', will be selected using
# the argwhere, because True evaluates to True.
- return np.argwhere(truthy_gradient)
+ truthy_gradient_reduced = np.prod(truthy_gradient, axis=0)
+ for indx, truthy in enumerate(truthy_gradient_reduced):
+ suspect_cheating = clients_round[indx].name
+ previous_array = self.cheating_client.get(suspect_cheating, [0])
+ previous_array.append(self.ema(previous_array[-1], 1 if truthy != 1 else 0, len(previous_array)))
+ self.cheating_client[suspect_cheating] = previous_array
+ selected_grads = np.argwhere(truthy_gradient_reduced == 1).reshape(-1)
+
+ self.krum_proposed.append(most_likely_good)
+ # Keep track of the gradients updates that we selected.
+ self.selected_updates.append([clients_round[indx].name for indx in selected_grads])
+ self.logger.info(f"KRUM: {most_likely_good}, clustered: {selected_grads}")
+ self.logger.info(f"Suspicion classes: {self.class_targeted[:, epoch_indx + 1]}")
+ self.logger.info(f"Suspicion clients: {[(k, v[-1]) for k, v in self.cheating_client.items()]}")
+ return selected_grads
+
+ def unsupervised_classification(self, sub_sample):
+ clf = KMeans(2)
+ scaler = StandardScaler()
+ fitter = PCA(n_components=2)
+ scaled_param_diff = scaler.fit_transform(sub_sample)
+ dim_reduced_gradients = fitter.fit_transform(scaled_param_diff)
+ classified = clf.fit_predict(dim_reduced_gradients)
+ return classified
+
+ def save_data_and_reset(self, ratio, iteration=0):
+ data = {
+ "selected_updates": self.selected_updates,
+ "cheating_clients": self.cheating_client,
+ "targeted_classes": self.class_targeted,
+ "krum_proposal": self.krum_proposed,
+ "gradients": self.past_gradients
+ }
+ np.save(f'./output/cluster_antidote_{ratio}_{iteration}.npy', data)
+ self.selected_updates = list()
+ self.cheating_client = dict()
+ self.class_targeted = np.zeros_like(self.class_targeted)
+ self.krum_proposed = list()
+ self.past_gradients = np.array([])
def create_antidote(cfg: BareConfig, **kwargs) -> Antidote:
assert cfg is not None
if cfg.antidote is None:
return DummyAntidote(cfg)
- medicine_cabinet = {'dummy': DummyAntidote, 'multikrum': MultiKrumAntidote, 'cluster': ClusterAntidote}
+ medicine_cabinet = {'dummy': DummyAntidote, 'multikrum': MultiKrumAntidote, 'clustering': ClusterAntidote}
antidote_class = medicine_cabinet.get(cfg.get_antidote_type(), None)
diff --git a/notebooks/convert_experiment_data.ipynb b/notebooks/convert_experiment_data.ipynb
index 039fafe8..ddabbe44 100644
--- a/notebooks/convert_experiment_data.ipynb
+++ b/notebooks/convert_experiment_data.ipynb
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 53,
+ "execution_count": 58,
"id": "efc3c88d",
"metadata": {},
"outputs": [
@@ -10,45 +10,35 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "Requirement already satisfied: pandas in ./Documents/CSE/MSc/year/1/Q4/CS4245/repo/BMT/venv/lib/python3.9/site-packages (1.2.4)\n",
- "Requirement already satisfied: numpy in ./Documents/CSE/MSc/year/1/Q4/CS4245/repo/BMT/venv/lib/python3.9/site-packages (1.19.5)\n",
- "Requirement already satisfied: python-dateutil>=2.7.3 in ./Documents/CSE/MSc/year/1/Q4/CS4245/repo/BMT/venv/lib/python3.9/site-packages (from pandas) (2.8.1)\n",
- "Requirement already satisfied: pytz>=2017.3 in ./Documents/CSE/MSc/year/1/Q4/CS4245/repo/BMT/venv/lib/python3.9/site-packages (from pandas) (2021.1)\n",
- "Requirement already satisfied: six>=1.5 in ./Documents/CSE/MSc/year/1/Q4/CS4245/repo/BMT/venv/lib/python3.9/site-packages (from python-dateutil>=2.7.3->pandas) (1.15.0)\n",
- "\u001b[33mWARNING: You are using pip version 21.1.1; however, version 21.1.2 is available.\n",
- "You should consider upgrading via the '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4245/repo/BMT/venv/bin/python -m pip install --upgrade pip' command.\u001b[0m\n"
+ "0 [0.32, 0.40277778, 0.52941176, 0.25, nan, 0.46...\n",
+ "1 [0.46808511, 0.39007092, 0.90909091, 0.1923076...\n",
+ "2 [0.46268657, 0.35802469, 0.39240506, 0.2745098...\n",
+ "3 [0.39252336, 0.3875, 0.31481481, 0.3030303, 0....\n",
+ "4 [0.51612903, 0.57, 0.30769231, 0.27737226, 0.2...\n",
+ " ... \n",
+ "195 [0.87096774, 0.95294118, 0.576, 0.71428571, 0....\n",
+ "196 [0.87628866, 0.94186047, 0.77027027, 0.75, 0.7...\n",
+ "197 [0.80555556, 0.90217391, 0.70114943, 0.7012987...\n",
+ "198 [0.84466019, 0.93258427, 0.66666667, 0.7215189...\n",
+ "199 [0.85576923, 0.94117647, 0.75949367, 0.6867469...\n",
+ "Name: class_precision, Length: 2000, dtype: object\n"
]
}
],
"source": [
- "!pip install pandas numpy\n",
+ "\n",
"from io import StringIO\n",
"import pandas as pd\n",
"import numpy as np\n",
- "from pathlib import Path"
+ "from pathlib import Path\n",
+ "print(data.class_precision)"
]
},
{
"cell_type": "code",
- "execution_count": 80,
+ "execution_count": 62,
"id": "231acd9e",
"metadata": {},
- "outputs": [],
- "source": [
- "path = \"/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/charts/extractor/fig8/output_0.0/\"\n",
- "\n",
- "def mapper(x: str) -> np.array:\n",
- " st = ','.join(x.split())\n",
- " return np.genfromtxt(StringIO(st[1:-2]), delimiter=',')\n",
- "\n",
- "data = pd.concat([pd.read_csv(client, converters={'class_precision': mapper, 'class_recall': mapper}) for client in Path(path).rglob('*.csv')])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 81,
- "id": "7b6d2327",
- "metadata": {},
"outputs": [
{
"data": {
@@ -263,15 +253,251 @@
"[2000 rows x 9 columns]"
]
},
- "execution_count": 81,
+ "execution_count": 62,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
+ "path = \"/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/charts/extractor/fig8/output_0.0/\"\n",
+ "\n",
+ "def mapper(x: str) -> np.array:\n",
+ " st = ','.join(x.split())\n",
+ " return np.genfromtxt(StringIO(st[1:-2]), delimiter=',')\n",
+ "\n",
+ "data = pd.concat([pd.read_csv(client, converters={'class_precision': mapper, 'class_recall': mapper}) for client in Path(path).rglob('*.csv')])\n",
"data"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": 75,
+ "id": "7b6d2327",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " epoch_id | \n",
+ " client_id | \n",
+ " duration_train | \n",
+ " duration_test | \n",
+ " loss_train | \n",
+ " accuracy | \n",
+ " loss | \n",
+ " class_precision | \n",
+ " class_recall | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 3 | \n",
+ " 1 | \n",
+ " client3 | \n",
+ " 49852 | \n",
+ " 3028 | \n",
+ " 2.147378 | \n",
+ " 34.763476 | \n",
+ " 121.799125 | \n",
+ " 0.32 | \n",
+ " [0.48780488, 0.725, 0.08737864, 0.03448276, 0.... | \n",
+ "
\n",
+ " \n",
+ " 13 | \n",
+ " 2 | \n",
+ " client3 | \n",
+ " 43976 | \n",
+ " 2914 | \n",
+ " 2.055118 | \n",
+ " 37.953795 | \n",
+ " 118.671685 | \n",
+ " 0.468085 | \n",
+ " [0.26829268, 0.6875, 0.09708738, 0.05747126, 0... | \n",
+ "
\n",
+ " \n",
+ " 23 | \n",
+ " 3 | \n",
+ " client3 | \n",
+ " 44172 | \n",
+ " 2872 | \n",
+ " 2.081944 | \n",
+ " 40.044004 | \n",
+ " 117.759390 | \n",
+ " 0.462687 | \n",
+ " [0.37804878, 0.725, 0.30097087, 0.32183908, 0.... | \n",
+ "
\n",
+ " \n",
+ " 33 | \n",
+ " 4 | \n",
+ " client3 | \n",
+ " 43971 | \n",
+ " 2851 | \n",
+ " 2.061278 | \n",
+ " 39.493949 | \n",
+ " 117.232983 | \n",
+ " 0.392523 | \n",
+ " [0.51219512, 0.775, 0.33009709, 0.34482759, 0.... | \n",
+ "
\n",
+ " \n",
+ " 43 | \n",
+ " 5 | \n",
+ " client3 | \n",
+ " 44330 | \n",
+ " 2730 | \n",
+ " 2.008247 | \n",
+ " 42.684268 | \n",
+ " 115.907113 | \n",
+ " 0.516129 | \n",
+ " [0.3902439, 0.7125, 0.15533981, 0.43678161, 0.... | \n",
+ "
\n",
+ " \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " 1953 | \n",
+ " 196 | \n",
+ " client3 | \n",
+ " 43801 | \n",
+ " 2963 | \n",
+ " 1.585445 | \n",
+ " 81.408141 | \n",
+ " 93.820704 | \n",
+ " 0.804878 | \n",
+ " [0.80487805, 0.9375, 0.77669903, 0.71264368, 0... | \n",
+ "
\n",
+ " \n",
+ " 1963 | \n",
+ " 197 | \n",
+ " client3 | \n",
+ " 44140 | \n",
+ " 2844 | \n",
+ " 1.593065 | \n",
+ " 81.078108 | \n",
+ " 93.799207 | \n",
+ " 0.764706 | \n",
+ " [0.79268293, 0.95, 0.76699029, 0.64367816, 0.8... | \n",
+ "
\n",
+ " \n",
+ " 1973 | \n",
+ " 198 | \n",
+ " client3 | \n",
+ " 43470 | \n",
+ " 2909 | \n",
+ " 1.600651 | \n",
+ " 82.618262 | \n",
+ " 93.303663 | \n",
+ " 0.770115 | \n",
+ " [0.81707317, 0.95, 0.75728155, 0.68965517, 0.8... | \n",
+ "
\n",
+ " \n",
+ " 1983 | \n",
+ " 199 | \n",
+ " client3 | \n",
+ " 44220 | \n",
+ " 2961 | \n",
+ " 1.695308 | \n",
+ " 81.958196 | \n",
+ " 93.492339 | \n",
+ " 0.802326 | \n",
+ " [0.84146341, 0.925, 0.74757282, 0.72413793, 0.... | \n",
+ "
\n",
+ " \n",
+ " 1993 | \n",
+ " 200 | \n",
+ " client3 | \n",
+ " 44299 | \n",
+ " 2863 | \n",
+ " 1.600772 | \n",
+ " 81.848185 | \n",
+ " 93.687668 | \n",
+ " 0.784091 | \n",
+ " [0.84146341, 0.925, 0.7184466, 0.66666667, 0.7... | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
200 rows × 9 columns
\n",
+ "
"
+ ],
+ "text/plain": [
+ " epoch_id client_id duration_train duration_test loss_train \\\n",
+ "3 1 client3 49852 3028 2.147378 \n",
+ "13 2 client3 43976 2914 2.055118 \n",
+ "23 3 client3 44172 2872 2.081944 \n",
+ "33 4 client3 43971 2851 2.061278 \n",
+ "43 5 client3 44330 2730 2.008247 \n",
+ "... ... ... ... ... ... \n",
+ "1953 196 client3 43801 2963 1.585445 \n",
+ "1963 197 client3 44140 2844 1.593065 \n",
+ "1973 198 client3 43470 2909 1.600651 \n",
+ "1983 199 client3 44220 2961 1.695308 \n",
+ "1993 200 client3 44299 2863 1.600772 \n",
+ "\n",
+ " accuracy loss class_precision \\\n",
+ "3 34.763476 121.799125 0.32 \n",
+ "13 37.953795 118.671685 0.468085 \n",
+ "23 40.044004 117.759390 0.462687 \n",
+ "33 39.493949 117.232983 0.392523 \n",
+ "43 42.684268 115.907113 0.516129 \n",
+ "... ... ... ... \n",
+ "1953 81.408141 93.820704 0.804878 \n",
+ "1963 81.078108 93.799207 0.764706 \n",
+ "1973 82.618262 93.303663 0.770115 \n",
+ "1983 81.958196 93.492339 0.802326 \n",
+ "1993 81.848185 93.687668 0.784091 \n",
+ "\n",
+ " class_recall \n",
+ "3 [0.48780488, 0.725, 0.08737864, 0.03448276, 0.... \n",
+ "13 [0.26829268, 0.6875, 0.09708738, 0.05747126, 0... \n",
+ "23 [0.37804878, 0.725, 0.30097087, 0.32183908, 0.... \n",
+ "33 [0.51219512, 0.775, 0.33009709, 0.34482759, 0.... \n",
+ "43 [0.3902439, 0.7125, 0.15533981, 0.43678161, 0.... \n",
+ "... ... \n",
+ "1953 [0.80487805, 0.9375, 0.77669903, 0.71264368, 0... \n",
+ "1963 [0.79268293, 0.95, 0.76699029, 0.64367816, 0.8... \n",
+ "1973 [0.81707317, 0.95, 0.75728155, 0.68965517, 0.8... \n",
+ "1983 [0.84146341, 0.925, 0.74757282, 0.72413793, 0.... \n",
+ "1993 [0.84146341, 0.925, 0.7184466, 0.66666667, 0.7... \n",
+ "\n",
+ "[200 rows x 9 columns]"
+ ]
+ },
+ "execution_count": 75,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "test = data.explode('class_precision').sort_index(ascending=False, kind='mergesort').groupby(['epoch_id', 'client_id']).nth(0).reset_index()\n",
+ "test[test.client_id == 'client3']"
+ ]
+ },
{
"cell_type": "code",
"execution_count": 94,
diff --git a/notebooks/gradient-PCA.ipynb b/notebooks/gradient-PCA.ipynb
index ede417b0..44a8349e 100644
--- a/notebooks/gradient-PCA.ipynb
+++ b/notebooks/gradient-PCA.ipynb
@@ -9,10 +9,11 @@
"import re\n",
"from collections import OrderedDict\n",
"from pathlib import Path\n",
- "\n",
+ "import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import sklearn.decomposition\n",
"import torch\n",
+ "import torch.nn.functional as F\n",
"from natsort import natsorted\n",
"from sklearn.preprocessing import StandardScaler\n",
"\n",
@@ -21,17 +22,6 @@
"from fltk.util.base_config import BareConfig"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "pycharm": {
- "name": "#%%\n"
- }
- },
- "outputs": [],
- "source": []
- },
{
"cell_type": "code",
"execution_count": 2,
@@ -52,33 +42,6 @@
" return torch.stack(directories)"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "pycharm": {
- "name": "#%%\n"
- }
- },
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "pycharm": {
- "name": "#%%\n"
- }
- },
- "outputs": [],
- "source": []
- },
{
"cell_type": "code",
"execution_count": 3,
@@ -97,65 +60,6 @@
" return scaler.fit_transform(gradients)"
]
},
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {
- "pycharm": {
- "name": "#%%\n"
- }
- },
- "outputs": [
- {
- "ename": "NameError",
- "evalue": "name 'directories' is not defined",
- "output_type": "error",
- "traceback": [
- "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
- "\u001B[0;31mNameError\u001B[0m Traceback (most recent call last)",
- "\u001B[0;32m\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0mdirectories\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mdirectories\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msqueeze\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m-\u001B[0m\u001B[0;36m1\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 2\u001B[0m \u001B[0mprint\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdirectories\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mshape\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mlen\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mpoisoned\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mlen\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdirectories\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;31mNameError\u001B[0m: name 'directories' is not defined"
- ]
- }
- ],
- "source": [
- "directories = directories.squeeze(-1)\n",
- "print(directories.shape, len(poisoned), len(directories))\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {
- "pycharm": {
- "name": "#%%\n"
- }
- },
- "outputs": [
- {
- "ename": "NameError",
- "evalue": "name 'directories' is not defined",
- "output_type": "error",
- "traceback": [
- "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
- "\u001B[0;31mNameError\u001B[0m Traceback (most recent call last)",
- "\u001B[0;32m\u001B[0m in \u001B[0;36m\u001B[0;34m\u001B[0m\n\u001B[0;32m----> 1\u001B[0;31m \u001B[0mtest\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mdirectories\u001B[0m\u001B[0;34m[\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;36m551078\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;36m552358\u001B[0m\u001B[0;34m]\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mview\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;36m1300\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;36m1280\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 2\u001B[0m \u001B[0mfitter\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0msklearn\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mdecomposition\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mPCA\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mn_components\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;36m2\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 3\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 4\u001B[0m \u001B[0mscaled_param_diff\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mapply_standard_scaler\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mtest\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 5\u001B[0m \u001B[0mdim_reduced_gradients\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mfitter\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mfit_transform\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mscaled_param_diff\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n",
- "\u001B[0;31mNameError\u001B[0m: name 'directories' is not defined"
- ]
- }
- ],
- "source": [
- "\n",
- "test = directories[:, 551078:552358].view(1300, 1280)\n",
- "fitter = sklearn.decomposition.PCA(n_components=2)\n",
- "\n",
- "scaled_param_diff = apply_standard_scaler(test)\n",
- "dim_reduced_gradients = fitter.fit_transform(scaled_param_diff)\n",
- "for indx in range(1300):\n",
- " plt.scatter(dim_reduced_gradients[indx, 0], dim_reduced_gradients[indx, 1], color='r' if poisoned[indx] else 'b')\n",
- "plt.show()"
- ]
- },
{
"cell_type": "code",
"execution_count": 4,
@@ -178,7 +82,7 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 5,
"metadata": {
"pycharm": {
"name": "#%%\n"
@@ -186,11 +90,6 @@
},
"outputs": [],
"source": [
- "torch\n",
- "import torch.nn as nn\n",
- "import torch.nn.functional as F\n",
- "\n",
- "\n",
"\n",
"def flatten_params(parameters):\n",
" \"\"\"\n",
@@ -242,365 +141,384 @@
},
"outputs": [],
"source": [
- "import numpy as np\n",
+ "\n",
"\n",
"model = Cifar10CNN()\n",
"default_model_path = f\"../default_models/Cifar10CNN.model\"\n",
"model.load_state_dict(torch.load(default_model_path))\n",
- "flattened_default = flatten_params(model.state_dict())\n",
- "\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 65,
- "metadata": {
- "pycharm": {
- "name": "#%%\n"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "torch.Size([552368])"
- ]
- },
- "execution_count": 65,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "test.shape"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "metadata": {
- "pycharm": {
- "name": "#%%\n"
- }
- },
- "outputs": [],
- "source": [
- "restored = flattened_default['params'].view(-1) + test"
+ "flattened_default = flatten_params(model.state_dict())"
]
},
{
"cell_type": "code",
- "execution_count": 10,
- "metadata": {
- "pycharm": {
- "name": "#%%\n"
- }
- },
+ "execution_count": 15,
+ "metadata": {},
"outputs": [
{
- "name": "stdout",
+ "name": "stderr",
"output_type": "stream",
"text": [
- "fail\n",
- "fail\n",
- "fail\n",
- "fail\n",
- "fail\n",
- "fail\n"
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n"
]
- }
- ],
- "source": [
- "recovered_params, state_dict = recover_flattened(restored.unsqueeze(-1), flattened_default['indices'], model)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 11,
- "metadata": {
- "pycharm": {
- "name": "#%%\n"
- }
- },
- "outputs": [],
- "source": [
- "recovered_model = model.load_state_dict(state_dict)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 13,
- "metadata": {
- "pycharm": {
- "name": "#%%\n"
- }
- },
- "outputs": [
+ },
{
"name": "stderr",
"output_type": "stream",
"text": [
- "INFO:root:Welcome to client test\n",
- "WARNING:root:Could not find model: default_models/Cifar10CNN.model\n"
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ ":5: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (`matplotlib.pyplot.figure`) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam `figure.max_open_warning`).\n",
+ " f, ax = plt.subplots(nrows=1, ncols=3, figsize=(18, 6), sharex=True, sharey=True)\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Assigning font /b'F1' = '/home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf'\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Embedding font /home/jeroen/Documents/CSE/MSc/year/1/Q4/CS4290/repo/fltk-testbed-gr-30/venv/lib/python3.9/site-packages/matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf.\n",
+ "DEBUG:matplotlib.backends.backend_pdf:Writing TrueType font.\n"
]
},
{
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Client test is stopping\n"
- ]
- }
- ],
- "source": [
- "test_data = Client(\"test\", None, 1, 2, BareConfig())"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "metadata": {
- "pycharm": {
- "name": "#%%\n"
- }
- },
- "outputs": [
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ "