Skip to content

Commit 6ebce95

Browse files
improve attacks-frontend compatibility
1 parent 079ce48 commit 6ebce95

File tree

8 files changed

+252
-165
lines changed

8 files changed

+252
-165
lines changed

nebula/addons/attacks/dataset/datapoison.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ class SamplePoisoningAttack(DatasetAttack):
3232
engine (object): The training engine object, including the associated
3333
datamodule.
3434
attack_params (dict): Attack parameters including:
35-
- poisoned_percent (float): The percentage of data points to be poisoned.
36-
- poisoned_ratio (float): The ratio of poisoned data relative to the total dataset.
35+
- poisoned_sample_percent (float): The percentage of data points to be poisoned (0-100).
36+
- poisoned_noise_percent (float): The percentage of noise to be added to poisoned data (0-100).
3737
- targeted (bool): Whether the attack is targeted at a specific label.
38-
- target_label (int): The target label for the attack (used if targeted is True).
39-
- noise_type (str): The type of noise to introduce during the attack.
38+
- target_label/targetLabel (int): The target label for the attack (used if targeted is True).
39+
- noise_type/noiseType (str): The type of noise to introduce during the attack.
4040
"""
4141

4242
def __init__(self, engine, attack_params):
@@ -58,15 +58,17 @@ def __init__(self, engine, attack_params):
5858

5959
super().__init__(engine, round_start, round_stop, attack_interval)
6060
self.datamodule = engine._trainer.datamodule
61-
self.poisoned_percent = float(attack_params["poisoned_percent"])
62-
self.poisoned_ratio = float(attack_params["poisoned_ratio"])
61+
self.poisoned_percent = float(attack_params["poisoned_sample_percent"])
62+
self.poisoned_noise_percent = float(attack_params["poisoned_noise_percent"])
6363
self.targeted = attack_params["targeted"]
64-
self.target_label = int(attack_params["target_label"])
65-
self.noise_type = attack_params["noise_type"].lower()
64+
65+
# Handle both camelCase and snake_case parameter names
66+
self.target_label = int(attack_params.get("target_label") or attack_params.get("targetLabel", 4))
67+
self.noise_type = (attack_params.get("noise_type") or attack_params.get("noiseType", "Gaussian")).lower()
6668

67-
def apply_noise(self, t, noise_type, poisoned_ratio):
69+
def apply_noise(self, t, noise_type, poisoned_noise_percent):
6870
"""
69-
Applies noise to a tensor based on the specified noise type and poisoning ratio.
71+
Applies noise to a tensor based on the specified noise type and poisoning percentage.
7072
7173
Args:
7274
t (torch.Tensor): The input tensor to which noise will be applied.
@@ -75,7 +77,7 @@ def apply_noise(self, t, noise_type, poisoned_ratio):
7577
- "gaussian": Gaussian noise with mean 0 and specified variance.
7678
- "s&p": Salt-and-pepper noise.
7779
- "nlp_rawdata": Applies a custom NLP raw data poisoning function.
78-
poisoned_ratio (float): The ratio or variance of noise to be applied, depending on the noise type.
80+
poisoned_noise_percent (float): The percentage of noise to be applied (0-100).
7981
8082
Returns:
8183
torch.Tensor: The tensor with noise applied. If the noise type is not supported,
@@ -90,6 +92,9 @@ def apply_noise(self, t, noise_type, poisoned_ratio):
9092
the `skimage.util` package, and returned as a `torch.Tensor`.
9193
"""
9294
arr = t.detach().cpu().numpy() if isinstance(t, torch.Tensor) else np.array(t)
95+
96+
# Convert percentage to ratio for noise application
97+
poisoned_ratio = poisoned_noise_percent / 100.0
9398

9499
if noise_type == "salt":
95100
return torch.tensor(random_noise(arr, mode=noise_type, amount=poisoned_ratio))
@@ -108,7 +113,7 @@ def datapoison(
108113
dataset,
109114
indices,
110115
poisoned_percent,
111-
poisoned_ratio,
116+
poisoned_noise_percent,
112117
targeted=False,
113118
target_label=3,
114119
noise_type="salt",
@@ -118,14 +123,14 @@ def datapoison(
118123
119124
This function applies noise to randomly selected samples within a dataset.
120125
Noise can be targeted or non-targeted. In non-targeted poisoning, random samples
121-
are chosen and altered using the specified noise type and ratio. In targeted poisoning,
126+
are chosen and altered using the specified noise type and percentage. In targeted poisoning,
122127
only samples with a specified label are altered by adding an 'X' pattern.
123128
124129
Args:
125130
dataset (Dataset): The dataset to poison, expected to have `.data` and `.targets` attributes.
126131
indices (list of int): The list of indices in the dataset to consider for poisoning.
127-
poisoned_percent (float): The percentage of `indices` to poison, as a fraction (0 <= poisoned_percent <= 1).
128-
poisoned_ratio (float): The intensity or probability parameter for the noise, depending on the noise type.
132+
poisoned_percent (float): The percentage of `indices` to poison (0-100).
133+
poisoned_noise_percent (float): The percentage of noise to apply to poisoned samples (0-100).
129134
targeted (bool, optional): If True, applies targeted poisoning by adding an 'X' only to samples with `target_label`.
130135
Default is False.
131136
target_label (int, optional): The label to target when `targeted` is True. Default is 3.
@@ -139,11 +144,11 @@ def datapoison(
139144
Dataset: A deep copy of the original dataset with poisoned data in `.data`.
140145
141146
Raises:
142-
ValueError: If `poisoned_percent` is not between 0 and 1, or if `noise_type` is unsupported.
147+
ValueError: If `poisoned_percent` or `poisoned_noise_percent` is not between 0 and 100, or if `noise_type` is unsupported.
143148
144149
Notes:
145150
- Non-targeted poisoning randomly selects samples from `indices` based on `poisoned_percent`.
146-
- Targeted poisoning modifies only samples with `target_label` by adding an 'X' pattern, regardless of `poisoned_ratio`.
151+
- Targeted poisoning modifies only samples with `target_label` by adding an 'X' pattern, regardless of `poisoned_noise_percent`.
147152
"""
148153
new_dataset = copy.deepcopy(dataset)
149154
if not isinstance(new_dataset.targets, np.ndarray):
@@ -156,7 +161,7 @@ def datapoison(
156161
noise_type = noise_type[0]
157162

158163
if not targeted:
159-
num_poisoned = int(poisoned_percent * num_indices)
164+
num_poisoned = int(poisoned_percent * num_indices / 100.0) # Convert percentage to count
160165
if num_indices == 0:
161166
return new_dataset
162167
if num_poisoned > num_indices:
@@ -168,7 +173,7 @@ def datapoison(
168173
t = new_dataset.data[i]
169174
if isinstance(t, tuple):
170175
t = t[0]
171-
poisoned = self.apply_noise(t, noise_type, poisoned_ratio)
176+
poisoned = self.apply_noise(t, noise_type, poisoned_noise_percent)
172177
if isinstance(t, tuple):
173178
poisoned = (poisoned, t[1])
174179
if isinstance(poisoned, torch.Tensor):
@@ -267,7 +272,7 @@ def get_malicious_dataset(self):
267272
self.datamodule.train_set,
268273
self.datamodule.train_set_indices,
269274
self.poisoned_percent,
270-
self.poisoned_ratio,
275+
self.poisoned_noise_percent,
271276
self.targeted,
272277
self.target_label,
273278
self.noise_type,

nebula/addons/attacks/dataset/labelflipping.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ class LabelFlippingAttack(DatasetAttack):
2222
2323
This attack alters the labels of certain data points in the training set to
2424
mislead the training process.
25+
26+
Args:
27+
engine (object): The training engine object.
28+
attack_params (dict): Parameters for the attack, including:
29+
- poisoned_sample_percent (float): The percentage of data points to be poisoned (0-100).
30+
- targeted (bool): Whether the attack is targeted at a specific label.
31+
- target_label/targetLabel (int): The target label for the attack (used if targeted is True).
32+
- target_changed_label/targetChangedLabel (int): The label to change to when targeted is True.
2533
"""
2634

2735
def __init__(self, engine, attack_params):
@@ -44,10 +52,12 @@ def __init__(self, engine, attack_params):
4452

4553
super().__init__(engine, round_start, round_stop, attack_interval)
4654
self.datamodule = engine._trainer.datamodule
47-
self.poisoned_percent = float(attack_params["poisoned_percent"])
55+
self.poisoned_percent = float(attack_params["poisoned_sample_percent"])
4856
self.targeted = attack_params["targeted"]
49-
self.target_label = int(attack_params["target_label"])
50-
self.target_changed_label = int(attack_params["target_changed_label"])
57+
58+
# Handle both camelCase and snake_case parameter names
59+
self.target_label = int(attack_params.get("target_label") or attack_params.get("targetLabel", 4))
60+
self.target_changed_label = int(attack_params.get("target_changed_label") or attack_params.get("targetChangedLabel", 7))
5161

5262
def labelFlipping(
5363
self,
@@ -69,8 +79,7 @@ def labelFlipping(
6979
dataset (Dataset): The dataset containing training data, expected to be a PyTorch dataset
7080
with a `.targets` attribute.
7181
indices (list of int): The list of indices in the dataset to consider for label flipping.
72-
poisoned_percent (float, optional): The ratio of labels to change, expressed as a fraction
73-
(0 <= poisoned_percent <= 1). Default is 0.
82+
poisoned_percent (float, optional): The percentage of labels to change (0-100). Default is 0.
7483
targeted (bool, optional): If True, flips only labels matching `target_label` to `target_changed_label`.
7584
Default is False.
7685
target_label (int, optional): The label to change when `targeted` is True. Default is 4.
@@ -80,7 +89,7 @@ def labelFlipping(
8089
Dataset: A deep copy of the original dataset with modified labels in `.targets`.
8190
8291
Raises:
83-
ValueError: If `poisoned_percent` is not between 0 and 1, or if `flipping_percent` is invalid.
92+
ValueError: If `poisoned_percent` is not between 0 and 100.
8493
8594
Notes:
8695
- When not in targeted mode, labels are flipped for a random selection of indices based on the specified
@@ -98,7 +107,7 @@ def labelFlipping(
98107

99108
if not targeted:
100109
num_indices = len(indices)
101-
num_flipped = int(poisoned_percent * num_indices)
110+
num_flipped = int(poisoned_percent * num_indices / 100.0) # Convert percentage to count
102111
if num_indices == 0 or num_flipped > num_indices:
103112
return
104113
flipped_indices = random.sample(indices, num_flipped)

nebula/addons/attacks/model/modelpoison.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ModelPoisonAttack(ModelAttack):
2929
Args:
3030
engine (object): The training engine object that manages the aggregator.
3131
attack_params (dict): Parameters for the attack, including:
32-
- poisoned_ratio (float): The ratio of model weights to be poisoned.
32+
- poisoned_noise_percent (float): The percentage of noise to be added to model weights (0-100).
3333
- noise_type (str): The type of noise to introduce during the attack.
3434
"""
3535

@@ -52,21 +52,21 @@ def __init__(self, engine, attack_params):
5252

5353
super().__init__(engine, round_start, round_stop, attack_interval)
5454

55-
self.poisoned_ratio = float(attack_params["poisoned_ratio"])
55+
self.poisoned_noise_percent = float(attack_params["poisoned_noise_percent"])
5656
self.noise_type = attack_params["noise_type"].lower()
5757

58-
def modelPoison(self, model: OrderedDict, poisoned_ratio, noise_type="gaussian"):
58+
def modelPoison(self, model: OrderedDict, poisoned_noise_percent, noise_type="gaussian"):
5959
"""
6060
Adds random noise to the parameters of a model for the purpose of data poisoning.
6161
6262
This function modifies the model's parameters by injecting noise according to the specified
63-
noise type and ratio. Various types of noise can be applied, including salt noise, Gaussian
63+
noise type and percentage. Various types of noise can be applied, including salt noise, Gaussian
6464
noise, and salt-and-pepper noise.
6565
6666
Args:
6767
model (OrderedDict): The model's parameters organized as an `OrderedDict`. Each key corresponds
6868
to a layer, and each value is a tensor representing the parameters of that layer.
69-
poisoned_ratio (float): The proportion of noise to apply, expressed as a fraction (0 <= poisoned_ratio <= 1).
69+
poisoned_noise_percent (float): The percentage of noise to apply (0-100).
7070
noise_type (str, optional): The type of noise to apply to the model parameters. Supported types are:
7171
- "salt": Applies salt noise, replacing random elements with 1.
7272
- "gaussian": Applies Gaussian-distributed additive noise.
@@ -77,38 +77,47 @@ def modelPoison(self, model: OrderedDict, poisoned_ratio, noise_type="gaussian")
7777
OrderedDict: A new `OrderedDict` containing the model parameters with noise added.
7878
7979
Raises:
80-
ValueError: If `poisoned_ratio` is not between 0 and 1, or if `noise_type` is unsupported.
80+
ValueError: If `poisoned_noise_percent` is not between 0 and 100, or if `noise_type` is unsupported.
8181
8282
Notes:
8383
- If a layer's tensor is a single point (0-dimensional), it will be reshaped for processing.
8484
- Unsupported noise types will result in an error message, and the original tensor will be retained.
8585
"""
8686
poisoned_model = OrderedDict()
8787
if not isinstance(noise_type, str):
88+
logging.info(f"Noise type is not a string, converting to string: {noise_type}")
8889
noise_type = noise_type[0]
8990

91+
# Convert percentage to ratio for noise application
92+
poisoned_ratio = poisoned_noise_percent / 100.0
93+
9094
for layer in model:
9195
bt = model[layer]
9296
t = bt.detach().clone()
9397
single_point = False
9498
if len(t.shape) == 0:
9599
t = t.view(-1)
96100
single_point = True
101+
logging.info(f"Layer {layer} is a single point, reshaping to {t.shape}")
97102

98103
if noise_type == "salt":
104+
logging.info(f"Applying salt noise to layer {layer}")
99105
# Replaces random pixels with 1.
100106
poisoned = torch.tensor(random_noise(t, mode=noise_type, amount=poisoned_ratio))
101107
elif noise_type == "gaussian":
108+
logging.info(f"Applying gaussian noise to layer {layer}")
102109
# Gaussian-distributed additive noise.
103110
poisoned = torch.tensor(random_noise(t, mode=noise_type, mean=0, var=poisoned_ratio, clip=True))
104111
elif noise_type == "s&p":
112+
logging.info(f"Applying salt-and-pepper noise to layer {layer}")
105113
# Replaces random pixels with either 1 or low_val, where low_val is 0 for unsigned images or -1 for signed images.
106114
poisoned = torch.tensor(random_noise(t, mode=noise_type, amount=poisoned_ratio))
107115
else:
108116
logging.info(f"ERROR: noise_type '{noise_type}' not supported in model poison attack.")
109117
poisoned = t
110118
if single_point:
111119
poisoned = poisoned[0]
120+
logging.info(f"Layer {layer} is a single point, reshaping to {poisoned.shape}")
112121
poisoned_model[layer] = poisoned
113122

114123
return poisoned_model
@@ -123,6 +132,6 @@ def model_attack(self, received_weights):
123132
Returns:
124133
any: The modified model weights after applying the poisoning attack.
125134
"""
126-
logging.info("[ModelPoisonAttack] Performing model poison attack")
127-
received_weights = self.modelPoison(received_weights, self.poisoned_ratio, self.noise_type)
135+
logging.info(f"[ModelPoisonAttack] Performing model poison attack with poisoned_noise_percent={self.poisoned_noise_percent} and noise_type={self.noise_type}")
136+
received_weights = self.modelPoison(received_weights, self.poisoned_noise_percent, self.noise_type)
128137
return received_weights

0 commit comments

Comments
 (0)