Skip to content

Commit

Permalink
Distance values are now relative to the max possible distance and tur…
Browse files Browse the repository at this point in the history
…ned methods into properties (#14)
  • Loading branch information
jonasrauber authored Jun 16, 2017
1 parent eda1b0d commit 66bd362
Show file tree
Hide file tree
Showing 22 changed files with 113 additions and 114 deletions.
7 changes: 4 additions & 3 deletions foolbox/adversarial.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,10 @@ def normalized_distance(self, image):
The distance between the given image and the original image.
"""
min_, max_ = self.bounds()
r = max_ - min_
return self.__distance(self.__original_image / r, image / r)
return self.__distance(
self.__original_image,
image,
bounds=self.bounds())

def __new_adversarial(self, image):
distance = self.normalized_distance(image)
Expand Down
2 changes: 1 addition & 1 deletion foolbox/attacks/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def __call__(

adversarial = find

if adversarial.distance.value() == 0.:
if adversarial.distance.value == 0.:
logging.info('Not running the attack because the original image is already misclassified and the adversarial thus has a distance of 0.') # noqa: E501
else:
_ = self._apply(adversarial, **kwargs)
Expand Down
8 changes: 4 additions & 4 deletions foolbox/attacks/lbfgs.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ def _optimize(self, a, target_class, epsilon, maxiter, verbose):
if self._approximate_gradient:

def distance(x):
distance = a.normalized_distance(x.reshape(shape))
return distance.value()
d = a.normalized_distance(x.reshape(shape))
return d.value

def crossentropy(x):
# lbfgs with approx grad does not seem to respect the bounds
Expand All @@ -135,8 +135,8 @@ def loss(x, c):
else:

def distance(x):
distance = a.normalized_distance(x.reshape(shape))
return distance.value(), distance.gradient().reshape(-1)
d = a.normalized_distance(x.reshape(shape))
return d.value, d.gradient.reshape(-1)

def crossentropy(x):
logits, gradient, _ = a.predictions_and_gradient(
Expand Down
2 changes: 1 addition & 1 deletion foolbox/attacks/slsqp.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def _apply(self, a):
def fun(x, *args):
"""Objective function with derivative"""
distance = a.normalized_distance(x.reshape(shape))
return distance.value(), distance.gradient().reshape(-1)
return distance.value, distance.gradient.reshape(-1)

def eq_constraint(x, *args):
"""Equality constraint"""
Expand Down
75 changes: 32 additions & 43 deletions foolbox/distances.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,12 @@
Distance
Examples
--------
>>> import numpy as np
>>> from foolbox.distances import MeanSquaredDistance
>>> d = MeanSquaredDistance(np.array([1, 2]), np.array([2, 2]))
>>> print(d)
MSE = 5.000000e-01 (rel. MSE = 20.0 %)
>>> assert d.value() == 0.5
"""

from abc import ABC, abstractmethod
import functools
import numpy as np
from numbers import Number


@functools.total_ordering
Expand All @@ -60,28 +51,40 @@ def __init__(
reference=None,
other=None,
*,
value=None,
gradient=None):

self.reference = reference
self.other = other
bounds=None,
value=None):

if value is not None or gradient is not None:
if value is not None:
# alternative constructor
assert isinstance(value, Number)
assert reference is None
assert other is None
assert bounds is None
self.reference = None
self.other = None
self._bounds = None
self._value = value
self._gradient = gradient
self._gradient = None
else:
# standard constructor
self.reference = reference
self.other = other
self._bounds = bounds
self._value, self._gradient = self._calculate()

assert self._value is not None

@property
def value(self):
return self._value

@property
def gradient(self):
return self._gradient

@abstractmethod
def _calculate(self):
"""Returns distance and gradient of distance w.r.t. to self.other"""
raise NotImplementedError

def name(self):
Expand All @@ -95,13 +98,13 @@ def __repr__(self):

def __eq__(self, other):
if other.__class__ != self.__class__:
raise NotImplementedError('Comparisons are only possible between the same distance types.') # noqa: E501
return self.value().__eq__(other.value())
raise TypeError('Comparisons are only possible between the same distance types.') # noqa: E501
return self.value.__eq__(other.value)

