1818"""
1919This module implements the Label-Only Inference Attack based on Decision Boundary.
2020
21- | Paper link: https://arxiv.org/abs/2007.14321
21+ | Paper link: https://arxiv.org/abs/2007.14321 (Choquette-Choo et al.) and https://arxiv.org/abs/2007.15528 (Li and
22+ Zhang)
2223"""
2324import logging
2425from typing import Optional , TYPE_CHECKING
@@ -40,7 +41,10 @@ class LabelOnlyDecisionBoundary(MembershipInferenceAttack):
4041 """
4142 Implementation of Label-Only Inference Attack based on Decision Boundary.
4243
43- | Paper link: https://arxiv.org/abs/2007.14321
44+ | Paper link: https://arxiv.org/abs/2007.14321 (Choquette-Choo et al.) and https://arxiv.org/abs/2007.15528 (Li
45+ and Zhang)
46+
47+ You only need to call ONE of the calibrate methods, depending on which attack you want to launch.
4448 """
4549
4650 attack_params = MembershipInferenceAttack .attack_params + [
@@ -136,6 +140,8 @@ def calibrate_distance_threshold(
136140 """
137141 Calibrate distance threshold maximising the membership inference accuracy on `x_train` and `x_test`.
138142
143+ | Paper link: https://arxiv.org/abs/2007.14321
144+
139145 :param x_train: Training data.
140146 :param y_train: Labels of training data `x_train`.
141147 :param x_test: Test data.
@@ -196,6 +202,70 @@ def calibrate_distance_threshold(
196202
197203 self .distance_threshold_tau = distance_threshold_tau
198204
205+ def calibrate_distance_threshold_unsupervised (
206+ self , top_t : int = 50 , num_samples : int = 100 , max_queries : int = 1 , ** kwargs
207+ ):
208+ """
209+ Calibrate distance threshold on randomly generated samples, choosing the top-t percentile of the noise needed
210+ to change the classifier's initial prediction. This method requires the model's clip_values to be set.
211+
212+ | Paper link: https://arxiv.org/abs/2007.15528
213+
214+ :param top_t: Top-t percentile.
215+ :param num_samples: Number of random samples to generate.
216+ :param max_queries: Maximum number of queries. Maximum number of HSJ iterations on a single sample will be
217+ max_queries * max_iter.
218+ :Keyword Arguments for HopSkipJump:
219+ * *norm*: Order of the norm. Possible values: "inf", np.inf or 2.
220+ * *max_iter*: Maximum number of iterations.
221+ * *max_eval*: Maximum number of evaluations for estimating gradient.
222+ * *init_eval*: Initial number of evaluations for estimating gradient.
223+ * *init_size*: Maximum number of trials for initial generation of adversarial examples.
224+ * *verbose*: Show progress bars.
225+ """
226+ from art .attacks .evasion .hop_skip_jump import HopSkipJump
227+
228+ if self .estimator .clip_values is not None :
229+ x_min , x_max = self .estimator .clip_values
230+ else :
231+ raise RuntimeError (
232+ "You need to set the estimator's clip_values in order to calibrate the distance threshold."
233+ )
234+
235+ x_rand = np .random .rand (* (num_samples ,) + self .estimator .input_shape ).astype (np .float32 )
236+ x_rand *= x_max - x_min # scale
237+ x_rand += x_min # shift
238+
239+ y_rand = self .estimator .predict (x = x_rand )
240+ y_rand = check_and_transform_label_format (y_rand , self .estimator .nb_classes )
241+
242+ hsj = HopSkipJump (classifier = self .estimator , targeted = False , ** kwargs )
243+
244+ distances = []
245+
246+ i = 0
247+ while len (x_rand ) != 0 and i < max_queries :
248+ x_adv = hsj .generate (x = x_rand , y = y_rand )
249+
250+ distance = np .linalg .norm ((x_adv - x_rand ).reshape ((x_rand .shape [0 ], - 1 )), ord = 2 , axis = 1 )
251+
252+ y_pred = self .estimator .predict (x = x_adv )
253+
254+ changed_predictions = np .argmax (y_pred , axis = 1 ) != np .argmax (y_rand , axis = 1 )
255+
256+ distances .extend (distance [changed_predictions ])
257+
258+ x_rand , y_rand = x_adv [~ changed_predictions ], y_rand [~ changed_predictions ]
259+
260+ i += 1
261+
262+ if len (distances ) == 0 :
263+ raise RuntimeWarning (
264+ "No successful adversarial examples were generated - no distances were obtained."
265+ "Distance threshold will not be set."
266+ )
267+ self .distance_threshold_tau = np .percentile (distances , top_t )
268+
199269 def _check_params (self ) -> None :
200270 if self .distance_threshold_tau is not None and (
201271 not isinstance (self .distance_threshold_tau , (int , float )) or self .distance_threshold_tau <= 0.0
0 commit comments