Skip to content

Commit 1478b6d

Browse files
committed
Support scaling in MembershipInferenceBlackbox (#2152)
Signed-off-by: abigailt <[email protected]>
1 parent 044f87e commit 1478b6d

File tree

2 files changed

+72
-6
lines changed

2 files changed

+72
-6
lines changed

art/attacks/inference/membership_inference/black_box.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from sklearn.neighbors import KNeighborsClassifier
3232
from sklearn.tree import DecisionTreeClassifier
3333
from sklearn.svm import SVC
34+
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
3435

3536
from art.attacks.attack import MembershipInferenceAttack
3637
from art.estimators.estimator import BaseEstimator
@@ -56,6 +57,10 @@ class MembershipInferenceBlackBox(MembershipInferenceAttack):
5657
"input_type",
5758
"attack_model_type",
5859
"attack_model",
60+
"scaler_type",
61+
"nn_model_epochs",
62+
"nn_model_batch_size",
63+
"nn_model_learning_rate"
5964
]
6065
_estimator_requirements = (BaseEstimator, (ClassifierMixin, RegressorMixin))
6166

@@ -65,6 +70,7 @@ def __init__(
6570
input_type: str = "prediction",
6671
attack_model_type: str = "nn",
6772
attack_model: Optional[Any] = None,
73+
scaler_type: Optional[str] = "standard",
6874
nn_model_epochs: int = 100,
6975
nn_model_batch_size: int = 100,
7076
nn_model_learning_rate: float = 0.0001,
@@ -73,6 +79,9 @@ def __init__(
7379
Create a MembershipInferenceBlackBox attack instance.
7480
7581
:param estimator: Target estimator.
82+
:param input_type: the type of input to train the attack on. Can be one of: 'prediction' or 'loss'. Default is
83+
`prediction`. Predictions can be either probabilities or logits, depending on the return type
84+
of the model. If the model is a regressor, only `loss` can be used.
7685
:param attack_model_type: the type of default attack model to train, optional. Should be one of:
7786
`nn` (neural network, default),
7887
`rf` (random forest),
@@ -82,10 +91,10 @@ def __init__(
8291
`knn` (k nearest neighbors),
8392
`svm` (support vector machine).
8493
If `attack_model` is supplied, this option will be ignored.
85-
:param input_type: the type of input to train the attack on. Can be one of: 'prediction' or 'loss'. Default is
86-
`prediction`. Predictions can be either probabilities or logits, depending on the return type
87-
of the model. If the model is a regressor, only `loss` can be used.
8894
:param attack_model: The attack model to train, optional. If none is provided, a default model will be created.
95+
:param scaler_type: The type of scaling to apply to the input features to the attack. Can be one of: "standard",
96+
"minmax", "robust" or None. If not None, the appropriate scaler from scikit-learn will be
97+
applied. If None, no scaling will be applied.
8998
:param nn_model_epochs: the number of epochs to use when training a nn attack model
9099
:param nn_model_batch_size: the batch size to use when training a nn attack model
91100
:param nn_model_learning_rate: the learning rate to use when training a nn attack model
@@ -95,6 +104,8 @@ def __init__(
95104
self.input_type = input_type
96105
self.attack_model_type = attack_model_type
97106
self.attack_model = attack_model
107+
self.scaler_type = scaler_type
108+
self.scaler: Optional[Any] = None
98109
self.epochs = nn_model_epochs
99110
self.batch_size = nn_model_batch_size
100111
self.learning_rate = nn_model_learning_rate
@@ -245,13 +256,29 @@ def fit( # pylint: disable=W0613
245256
if x_2 is None:
246257
self.use_label = False
247258

259+
scaler: Optional[Any] = None
260+
if self.scaler_type:
261+
if self.scaler_type == "standard":
262+
self.scaler = StandardScaler()
263+
elif self.scaler_type == "minmax":
264+
self.scaler = MinMaxScaler()
265+
elif self.scaler_type == "robust":
266+
self.scaler = RobustScaler()
267+
else:
268+
raise ValueError("Illegal scaler_type: ", self.scaler_type)
269+
270+
248271
if self.default_model and self.attack_model_type == "nn":
249272
import torch
250273
from torch import nn
251274
from torch import optim
252275
from torch.utils.data import DataLoader
253276
from art.utils import to_cuda
254277

278+
if self.scaler:
279+
self.scaler.fit(x_1)
280+
x_1 = self.scaler.transform(x_1)
281+
255282
if x_2 is not None:
256283

257284
class MembershipInferenceAttackModel(nn.Module):
@@ -393,8 +420,15 @@ def forward(self, x_1):
393420
else: # not nn
394421
y_ready = check_and_transform_label_format(y_new, nb_classes=2, return_one_hot=False)
395422
if x_2 is not None:
396-
self.attack_model.fit(np.c_[x_1, x_2], y_ready.ravel()) # type: ignore
423+
x = np.c_[x_1, x_2]
424+
if self.scaler:
425+
self.scaler.fit(x)
426+
x = self.scaler.transform(x)
427+
self.attack_model.fit(x, y_ready.ravel()) # type: ignore
397428
else:
429+
if self.scaler:
430+
self.scaler.fit(x_1)
431+
x_1 = self.scaler.transform(x_1)
398432
self.attack_model.fit(x_1, y_ready.ravel()) # type: ignore
399433

400434
def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.ndarray:
@@ -467,6 +501,9 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n
467501
from torch.utils.data import DataLoader
468502
from art.utils import to_cuda, from_cuda
469503

504+
if self.scaler:
505+
features = self.scaler.transform(features)
506+
470507
self.attack_model.eval() # type: ignore
471508
predictions: Optional[np.ndarray] = None
472509

@@ -512,17 +549,27 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n
512549
elif not self.default_model:
513550
# assumes the predict method of the supplied model returns probabilities
514551
if y is not None and self.use_label:
515-
inferred = self.attack_model.predict(np.c_[features, y]) # type: ignore
552+
features = np.c_[features, y]
553+
if self.scaler:
554+
features = self.scaler.transform(features)
555+
inferred = self.attack_model.predict(features) # type: ignore
516556
else:
557+
if self.scaler:
558+
features = self.scaler.transform(features)
517559
inferred = self.attack_model.predict(features) # type: ignore
518560
if probabilities:
519561
inferred_return = inferred
520562
else:
521563
inferred_return = np.round(inferred)
522564
else:
523565
if y is not None and self.use_label:
524-
inferred = self.attack_model.predict_proba(np.c_[features, y]) # type: ignore
566+
features = np.c_[features, y]
567+
if self.scaler:
568+
features = self.scaler.transform(features)
569+
inferred = self.attack_model.predict_proba(features) # type: ignore
525570
else:
571+
if self.scaler:
572+
features = self.scaler.transform(features)
526573
inferred = self.attack_model.predict_proba(features) # type: ignore
527574
if probabilities:
528575
inferred_return = inferred[:, [1]]

tests/attacks/inference/membership_inference/test_black_box.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,25 @@ def test_black_box_tabular(art_warning, model_type, decision_tree_estimator, get
5757
art_warning(e)
5858

5959

60+
@pytest.mark.parametrize("scaler_type", ["standard", "robust", "minmax"])
61+
def test_black_box_tabular_scalers(art_warning, scaler_type, decision_tree_estimator, get_iris_dataset):
62+
try:
63+
classifier = decision_tree_estimator()
64+
attack = MembershipInferenceBlackBox(classifier, scaler_type=scaler_type)
65+
backend_check_membership_accuracy(attack, get_iris_dataset, attack_train_ratio, 0.25)
66+
except ARTTestException as e:
67+
art_warning(e)
68+
69+
70+
def test_black_box_tabular_no_scaler(art_warning, decision_tree_estimator, get_iris_dataset):
71+
try:
72+
classifier = decision_tree_estimator()
73+
attack = MembershipInferenceBlackBox(classifier, scaler_type=None)
74+
backend_check_membership_accuracy(attack, get_iris_dataset, attack_train_ratio, 0.25)
75+
except ARTTestException as e:
76+
art_warning(e)
77+
78+
6079
@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"])
6180
def test_black_box_tabular_no_label(art_warning, model_type, decision_tree_estimator, get_iris_dataset):
6281
try:

0 commit comments

Comments
 (0)