def __lt__(self, other):
if other.__class__ != self.__class__:
raise NotImplementedError('Comparisons are only possible between the same distance types.') # noqa: E501
return self.value().__lt__(other.value())
raise TypeError('Comparisons are only possible between the same distance types.') # noqa: E501
return self.value.__lt__(other.value)


class MeanSquaredDistance(Distance):
Expand All @@ -110,22 +113,15 @@ class MeanSquaredDistance(Distance):
"""

def _calculate(self):
diff = self.other - self.reference
min_, max_ = self._bounds
diff = (self.other - self.reference) / (max_ - min_)
value = np.mean(np.square(diff))
n = np.prod(self.reference.shape)
gradient = 1 / n * 2 * diff

rel = value / np.mean(np.square(self.reference)) * 100
self._rel = rel
gradient = 1 / n * 2 * diff / (max_ - min_)
return value, gradient

def __str__(self):
try:
return 'MSE = {:.6e} (rel. MSE = {:.1f} %)'.format(
self._value, self._rel)
except AttributeError:
return 'MSE = {:.6e}'.format(
self._value)
return 'rel. MSE = {:.5f} %'.format(self._value * 100)


MSE = MeanSquaredDistance
Expand All @@ -137,19 +133,12 @@ class MeanAbsoluteDistance(Distance):
"""

def _calculate(self):
diff = self.other - self.reference
min_, max_ = self._bounds
diff = (self.other - self.reference) / (max_ - min_)
value = np.mean(np.abs(diff))
n = np.prod(self.reference.shape)
gradient = 1 / n * np.sign(diff) * np.abs(diff)

rel = value / np.mean(np.abs(self.reference)) * 100
self._rel = rel
gradient = 1 / n * np.sign(diff) / (max_ - min_)
return value, gradient

def __str__(self):
try:
return 'MAE = {:.6e} (rel. MAE = {:.1f} %)'.format(
self._value, self._rel)
except AttributeError:
return 'MAE = {:.6e}'.format(
self._value)
return 'rel. MAE = {:.5f} %'.format(self._value * 100)
4 changes: 2 additions & 2 deletions foolbox/tests/test_adversarial.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_adversarial(model, criterion, image, label):
assert adversarial.original_class == label
assert adversarial.target_class() is None
assert adversarial.normalized_distance(image) == MSE(value=0)
assert adversarial.normalized_distance(image).value() == 0
assert adversarial.normalized_distance(image).value == 0

label = 22 # wrong label
adversarial = Adversarial(model, criterion, image, label)
Expand All @@ -34,7 +34,7 @@ def test_adversarial(model, criterion, image, label):
assert adversarial.original_class == label
assert adversarial.target_class() is None
assert adversarial.normalized_distance(image) == MSE(value=0)
assert adversarial.normalized_distance(image).value() == 0
assert adversarial.normalized_distance(image).value == 0

predictions, is_adversarial = adversarial.predictions(image)
first_predictions = predictions
Expand Down
4 changes: 2 additions & 2 deletions foolbox/tests/test_attacks_approx_lbfgs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ def test_attack(bn_adversarial):
attack = Attack()
attack(adv, verbose=True, maxiter=1, epsilon=1000)
assert adv.image is not None
assert adv.distance.value() < np.inf
assert adv.distance.value < np.inf


# def test_targeted_attack(bn_targeted_adversarial):
# adv = bn_targeted_adversarial
# attack = Attack()
# attack(adv)
# assert adv.image is not None
# assert adv.distance.value() < np.inf
# assert adv.distance.value < np.inf
6 changes: 3 additions & 3 deletions foolbox/tests/test_attacks_blur.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def test_attack(bn_adversarial):
attack = Attack()
attack(adv)
assert adv.image is None
assert adv.distance.value() == np.inf
assert adv.distance.value == np.inf
# BlurAttack will fail for brightness model


Expand All @@ -17,7 +17,7 @@ def test_attack_gl(gl_bn_adversarial):
attack = Attack()
attack(adv)
assert adv.image is None
assert adv.distance.value() == np.inf
assert adv.distance.value == np.inf
# BlurAttack will fail for brightness model


