bat.attacks.simba_attack

This module implements the black-box attack SimBA.

  1"""
  2This module implements the black-box attack `SimBA`.
  3"""
  4
  5import os
  6import gc
  7import numpy as np
  8from tqdm import tqdm
  9import concurrent.futures
 10
 11from bat.attacks.base_attack import BaseAttack
 12
 13SCALE = 255
 14PREPROCESS = lambda x: x
 15
 16def proj_lp(v, xi=0.1, p=2):
 17    """
 18    SUPPORTS only p = 2 and p = Inf for now
 19    """
 20    if p == 2:
 21        v = v * min(1, xi/np.linalg.norm(v.flatten('C')))
 22        # v = v / np.linalg.norm(v.flatten(1)) * xi
 23    elif p == np.inf:
 24        v = np.sign(v) * np.minimum(abs(v), xi)
 25    else:
 26        raise ValueError('Values of p different from 2 and Inf are currently not supported...')
 27
 28    return v
 29
 30class SimBA(BaseAttack):
 31    """
 32    Implementation of the `SimBA` attack. Paper link: https://arxiv.org/abs/1905.07121
 33    """
 34
 35    def __init__(self,  classifier):
 36        """
 37        Create a class: `SimBA` instance.
 38        - classifier: model to attack
 39        """
 40        super().__init__(classifier)
 41
 42    def init(self, x, max_it):
 43        """
 44        Initialize the attack.
 45        """
 46
 47        x_adv = x.copy()
 48        y_pred = self.classifier.predict(PREPROCESS(x.copy()))
 49
 50        perm = []
 51        for xi in x:
 52            perm.append(np.random.permutation(xi.reshape(-1).shape[0]))
 53            assert len(perm[-1]) > max_it, 'The maxinum number of iteration should be smaller than the image dimension.'
 54
 55        return x_adv, y_pred, perm
 56
 57    def step(self, x_adv, y_pred, perm, index, epsilon):
 58        """
 59        Single step for non-distributed attack.
 60        """
 61
 62        x_adv_plus = []
 63        x_adv_minus = []
 64        x_adv_diff = []
 65        for i in range(0, len(x_adv)):
 66            diff = np.zeros(x_adv[i].reshape(-1).shape[0])
 67            diff[perm[i][index]] = epsilon
 68            diff = diff.reshape(x_adv[i].shape)
 69            x_adv_plus.append(np.clip(x_adv[i] + diff, 0, 1 * SCALE))
 70            x_adv_minus.append(np.clip(x_adv[i] - diff, 0, 1 * SCALE))
 71            x_adv_diff.append(diff)
 72
 73        plus = self.classifier.predict(PREPROCESS(x_adv_plus.copy()))
 74        minus = self.classifier.predict(PREPROCESS(x_adv_minus.copy()))
 75        
 76        for i in range(0, len(x_adv)):
 77            if plus[i][np.argmax(y_pred[i])] < y_pred[i][np.argmax(y_pred[i])]:
 78                x_adv[i] = x_adv[i] + x_adv_diff[i]
 79                y_pred[i] = plus[i]
 80            elif minus[i][np.argmax(y_pred[i])] < y_pred[i][np.argmax(y_pred[i])]:
 81                x_adv[i] = x_adv[i] - x_adv_diff[i]
 82                y_pred[i] = minus[i]
 83            else:
 84                pass
 85
 86        return x_adv, y_pred
 87
 88    def batch(self, x_adv, y_pred, perm, index, epsilon, concurrency):
 89        """
 90        Single step for distributed attack.
 91        """
 92        noises = []
 93        for i in range(0, len(x_adv)):
 94            noises.append(np.zeros(x_adv[i].shape))
 95
 96        with concurrent.futures.ThreadPoolExecutor() as executor:
 97            future_to_url = {executor.submit(self.step, x_adv, y_pred, perm, index+j, epsilon): j for j in range(0, concurrency)}
 98            for future in concurrent.futures.as_completed(future_to_url):
 99                j = future_to_url[future]
