Skip to content

Commit 06b804e

Browse files
authored
Merge pull request #878 from carlini/fix-generate-np
Fix generate np
2 parents e204014 + 5f9da80 commit 06b804e

File tree

2 files changed

+125
-120
lines changed

2 files changed

+125
-120
lines changed

cleverhans/attacks.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,19 @@ def generate(self, x, **kwargs):
528528
# Parse and save attack-specific parameters
529529
assert self.parse_params(**kwargs)
530530

531+
asserts = []
532+
533+
# If a data range was specified, check that the input was in that range
534+
if self.clip_min is not None:
535+
asserts.append(utils_tf.assert_greater_equal(x,
536+
tf.cast(self.clip_min,
537+
x.dtype)))
538+
539+
if self.clip_max is not None:
540+
asserts.append(utils_tf.assert_less_equal(x,
541+
tf.cast(self.clip_max,
542+
x.dtype)))
543+
531544
# Initialize loop variables
532545
if self.rand_init:
533546
eta = tf.random_uniform(tf.shape(x),
@@ -599,20 +612,22 @@ def body(i, adv_x):
599612

600613
_, adv_x = tf.while_loop(cond, body, [tf.zeros([]), adv_x], back_prop=True)
601614

602-
asserts = []
603615

604616
# Asserts run only on CPU.
605617
# When multi-GPU eval code tries to force all PGD ops onto GPU, this
606618
# can cause an error.
607-
with tf.device("/CPU:0"):
608-
asserts.append(tf.assert_less_equal(self.eps_iter, self.eps))
609-
if self.ord == np.inf and self.clip_min is not None:
610-
# The 1e-6 is needed to compensate for numerical error.
611-
# Without the 1e-6 this fails when e.g. eps=.2, clip_min=.5,
612-
# clip_max=.7
613-
asserts.append(tf.assert_less_equal(self.eps,
614-
1e-6 + self.clip_max
615-
- self.clip_min))
619+
asserts.append(utils_tf.assert_less_equal(tf.cast(self.eps_iter,
620+
dtype=self.eps.dtype),
621+
self.eps))
622+
if self.ord == np.inf and self.clip_min is not None:
623+
# The 1e-6 is needed to compensate for numerical error.
624+
# Without the 1e-6 this fails when e.g. eps=.2, clip_min=.5,
625+
# clip_max=.7
626+
asserts.append(utils_tf.assert_less_equal(tf.cast(self.eps, x.dtype),
627+
1e-6 + tf.cast(self.clip_max,
628+
x.dtype)
629+
- tf.cast(self.clip_min,
630+
x.dtype)))
616631

617632
if self.sanity_checks:
618633
with tf.control_dependencies(asserts):

tests_tf/test_attacks.py

Lines changed: 100 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,10 @@ def help_generate_np_gives_adversarial_example(self, ord, eps=.5,
202202
**kwargs):
203203
x_val, x_adv, delta = self.generate_adversarial_examples_np(ord, eps,
204204
**kwargs)
205-
self.assertClose(delta, eps)
205+
self.assertLess(np.max(np.abs(delta-eps)), 1e-3)
206206
orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1)
207207
new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1)
208-
self.assertTrue(np.mean(orig_labs == new_labs) < 0.5)
208+
self.assertLess(np.max(np.mean(orig_labs == new_labs)), .5)
209209

210210
def test_invalid_input(self):
211211
x_val = -np.ones((2, 2), dtype='float32')
@@ -241,7 +241,7 @@ def test_targeted_generate_np_gives_adversarial_example(self):
241241
except NotImplementedError:
242242
raise SkipTest()
243243

244-
self.assertClose(delta, 0.5)
244+
self.assertLessEqual(np.max(delta), 0.5001)
245245

246246
new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1)
247247
self.assertTrue(np.mean(random_labs == new_labs) > 0.7)
@@ -255,7 +255,7 @@ def test_generate_np_can_be_called_with_different_eps(self):
255255
clip_min=-5.0, clip_max=5.0)
256256

