Skip to content

Commit e31cce6

Browse files
Irina NicolaeIrina Nicolae
authored andcommitted
Create Preprocessor abstract class for all preprocessing defenses
Also: - convert feature squeezing and label smoothing to classes extending Preprocessor - change Classifier to use these defenses
1 parent b5c8f86 commit e31cce6

File tree

8 files changed

+267
-86
lines changed

8 files changed

+267
-86
lines changed

src/classifiers/classifier.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from keras.layers import Activation
88
import tensorflow as tf
99

10-
from src.defences.preprocessing import label_smoothing, feature_squeezing, tf_feature_squeezing
10+
from src.defences.feature_squeezing import FeatureSqueezing
11+
from src.defences.label_smoothing import LabelSmoothing
1112
from src.layers.activations import BoundedReLU
1213

1314

@@ -59,14 +60,14 @@ def fit(self, inputs_val, outputs_val, **kwargs):
5960
:param kwargs: Other parameters
6061
"""
6162
# Apply label smoothing if option is set
62-
if self.label_smooth:
63-
y = label_smoothing(outputs_val)
63+
if hasattr(self, 'label_smooth'):
64+
_, y = self.label_smooth(None, outputs_val)
6465
else:
6566
y = outputs_val
6667

6768
# Apply feature squeezing if option is set
68-
if self.feature_squeeze:
69-
x = feature_squeezing(inputs_val, self.bit_depth)
69+
if hasattr(self, 'feature_squeeze'):
70+
x = self.feature_squeeze(inputs_val)
7071
else:
7172
x = inputs_val
7273

@@ -81,8 +82,8 @@ def predict(self, x_val, **kwargs):
8182
:param kwargs: Other parameters
8283
:return: Predictions for test set
8384
"""
84-
if self.feature_squeeze:
85-
x = feature_squeezing(x_val, self.bit_depth)
85+
if hasattr(self, 'feature_squeeze'):
86+
x = self.feature_squeeze(x_val, self.bit_depth)
8687

8788
else:
8889
x = x_val
@@ -99,8 +100,8 @@ def evaluate(self, x_val, y_val, **kwargs):
99100
:return: The accuracy of the model on (x_val, y_val)
100101
:rtype: float
101102
"""
102-
if self.feature_squeeze:
103-
x = feature_squeezing(x_val, self.bit_depth)
103+
if hasattr(self, 'feature_squeeze'):
104+
x = self.feature_squeeze(x_val)
104105
else:
105106
x = x_val
106107

@@ -143,8 +144,6 @@ def _parse_defences(self, defences):
143144
144145
:param defences: (string) names of the defences to add, supports "featsqueeze[1-8]" and "labsmooth"
145146
"""
146-
self.label_smooth = False
147-
self.feature_squeeze = False
148147
self.defences = defences
149148

150149
if defences:
@@ -153,16 +152,15 @@ def _parse_defences(self, defences):
153152
for d in defences:
154153
# Add feature squeezing
155154
if pattern.match(d):
156-
self.feature_squeeze = True
157-
158155
try:
159-
self.bit_depth = int(d[-1])
156+
bit_depth = int(d[-1])
157+
self.feature_squeeze = FeatureSqueezing(bit_depth=bit_depth)
160158
except:
161159
raise ValueError("You must specify the bit depth for feature squeezing: featsqueeze[1-8]")
162160

163161
# Add label smoothing
164162
if d == "labsmooth":
165-
self.label_smooth = True
163+
self.label_smooth = LabelSmoothing()
166164

