|
|
@@ -29,9 +29,8 @@ TAG = 'PointWiseAttack' |
|
|
|
|
|
|
|
class PointWiseAttack(Attack): |
|
|
|
""" |
|
|
|
The Pointwise Attack make sure use the minimum number of changed pixels |
|
|
|
to generate adversarial sample for each original sample.Those changed pixels |
|
|
|
will use binary seach to make sure the distance between adversarial sample |
|
|
|
The Pointwise Attack make sure use the minimum number of changed pixels to generate adversarial sample for each |
|
|
|
original sample.Those changed pixels will use binary search to make sure the distance between adversarial sample |
|
|
|
and original sample is as close as possible. |
|
|
|
|
|
|
|
References: `L. Schott, J. Rauber, M. Bethge, W. Brendel: "Towards the |
|
|
@@ -42,32 +41,23 @@ class PointWiseAttack(Attack): |
|
|
|
model (BlackModel): Target model. |
|
|
|
max_iter (int): Max rounds of iteration to generate adversarial image. |
|
|
|
search_iter (int): Max rounds of binary search. |
|
|
|
is_targeted (bool): If True, targeted attack. If False, untargeted |
|
|
|
attack. Default: False. |
|
|
|
init_attack (Attack): Attack used to find a starting point. Default: |
|
|
|
None. |
|
|
|
sparse (bool): If True, input labels are sparse-encoded. If False, |
|
|
|
input labels are one-hot-encoded. Default: True. |
|
|
|
is_targeted (bool): If True, targeted attack. If False, untargeted attack. Default: False. |
|
|
|
init_attack (Attack): Attack used to find a starting point. Default: None. |
|
|
|
sparse (bool): If True, input labels are sparse-encoded. If False, input labels are one-hot-encoded. |
|
|
|
Default: True. |
|
|
|
|
|
|
|
Examples: |
|
|
|
>>> attack = PointWiseAttack(model) |
|
|
|
""" |
|
|
|
|
|
|
|
def __init__(self, |
|
|
|
model, |
|
|
|
max_iter=1000, |
|
|
|
search_iter=10, |
|
|
|
is_targeted=False, |
|
|
|
init_attack=None, |
|
|
|
sparse=True): |
|
|
|
def __init__(self, model, max_iter=1000, search_iter=10, is_targeted=False, init_attack=None, sparse=True): |
|
|
|
super(PointWiseAttack, self).__init__() |
|
|
|
self._model = check_model('model', model, BlackModel) |
|
|
|
self._max_iter = check_int_positive('max_iter', max_iter) |
|
|
|
self._search_iter = check_int_positive('search_iter', search_iter) |
|
|
|
self._is_targeted = check_param_type('is_targeted', is_targeted, bool) |
|
|
|
if init_attack is None: |
|
|
|
self._init_attack = SaltAndPepperNoiseAttack(model, |
|
|
|
is_targeted=self._is_targeted) |
|
|
|
self._init_attack = SaltAndPepperNoiseAttack(model, is_targeted=self._is_targeted) |
|
|
|
else: |
|
|
|
self._init_attack = init_attack |
|
|
|
self._sparse = check_param_type('sparse', sparse, bool) |
|
|
@@ -77,10 +67,9 @@ class PointWiseAttack(Attack): |
|
|
|
Generate adversarial examples based on input samples and targeted labels. |
|
|
|
|
|
|
|
Args: |
|
|
|
inputs (numpy.ndarray): Benign input samples used as references to create |
|
|
|
adversarial examples. |
|
|
|
labels (numpy.ndarray): For targeted attack, labels are adversarial |
|
|
|
target labels. For untargeted attack, labels are ground-truth labels. |
|
|
|
inputs (numpy.ndarray): Benign input samples used as references to create adversarial examples. |
|
|
|
labels (numpy.ndarray): For targeted attack, labels are adversarial target labels. |
|
|
|
For untargeted attack, labels are ground-truth labels. |
|
|
|
|
|
|
|
Returns: |
|
|
|
- numpy.ndarray, bool values for each attack result. |
|
|
@@ -90,38 +79,26 @@ class PointWiseAttack(Attack): |
|
|
|
- numpy.ndarray, query times for each sample. |
|
|
|
|
|
|
|
Examples: |
|
|
|
>>> is_adv_list, adv_list, query_times_each_adv = attack.generate( |
|
|
|
>>> [[0.1, 0.2, 0.6], [0.3, 0, 0.4]], |
|
|
|
>>> [2, 3]) |
|
|
|
>>> is_adv_list, adv_list, query_times_each_adv = attack.generate([[0.1, 0.2, 0.6], [0.3, 0, 0.4]], [2, 3]) |
|
|
|
""" |
|
|
|
arr_x, arr_y = check_pair_numpy_param('inputs', inputs, 'labels', |
|
|
|
labels) |
|
|
|
arr_x, arr_y = check_pair_numpy_param('inputs', inputs, 'labels', labels) |
|
|
|
if not self._sparse: |
|
|
|
arr_y = np.argmax(arr_y, axis=1) |
|
|
|
ini_bool, ini_advs, ini_count = self._initialize_starting_point(arr_x, |
|
|
|
arr_y) |
|
|
|
ini_bool, ini_advs, ini_count = self._initialize_starting_point(arr_x, arr_y) |
|
|
|
is_adv_list = list() |
|
|
|
adv_list = list() |
|
|
|
query_times_each_adv = list() |
|
|
|
for sample, sample_label, start_adv, ite_bool, ite_c in zip(arr_x, |
|
|
|
arr_y, |
|
|
|
ini_advs, |
|
|
|
ini_bool, |
|
|
|
ini_count): |
|
|
|
for sample, sample_label, start_adv, ite_bool, ite_c in zip(arr_x, arr_y, ini_advs, ini_bool, ini_count): |
|
|
|
if ite_bool: |
|
|
|
LOGGER.info(TAG, 'Start optimizing.') |
|
|
|
ori_label = np.argmax( |
|
|
|
self._model.predict(np.expand_dims(sample, axis=0))[0]) |
|
|
|
ori_label = np.argmax(self._model.predict(np.expand_dims(sample, axis=0))[0]) |
|
|
|
ini_label = np.argmax(self._model.predict(np.expand_dims(start_adv, axis=0))[0]) |
|
|
|
is_adv, adv_x, query_times = self._decision_optimize(sample, |
|
|
|
sample_label, |
|
|
|
start_adv) |
|
|
|
adv_label = np.argmax( |
|
|
|
self._model.predict(np.expand_dims(adv_x, axis=0))[0]) |
|
|
|
LOGGER.debug(TAG, 'before ini attack label is :{}'.format(ori_label)) |
|
|
|
LOGGER.debug(TAG, 'after ini attack label is :{}'.format(ini_label)) |
|
|
|
LOGGER.debug(TAG, 'INPUT optimize label is :{}'.format(sample_label)) |
|
|
|
LOGGER.debug(TAG, 'after pointwise attack label is :{}'.format(adv_label)) |
|
|
|
is_adv, adv_x, query_times = self._decision_optimize(sample, sample_label, start_adv) |
|
|
|
adv_label = np.argmax(self._model.predict(np.expand_dims(adv_x, axis=0))[0]) |
|
|
|
LOGGER.info(TAG, 'before ini attack label is :{}'.format(ori_label)) |
|
|
|
LOGGER.info(TAG, 'after ini attack label is :{}'.format(ini_label)) |
|
|
|
LOGGER.info(TAG, 'INPUT optimize label is :{}'.format(sample_label)) |
|
|
|
LOGGER.info(TAG, 'after pointwise attack label is :{}'.format(adv_label)) |
|
|
|
is_adv_list.append(is_adv) |
|
|
|
adv_list.append(adv_x) |
|
|
|
query_times_each_adv.append(query_times + ite_c) |
|
|
@@ -133,7 +110,7 @@ class PointWiseAttack(Attack): |
|
|
|
is_adv_list = np.array(is_adv_list) |
|
|
|
adv_list = np.array(adv_list) |
|
|
|
query_times_each_adv = np.array(query_times_each_adv) |
|
|
|
LOGGER.debug(TAG, 'ret list is: {}'.format(adv_list)) |
|
|
|
LOGGER.info(TAG, 'ret list is: {}'.format(adv_list)) |
|
|
|
return is_adv_list, adv_list, query_times_each_adv |
|
|
|
|
|
|
|
def _decision_optimize(self, unperturbed_img, input_label, perturbed_img): |
|
|
@@ -167,8 +144,8 @@ class PointWiseAttack(Attack): |
|
|
|
LOGGER.error(TAG, msg) |
|
|
|
raise ValueError(msg) |
|
|
|
l2_dis = np.linalg.norm(perturbed_img - unperturbed_img) |
|
|
|
LOGGER.debug(TAG, 'Before optimize, the l2 distance between original ' |
|
|
|
'sample and adversarial sample is: {}'.format(l2_dis)) |
|
|
|
LOGGER.info(TAG, 'Before optimize, the l2 distance between original ' \ |
|
|
|
'sample and adversarial sample is: {}'.format(l2_dis)) |
|
|
|
# recover pixel if image is adversarial |
|
|
|
for _ in range(self._max_iter): |
|
|
|
is_improve = False |
|
|
@@ -180,8 +157,7 @@ class PointWiseAttack(Attack): |
|
|
|
if mask[ite_ind]: |
|
|
|
recover[ite_ind] = unperturbed_img[ite_ind] |
|
|
|
query_count += 1 |
|
|
|
is_adv = self._model.is_adversarial( |
|
|
|
recover.reshape(img_shape), input_label, self._is_targeted) |
|
|
|
is_adv = self._model.is_adversarial(recover.reshape(img_shape), input_label, self._is_targeted) |
|
|
|
if is_adv: |
|
|
|
is_improve = True |
|
|
|
perturbed_img[ite_ind] = recover[ite_ind] |
|
|
@@ -189,8 +165,7 @@ class PointWiseAttack(Attack): |
|
|
|
else: |
|
|
|
recover[ite_ind] = perturbed_img[ite_ind] |
|
|
|
l2_dis = np.linalg.norm(perturbed_img - unperturbed_img) |
|
|
|
if not is_improve or (np.square(l2_dis) / np.sqrt(len(pixels_ind)) |
|
|
|
<= self._get_threthod()): |
|
|
|
if not is_improve or (np.square(l2_dis) / np.sqrt(len(pixels_ind)) <= self._get_threthod()): |
|
|
|
break |
|
|
|
LOGGER.debug(TAG, 'first round: Query count {}'.format(query_count)) |
|
|
|
LOGGER.debug(TAG, 'Starting binary searches.') |
|
|
@@ -205,47 +180,36 @@ class PointWiseAttack(Attack): |
|
|
|
continue |
|
|
|
recover[ite_ind] = unperturbed_img[ite_ind] |
|
|
|
query_count += 1 |
|
|
|
is_adv = self._model.is_adversarial(recover.reshape(img_shape), |
|
|
|
input_label, |
|
|
|
self._is_targeted) |
|
|
|
is_adv = self._model.is_adversarial(recover.reshape(img_shape), input_label, self._is_targeted) |
|
|
|
if is_adv: |
|
|
|
is_improve = True |
|
|
|
mask[ite_ind] = True |
|
|
|
mask[ite_ind] = False |
|
|
|
perturbed_img[ite_ind] = recover[ite_ind] |
|
|
|
l2_dis = np.linalg.norm(perturbed_img - unperturbed_img) |
|
|
|
LOGGER.debug(TAG, |
|
|
|
'Reset {}th pixel value to original, ' |
|
|
|
'l2 distance: {}.'.format(ite_ind, l2_dis)) |
|
|
|
LOGGER.info(TAG, 'Reset {}th pixel value to original, l2 distance: {}.'.format(ite_ind, l2_dis)) |
|
|
|
break |
|
|
|
else: |
|
|
|
# use binary searches |
|
|
|
optimized_value, b_query = self._binary_search( |
|
|
|
perturbed_img, |
|
|
|
unperturbed_img, |
|
|
|
ite_ind, |
|
|
|
input_label, img_shape) |
|
|
|
optimized_value, b_query = self._binary_search(perturbed_img, |
|
|
|
unperturbed_img, |
|
|
|
ite_ind, |
|
|
|
input_label, img_shape) |
|
|
|
query_count += b_query |
|
|
|
if optimized_value != perturbed_img[ite_ind]: |
|
|
|
is_improve = True |
|
|
|
mask[ite_ind] = True |
|
|
|
perturbed_img[ite_ind] = optimized_value |
|
|
|
l2_dis = np.linalg.norm(perturbed_img - unperturbed_img) |
|
|
|
LOGGER.debug(TAG, |
|
|
|
'Reset {}th pixel value to original, ' |
|
|
|
'l2 distance: {}.'.format(ite_ind, |
|
|
|
l2_dis)) |
|
|
|
LOGGER.info(TAG, 'Reset {}th pixel value to original, l2 distance: {}.'.format(ite_ind, l2_dis)) |
|
|
|
break |
|
|
|
l2_dis = np.linalg.norm(perturbed_img - unperturbed_img) |
|
|
|
if not is_improve or (np.square(l2_dis) / np.sqrt(len(pixels_ind)) |
|
|
|
<= self._get_threthod()): |
|
|
|
LOGGER.debug(TAG, 'second optimized finish.') |
|
|
|
if not is_improve or (np.square(l2_dis) / np.sqrt(len(pixels_ind)) <= self._get_threthod()): |
|
|
|
LOGGER.info(TAG, 'second optimized finish.') |
|
|
|
break |
|
|
|
LOGGER.info(TAG, 'Optimized finished, query count is {}'.format(query_count)) |
|
|
|
# this method use to optimized the adversarial sample |
|
|
|
return True, perturbed_img.reshape(img_shape), query_count |
|
|
|
|
|
|
|
def _binary_search(self, perturbed_img, unperturbed_img, ite_ind, |
|
|
|
input_label, img_shape): |
|
|
|
def _binary_search(self, perturbed_img, unperturbed_img, ite_ind, input_label, img_shape): |
|
|
|
""" |
|
|
|
For original pixel of inputs, use binary search to get the nearest pixel |
|
|
|
value with original value with adversarial feature. |
|
|
|