Skip to content

Commit 70de243

Browse files
committed
Refactor code in poisoning attacks to improve type checking and readability
1 parent 47fcb34 commit 70de243

File tree

3 files changed

+163
-29
lines changed

3 files changed

+163
-29
lines changed

nebula/addons/attacks/poisoning/datapoison.py

+110-22
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,94 @@
66
from skimage.util import random_noise
77

88

9+
def apply_noise(t, noise_type, poisoned_ratio):
10+
"""
11+
Applies noise to a tensor based on the specified noise type and poisoning ratio.
12+
13+
Args:
14+
t (torch.Tensor): The input tensor to which noise will be applied.
15+
noise_type (str): The type of noise to apply. Supported types are:
16+
- "salt": Salt noise (binary salt-and-pepper noise with only 'salt').
17+
- "gaussian": Gaussian noise with mean 0 and specified variance.
18+
- "s&p": Salt-and-pepper noise.
19+
- "nlp_rawdata": Applies a custom NLP raw data poisoning function.
20+
poisoned_ratio (float): The ratio or variance of noise to be applied, depending on the noise type.
21+
22+
Returns:
23+
torch.Tensor: The tensor with noise applied. If the noise type is not supported,
24+
returns the original tensor with an error message printed.
25+
26+
Raises:
27+
ValueError: If the specified noise_type is not supported.
28+
29+
Notes:
30+
- The "nlp_rawdata" noise type requires the custom `poison_to_nlp_rawdata` function.
31+
- Noise for types "salt", "gaussian", and "s&p" is generated using `random_noise` from
32+
the `skimage.util` package, and returned as a `torch.Tensor`.
33+
"""
34+
if noise_type == "salt":
35+
return torch.tensor(random_noise(t, mode=noise_type, amount=poisoned_ratio))
36+
elif noise_type == "gaussian":
37+
return torch.tensor(random_noise(t, mode=noise_type, mean=0, var=poisoned_ratio, clip=True))
38+
elif noise_type == "s&p":
39+
return torch.tensor(random_noise(t, mode=noise_type, amount=poisoned_ratio))
40+
elif noise_type == "nlp_rawdata":
41+
return poison_to_nlp_rawdata(t, poisoned_ratio)
42+
else:
43+
print("ERROR: poison attack type not supported.")
44+
return t
45+
46+
947
def datapoison(
1048
dataset,
1149
indices,
12-
poisoned_persent,
50+
poisoned_percent,
1351
poisoned_ratio,
1452
targeted=False,
1553
target_label=3,
1654
noise_type="salt",
1755
):
1856
"""
19-
Function to add random noise of various types to the dataset.
57+
Adds noise to a specified portion of a dataset for data poisoning purposes.
58+
59+
This function applies noise to randomly selected samples within a dataset.
60+
Noise can be targeted or non-targeted. In non-targeted poisoning, random samples
61+
are chosen and altered using the specified noise type and ratio. In targeted poisoning,
62+
only samples with a specified label are altered by adding an 'X' pattern.
63+
64+
Args:
65+
dataset (Dataset): The dataset to poison, expected to have `.data` and `.targets` attributes.
66+
indices (list of int): The list of indices in the dataset to consider for poisoning.
67+
poisoned_percent (float): The percentage of `indices` to poison, as a fraction (0 <= poisoned_percent <= 1).
68+
poisoned_ratio (float): The intensity or probability parameter for the noise, depending on the noise type.
69+
targeted (bool, optional): If True, applies targeted poisoning by adding an 'X' only to samples with `target_label`.
70+
Default is False.
71+
target_label (int, optional): The label to target when `targeted` is True. Default is 3.
72+
noise_type (str, optional): The type of noise to apply in non-targeted poisoning. Supported types are:
73+
- "salt": Applies salt noise.
74+
- "gaussian": Applies Gaussian noise.
75+
- "s&p": Applies salt-and-pepper noise.
76+
Default is "salt".
77+
78+
Returns:
79+
Dataset: A deep copy of the original dataset with poisoned data in `.data`.
80+
81+
Raises:
82+
ValueError: If `poisoned_percent` is not between 0 and 1, or if `noise_type` is unsupported.
83+
84+
Notes:
85+
- Non-targeted poisoning randomly selects samples from `indices` based on `poisoned_percent`.
86+
- Targeted poisoning modifies only samples with `target_label` by adding an 'X' pattern, regardless of `poisoned_ratio`.
2087
"""
2188
new_dataset = copy.deepcopy(dataset)
2289
train_data = new_dataset.data
2390
targets = new_dataset.targets
2491
num_indices = len(indices)
25-
if type(noise_type) != str:
92+
if not isinstance(noise_type, str):
2693
noise_type = noise_type[0]
2794