Expand All @@ -26,4 +26,4 @@ def test_attack_trivial(bn_trivial):
attack = Attack()
attack(adv)
assert adv.image is not None
assert adv.distance.value() < np.inf
assert adv.distance.value < np.inf
4 changes: 2 additions & 2 deletions foolbox/tests/test_attacks_contrast.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ def test_attack(bn_adversarial):
attack = Attack()
attack(adv)
assert adv.image is not None
assert adv.distance.value() < np.inf
assert adv.distance.value < np.inf


def test_attack_gl(gl_bn_adversarial):
adv = gl_bn_adversarial
attack = Attack()
attack(adv)
assert adv.image is not None
assert adv.distance.value() < np.inf
assert adv.distance.value < np.inf
10 changes: 5 additions & 5 deletions foolbox/tests/test_attacks_deepfool.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,36 @@ def test_attack(bn_adversarial):
attack = Attack()
attack(adv)
assert adv.image is not None
assert adv.distance.value() < np.inf
assert adv.distance.value < np.inf


def test_attack_gl(gl_bn_adversarial):
adv = gl_bn_adversarial
attack = Attack()
attack(adv)
assert adv.image is None
assert adv.distance.value() == np.inf
assert adv.distance.value == np.inf


def test_targeted_attack(bn_targeted_adversarial):
adv = bn_targeted_adversarial
attack = Attack()
attack(adv)
assert adv.image is None
assert adv.distance.value() == np.inf
assert adv.distance.value == np.inf


def test_subsample(bn_adversarial):
adv = bn_adversarial
attack = Attack()
attack(adv, subsample=5)
assert adv.image is not None
assert adv.distance.value() < np.inf
assert adv.distance.value < np.inf


def test_attack_impossible(bn_impossible):
adv = bn_impossible
attack = Attack()
attack(adv)
assert adv.image is None
assert adv.distance.value() == np.inf
assert adv.distance.value == np.inf
4 changes: 2 additions & 2 deletions foolbox/tests/test_attacks_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ def test_attack(bn_adversarial):
attack = Attack()
attack(adv)
assert adv.image is not None
assert adv.distance.value() < np.inf
assert adv.distance.value < np.inf


def test_attack_gl(gl_bn_adversarial):
adv = gl_bn_adversarial
attack = Attack()
attack(adv)
assert adv.image is None
assert adv.distance.value() == np.inf
assert adv.distance.value == np.inf
4 changes: 2 additions & 2 deletions foolbox/tests/test_attacks_gradient_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ def test_attack(bn_adversarial):
attack = Attack()
attack(adv)
assert adv.image is not None
assert adv.distance.value() < np.inf
assert adv.distance.value < np.inf


def test_attack_gl(gl_bn_adversarial):
adv = gl_bn_adversarial
attack = Attack()
attack(adv)
assert adv.image is None
assert adv.distance.value() == np.inf
assert adv.distance.value == np.inf
4 changes: 2 additions & 2 deletions foolbox/tests/test_attacks_iterative_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ def test_attack(bn_adversarial):
attack = Attack()
attack(adv, epsilons=10, steps=5)
assert adv.image is not None
assert adv.distance.value() < np.inf
assert adv.distance.value < np.inf


def test_attack_gl(gl_bn_adversarial):
adv = gl_bn_adversarial
attack = Attack()
attack(adv, epsilons=10, steps=5)
assert adv.image is None
assert adv.distance.value() == np.inf
assert adv.distance.value == np.inf
4 changes: 2 additions & 2 deletions foolbox/tests/test_attacks_iterative_gradient_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ def test_attack(bn_adversarial):
attack = Attack()
attack(adv, epsilons=10, steps=5)
assert adv.image is not None
assert adv.distance.value() < np.inf
assert adv.distance.value < np.inf


def test_attack_gl(gl_bn_adversarial):
adv = gl_bn_adversarial
attack = Attack()
attack(adv, epsilons=10, steps=5)
assert adv.image is None
assert adv.distance.value() == np.inf
assert adv.distance.value == np.inf
Loading

0 comments on commit 66bd362

Please sign in to comment.