100                try:
101                    x_adv_new, _ = future.result()
102                    for i in range(0, len(x_adv)):
103                        noises[i] = noises[i] + x_adv_new[i] - x_adv[i]
104                except Exception as exc:
105                    print('Task %r generated an exception: %s' % (j, exc))
106                else:
107                    pass
108
109        for i in range(0, len(x_adv)):
110            if(np.sum(noises[i]) != 0):
111                noises = proj_lp(noises[i], xi = 10)
112            x_adv[i] = np.clip(x_adv[i] + noises[i], 0, 1 * SCALE)
113
114        y_adv = self.classifier.predict(PREPROCESS(x_adv.copy())) 
115
116        return x_adv, y_adv
117
118    def attack(self, x, y, epsilon=0.05, max_it=1000, concurrency=1):
119        """
120        Initiate the attack.
121
122        - x: input data
123        - y: input labels
124        - epsilon: perturbation on each pixel
125        - max_it: number of iterations
126        - concurrency: number of concurrent threads
127        """
128
129        n_targets = 0
130        if type(x) == list:
131            n_targets = len(x)
132        elif type(x) == np.ndarray:
133            n_targets = x.shape[0]
134        else:
135            raise ValueError('Input type not supported...')
136
137        assert n_targets > 0
138
139        # Initialize attack
140        x_adv, y_pred, perm = self.init(x, max_it)
141
142        # Compute number of images correctly classified
143        y_pred_classes = np.argmax(y_pred, axis=1)
144        correct_classified_mask = (y_pred_classes == y)
145        correct_classified = [i for i, v in enumerate(correct_classified_mask) if v]
146
147        # Images to attack
148        not_dones_mask = correct_classified_mask.copy()
149
150        print('Clean accuracy: {:.2%}'.format(np.mean(correct_classified_mask)))
151
152        if np.mean(correct_classified_mask) == 0:
153            print('No clean examples classified correctly. Aborting...')
154            n_queries = np.ones(len(x))  # ones because we have already used 1 query
155
156            mean_nq, mean_nq_ae = np.mean(n_queries), np.mean(n_queries)
157
158            return x
159
160        else:
161            if n_targets > 1:
162                # Horizontally Distributed Attack
163                pbar = tqdm(range(0, max_it), desc="Distributed SimBA Attack (Horizontal)")
164            else:
165                # Vertically Distributed Attack
166                pbar = tqdm(range(0, max_it, concurrency), desc="Distributed SimBA Attack (Vertical)")
167
168        total_queries = np.zeros(len(x))
169
170        for i_iter in pbar:
171
172            not_dones = [i for i, v in enumerate(not_dones_mask) if v]
173
174            x_adv_curr = [x_adv[idx] for idx in not_dones]
175            y_curr = [y_pred[idx] for idx in not_dones]
176            perm_curr = [perm[idx] for idx in not_dones]
177
178            y_curr = np.array(y_curr)
179
180            if n_targets > 1:
181                # Horizontally Distributed Attack
182                x_adv_curr, y_curr = self.step(x_adv_curr, y_curr, perm_curr, i_iter, epsilon*SCALE)
183            else:
184                # Vertically Distributed Attack
185                x_adv_curr, y_curr = self.batch(x_adv_curr, y_curr, perm_curr, i_iter, epsilon*SCALE, concurrency)
186
187            for i in range(len(not_dones)):
188                x_adv[not_dones[i]] = x_adv_curr[i]
189                y_pred[not_dones[i]] = y_curr[i]
190
191            # Logging stuff
192            if n_targets > 1:
193                # Horizontally Distributed Attack
194                total_queries += 2 * not_dones_mask
195            else:
196                # Vertically Distributed Attack
197                total_queries += 2 * concurrency * not_dones_mask + 1
198
199            y_pred_classes = np.argmax(y_pred, axis=1)
200            not_dones_mask = not_dones_mask * (y_pred_classes == y)
201
202            success_mask = correct_classified_mask * (1 - not_dones_mask)
203            num_success = success_mask.sum()
204            current_success_rate = (num_success / correct_classified_mask.sum())
205
206            if num_success == 0:
207                success_queries = -1
208            else:
209                success_queries = ((success_mask * total_queries).sum() / num_success)
210
211            pbar.set_postfix({'Total Queries': total_queries.sum(), 'Mean Higest Prediction': y_pred[correct_classified].max(axis=1).mean(), 'Attack Success Rate': current_success_rate, 'Avg Queries': success_queries})
212
213            acc = not_dones_mask.sum() / correct_classified_mask.sum()
214            mean_nq, mean_nq_ae = np.mean(total_queries), np.mean(total_queries *success_mask)
215
216            # Early break
217            if current_success_rate == 1.0:
218                break
219
220            gc.collect()
221
222        return x_adv
SCALE = 255
def PREPROCESS(x):
15PREPROCESS = lambda x: x
def proj_lp(v, xi=0.1, p=2):
17def proj_lp(v, xi=0.1, p=2):
18    """
19    SUPPORTS only p = 2 and p = Inf for now
20    """
21    if p == 2:
22        v = v * min(1, xi/np.linalg.norm(v.flatten('C')))
23        # v = v / np.linalg.norm(v.flatten(1)) * xi
24    elif p == np.inf:
25        v = np.sign(v) * np.minimum(abs(v), xi)
26    else:
27        raise ValueError('Values of p different from 2 and Inf are currently not supported...')
28
29    return v

