Skip to content

Commit 14338d2

Browse files
committed
Support scaling in AttributeInferenceBlackbox
Signed-off-by: abigailt <[email protected]>
1 parent 06bd01a commit 14338d2

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-1
lines changed

art/attacks/inference/attribute_inference/black_box.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from sklearn.svm import SVC, SVR
3333
from sklearn.preprocessing import minmax_scale, OneHotEncoder, OrdinalEncoder
3434
from sklearn.compose import ColumnTransformer
35+
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
3536

3637
from art.estimators.estimator import BaseEstimator
3738
from art.estimators.classification.classifier import ClassifierMixin
@@ -80,6 +81,7 @@ def __init__(
8081
is_continuous: Optional[bool] = False,
8182
scale_range: Optional[Tuple[float, float]] = None,
8283
prediction_normal_factor: Optional[float] = 1,
84+
scaler_type: Optional[str] = "standard",
8385
non_numerical_features: Optional[List[int]] = None,
8486
encoder: Optional[Union[OrdinalEncoder, OneHotEncoder, ColumnTransformer]] = None,
8587
nn_model_epochs: int = 100,
@@ -109,7 +111,11 @@ def __init__(
109111
Only applicable when `estimator` is a regressor.
110112
:param prediction_normal_factor: If supplied, the class labels (both true and predicted) are multiplied by the
111113
factor when used as inputs to the attack-model. Only applicable when
112-
`estimator` is a regressor and if `scale_range` is not supplied
114+
`estimator` is a regressor and if `scale_range` is not supplied.
115+
:param scaler_type: The type of scaling to apply to all input features to the attack. Can be one of: "standard",
116+
"minmax", "robust" or None. If not None, the appropriate scaler from scikit-learn will be
117+
applied. If None, no scaling will be applied. This is in addition to any specific scaling
118+
performed on the class labels based on the params scale_range or prediction_normal_factor.
113119
:param non_numerical_features: a list of feature indexes that require encoding in order to feed into an ML model
114120
(i.e., strings), not including the attacked feature. Should only be supplied if
115121
non-numeric features exist in the input data not including the attacked feature,
@@ -130,6 +136,8 @@ def __init__(
130136
self.attack_model: Optional[Any] = None
131137
self.prediction_normal_factor = prediction_normal_factor
132138
self.scale_range = scale_range
139+
self.scaler_type = scaler_type
140+
self.scaler: Optional[Any] = None
133141
self.epochs = nn_model_epochs
134142
self.batch_size = nn_model_batch_size
135143
self.learning_rate = nn_model_learning_rate
@@ -252,6 +260,19 @@ def fit(self, x: np.ndarray, y: Optional[np.ndarray] = None) -> None:
252260
if y is not None:
253261
x_train = np.concatenate((x_train, y), axis=1)
254262

263+
if self.scaler_type:
264+
if self.scaler_type == "standard":
265+
self.scaler = StandardScaler()
266+
elif self.scaler_type == "minmax":
267+
self.scaler = MinMaxScaler()
268+
elif self.scaler_type == "robust":
269+
self.scaler = RobustScaler()
270+
else:
271+
raise ValueError("Illegal scaler_type: ", self.scaler_type)
272+
if self.scaler:
273+
self.scaler.fit(x_train)
274+
x_train = self.scaler.transform(x_train)
275+
255276
# train attack model
256277
if self._attack_model_type == "nn":
257278
import torch
@@ -407,6 +428,9 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n
407428
if y is not None:
408429
x_test = np.concatenate((x_test, y), axis=1)
409430

431+
if self.scaler:
432+
x_test = self.scaler.transform(x_test)
433+
410434
if self._attack_model_type == "nn":
411435
from torch.utils.data import DataLoader
412436
from art.utils import to_cuda, from_cuda

tests/attacks/inference/attribute_inference/test_black_box.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,107 @@ def transform_feature(x):
9494
art_warning(e)
9595

9696

97+
@pytest.mark.skip_framework("dl_frameworks")
98+
@pytest.mark.parametrize("scaler_type", ["standard", "robust", "minmax"])
99+
def test_black_box_scalers(art_warning, scaler_type, decision_tree_estimator, get_iris_dataset):
100+
try:
101+
attack_feature = 2 # petal length
102+
103+
# need to transform attacked feature into categorical
104+
def transform_feature(x):
105+
x[x > 0.5] = 2.0
106+
x[(x > 0.2) & (x <= 0.5)] = 1.0
107+
x[x <= 0.2] = 0.0
108+
109+
values = [0.0, 1.0, 2.0]
110+
111+
(x_train_iris, y_train_iris), (x_test_iris, y_test_iris) = get_iris_dataset
112+
# training data without attacked feature
113+
x_train_for_attack = np.delete(x_train_iris, attack_feature, 1)
114+
# only attacked feature
115+
x_train_feature = x_train_iris[:, attack_feature].copy().reshape(-1, 1)
116+
transform_feature(x_train_feature)
117+
# training data with attacked feature (after transformation)
118+
x_train = np.concatenate((x_train_for_attack[:, :attack_feature], x_train_feature), axis=1)
119+
x_train = np.concatenate((x_train, x_train_for_attack[:, attack_feature:]), axis=1)
120+
121+
# test data without attacked feature
122+
x_test_for_attack = np.delete(x_test_iris, attack_feature, 1)
123+
# only attacked feature
124+
x_test_feature = x_test_iris[:, attack_feature].copy().reshape(-1, 1)
125+
transform_feature(x_test_feature)
126+
127+
classifier = decision_tree_estimator()
128+
129+
attack = AttributeInferenceBlackBox(classifier, attack_feature=attack_feature, scaler_type=scaler_type)
130+
# get original model's predictions
131+
x_train_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_train_iris)]).reshape(-1, 1)
132+
x_test_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_test_iris)]).reshape(-1, 1)
133+
# train attack model
134+
attack.fit(x_train)
135+
# infer attacked feature
136+
inferred_train = attack.infer(x_train_for_attack, pred=x_train_predictions, values=values)
137+
inferred_test = attack.infer(x_test_for_attack, pred=x_test_predictions, values=values)
138+
# check accuracy
139+
train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train)
140+
test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test)
141+
assert pytest.approx(0.8285, abs=0.3) == train_acc
142+
assert pytest.approx(0.8888, abs=0.3) == test_acc
143+
144+
except ARTTestException as e:
145+
art_warning(e)
146+
147+
148+
@pytest.mark.skip_framework("dl_frameworks")
149+
def test_black_box_tabular_no_scaler(art_warning, decision_tree_estimator, get_iris_dataset):
150+
try:
151+
attack_feature = 2 # petal length
152+
153+
# need to transform attacked feature into categorical
154+
def transform_feature(x):
155+
x[x > 0.5] = 2.0
156+
x[(x > 0.2) & (x <= 0.5)] = 1.0
157+
x[x <= 0.2] = 0.0
158+
159+
values = [0.0, 1.0, 2.0]
160+
161+
(x_train_iris, y_train_iris), (x_test_iris, y_test_iris) = get_iris_dataset
162+
# training data without attacked feature
163+
x_train_for_attack = np.delete(x_train_iris, attack_feature, 1)
164+
# only attacked feature
165+
x_train_feature = x_train_iris[:, attack_feature].copy().reshape(-1, 1)
166+
transform_feature(x_train_feature)
167+
# training data with attacked feature (after transformation)
168+
x_train = np.concatenate((x_train_for_attack[:, :attack_feature], x_train_feature), axis=1)
169+
x_train = np.concatenate((x_train, x_train_for_attack[:, attack_feature:]), axis=1)
170+
171+
# test data without attacked feature
172+
x_test_for_attack = np.delete(x_test_iris, attack_feature, 1)
173+
# only attacked feature
174+
x_test_feature = x_test_iris[:, attack_feature].copy().reshape(-1, 1)
175+
transform_feature(x_test_feature)
176+
177+
classifier = decision_tree_estimator()
178+
179+
attack = AttributeInferenceBlackBox(classifier, attack_feature=attack_feature, scaler_type=None)
180+
# get original model's predictions
181+
x_train_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_train_iris)]).reshape(-1, 1)
182+
x_test_predictions = np.array([np.argmax(arr) for arr in classifier.predict(x_test_iris)]).reshape(-1, 1)
183+
# train attack model
184+
attack.fit(x_train)
185+
# infer attacked feature
186+
inferred_train = attack.infer(x_train_for_attack, pred=x_train_predictions, values=values)
187+
inferred_test = attack.infer(x_test_for_attack, pred=x_test_predictions, values=values)
188+
# check accuracy
189+
train_acc = np.sum(inferred_train == x_train_feature.reshape(1, -1)) / len(inferred_train)
190+
test_acc = np.sum(inferred_test == x_test_feature.reshape(1, -1)) / len(inferred_test)
191+
assert pytest.approx(0.8285, abs=0.3) == train_acc
192+
assert pytest.approx(0.8888, abs=0.3) == test_acc
193+
194+
except ARTTestException as e:
195+
art_warning(e)
196+
197+
97198
@pytest.mark.skip_framework("dl_frameworks")
98199
@pytest.mark.parametrize("model_type", ["nn", "rf", "gb", "lr", "dt", "knn", "svm"])
99200
def test_black_box_continuous(art_warning, decision_tree_estimator, get_iris_dataset, model_type):

0 commit comments

Comments
 (0)