257257
delta = np.max(np.abs(x_adv - x_val), axis=1)
258-
self.assertClose(delta, eps)
258+
self.assertLessEqual(np.max(delta), eps+1e-4)
259259

260260
def test_generate_np_clip_works_as_expected(self):
261261
x_val = np.random.rand(100, 2)
@@ -455,13 +455,13 @@ def test_attack_strength_np_batched(self):
455455
self.assertLess(np.mean(feed_labs == new_labs), 0.1)
456456

457457

458-
class TestBasicIterativeMethod(CommonAttackProperties):
458+
class TestProjectedGradientDescent(CommonAttackProperties):
459459
def setUp(self):
460-
CommonAttackProperties.setUp(self)
460+
super(TestProjectedGradientDescent, self).setUp()
461461

462462
self.sess = tf.Session()
463463
self.model = SimpleModel()
464-
self.attack = BasicIterativeMethod(self.model, sess=self.sess)
464+
self.attack = ProjectedGradientDescent(self.model, sess=self.sess)
465465

466466
def test_generate_np_gives_adversarial_example_linfinity(self):
467467
self.help_generate_np_gives_adversarial_example(ord=np.infty, eps=.5,
@@ -510,61 +510,90 @@ def test_attack_strength(self):
510510

511511
orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1)
512512
new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1)
513-
self.assertTrue(np.mean(orig_labs == new_labs) < 0.1)
513+
self.assertLess(np.mean(orig_labs == new_labs), 0.1)
514+
515+
def test_clip_eta(self):
516+
x_val = np.random.rand(100, 2)
517+
x_val = np.array(x_val, dtype=np.float32)
518+
519+
x_adv = self.attack.generate_np(x_val, eps=1.0, eps_iter=0.1,
520+
nb_iter=5)
521+
522+
delta = np.max(np.abs(x_adv - x_val), axis=1)
523+
self.assertLessEqual(np.max(delta), 1.)
524+
525+
def test_generate_np_gives_clipped_adversarial_examples(self):
526+
x_val = np.random.rand(100, 2)
527+
x_val = np.array(x_val, dtype=np.float32)
528+
529+
x_adv = self.attack.generate_np(x_val, eps=1.0, eps_iter=0.1,
530+
nb_iter=5,
531+
clip_min=-0.2, clip_max=0.3,
532+
sanity_checks=False)
533+
534+
self.assertLess(-0.201, np.min(x_adv))
535+
self.assertLess(np.max(x_adv), .301)
536+
514537

515538
def test_generate_np_does_not_cache_graph_computation_for_nb_iter(self):
516539
x_val = np.random.rand(100, 2)
517540
x_val = np.array(x_val, dtype=np.float32)
518541

542+
# Call it once
519543
x_adv = self.attack.generate_np(x_val, eps=1.0, ord=np.inf,
520544
clip_min=-5.0, clip_max=5.0,
521545
nb_iter=10)
522546

523547
orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1)
524548
new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1)
525-
self.assertTrue(np.mean(orig_labs == new_labs) < 0.1)
526549

550+
# Call it again
527551
ok = [False]
528552
old_grads = tf.gradients
529-
530-
def fn(*x, **y):
531-
ok[0] = True
532-
return old_grads(*x, **y)
533-
534-
tf.gradients = fn
535-
536-
x_adv = self.attack.generate_np(x_val, eps=1.0, ord=np.inf,
537-
clip_min=-5.0, clip_max=5.0,
538-
nb_iter=11)
539-
553+
try:
554+
def fn(*x, **y):
555+
ok[0] = True
556+
return old_grads(*x, **y)
557+
558+
tf.gradients = fn
559+
560+
x_adv = self.attack.generate_np(x_val, eps=1.0, ord=np.inf,
561+
clip_min=-5.0, clip_max=5.0,
562+
nb_iter=11)
563+
finally:
564+
tf.gradients = old_grads
565+
540566
orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1)
541567
new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1)
542-
self.assertTrue(np.mean(orig_labs == new_labs) < 0.1)
543568