SUPPORTS only p = 2 and p = Inf for now

class SimBA(bat.attacks.base_attack.BaseAttack):
 31class SimBA(BaseAttack):
 32    """
 33    Implementation of the `SimBA` attack. Paper link: https://arxiv.org/abs/1905.07121
 34    """
 35
 36    def __init__(self,  classifier):
 37        """
 38        Create a class: `SimBA` instance.
 39        - classifier: model to attack
 40        """
 41        super().__init__(classifier)
 42
 43    def init(self, x, max_it):
 44        """
 45        Initialize the attack.
 46        """
 47
 48        x_adv = x.copy()
 49        y_pred = self.classifier.predict(PREPROCESS(x.copy()))
 50
 51        perm = []
 52        for xi in x:
 53            perm.append(np.random.permutation(xi.reshape(-1).shape[0]))
 54            assert len(perm[-1]) > max_it, 'The maxinum number of iteration should be smaller than the image dimension.'
 55
 56        return x_adv, y_pred, perm
 57
 58    def step(self, x_adv, y_pred, perm, index, epsilon):
 59        """
 60        Single step for non-distributed attack.
 61        """
 62
 63        x_adv_plus = []
 64        x_adv_minus = []
 65        x_adv_diff = []
 66        for i in range(0, len(x_adv)):
 67            diff = np.zeros(x_adv[i].reshape(-1).shape[0])
 68            diff[perm[i][index]] = epsilon
 69            diff = diff.reshape(x_adv[i].shape)
 70            x_adv_plus.append(np.clip(x_adv[i] + diff, 0, 1 * SCALE))
 71            x_adv_minus.append(np.clip(x_adv[i] - diff, 0, 1 * SCALE))
 72            x_adv_diff.append(diff)
 73
 74        plus = self.classifier.predict(PREPROCESS(x_adv_plus.copy()))
 75        minus = self.classifier.predict(PREPROCESS(x_adv_minus.copy()))
 76        
 77        for i in range(0, len(x_adv)):
 78            if plus[i][np.argmax(y_pred[i])] < y_pred[i][np.argmax(y_pred[i])]:
 79                x_adv[i] = x_adv[i] + x_adv_diff[i]
 80                y_pred[i] = plus[i]
 81            elif minus[i][np.argmax(y_pred[i])] < y_pred[i][np.argmax(y_pred[i])]:
 82                x_adv[i] = x_adv[i] - x_adv_diff[i]
 83                y_pred[i] = minus[i]
 84            else:
 85                pass
 86
 87        return x_adv, y_pred
 88
 89    def batch(self, x_adv, y_pred, perm, index, epsilon, concurrency):
 90        """
 91        Single step for distributed attack.
 92        """
 93        noises = []
 94        for i in range(0, len(x_adv)):
 95            noises.append(np.zeros(x_adv[i].shape))
 96
 97        with concurrent.futures.ThreadPoolExecutor() as executor:
 98            future_to_url = {executor.submit(self.step, x_adv, y_pred, perm, index+j, epsilon): j for j in range(0, concurrency)}
 99            for future in concurrent.futures.as_completed(future_to_url):
