viceroy

git clone git://git.codymlewis.com/viceroy.git
Log | Files | Refs | README

commit cfaed5a4815e3fefd842367530eee734d28b1ec4
parent 0000d20701976f59eabc242e91e5f41a52b3ac71
Author: Cody Lewis <cody@codymlewis.com>
Date:   Mon, 26 Oct 2020 11:06:50 +1100

Fixed attack rate calculation, added dynamic plot titling and sepated
adversaries from client

Diffstat:
Aadversaries.py | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mclient.py | 44--------------------------------------------
Mplot.R | 28+++++++++++++++++++++++++---
Mrequirements.txt | 2++
Mserver.py | 7++++---
Mutils.py | 2+-
6 files changed, 86 insertions(+), 51 deletions(-)

diff --git a/adversaries.py b/adversaries.py @@ -0,0 +1,54 @@ +""" +Defines the adversaries within the system + +Author: Cody Lewis +""" + + +import torch + +from client import Client + + +class Flipper(Client): + """A simple label-flipping model poisoner""" + def __init__(self, data, options, classes): + super().__init__(data, options, classes) + ids = data['y'][0] == options.adversaries['from'] + self.x = data['x'][ids] + self.y = torch.tensor( + [options.adversaries['to'] for _ in ids] + ).unsqueeze(dim=0) + + +class OnOff(Client): + """ + Label flipping poisoner that switches its attack on and off every few + epochs + """ + def __init__(self, data, options, classes): + super().__init__(data, options, classes) + ids = data['y'][0] == options.adversaries['from'] + self.shadow_x = data['x'][ids] + self.shadow_y = torch.tensor( + [options.adversaries['to'] for _ in ids] + ).unsqueeze(dim=0) + self.epochs = 0 + + def fit(self, verbose=False): + self.epochs += 1 + if self.epochs % self.options.adversaries['toggle_time'] == 0: + temp = self.x + self.x = self.shadow_x + self.shadow_x = temp + temp = self.y + self.y = self.shadow_y + self.shadow_y = temp + return super().fit(verbose=verbose) + + +# Dictionary for factory stuctures of adversary construction +ADVERSARY_TYPES = { + "label flip": Flipper, + "onoff": OnOff +} diff --git a/client.py b/client.py @@ -42,47 +42,3 @@ class Client: def fit_async(self, verbose=False): """Run the fit method in a suitable way for async running""" self.latest_loss, self.latest_grad = self.fit(verbose=verbose) - - -class Flipper(Client): - """A simple label-flipping model poisoner""" - def __init__(self, data, options, classes): - super().__init__(data, options, classes) - ids = data['y'][0] == options.adversaries['from'] - self.x = data['x'][ids] - self.y = torch.tensor( - [options.adversaries['to'] for _ in ids] - ).unsqueeze(dim=0) - - -class OnOff(Client): - """ - Label flipping poisoner that switches its attack on and off every few - epochs - """ - def __init__(self, data, options, classes): - super().__init__(data, options, classes) - ids = data['y'][0] == options.adversaries['from'] - self.shadow_x = data['x'][ids] - self.shadow_y = torch.tensor( - [options.adversaries['to'] for _ in ids] - ).unsqueeze(dim=0) - self.epochs = 0 - - def fit(self, verbose=False): - self.epochs += 1 - if self.epochs % self.options.adversaries['toggle_time'] == 0: - temp = self.x - self.x = self.shadow_x - self.shadow_x = temp - temp = self.y - self.y = self.shadow_y - self.shadow_y = temp - return super().fit(verbose=verbose) - - -# Dictionary for factory stuctures of adversary construction -ADVERSARY_TYPES = { - "flip": Flipper, - "onoff": OnOff -} diff --git a/plot.R b/plot.R @@ -1,22 +1,44 @@ #!/usr/bin/env Rscript library(ggplot2) +library(jsonlite) +library(stringr) main <- function() { + options <- fromJSON("options.json") + attack <- `if`(options$adversaries$type == "onoff", + sprintf( + "On-Off Attack with %d Epoch Toggle", + options$adversaries$toggle_time + ), + sprintf( + "%s Attack", + str_to_title(options$adversaries$type) + ) + ) + title <- sprintf( + "Performance of %s under %d%% %d->%d %s", + str_to_title(options$fit_fun), + options$adversaries$percent_adv * 100, + options$adversaries$from, + options$adversaries$to, + attack + ) df <- read.csv('results.log') df$epoch <- 1:length(df$accuracy) ggplot(data=df, aes(x=epoch)) + geom_line(aes(y=accuracy, colour="Accuracy")) + - geom_line(aes(y=attack_success, colour="Attack rate")) + + geom_line(aes(y=attack_success, colour="Attack Success Rate")) + labs( - title="Attack Success Rate and Accuracy of FoolsGold under no Attacks", + title=title, x="Epochs", y="Rate", colour=NULL ) + - scale_y_continuous(lim=c(0,1)) + scale_y_continuous(lim=c(0,1)) + + theme(legend.position="bottom") ggsave("plot.png") } diff --git a/requirements.txt b/requirements.txt @@ -1,2 +1,4 @@ pytorch torchvision +ggplot +pandas diff --git a/server.py b/server.py @@ -9,8 +9,9 @@ import threading import torch.nn as nn +from client import Client +from adversaries import ADVERSARY_TYPES from global_model import GlobalModel -from client import Client, ADVERSARY_TYPES import utils # TODO: Maybe verbosity for log file writing @@ -46,8 +47,8 @@ class Server: print( f"Epoch: {e + 1}/{epochs}, " + f"Loss: {criterion(self.net.predict(X), Y[0]):.6f}, " + - f"Accuracy: {stats['accuracy']}, " + - f"Attack Success Rate: {stats['attack_success']}", + f"Accuracy: {stats['accuracy']:.6f}, " + + f"Attack Success Rate: {stats['attack_success']:.6f}", end="\r" if self.options.verbosity < 2 else "\n" ) if self.options.verbosity > 0: diff --git a/utils.py b/utils.py @@ -44,7 +44,7 @@ def find_stats(model, X, Y, options): ids = Y[0] == options.adversaries['from'] attack_success = ( predictions[ids] == options.adversaries['to'] - ).sum().item() / len(ids) + ).sum().item() / ids.sum().item() return { "accuracy": accuracy, "attack_success": attack_success