544-
tf.gradients = old_grads
545569

546570
self.assertTrue(ok[0])
547571

572+
def test_multiple_initial_random_step(self):
573+
"""
574+
This test generates multiple adversarial examples until an adversarial
575+
example is generated with a different label compared to the original
576+
label. This is the procedure suggested in Madry et al. (2017).
548577
549-
class TestMomentumIterativeMethod(TestBasicIterativeMethod):
550-
def setUp(self):
551-
super(TestMomentumIterativeMethod, self).setUp()
578+
This test will fail if an initial random step is not taken (error>0.5).
579+
"""
580+
x_val = np.array(np.random.rand(100, 2), dtype=np.float32)
552581

553-
self.sess = tf.Session()
554-
self.model = SimpleModel()
555-
self.attack = MomentumIterativeMethod(self.model, sess=self.sess)
582+
orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1)
583+
new_labs_multi = orig_labs.copy()
556584

557-
def test_generate_np_can_be_called_with_different_decay_factor(self):
558-
x_val = np.random.rand(100, 2)
559-
x_val = np.array(x_val, dtype=np.float32)
585+
# Generate multiple adversarial examples
586+
for i in range(10):
587+
x_adv = self.attack.generate_np(x_val, eps=.5, eps_iter=0.05,
588+
clip_min=0.5, clip_max=0.7,
589+
nb_iter=2, sanity_checks=False)
590+
new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1)
560591

561-
for decay_factor in [0.0, 0.5, 1.0]:
562-
x_adv = self.attack.generate_np(x_val, eps=0.5, ord=np.inf,
563-
decay_factor=decay_factor,
564-
clip_min=-5.0, clip_max=5.0)
592+
# Examples for which we have not found adversarial examples
593+
I = (orig_labs == new_labs_multi)
594+
new_labs_multi[I] = new_labs[I]
565595

566-
delta = np.max(np.abs(x_adv - x_val), axis=1)
567-
self.assertClose(delta, 0.5)
596+
self.assertLess(np.mean(orig_labs == new_labs_multi), 0.5)
568597

569598

570599
class TestCarliniWagnerL2(CleverHansTest):
@@ -926,96 +955,57 @@ def test_generate_np_gives_clipped_adversarial_examples(self):
926955
self.assertTrue(-0.201 < np.min(x_adv))
927956
self.assertTrue(np.max(x_adv) < .301)
928957

929-
930-
class TestMadryEtAl(CleverHansTest):
958+
class TestMomentumIterativeMethod(TestProjectedGradientDescent):
931959
def setUp(self):
932-
super(TestMadryEtAl, self).setUp()
933-
934-
self.sess = tf.Session()
935-
self.model = SimpleModel()
936-
self.attack = MadryEtAl(self.model, sess=self.sess)
937-
938-
def test_attack_strength(self):
939-
"""
940-
If clipping is not done at each iteration (not using clip_min and
941-
clip_max), this attack fails by
942-
np.mean(orig_labels == new_labels) == .5
943-
"""
944-
x_val = np.random.rand(100, 2)
945-
x_val = np.array(x_val, dtype=np.float32)
946-
947-
x_adv = self.attack.generate_np(x_val, eps=1.0, eps_iter=0.05,
948-
clip_min=0.5, clip_max=0.7,
949-
nb_iter=5, sanity_checks=False)
950-
951-
orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1)
952-
new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1)
953-
self.assertLess(np.mean(orig_labs == new_labs), 0.1)
954-
955-
def test_clip_eta(self):
956-
x_val = np.random.rand(100, 2)
957-
x_val = np.array(x_val, dtype=np.float32)
958-
959-
x_adv = self.attack.generate_np(x_val, eps=1.0, eps_iter=0.1,
960-
nb_iter=5)
960+
super(TestMomentumIterativeMethod, self).setUp()
961961