100                j = future_to_url[future]
101                try:
102                    x_adv_new, _ = future.result()
103                    for i in range(0, len(x_adv)):
104                        noises[i] = noises[i] + x_adv_new[i] - x_adv[i]
105                except Exception as exc:
106                    print('Task %r generated an exception: %s' % (j, exc))
107                else:
108                    pass
109
110        for i in range(0, len(x_adv)):
111            if(np.sum(noises[i]) != 0):
112                noises = proj_lp(noises[i], xi = 10)
113            x_adv[i] = np.clip(x_adv[i] + noises[i], 0, 1 * SCALE)
114
115        y_adv = self.classifier.predict(PREPROCESS(x_adv.copy())) 
116
117        return x_adv, y_adv
118
119    def attack(self, x, y, epsilon=0.05, max_it=1000, concurrency=1):
120        """
121        Initiate the attack.
122
123        - x: input data
124        - y: input labels
125        - epsilon: perturbation on each pixel
126        - max_it: number of iterations
127        - concurrency: number of concurrent threads
128        """
129
130        n_targets = 0
131        if type(x) == list:
132            n_targets = len(x)
133        elif type(x) == np.ndarray:
134            n_targets = x.shape[0]
135        else:
136            raise ValueError('Input type not supported...')
137
138        assert n_targets > 0
139
140        # Initialize attack
141        x_adv, y_pred, perm = self.init(x, max_it)
142
143        # Compute number of images correctly classified
144        y_pred_classes = np.argmax(y_pred, axis=1)
145        correct_classified_mask = (y_pred_classes == y)
146        correct_classified = [i for i, v in enumerate(correct_classified_mask) if v]
147
148        # Images to attack
149        not_dones_mask = correct_classified_mask.copy()
150
151        print('Clean accuracy: {:.2%}'.format(np.mean(correct_classified_mask)))
152
153        if np.mean(correct_classified_mask) == 0:
154            print('No clean examples classified correctly. Aborting...')
155            n_queries = np.ones(len(x))  # ones because we have already used 1 query
156
157            mean_nq, mean_nq_ae = np.mean(n_queries), np.mean(n_queries)
158
159            return x
160
161        else:
162            if n_targets > 1:
163                # Horizontally Distributed Attack
164                pbar = tqdm(range(0, max_it), desc="Distributed SimBA Attack (Horizontal)")
165            else:
166                # Vertically Distributed Attack
167                pbar = tqdm(range(0, max_it, concurrency), desc="Distributed SimBA Attack (Vertical)")
168
169        total_queries = np.zeros(len(x))
170
171        for i_iter in pbar:
172
173            not_dones = [i for i, v in enumerate(not_dones_mask) if v]
174
175            x_adv_curr = [x_adv[idx] for idx in not_dones]
176            y_curr = [y_pred[idx] for idx in not_dones]
177            perm_curr = [perm[idx] for idx in not_dones]
178
179            y_curr = np.array(y_curr)
180
181            if n_targets > 1:
182                # Horizontally Distributed Attack
183                x_adv_curr, y_curr = self.step(x_adv_curr, y_curr, perm_curr, i_iter, epsilon*SCALE)
184            else:
185                # Vertically Distributed Attack
186                x_adv_curr, y_curr = self.batch(x_adv_curr, y_curr, perm_curr, i_iter, epsilon*SCALE, concurrency)
187
188            for i in range(len(not_dones)):
189                x_adv[not_dones[i]] = x_adv_curr[i]
190                y_pred[not_dones[i]] = y_curr[i]
191
192            # Logging stuff
193            if n_targets > 1:
194                # Horizontally Distributed Attack
195                total_queries += 2 * not_dones_mask
196            else:
197                # Vertically Distributed Attack
198                total_queries += 2 * concurrency * not_dones_mask + 1
199
200            y_pred_classes = np.argmax(y_pred, axis=1)
201            not_dones_mask = not_dones_mask * (y_pred_classes == y)
202
203            success_mask = correct_classified_mask * (1 - not_dones_mask)
204            num_success = success_mask.sum()
205            current_success_rate = (num_success / correct_classified_mask.sum())
206
207            if num_success == 0:
208                success_queries = -1
209            else:
210                success_queries = ((success_mask * total_queries).sum() / num_success)
211
212            pbar.set_postfix({'Total Queries': total_queries.sum(), 'Mean Higest Prediction': y_pred[correct_classified].max(axis=1).mean(), 'Attack Success Rate': current_success_rate, 'Avg Queries': success_queries})
213
214            acc = not_dones_mask.sum() / correct_classified_mask.sum()
215            mean_nq, mean_nq_ae = np.mean(total_queries), np.mean(total_queries *success_mask)
216
217            # Early break
218            if current_success_rate == 1.0:
219                break
220
221            gc.collect()
222
223        return x_adv

