Compute saliency map an order of magnitude more efficiently.
Instead of computing saliency map in python with Pool.map,
use efficient numpy operations. This gives a speedup of ~50x
on MNIST/CIFAR tests using only a single thread.
@@ -82,29 +82,6 @@ def apply_perturbations(i, j, X, increase, theta, clip_min, clip_max):
return X
-def saliency_score(packed_data):
- """
- Helper function for saliency_map. This is used for a parallelized map()
- operation via multiprocessing.Pool()
- :param packed_data: tuple containing (index, index, gradients, target,
- other_classes, increase).
- : return: saliency score for the pair of indices i, j. Either
- target_sum * abs(other_sum) if the conditions are met, or 0 otherwise.
- """
-
- # compute the saliency score for the given pair
- i, j, grads_target, grads_others, increase = packed_data
- target_sum = grads_target[i] + grads_target[j]
- other_sum = grads_others[i] + grads_others[j]
-
- # evaluate the saliency map conditions
- if ((increase and target_sum > 0 and other_sum < 0) or
- (not increase and target_sum < 0 and other_sum > 0)):
- return -target_sum * other_sum
- else:
- return 0
-
-
def saliency_map(grads_target, grads_other, search_domain, increase):
"""
TensorFlow implementation for computing salency maps
@@ -118,26 +95,35 @@ def saliency_map(grads_target, grads_other, search_domain, increase):
updated search domain
"""
- # determine the saliency score for every pair of pixels from our search
- # domain
- pool = mp.Pool()
- scores = pool.map(saliency_score,
- [(i, j, grads_target, grads_other, increase)
- for i, j in itertools.combinations(search_domain, 2)])
+ size = len(grads_target)
+
+ # remove the already-used targets from the search space
+ invalid = list(set(range(size)) - search_domain)
+ grads_target[invalid] = 0
+ grads_other[invalid] = 0
+
+ # create a 2-d numpy array of the sum of grads_target and grads_other
+ target_sum = grads_target.reshape((1,size))+grads_target.reshape((size,1))
+ other_sum = grads_other.reshape((1,size))+grads_other.reshape((size,1))
+
+ # create a 2-d numpy array of the scores for each pair of inputs to change
+ if increase:
+ scores = ((target_sum > 0) & (other_sum < 0)) * (-target_sum * other_sum)
+ else:
+ scores = ((target_sum < 0) & (other_sum > 0)) * (-target_sum * other_sum)
- # wait for the threads to finish to free up memory
- pool.close()
- pool.join()
+ # no changing one pixel twice
+ np.fill_diagonal(scores, 0)
- # grab the pixels with the largest scores
- candidates = np.argmax(scores)
- pairs = [elt for elt in itertools.combinations(search_domain, 2)]
+ # extract the best two pixels
+ best = np.argmax(scores)
+ p1, p2 = best%size, best//size
# update our search domain
- search_domain.remove(pairs[candidates][0])
- search_domain.remove(pairs[candidates][1])
+ search_domain.remove(p1)
+ search_domain.remove(p2)
- return pairs[candidates][0], pairs[candidates][1], search_domain
+ return p1, p2, search_domain
def jacobian(sess, x, grads, target, X, nb_features, nb_classes):
