Skip to content

Commit a031768

Browse files
Irina NicolaeIrina Nicolae
authored andcommitted
Merge batching (close #41)
2 parents a34a140 + 7eb6a8e commit a031768

File tree

8 files changed

+91
-70
lines changed

8 files changed

+91
-70
lines changed

art/attacks/fast_gradient.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ class FastGradientMethod(Attack):
1212
Gradient Sign Method"). This implementation extends the attack to other norms, and is therefore called the Fast
1313
Gradient Method. Paper link: https://arxiv.org/abs/1412.6572
1414
"""
15-
attack_params = Attack.attack_params + ['norm', 'eps', 'targeted', 'random_init']
15+
attack_params = Attack.attack_params + ['norm', 'eps', 'targeted', 'random_init', 'batch_size']
1616

17-
def __init__(self, classifier, norm=np.inf, eps=.3, targeted=False, random_init=False):
17+
def __init__(self, classifier, norm=np.inf, eps=.3, targeted=False, random_init=False, batch_size=128):
1818
"""
1919
Create a :class:`FastGradientMethod` instance.
2020
@@ -28,13 +28,16 @@ def __init__(self, classifier, norm=np.inf, eps=.3, targeted=False, random_init=
2828
:type targeted: `bool`
2929
:param random_init: Whether to start at the original input or a random point within the epsilon ball
3030
:type random_init: `bool`
31+
:param batch_size: Batch size
32+
:type batch_size: `int`
3133
"""
3234
super(FastGradientMethod, self).__init__(classifier)
3335

3436
self.norm = norm
3537
self.eps = eps
3638
self.targeted = targeted
3739
self.random_init = random_init
40+
self.batch_size = batch_size
3841

3942
def _minimal_perturbation(self, x, y, eps_step=0.1, eps_max=1., **kwargs):
4043
"""Iteratively compute the minimal perturbation necessary to make the class prediction change. Stop when the
@@ -55,9 +58,8 @@ def _minimal_perturbation(self, x, y, eps_step=0.1, eps_max=1., **kwargs):
5558
adv_x = x.copy()
5659

5760
# Compute perturbation with implicit batching
58-
batch_size = 128
59-
for batch_id in range(adv_x.shape[0] // batch_size + 1):
60-
batch_index_1, batch_index_2 = batch_id * batch_size, min((batch_id + 1) * batch_size, x.shape[0])
61+
for batch_id in range(int(np.ceil(adv_x.shape[0] / float(self.batch_size)))):
62+
batch_index_1, batch_index_2 = batch_id * self.batch_size, (batch_id + 1) * self.batch_size
6163
batch = adv_x[batch_index_1:batch_index_2]
6264
batch_labels = y[batch_index_1:batch_index_2]
6365

@@ -101,6 +103,8 @@ def generate(self, x, **kwargs):
101103
:type minimal: `bool`
102104
:param random_init: Whether to start at the original input or a random point within the epsilon ball
103105
:type random_init: `bool`
106+
:param batch_size: Batch size
107+
:type batch_size: `int`
104108
:return: An array holding the adversarial examples.
105109
:rtype: `np.ndarray`
106110
"""
@@ -134,6 +138,8 @@ def set_params(self, **kwargs):
134138
:type eps: `float`
135139
:param targeted: Should the attack target one specific class
136140
:type targeted: `bool`
141+
:param batch_size: Batch size
142+
:type batch_size: `int`
137143
"""
138144
# Save attack-specific parameters
139145
super(FastGradientMethod, self).set_params(**kwargs)
@@ -144,6 +150,10 @@ def set_params(self, **kwargs):
144150

145151
if self.eps <= 0:
146152
raise ValueError('The perturbation size `eps` has to be positive.')
153+
154+
if self.batch_size <= 0:
155+
raise ValueError('The batch size `batch_size` has to be positive.')
156+
147157
return True
148158

149159
def _compute_perturbation(self, batch, batch_labels):
@@ -179,9 +189,8 @@ def _compute(self, x, y, eps, random_init):
179189
adv_x = x.copy()
180190

181191
# Compute perturbation with implicit batching
182-
batch_size = 128
183-
for batch_id in range(adv_x.shape[0] // batch_size + 1):
184-
batch_index_1, batch_index_2 = batch_id * batch_size, (batch_id + 1) * batch_size
192+
for batch_id in range(int(np.ceil(adv_x.shape[0] / float(self.batch_size)))):
193+
batch_index_1, batch_index_2 = batch_id * self.batch_size, (batch_id + 1) * self.batch_size
185194
batch = adv_x[batch_index_1:batch_index_2]
186195
batch_labels = y[batch_index_1:batch_index_2]
187196

art/attacks/fast_gradient_unittest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ def _test_backend_mnist(self, classifier):
105105

106106
# Test FGSM with np.inf norm
107107
attack = FastGradientMethod(classifier, eps=1)
108-
x_test_adv = attack.generate(x_test)
109-
x_train_adv = attack.generate(x_train)
108+
x_test_adv = attack.generate(x_test, **{'batch_size': 2})
109+
x_train_adv = attack.generate(x_train, **{'batch_size': 4})
110110

111111
self.assertFalse((x_train == x_train_adv).all())
112112
self.assertFalse((x_test == x_test_adv).all())

art/attacks/universal_perturbation_unittest.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from art.classifiers import KerasClassifier, PyTorchClassifier, TFClassifier
1515
from art.utils import load_mnist
1616

17-
BATCH_SIZE, NB_TRAIN, NB_TEST = 100, 1000, 10
17+
BATCH_SIZE, NB_TRAIN, NB_TEST = 100, 500, 10
1818

1919

2020
class Model(nn.Module):
@@ -82,7 +82,7 @@ def test_tfclassifier(self):
8282

8383
# Attack
8484
# TODO Launch with all possible attacks
85-
attack_params = {"attacker": "newtonfool", "attacker_params": {"max_iter": 20}}
85+
attack_params = {"attacker": "newtonfool", "attacker_params": {"max_iter": 5}}
8686
up = UniversalPerturbation(tfc)
8787
x_train_adv = up.generate(x_train, **attack_params)
8888
self.assertTrue((up.fooling_rate >= 0.2) or not up.converged)
@@ -123,7 +123,7 @@ def test_krclassifier(self):
123123

124124
# Attack
125125
# TODO Launch with all possible attacks
126-
attack_params = {"attacker": "newtonfool", "attacker_params": {"max_iter": 20}}
126+
attack_params = {"attacker": "newtonfool", "attacker_params": {"max_iter": 5}}
127127
up = UniversalPerturbation(krc)
128128
x_train_adv = up.generate(x_train, **attack_params)
129129
self.assertTrue((up.fooling_rate >= 0.2) or not up.converged)
@@ -160,7 +160,7 @@ def test_ptclassifier(self):
160160

161161
# Attack
162162
# TODO Launch with all possible attacks
163-
attack_params = {"attacker": "newtonfool", "attacker_params": {"max_iter": 20}}
163+
attack_params = {"attacker": "newtonfool", "attacker_params": {"max_iter": 5}}
164164
up = UniversalPerturbation(ptc)
165165
x_train_adv = up.generate(x_train, **attack_params)
166166
self.assertTrue((up.fooling_rate >= 0.2) or not up.converged)

art/classifiers/classifier.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,16 @@ def __init__(self, clip_values, channel_index, defences=None, preprocessing=(0,
4444
self._preprocessing = preprocessing
4545

4646
@abc.abstractmethod
47-
def predict(self, x, logits=False):
47+
def predict(self, x, logits=False, batch_size=128):
4848
"""
4949
Perform prediction for a batch of inputs.
5050
5151
:param x: Test set.
5252
:type x: `np.ndarray`
5353
:param logits: `True` if the prediction should be done at the logits layer.
5454
:type logits: `bool`
55+
:param batch_size: Size of batches.
56+
:type batch_size: `int`
5557
:return: Array of predictions of shape `(nb_inputs, self.nb_classes)`.
5658
:rtype: `np.ndarray`
5759
"""

art/classifiers/keras.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,14 +146,16 @@ def class_gradient(self, x, label=None, logits=False):
146146

147147
return grads
148148

149-
def predict(self, x, logits=False):
149+
def predict(self, x, logits=False, batch_size=128):
150150
"""
151151
Perform prediction for a batch of inputs.
152152
153153
:param x: Test set.
154154
:type x: `np.ndarray`
155155
:param logits: `True` if the prediction should be done at the logits layer.
156156
:type logits: `bool`
157+
:param batch_size: Size of batches.
158+
:type batch_size: `int`
157159
:return: Array of predictions of shape `(nb_inputs, self.nb_classes)`.
158160
:rtype: `np.ndarray`
159161
"""
@@ -165,9 +167,8 @@ def predict(self, x, logits=False):
165167
x_ = self._apply_defences_predict(x_)
166168

167169
# Run predictions with batching
168-
batch_size = 512
169170
preds = np.zeros((x_.shape[0], self.nb_classes), dtype=np.float32)
170-
for b in range(x_.shape[0] // batch_size + 1):
171+
for b in range(int(np.ceil(x_.shape[0] / float(batch_size)))):
171172
begin, end = b * batch_size, min((b + 1) * batch_size, x_.shape[0])
172173
preds[begin:end] = self._preds([x_[begin:end]])[0]
173174

art/classifiers/mxnet.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,16 @@ def fit(self, x, y, batch_size=128, nb_epochs=20):
9999
# Update parameters
100100
self._optimizer.step(batch_size)
101101

102-
def predict(self, x, logits=False):
102+
def predict(self, x, logits=False, batch_size=128):
103103
"""
104104
Perform prediction for a batch of inputs.
105105
106106
:param x: Test set.
107107
:type x: `np.ndarray`
108108
:param logits: `True` if the prediction should be done at the logits layer.
109109
:type logits: `bool`
110+
:param batch_size: Size of batches.
111+
:type batch_size: `int`
110112
:return: Array of predictions of shape `(nb_inputs, self.nb_classes)`.
111113
:rtype: `np.ndarray`
112114
"""
@@ -116,26 +118,25 @@ def predict(self, x, logits=False):
116118
x_ = self._apply_processing(x)
117119
x_ = self._apply_defences_predict(x_)
118120

119-
# Predict
120-
# TODO add batching?
121-
x_ = nd.array(x_, ctx=self._ctx)
122-
x_.attach_grad()
123-
with autograd.record(train_mode=False):
124-
preds = self._model(x_)
121+
# Run prediction with batch processing
122+
results = np.zeros((x_.shape[0], self.nb_classes), dtype=np.float32)
123+
num_batch = int(np.ceil(len(x_) / float(batch_size)))
124+
for m in range(num_batch):
125+
# Batch indexes
126+
begin, end = m * batch_size, min((m + 1) * batch_size, x_.shape[0])
127+
128+
# Predict
129+
x_batch = nd.array(x_[begin:end], ctx=self._ctx)
130+
x_batch.attach_grad()
131+
with autograd.record(train_mode=False):
132+
preds = self._model(x_batch)
125133

126-
if logits is True:
127-
preds = preds.softmax()
134+
if logits is False:
135+
preds = preds.softmax()
128136

129-
# preds = np.empty((x.shape[0], self.nb_classes), dtype=float)
130-
# pred_iter = mx.io.NDArrayIter(data=x_, batch_size=128)
131-
# if logits is True:
132-
# for preds_i, i, batch in mod.iter_predict(pred_iter):
133-
# pred_label = preds_i[0].asnumpy()
134-
# else:
135-
# for preds_i, i, batch in mod.iter_predict(pred_iter):
136-
# pred_label = preds_i[0].softmax().asnumpy()
137+
results[begin:end] = preds.asnumpy()
137138

138-
return preds.asnumpy()
139+
return results
139140

140141
def class_gradient(self, x, label=None, logits=False):
141142
"""

art/classifiers/pytorch.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,16 @@ def __init__(self, clip_values, model, loss, optimizer, input_shape, nb_classes,
5959
self._device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
6060
self._model.to(self._device)
6161

62-
def predict(self, x, logits=False):
62+
def predict(self, x, logits=False, batch_size=128):
6363
"""
6464
Perform prediction for a batch of inputs.
6565
6666
:param x: Test set.
6767
:type x: `np.ndarray`
6868
:param logits: `True` if the prediction should be done at the logits layer.
6969
:type logits: `bool`
70+
:param batch_size: Size of batches.
71+
:type batch_size: `int`
7072
:return: Array of predictions of shape `(nb_inputs, self.nb_classes)`.
7173
:rtype: `np.ndarray`
7274
"""
@@ -85,15 +87,22 @@ def predict(self, x, logits=False):
8587
# exp = np.exp(preds - np.max(preds, axis=1, keepdims=True))
8688
# preds = exp / np.sum(exp, axis=1, keepdims=True)
8789

88-
model_outputs = self._model(torch.from_numpy(x_).to(self._device).float())
89-
(logit_output, output) = (model_outputs[-2], model_outputs[-1])
90+
# Run prediction with batch processing
91+
results = np.zeros((x_.shape[0], self.nb_classes), dtype=np.float32)
92+
num_batch = int(np.ceil(len(x_) / float(batch_size)))
93+
for m in range(num_batch):
94+
# Batch indexes
95+
begin, end = m * batch_size, min((m + 1) * batch_size, x_.shape[0])
9096

91-
if logits:
92-
preds = logit_output.detach().cpu().numpy()
93-
else:
94-
preds = output.detach().cpu().numpy()
97+
model_outputs = self._model(torch.from_numpy(x_[begin:end]).to(self._device).float())
98+
(logit_output, output) = (model_outputs[-2], model_outputs[-1])
99+
100+
if logits:
101+
results[begin:end] = logit_output.detach().cpu().numpy()
102+
else:
103+
results[begin:end] = output.detach().cpu().numpy()
95104

96-
return preds
105+
return results
97106

98107
def fit(self, x, y, batch_size=128, nb_epochs=10):
99108
"""
@@ -119,7 +128,7 @@ def fit(self, x, y, batch_size=128, nb_epochs=10):
119128
# Set train phase
120129
self._model.train(True)
121130

122-
num_batch = int(np.ceil(len(x_) / batch_size))
131+
num_batch = int(np.ceil(len(x_) / float(batch_size)))
123132
ind = np.arange(len(x_))
124133

125134
# Start training
@@ -129,12 +138,8 @@ def fit(self, x, y, batch_size=128, nb_epochs=10):
129138

130139
# Train for one epoch
131140
for m in range(num_batch):
132-
if m < num_batch - 1:
133-
i_batch = torch.from_numpy(x_[ind[m * batch_size:(m + 1) * batch_size]]).to(self._device)
134-
o_batch = torch.from_numpy(y_[ind[m * batch_size:(m + 1) * batch_size]]).to(self._device)
135-
else:
136-
i_batch = torch.from_numpy(x_[ind[m * batch_size:]]).to(self._device)
137-
o_batch = torch.from_numpy(y_[ind[m * batch_size:]]).to(self._device)
141+
i_batch = torch.from_numpy(x_[ind[m * batch_size:(m + 1) * batch_size]]).to(self._device)
142+
o_batch = torch.from_numpy(y_[ind[m * batch_size:(m + 1) * batch_size]]).to(self._device)
138143

139144
# Cast to float
140145
i_batch = i_batch.float()

art/classifiers/tensorflow.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,33 +75,40 @@ def __init__(self, clip_values, input_ph, logits, output_ph=None, train=None, lo
7575
if self._loss is not None:
7676
self._loss_grads = tf.gradients(self._loss, self._input_ph)[0]
7777

78-
def predict(self, x, logits=False):
78+
def predict(self, x, logits=False, batch_size=128):
7979
"""
8080
Perform prediction for a batch of inputs.
8181
8282
:param x: Test set.
8383
:type x: `np.ndarray`
8484
:param logits: `True` if the prediction should be done at the logits layer.
8585
:type logits: `bool`
86+
:param batch_size: Size of batches.
87+
:type batch_size: `int`
8688
:return: Array of predictions of shape `(nb_inputs, self.nb_classes)`.
8789
:rtype: `np.ndarray`
8890
"""
89-
import tensorflow as tf
90-
9191
# Apply defences
9292
x_ = self._apply_processing(x)
9393
x_ = self._apply_defences_predict(x_)
9494

95-
# Create feed_dict
96-
fd = {self._input_ph: x_}
97-
if self._learning is not None:
98-
fd[self._learning] = False
95+
# Run prediction with batch processing
96+
results = np.zeros((x_.shape[0], self.nb_classes), dtype=np.float32)
97+
num_batch = int(np.ceil(len(x_) / float(batch_size)))
98+
for m in range(num_batch):
99+
# Batch indexes
100+
begin, end = m * batch_size, min((m + 1) * batch_size, x_.shape[0])
99101

100-
# Run prediction
101-
if logits:
102-
results = self._sess.run(self._logits, feed_dict=fd)
103-
else:
104-
results = self._sess.run(self._probs, feed_dict=fd)
102+
# Create feed_dict
103+
fd = {self._input_ph: x_[begin:end]}
104+
if self._learning is not None:
105+
fd[self._learning] = False
106+
107+
# Run prediction
108+
if logits:
109+
results[begin:end] = self._sess.run(self._logits, feed_dict=fd)
110+
else:
111+
results[begin:end] = self._sess.run(self._probs, feed_dict=fd)
105112

106113
return results
107114

@@ -127,7 +134,7 @@ def fit(self, x, y, batch_size=128, nb_epochs=10):
127134
x_ = self._apply_processing(x)
128135
x_, y_ = self._apply_defences_fit(x_, y)
129136

130-
num_batch = int(np.ceil(len(x_) / batch_size))
137+
num_batch = int(np.ceil(len(x_) / float(batch_size)))
131138
ind = np.arange(len(x_))
132139

133140
# Start training
@@ -137,12 +144,8 @@ def fit(self, x, y, batch_size=128, nb_epochs=10):
137144

138145
# Train for one epoch
139146
for m in range(num_batch):
140-
if m < num_batch - 1:
141-
i_batch = x_[ind[m * batch_size:(m + 1) * batch_size]]
142-
o_batch = y_[ind[m * batch_size:(m + 1) * batch_size]]
143-
else:
144-
i_batch = x_[ind[m * batch_size:]]
145-
o_batch = y_[ind[m * batch_size:]]
147+
i_batch = x_[ind[m * batch_size:(m + 1) * batch_size]]
148+
o_batch = y_[ind[m * batch_size:(m + 1) * batch_size]]
146149

147150
# Run train step
148151
if self._learning is None:

0 commit comments

Comments
 (0)