962-
delta = np.max(np.abs(x_adv - x_val), axis=1)
963-
self.assertTrue(np.all(delta <= 1.))
962+
self.attack = MomentumIterativeMethod(self.model, sess=self.sess)
964963

965-
def test_generate_np_gives_clipped_adversarial_examples(self):
964+
def test_generate_np_can_be_called_with_different_decay_factor(self):
966965
x_val = np.random.rand(100, 2)
967966
x_val = np.array(x_val, dtype=np.float32)
968967

969-
x_adv = self.attack.generate_np(x_val, eps=1.0, eps_iter=0.1,
970-
nb_iter=5,
971-
clip_min=-0.2, clip_max=0.3,
972-
sanity_checks=False)
968+
for decay_factor in [0.0, 0.5, 1.0]:
969+
x_adv = self.attack.generate_np(x_val, eps=0.5, ord=np.inf,
970+
decay_factor=decay_factor,
971+
clip_min=-5.0, clip_max=5.0)
973972

974-
self.assertLess(-0.201, np.min(x_adv))
975-
self.assertLess(np.max(x_adv), .301)
973+
delta = np.max(np.abs(x_adv - x_val), axis=1)
974+
self.assertClose(delta, 0.5)
976975

977976
def test_multiple_initial_random_step(self):
978-
"""
979-
This test generates multiple adversarial examples until an adversarial
980-
example is generated with a different label compared to the original
981-
label. This is the procedure suggested in Madry et al. (2017).
982-
983-
This test will fail if an initial random step is not taken (error>0.5).
984-
"""
985-
x_val = np.random.rand(100, 2)
986-
x_val = np.array(x_val, dtype=np.float32)
987-
988-
orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1)
989-
new_labs_multi = orig_labs.copy()
990-
991-
# Generate multiple adversarial examples
992-
for i in range(10):
993-
x_adv = self.attack.generate_np(x_val, eps=.5, eps_iter=0.05,
994-
clip_min=0.5, clip_max=0.7,
995-
nb_iter=2, sanity_checks=False)
996-
new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1)
997-
998-
# Examples for which we have not found adversarial examples
999-
I = (orig_labs == new_labs_multi)
1000-
new_labs_multi[I] = new_labs[I]
1001-
1002-
self.assertLess(np.mean(orig_labs == new_labs_multi), 0.5)
977+
# There is no initial random step, so nothing to test here
978+
pass
1003979

1004980

1005-
class TestProjectedGradientDescent(TestMadryEtAl):
981+
class TestMadryEtAl(CleverHansTest):
1006982
def setUp(self):
1007-
super(TestProjectedGradientDescent, self).setUp()
1008-
self.attack = ProjectedGradientDescent(self.model, sess=self.sess)
983+
super(TestMadryEtAl, self).setUp()
984+
self.model = DummyModel('madryetal_dummy_model')
985+
self.sess = tf.Session()
1009986

987+
def test_attack_can_be_constructed(self):
988+
ok = True
989+
try:
990+
attack = MadryEtAl(self.model, sess=self.sess)
991+
except:
992+
ok = False
993+
self.assertTrue(ok)
1010994

1011-
class TestBasicIterativeMethod(TestMadryEtAl):
995+
996+
class TestBasicIterativeMethod(CleverHansTest):
1012997
def setUp(self):
1013998
super(TestBasicIterativeMethod, self).setUp()
1014-
self.attack = BasicIterativeMethod(self.model, sess=self.sess)
999+
self.model = DummyModel('bim_dummy_model')
1000+
self.sess = tf.Session()
10151001

1016-
def test_multiple_initial_random_step(self):
1017-
# There is no initial random step, so nothing to test here
1018-
pass
1002+
def test_attack_can_be_constructed(self):
1003+
ok = True
1004+
try:
1005+
self.attack = BasicIterativeMethod(self.model, sess=self.sess)
1006+
except:
1007+
ok = False
1008+
self.assertTrue(ok)
10191009

10201010

10211011
class TestFastFeatureAdversaries(CleverHansTest):

0 commit comments

Comments
 (0)