Implementation of the SimBA attack. Paper link: https://arxiv.org/abs/1905.07121

SimBA(classifier)
36    def __init__(self,  classifier):
37        """
38        Create a class: `SimBA` instance.
39        - classifier: model to attack
40        """
41        super().__init__(classifier)

Create a class: SimBA instance.

  • classifier: model to attack
def init(self, x, max_it):
43    def init(self, x, max_it):
44        """
45        Initialize the attack.
46        """
47
48        x_adv = x.copy()
49        y_pred = self.classifier.predict(PREPROCESS(x.copy()))
50
51        perm = []
52        for xi in x:
53            perm.append(np.random.permutation(xi.reshape(-1).shape[0]))
54            assert len(perm[-1]) > max_it, 'The maxinum number of iteration should be smaller than the image dimension.'
55
56        return x_adv, y_pred, perm

Initialize the attack.

def step(self, x_adv, y_pred, perm, index, epsilon):
58    def step(self, x_adv, y_pred, perm, index, epsilon):
59        """
60        Single step for non-distributed attack.
61        """
62
63        x_adv_plus = []
64        x_adv_minus = []
65        x_adv_diff = []
66        for i in range(0, len(x_adv)):
67            diff = np.zeros(x_adv[i].reshape(-1).shape[0])
68            diff[perm[i][index]] = epsilon
69            diff = diff.reshape(x_adv[i].shape)
70            x_adv_plus.append(np.clip(x_adv[i] + diff, 0, 1 * SCALE))
71            x_adv_minus.append(np.clip(x_adv[i] - diff, 0, 1 * SCALE))
72            x_adv_diff.append(diff)
73
74        plus = self.classifier.predict(PREPROCESS(x_adv_plus.copy()))
75        minus = self.classifier.predict(PREPROCESS(x_adv_minus.copy()))
76        
77        for i in range(0, len(x_adv)):
78            if plus[i][np.argmax(y_pred[i])] < y_pred[i][np.argmax(y_pred[i])]:
79                x_adv[i] = x_adv[i] + x_adv_diff[i]
80                y_pred[i] = plus[i]
81            elif minus[i][np.argmax(y_pred[i])] < y_pred[i][np.argmax(y_pred[i])]:
82                x_adv[i] = x_adv[i] - x_adv_diff[i]
83                y_pred[i] = minus[i]
84            else:
85                pass
86
87        return x_adv, y_pred

Single step for non-distributed attack.

def batch(self, x_adv, y_pred, perm, index, epsilon, concurrency):
 89    def batch(self, x_adv, y_pred, perm, index, epsilon, concurrency):
 90        """
 91        Single step for distributed attack.
 92        """
 93        noises = []
 94        for i in range(0, len(x_adv)):
 95            noises.append(np.zeros(x_adv[i].shape))
 96
 97        with concurrent.futures.ThreadPoolExecutor() as executor:
 98            future_to_url = {executor.submit(self.step, x_adv, y_pred, perm, index+j, epsilon): j for j in range(0, concurrency)}
 99            for future in concurrent.futures.as_completed(future_to_url):
100                j = future_to_url[future]
101                try:
102                    x_adv_new, _ = future.result()
103                    for i in range(0, len(x_adv)):
104                        noises[i] = noises[i] + x_adv_new[i] - x_adv[i]
105                except Exception as exc:
106                    print('Task %r generated an exception: %s' % (j, exc))
107                else:
108                    pass
109
110        for i in range(0, len(x_adv)):
111            if(np.sum(noises[i]) != 0):
112                noises = proj_lp(noises[i], xi = 10)
113            x_adv[i] = np.clip(x_adv[i] + noises[i], 0, 1 * SCALE)
114
115        y_adv = self.classifier.predict(PREPROCESS(x_adv.copy())) 
116
117        return x_adv, y_adv

Single step for distributed attack.