167165
def _preprocess(self, x):
168166
"""Apply preprocessing to x

src/classifiers/cnn_unittest.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def test_save_load_cnn(self):
130130
scores_loaded = loaded_classifier.evaluate(X_test, Y_test)
131131
self.assertAlmostEqual(scores, scores_loaded)
132132

133-
def test_defences(self):
133+
def test_feat_squeeze(self):
134134
session = tf.Session()
135135
keras.backend.set_session(session)
136136

@@ -147,5 +147,23 @@ def test_defences(self):
147147
scores = classifier.evaluate(X_test, Y_test)
148148
print("\naccuracy: %.2f%%" % (scores[1] * 100))
149149

150+
def test_label_smooth(self):
151+
152+
session = tf.Session()
153+
keras.backend.set_session(session)
154+
155+
# get MNIST
156+
(X_train, Y_train), (X_test, Y_test), _, _ = load_mnist()
157+
X_train, Y_train, X_test, Y_test = X_train[:NB_TRAIN], Y_train[:NB_TRAIN], X_test[:NB_TEST], Y_test[:NB_TEST]
158+
im_shape = X_train[0].shape
159+
160+
classifier = CNN(im_shape, act="relu", defences=["labsmooth"])
161+
classifier.compile({'loss': 'categorical_crossentropy', 'optimizer': 'adam', 'metrics': ['accuracy']})
162+
163+
# Fit the classifier
164+
classifier.fit(X_train, Y_train, epochs=1, batch_size=BATCH_SIZE)
165+
scores = classifier.evaluate(X_test, Y_test)
166+
print("\naccuracy: %.2f%%" % (scores[1] * 100))
167+
150168
if __name__ == '__main__':
151169
unittest.main()

src/defences/feature_squeezing.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from __future__ import absolute_import, division, print_function, unicode_literals
2+
3+
import numpy as np
4+
from tensorflow import rint
5+
6+
from src.defences.preprocessor import Preprocessor
7+
8+
9+
class FeatureSqueezing(Preprocessor):
10+
"""
11+
Reduces the sensibility of the features of a sample. Defence method from https://arxiv.org/abs/1704.01155.
12+
"""
13+
params = ['bit_depth']
14+
15+
def __init__(self, bit_depth=8):
16+
"""
17+
Create an instance of feature squeezing.
18+
19+
:param bit_depth: (int) The number of bits to encode data on
20+
"""
21+
self.is_fitted = True
22+
self.set_params(bit_depth=bit_depth)
23+
24+
def __call__(self, x_val, bit_depth=8):
25+
"""
26+
Apply feature squeezing to sample x_val.
27+
28+
:param x_val: (np.ndarray) Sample to squeeze. `x_val` values are supposed to be in the range [0,1]
29+
:param bit_depth: (int) The number of bits to encode data on
30+
:return: Squeezed sample
31+
:rtype: np.ndarray
32+
"""
33+
self.set_params(bit_depth=bit_depth)
34+
35+
max_value = int(2 ** bit_depth - 1)
36+
return np.rint(x_val * max_value) / max_value
37+
38+
def fit(self, x_val, y_val=None, **kwargs):
39+
"""No parameters to learn for this method; do nothing."""
40+
pass
41+
42+
def _tf_predict(self, x, bit_depth=8):
43+
"""
44+
Apply feature squeezing on tf.Tensor.
45+
46+
:param x: (tf.Tensor) Sample to squeeze. Values are supposed to be in the range [0,1]
47+
:param bit_depth: (int) The number of bits to encode data on
48+
:return: Squeezed sample
49+
:rtype: tf.Tensor
50+
"""
51+
self.set_params(bit_depth=bit_depth)
52+
53+
max_value = int(2 ** bit_depth - 1)
54+
x = rint(x * max_value) / max_value
55+
return x
56+
57+
def set_params(self, **kwargs):
58+
"""Take in a dictionary of parameters and applies defense-specific checks before saving them as attributes.
59+
60+
Defense-specific parameters:
61+
:param bit_depth: (int) The number of bits to encode data on
62+
"""
63+
# Save attack-specific parameters
64+
super(FeatureSqueezing, self).set_params(**kwargs)
65+
66+
if type(self.bit_depth) is not int or self.bit_depth <= 0 or self.bit_depth > 60:
67+
raise ValueError("The bit depth must be between 1 and 60.")
68+
69+
return True

src/defences/preprocessing_unittest.py renamed to src/defences/feature_squeezing_unittest.py

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,11 @@
1-
from __future__ import absolute_import, division, print_function
1+
from __future__ import absolute_import, division, print_function, unicode_literals
22

33
import unittest
44

55
import numpy as np
66
import tensorflow as tf
77

8-
from src.defences.preprocessing import feature_squeezing, label_smoothing, tf_feature_squeezing
9-
10-
11-
class TestLabelSmoothing(unittest.TestCase):
12-
def test_default(self):
13-
m, n = 1000, 20
14-
y = np.zeros((m, n))
15-
y[(range(m), np.random.choice(range(n), m))] = 1.
16-
17-
smooth_y = label_smoothing(y)
18-
self.assertTrue(np.isclose(np.sum(smooth_y, axis=1), np.ones(m)).all())
19-
self.assertTrue((np.max(smooth_y, axis=1) == np.ones(m)*0.9).all())
20-
21-
def test_customizing(self):
22-
m, n = 1000, 20
23-
y = np.zeros((m, n))
24-
y[(range(m), np.random.choice(range(n), m))] = 1.
25-
26-
smooth_y = label_smoothing(y, max_value=1./n)
27-
self.assertTrue(np.isclose(np.sum(smooth_y, axis=1), np.ones(m)).all())
28-
self.assertTrue((np.max(smooth_y, axis=1) == np.ones(m) / n).all())
29-
self.assertTrue(np.isclose(smooth_y, np.ones((m, n)) / n).all())
8+
from src.defences.feature_squeezing import FeatureSqueezing
309

3110

3211
class TestFeatureSqueezing(unittest.TestCase):
@@ -36,7 +15,8 @@ def test_ones(self):
3615

3716
for depth in range(1,50):
3817
with self.subTest("bit depth = {}".format(depth)):
39-
squeezed_x = feature_squeezing(x, depth)
18+
preproc = FeatureSqueezing()
19+
squeezed_x = preproc(x, depth)
4020
self.assertTrue((squeezed_x == 1).all())
4121

4222
def test_random(self):
@@ -45,11 +25,12 @@ def test_random(self):
4525
x_zero = np.where(x < 0.5)
4626
x_one = np.where(x >= 0.5)
4727

48-
squeezed_x = feature_squeezing(x, 1)
28+
preproc = FeatureSqueezing()
29+
squeezed_x = preproc(x, 1)
4930
self.assertTrue((squeezed_x[x_zero] == 0.).all())
5031
self.assertTrue((squeezed_x[x_one] == 1.).all())
5132

52-
squeezed_x = feature_squeezing(x, 2)
33+
squeezed_x = preproc(x, 2)
5334
self.assertFalse(np.logical_and(0. < squeezed_x, squeezed_x < 0.33).any())
5435
self.assertFalse(np.logical_and(0.34 < squeezed_x, squeezed_x < 0.66).any())
5536
self.assertFalse(np.logical_and(0.67 < squeezed_x, squeezed_x < 1.).any())
@@ -59,10 +40,11 @@ def test_tf_feature_squeezing(self):
5940
m, n = 10, 2
6041
sess = tf.Session()
6142
x = tf.ones((m, n))
43+
fs = FeatureSqueezing()
6244

6345
for depth in range(1, 10):
6446
with self.subTest("bit depth = {}".format(depth)):
65-
squeezed_x = sess.run(tf_feature_squeezing(x, depth))
47+
squeezed_x = sess.run(fs._tf_predict(x, depth))
6648
self.assertTrue((squeezed_x == 1).all())
6749

6850
# With placeholders
@@ -71,7 +53,7 @@ def test_tf_feature_squeezing(self):
7153
x_op = tf.placeholder(tf.float32, shape=[None, 2])
7254
for depth in range(1, 10):
7355
with self.subTest("bit depth = {}".format(depth)):
74-
squeezed_x = sess.run(tf_feature_squeezing(x_op, depth), feed_dict={x_op: x})
56+
squeezed_x = sess.run(fs._tf_predict(x_op, depth), feed_dict={x_op: x})
7557
self.assertTrue((squeezed_x == 1).all())
7658

7759

src/defences/label_smoothing.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from __future__ import absolute_import, division, print_function, unicode_literals
2+
3+
from src.defences.preprocessor import Preprocessor
4+
5+
6+
class LabelSmoothing(Preprocessor):
7+
"""
8+
Computes a vector of smooth labels from a vector of hard ones. The hard labels have to contain ones for the
9+
correct classes and zeros for all the others. The remaining probability mass between `max_value` and 1 is
10+
distributed uniformly between the incorrect classes for each instance.
11+
"""
12+
params = ['max_value']
13+
14+
def __init__(self, max_value=.9):
15+
"""
16+
Create an instance of label smoothing.
17+
"""
18+
self.is_fitted = True
19+
self.set_params(max_value=max_value)
20+
21+
def __call__(self, x_val, y_val, max_value=0.9):
22+
"""
23+
Apply label smoothing.
24+
25+
:param x_val: (np.ndarray) Input data, will not be modified by this method
26+
:param y_val: (np.ndarray) Original vector of label probabilities (one-vs-rest)
27+
:param max_value: (float) Value to affect to correct label
28+
:return: (np.ndarray, np.ndarray) Unmodified input data and the vector of smooth probabilities as labels
29+
"""
30+
self.set_params(max_value=max_value)
31+
32+
min_value = (1 - max_value) / (y_val.shape[1] - 1)
33+
assert max_value >= min_value
34+
35+
smooth_y = y_val.copy()
36+
smooth_y[smooth_y == 1.] = max_value
37+
smooth_y[smooth_y == 0.] = min_value
38+
return x_val, smooth_y
39+
40+
def fit(self, x_val, y_val=None, **kwargs):
41+
"""No parameters to learn for this method; do nothing."""
42+
pass
43+
44+
def set_params(self, **kwargs):
45+
"""Take in a dictionary of parameters and applies defense-specific checks before saving them as attributes.
46+
47+
Defense-specific parameters:
48+
:param max_value: (float) Value to affect to correct label
49+
"""
50+
# Save attack-specific parameters
51+
super(LabelSmoothing, self).set_params(**kwargs)
52+
53+
if self.max_value <= 0 or self.max_value > 1:
54+
raise ValueError("The maximum value for correct labels must be between 0 and 1.")
55+
56+
return True
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from __future__ import absolute_import, division, print_function, unicode_literals
2+
3+
import unittest
4+
5+
import numpy as np
6+
7+
from src.defences.label_smoothing import LabelSmoothing
8+
9+
10+
class TestLabelSmoothing(unittest.TestCase):
11+
def test_default(self):
12+
m, n = 1000, 20
13+
y = np.zeros((m, n))
14+
y[(range(m), np.random.choice(range(n), m))] = 1.
15+
16+
ls = LabelSmoothing()
17+
_, smooth_y = ls(None, y)
18+
self.assertTrue(np.isclose(np.sum(smooth_y, axis=1), np.ones(m)).all())
19+
self.assertTrue((np.max(smooth_y, axis=1) == np.ones(m)*0.9).all())
20+
21+
def test_customizing(self):
22+
m, n = 1000, 20
23+
y = np.zeros((m, n))
24+
y[(range(m), np.random.choice(range(n), m))] = 1.
25+
26+
ls = LabelSmoothing()
27+
_, smooth_y = ls(None, y, max_value=1./n)
28+
self.assertTrue(np.isclose(np.sum(smooth_y, axis=1), np.ones(m)).all())
29+
self.assertTrue((np.max(smooth_y, axis=1) == np.ones(m) / n).all())
30+
self.assertTrue(np.isclose(smooth_y, np.ones((m, n)) / n).all())
31+
32+
33+
if __name__ == '__main__':
34+
unittest.main()

src/defences/preprocessing.py

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)