28-
if targeted == False:
29-
num_poisoned = int(poisoned_persent * num_indices)
95+
if not targeted:
96+
num_poisoned = int(poisoned_percent * num_indices)
3097
if num_indices == 0:
3198
return new_dataset
3299
if num_poisoned > num_indices:
@@ -35,21 +102,7 @@ def datapoison(
35102

36103
for i in poisoned_indice:
37104
t = train_data[i]
38-
if noise_type == "salt":
39-
# Replaces random pixels with 1.
40-
poisoned = torch.tensor(random_noise(t, mode=noise_type, amount=poisoned_ratio))
41-
elif noise_type == "gaussian":
42-
# Gaussian-distributed additive noise.
43-
poisoned = torch.tensor(random_noise(t, mode=noise_type, mean=0, var=poisoned_ratio, clip=True))
44-
elif noise_type == "s&p":
45-
# Replaces random pixels with either 1 or low_val, where low_val is 0 for unsigned images or -1 for signed images.
46-
poisoned = torch.tensor(random_noise(t, mode=noise_type, amount=poisoned_ratio))
47-
elif noise_type == "nlp_rawdata":
48-
# for NLP data, change the word vector to 0 with p=poisoned_ratio
49-
poisoned = poison_to_nlp_rawdata(t, poisoned_ratio)
50-
else:
51-
print("ERROR: poison attack type not supported.")
52-
poisoned = t
105+
poisoned = apply_noise(t, noise_type, poisoned_ratio)
53106
train_data[i] = poisoned
54107
else:
55108
for i in indices:
@@ -63,7 +116,20 @@ def datapoison(
63116

64117
def add_x_to_image(img):
65118
"""
66-
Add a 10*10 pixels X at the top-left of an image
119+
Adds a 10x10 pixel 'X' mark to the top-left corner of an image.
120+
121+
This function modifies the input image by setting specific pixels in the
122+
top-left 10x10 region to a high intensity value, forming an 'X' shape.
123+
Pixels on or below the main diagonal and above the secondary diagonal
124+
are set to 255 (white).
125+
126+
Args:
127+
img (array-like): A 2D array or image tensor representing pixel values.
128+
It is expected to be in grayscale, where each pixel
129+
has a single intensity value.
130+
131+
Returns:
132+
torch.Tensor: A tensor representation of the modified image with the 'X' mark.
67133
"""
68134
for i in range(0, 10):
69135
for j in range(0, 10):
@@ -74,7 +140,29 @@ def add_x_to_image(img):
74140

75141
def poison_to_nlp_rawdata(text_data, poisoned_ratio):
76142
"""
77-
for NLP data, change the word vector to 0 with p=poisoned_ratio
143+
Poisons NLP data by setting word vectors to zero with a given probability.
144+
145+
This function randomly selects a portion of non-zero word vectors in the
146+
input text data and sets them to zero vectors based on the specified
147+
poisoning ratio. This simulates a form of data corruption by partially
148+
nullifying the information in the input data.
149+
150+
Args:
151+
text_data (list of torch.Tensor): A list where each entry is a tensor
152+
representing a word vector. Non-zero vectors are assumed to represent valid words.
153+
poisoned_ratio (float): The fraction of non-zero word vectors to set to zero,
154+
where 0 <= poisoned_ratio <= 1.
155+
156+
Returns:
157+
list of torch.Tensor: The modified text data with some word vectors set to zero.
158+
159+
Raises:
160+
ValueError: If `poisoned_ratio` is greater than 1 or less than 0.
161+
162+
Notes:
163+
- `poisoned_ratio` controls the percentage of non-zero vectors to poison.
164+
- If `num_poisoned_token` is zero or exceeds the number of non-zero vectors,
165+
the function returns the original `text_data` without modification.
78166
"""
79167
non_zero_vector_indice = [i for i in range(0, len(text_data)) if text_data[i][0] != 0]
80168
non_zero_vector_len = len(non_zero_vector_indice)

nebula/addons/attacks/poisoning/labelflipping.py

+27-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,33 @@ def labelFlipping(
1313
target_changed_label=7,
1414
):
1515
"""
16-
select flipping_persent of labels, and change them to random values.
16+
Flips the labels of a specified portion of a dataset to random values or to a specific target label.
17+
18+
This function modifies the labels of selected samples in the dataset based on the specified
19+
poisoning percentage. Labels can be flipped either randomly or targeted to change from a specific
20+
label to another specified label.
21+
1722
Args:
18-
dataset: the dataset of training data, torch.util.data.dataset like.
19-
indices: Indices of subsets, list like.
20-
flipping_persent: The ratio of labels want to change, float like.
23+
dataset (Dataset): The dataset containing training data, expected to be a PyTorch dataset
24+
with a `.targets` attribute.
25+
indices (list of int): The list of indices in the dataset to consider for label flipping.
26+
poisoned_percent (float, optional): The ratio of labels to change, expressed as a fraction
27+
(0 <= poisoned_percent <= 1). Default is 0.
28+
targeted (bool, optional): If True, flips only labels matching `target_label` to `target_changed_label`.
29+
Default is False.
30+
target_label (int, optional): The label to change when `targeted` is True. Default is 4.
31+
target_changed_label (int, optional): The label to which `target_label` will be changed. Default is 7.
32+
33+
Returns:
34+
Dataset: A deep copy of the original dataset with modified labels in `.targets`.
35+
36+
Raises:
37+
ValueError: If `poisoned_percent` is not between 0 and 1, or if `flipping_percent` is invalid.
38+
39+
Notes:
40+
- When not in targeted mode, labels are flipped for a random selection of indices based on the specified
41+
`poisoned_percent`. The new label is chosen randomly from the existing classes.
42+
- In targeted mode, labels that match `target_label` are directly changed to `target_changed_label`.
2143
"""
2244
new_dataset = copy.deepcopy(dataset)
2345
targets = new_dataset.targets.detach().clone()
@@ -26,7 +48,7 @@ def labelFlipping(
2648
# class_to_idx = new_dataset.class_to_idx
2749
# class_list = [class_to_idx[i] for i in classes]
2850
class_list = set(targets.tolist())
29-
if targeted == False:
51+
if not targeted:
3052
num_flipped = int(poisoned_persent * num_indices)
3153
if num_indices == 0:
3254
return new_dataset

nebula/addons/attacks/poisoning/modelpoison.py

+26-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,34 @@
66

77
def modelpoison(model: OrderedDict, poisoned_ratio, noise_type="gaussian"):
88
"""
9-
Function to add random noise of various types to the model parameter.
9+
Adds random noise to the parameters of a model for the purpose of data poisoning.
10+
11+
This function modifies the model's parameters by injecting noise according to the specified
12+
noise type and ratio. Various types of noise can be applied, including salt noise, Gaussian
13+
noise, and salt-and-pepper noise.
14+
15+
Args:
16+
model (OrderedDict): The model's parameters organized as an `OrderedDict`. Each key corresponds
17+
to a layer, and each value is a tensor representing the parameters of that layer.
18+
poisoned_ratio (float): The proportion of noise to apply, expressed as a fraction (0 <= poisoned_ratio <= 1).
19+
noise_type (str, optional): The type of noise to apply to the model parameters. Supported types are:
20+
- "salt": Applies salt noise, replacing random elements with 1.
21+
- "gaussian": Applies Gaussian-distributed additive noise.
22+
- "s&p": Applies salt-and-pepper noise, replacing random elements with either 1 or low_val.
23+
Default is "gaussian".
24+
25+
Returns:
26+
OrderedDict: A new `OrderedDict` containing the model parameters with noise added.
27+
28+
Raises:
29+
ValueError: If `poisoned_ratio` is not between 0 and 1, or if `noise_type` is unsupported.
30+
31+
Notes:
32+
- If a layer's tensor is a single point (0-dimensional), it will be reshaped for processing.
33+
- Unsupported noise types will result in an error message, and the original tensor will be retained.
1034
"""
1135
poisoned_model = OrderedDict()
12-
if type(noise_type) != str:
36+
if not isinstance(noise_type, str):
1337
noise_type = noise_type[0]
1438

1539
for layer in model:

0 commit comments

Comments
 (0)