def attack(self, x, y, epsilon=0.05, max_it=1000, concurrency=1):
119    def attack(self, x, y, epsilon=0.05, max_it=1000, concurrency=1):
120        """
121        Initiate the attack.
122
123        - x: input data
124        - y: input labels
125        - epsilon: perturbation on each pixel
126        - max_it: number of iterations
127        - concurrency: number of concurrent threads
128        """
129
130        n_targets = 0
131        if type(x) == list:
132            n_targets = len(x)
133        elif type(x) == np.ndarray:
134            n_targets = x.shape[0]
135        else:
136            raise ValueError('Input type not supported...')
137
138        assert n_targets > 0
139
140        # Initialize attack
141        x_adv, y_pred, perm = self.init(x, max_it)
142
143        # Compute number of images correctly classified
144        y_pred_classes = np.argmax(y_pred, axis=1)
145        correct_classified_mask = (y_pred_classes == y)
146        correct_classified = [i for i, v in enumerate(correct_classified_mask) if v]
147
148        # Images to attack
149        not_dones_mask = correct_classified_mask.copy()
150
151        print('Clean accuracy: {:.2%}'.format(np.mean(correct_classified_mask)))
152
153        if np.mean(correct_classified_mask) == 0:
154            print('No clean examples classified correctly. Aborting...')
155            n_queries = np.ones(len(x))  # ones because we have already used 1 query
156
157            mean_nq, mean_nq_ae = np.mean(n_queries), np.mean(n_queries)
158
159            return x
160
161        else:
162            if n_targets > 1:
163                # Horizontally Distributed Attack
164                pbar = tqdm(range(0, max_it), desc="Distributed SimBA Attack (Horizontal)")
165            else:
166                # Vertically Distributed Attack
167                pbar = tqdm(range(0, max_it, concurrency), desc="Distributed SimBA Attack (Vertical)")
168
169        total_queries = np.zeros(len(x))
170
171        for i_iter in pbar:
172
173            not_dones = [i for i, v in enumerate(not_dones_mask) if v]
174
175            x_adv_curr = [x_adv[idx] for idx in not_dones]
176            y_curr = [y_pred[idx] for idx in not_dones]
177            perm_curr = [perm[idx] for idx in not_dones]
178
179            y_curr = np.array(y_curr)
180
181            if n_targets > 1:
182                # Horizontally Distributed Attack
183                x_adv_curr, y_curr = self.step(x_adv_curr, y_curr, perm_curr, i_iter, epsilon*SCALE)
184            else:
185                # Vertically Distributed Attack
186                x_adv_curr, y_curr = self.batch(x_adv_curr, y_curr, perm_curr, i_iter, epsilon*SCALE, concurrency)
187
188            for i in range(len(not_dones)):
189                x_adv[not_dones[i]] = x_adv_curr[i]
190                y_pred[not_dones[i]] = y_curr[i]
191
192            # Logging stuff
193            if n_targets > 1:
194                # Horizontally Distributed Attack
195                total_queries += 2 * not_dones_mask
196            else:
197                # Vertically Distributed Attack
198                total_queries += 2 * concurrency * not_dones_mask + 1
199
200            y_pred_classes = np.argmax(y_pred, axis=1)
201            not_dones_mask = not_dones_mask * (y_pred_classes == y)
202
203            success_mask = correct_classified_mask * (1 - not_dones_mask)
204            num_success = success_mask.sum()
205            current_success_rate = (num_success / correct_classified_mask.sum())
206
207            if num_success == 0:
208                success_queries = -1
209            else:
210                success_queries = ((success_mask * total_queries).sum() / num_success)
211
212            pbar.set_postfix({'Total Queries': total_queries.sum(), 'Mean Higest Prediction': y_pred[correct_classified].max(axis=1).mean(), 'Attack Success Rate': current_success_rate, 'Avg Queries': success_queries})
213
214            acc = not_dones_mask.sum() / correct_classified_mask.sum()
215            mean_nq, mean_nq_ae = np.mean(total_queries), np.mean(total_queries *success_mask)
216
217            # Early break
218            if current_success_rate == 1.0:
219                break
220
221            gc.collect()
222
223        return x_adv

Initiate the attack.

  • x: input data
  • y: input labels
  • epsilon: perturbation on each pixel
  • max_it: number of iterations
  • concurrency: number of concurrent threads