diff --git a/.travis.yml b/.travis.yml index b0661165..1cafaa28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ sudo: false dist: trusty language: python python: + - 2.7 - 3.5 - 3.6 before_install: @@ -9,10 +10,11 @@ before_install: install: - travis_wait travis_retry pip install --upgrade numpy - travis_wait travis_retry pip install --upgrade scipy - - travis_wait travis_retry pip install -r requirements-dev.txt + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then travis_wait travis_retry pip install -r requirements-dev-py2.txt; else travis_wait travis_retry pip install -r requirements-dev.txt; fi - travis_wait travis_retry pip install --upgrade tensorflow - travis_wait travis_retry pip install --upgrade theano - travis_wait travis_retry pip install --upgrade https://github.com/Lasagne/Lasagne/archive/master.zip + - if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then pip install http://download.pytorch.org/whl/cu75/torch-0.1.12.post2-cp27-none-linux_x86_64.whl; fi - if [[ $TRAVIS_PYTHON_VERSION == 3.5 ]]; then pip install http://download.pytorch.org/whl/cu75/torch-0.1.12.post2-cp35-cp35m-linux_x86_64.whl; fi - if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then pip install http://download.pytorch.org/whl/cu75/torch-0.1.12.post2-cp36-cp36m-linux_x86_64.whl; fi - travis_wait travis_retry pip install --upgrade keras diff --git a/foolbox/adversarial.py b/foolbox/adversarial.py index cf418845..c9b0f635 100644 --- a/foolbox/adversarial.py +++ b/foolbox/adversarial.py @@ -37,7 +37,6 @@ def __init__( criterion, original_image, original_class, - *, distance=MSE): self.__model = model @@ -154,7 +153,7 @@ def in_bounds(self, input_): min_, max_ = self.bounds() return min_ <= input_.min() and input_.max() <= max_ - def channel_axis(self, *, batch): + def channel_axis(self, batch): """Interface to model.channel_axis for attacks. Parameters @@ -202,7 +201,7 @@ def predictions(self, image, strict=True): assert predictions.ndim == 1 return predictions, is_adversarial - def batch_predictions(self, images, *, increasing=False, strict=True): + def batch_predictions(self, images, increasing=False, strict=True): """Interface to model.batch_predictions for attacks. Parameters diff --git a/foolbox/attacks/base.py b/foolbox/attacks/base.py index 678bc392..1475f5b8 100644 --- a/foolbox/attacks/base.py +++ b/foolbox/attacks/base.py @@ -1,5 +1,12 @@ import logging -from abc import ABC, abstractmethod +import sys +import abc +abstractmethod = abc.abstractmethod + +if sys.version_info >= (3, 4): + ABC = abc.ABC +else: + ABC = abc.ABCMeta('ABC', (), {}) from ..adversarial import Adversarial from ..criteria import Misclassification @@ -36,7 +43,6 @@ def __call__( self, image, label=None, - *, unpack=True, **kwargs): diff --git a/foolbox/attacks/gradient.py b/foolbox/attacks/gradient.py index 2738b6bf..f1b1804d 100644 --- a/foolbox/attacks/gradient.py +++ b/foolbox/attacks/gradient.py @@ -1,3 +1,4 @@ +from __future__ import division import numpy as np from collections import Iterable diff --git a/foolbox/attacks/gradientsign.py b/foolbox/attacks/gradientsign.py index beb9e79e..89c50678 100644 --- a/foolbox/attacks/gradientsign.py +++ b/foolbox/attacks/gradientsign.py @@ -1,3 +1,4 @@ +from __future__ import division import numpy as np from collections import Iterable diff --git a/foolbox/attacks/lbfgs.py b/foolbox/attacks/lbfgs.py index 3389fdea..098aa2cc 100644 --- a/foolbox/attacks/lbfgs.py +++ b/foolbox/attacks/lbfgs.py @@ -1,3 +1,4 @@ +from __future__ import division import random import logging @@ -30,9 +31,14 @@ class LBFGSAttack(Attack): """ - def __init__(self, *args, approximate_gradient=False, **kwargs): - super().__init__(*args, **kwargs) - self._approximate_gradient = approximate_gradient + def __init__(self, *args, **kwargs): + if 'approximate_gradient' in kwargs: + self._approximate_gradient = kwargs['approximate_gradient'] + del kwargs['approximate_gradient'] + super(LBFGSAttack, self).__init__(*args, **kwargs) + else: + self._approximate_gradient = False + super(LBFGSAttack, self).__init__(*args, **kwargs) def name(self): prefix = 'Approximate' if self._approximate_gradient else '' @@ -212,4 +218,5 @@ class ApproximateLBFGSAttack(LBFGSAttack): def __init__(self, *args, **kwargs): assert 'approximate_gradient' not in kwargs - super().__init__(*args, approximate_gradient=True, **kwargs) + kwargs['approximate_gradient'] = True + super(ApproximateLBFGSAttack, self).__init__(*args, **kwargs) diff --git a/foolbox/attacks/localsearch.py b/foolbox/attacks/localsearch.py index be174a5b..b73289d6 100644 --- a/foolbox/attacks/localsearch.py +++ b/foolbox/attacks/localsearch.py @@ -1,3 +1,4 @@ +from __future__ import division import numpy as np from .base import Attack @@ -117,7 +118,6 @@ def cyclic(r, Ibxy): PxPy = random_locations() for _ in range(R): - # Computing the function g using the neighborhood L = [pert(Ii, p, x, y) for x, y in PxPy] @@ -149,8 +149,8 @@ def score(It): # Update a neighborhood of pixel locations for the next round PxPy = [ (x, y) - for a, b in PxPy_star - for x in range(a - d, a + d + 1) - for y in range(b - d, b + d + 1)] + for _a, _b in PxPy_star + for x in range(_a - d, _a + d + 1) + for y in range(_b - d, _b + d + 1)] PxPy = [(x, y) for x, y in PxPy if 0 <= x < w and 0 <= y < h] PxPy = np.array(PxPy) diff --git a/foolbox/attacks/precomputed.py b/foolbox/attacks/precomputed.py index 5562ba38..bb4035c5 100644 --- a/foolbox/attacks/precomputed.py +++ b/foolbox/attacks/precomputed.py @@ -15,7 +15,7 @@ class PrecomputedImagesAttack(Attack): """ def __init__(self, input_images, output_images, *args, **kwargs): - super().__init__(*args, **kwargs) + super(PrecomputedImagesAttack, self).__init__(*args, **kwargs) assert input_images.shape == output_images.shape diff --git a/foolbox/attacks/saltandpepper.py b/foolbox/attacks/saltandpepper.py index f5a39730..2a94f40d 100644 --- a/foolbox/attacks/saltandpepper.py +++ b/foolbox/attacks/saltandpepper.py @@ -9,7 +9,7 @@ class SaltAndPepperNoiseAttack(Attack): """ - def _apply(self, a, *, epsilons=100, repetitions=10): + def _apply(self, a, epsilons=100, repetitions=10): image = a.original_image min_, max_ = a.bounds() axis = a.channel_axis(batch=False) diff --git a/foolbox/attacks/slsqp.py b/foolbox/attacks/slsqp.py index 48459710..4c5885ea 100644 --- a/foolbox/attacks/slsqp.py +++ b/foolbox/attacks/slsqp.py @@ -12,7 +12,7 @@ class SLSQPAttack(Attack): # is differentiable) and use this to provide constraint gradients def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + super(SLSQPAttack, self).__init__(*args, **kwargs) self.last_result = None def _apply(self, a): diff --git a/foolbox/criteria.py b/foolbox/criteria.py index e6a26017..dc102815 100644 --- a/foolbox/criteria.py +++ b/foolbox/criteria.py @@ -43,8 +43,14 @@ >>> criterion5 = criterion2 & criterion3 """ +import sys +import abc +abstractmethod = abc.abstractmethod -from abc import ABC, abstractmethod +if sys.version_info >= (3, 4): + ABC = abc.ABC +else: + ABC = abc.ABCMeta('ABC', (), {}) import numpy as np @@ -127,7 +133,7 @@ class CombinedCriteria(Criterion): """ def __init__(self, *criteria): - super().__init__() + super(CombinedCriteria, self).__init__() self._criteria = criteria def name(self): @@ -203,8 +209,8 @@ class TopKMisclassification(Criterion): """ - def __init__(self, *, k): - super().__init__() + def __init__(self, k): + super(TopKMisclassification, self).__init__() self.k = k def name(self): @@ -232,7 +238,7 @@ class TargetClass(Criterion): """ def __init__(self, target_class): - super().__init__() + super(TargetClass, self).__init__() self._target_class = target_class def target_class(self): @@ -265,7 +271,7 @@ class OriginalClassProbability(Criterion): """ def __init__(self, p): - super().__init__() + super(OriginalClassProbability, self).__init__() assert 0 <= p <= 1 self.p = p @@ -299,8 +305,8 @@ class TargetClassProbability(Criterion): """ - def __init__(self, target_class, *, p): - super().__init__() + def __init__(self, target_class, p): + super(TargetClassProbability, self).__init__() self._target_class = target_class assert 0 <= p <= 1 self.p = p diff --git a/foolbox/distances.py b/foolbox/distances.py index 52971145..1374205f 100644 --- a/foolbox/distances.py +++ b/foolbox/distances.py @@ -30,8 +30,16 @@ Distance """ +from __future__ import division +import sys +import abc +abstractmethod = abc.abstractmethod + +if sys.version_info >= (3, 4): + ABC = abc.ABC +else: + ABC = abc.ABCMeta('ABC', (), {}) -from abc import ABC, abstractmethod import functools import numpy as np from numbers import Number @@ -50,7 +58,6 @@ def __init__( self, reference=None, other=None, - *, bounds=None, value=None): @@ -99,12 +106,12 @@ def __repr__(self): def __eq__(self, other): if other.__class__ != self.__class__: raise TypeError('Comparisons are only possible between the same distance types.') # noqa: E501 - return self.value.__eq__(other.value) + return self.value == other.value def __lt__(self, other): if other.__class__ != self.__class__: raise TypeError('Comparisons are only possible between the same distance types.') # noqa: E501 - return self.value.__lt__(other.value) + return self.value < other.value class MeanSquaredDistance(Distance): diff --git a/foolbox/models/base.py b/foolbox/models/base.py index 30dc9e60..e6957847 100644 --- a/foolbox/models/base.py +++ b/foolbox/models/base.py @@ -1,5 +1,12 @@ import numpy as np -from abc import ABC, abstractmethod +import sys +import abc +abstractmethod = abc.abstractmethod + +if sys.version_info >= (3, 4): + ABC = abc.ABC +else: + ABC = abc.ABCMeta('ABC', (), {}) class Model(ABC): @@ -22,7 +29,7 @@ class Model(ABC): """ - def __init__(self, *, bounds, channel_axis): + def __init__(self, bounds, channel_axis): assert len(bounds) == 2 self._bounds = bounds assert channel_axis in [0, 1, 2, 3] diff --git a/foolbox/models/keras.py b/foolbox/models/keras.py index 57b80c98..7fc3c7ae 100644 --- a/foolbox/models/keras.py +++ b/foolbox/models/keras.py @@ -1,5 +1,5 @@ +from __future__ import absolute_import import warnings - import numpy as np from .base import DifferentiableModel @@ -28,13 +28,13 @@ class KerasModel(DifferentiableModel): def __init__( self, model, - *, bounds, channel_axis=3, predicts='probabilities', preprocess_fn=None): - super().__init__(bounds=bounds, channel_axis=channel_axis) + super(KerasModel, self).__init__(bounds=bounds, + channel_axis=channel_axis) from keras import backend as K diff --git a/foolbox/models/lasagne.py b/foolbox/models/lasagne.py index b55881fd..27afff49 100644 --- a/foolbox/models/lasagne.py +++ b/foolbox/models/lasagne.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import numpy as np @@ -25,11 +26,11 @@ def __init__( self, input_layer, logits_layer, - *, bounds, channel_axis=1): - super().__init__(bounds=bounds, channel_axis=channel_axis) + super(LasagneModel, self).__init__(bounds=bounds, + channel_axis=channel_axis) # delay import until class is instantiated import theano as th diff --git a/foolbox/models/pytorch.py b/foolbox/models/pytorch.py index b95e0412..15f4d78e 100644 --- a/foolbox/models/pytorch.py +++ b/foolbox/models/pytorch.py @@ -25,13 +25,13 @@ class PyTorchModel(DifferentiableModel): def __init__( self, model, - *, bounds, num_classes, channel_axis=1, cuda=True): - super().__init__(bounds=bounds, channel_axis=channel_axis) + super(PyTorchModel, self).__init__(bounds=bounds, + channel_axis=channel_axis) self._num_classes = num_classes self._model = model diff --git a/foolbox/models/tensorflow.py b/foolbox/models/tensorflow.py index 1a7c3cac..bdf13cc0 100644 --- a/foolbox/models/tensorflow.py +++ b/foolbox/models/tensorflow.py @@ -1,5 +1,5 @@ +from __future__ import absolute_import import numpy as np - from .base import DifferentiableModel @@ -24,11 +24,11 @@ def __init__( self, images, logits, - *, bounds, channel_axis=3): - super().__init__(bounds=bounds, channel_axis=channel_axis) + super(TensorFlowModel, self).__init__(bounds=bounds, + channel_axis=channel_axis) # delay import until class is instantiated import tensorflow as tf diff --git a/foolbox/models/theano.py b/foolbox/models/theano.py index 7923f282..c7a36152 100644 --- a/foolbox/models/theano.py +++ b/foolbox/models/theano.py @@ -1,6 +1,5 @@ +from __future__ import absolute_import import numpy as np - - from .base import DifferentiableModel @@ -27,12 +26,12 @@ def __init__( self, images, logits, - *, bounds, num_classes, channel_axis=1): - super().__init__(bounds=bounds, channel_axis=channel_axis) + super(TheanoModel, self).__init__(bounds=bounds, + channel_axis=channel_axis) self._num_classes = num_classes diff --git a/foolbox/models/wrappers.py b/foolbox/models/wrappers.py index 7d6960c4..c1928e59 100644 --- a/foolbox/models/wrappers.py +++ b/foolbox/models/wrappers.py @@ -16,7 +16,7 @@ class ModelWrapper(Model): """ def __init__(self, model): - super().__init__( + super(ModelWrapper, self).__init__( bounds=model.bounds(), channel_axis=model.channel_axis()) diff --git a/foolbox/tests/conftest.py b/foolbox/tests/conftest.py index d91e9683..37edff69 100644 --- a/foolbox/tests/conftest.py +++ b/foolbox/tests/conftest.py @@ -1,4 +1,10 @@ -from unittest.mock import Mock +import sys +if sys.version_info > (3, 2): + from unittest.mock import Mock +else: + # for Python2.7 compatibility + from mock import Mock + from os.path import join from os.path import dirname from contextlib import contextmanager diff --git a/foolbox/tests/test_adversarial.py b/foolbox/tests/test_adversarial.py index 1994bab5..f0b4e9b8 100644 --- a/foolbox/tests/test_adversarial.py +++ b/foolbox/tests/test_adversarial.py @@ -1,4 +1,9 @@ -from unittest.mock import Mock +import sys +if sys.version_info > (3, 2): + from unittest.mock import Mock +else: + # for Python2.7 compatibility + from mock import Mock import numpy as np diff --git a/foolbox/tests/test_attacks.py b/foolbox/tests/test_attacks.py index 6e3a3897..e41bdd89 100644 --- a/foolbox/tests/test_attacks.py +++ b/foolbox/tests/test_attacks.py @@ -1,4 +1,9 @@ -from unittest.mock import Mock +import sys +if sys.version_info > (3, 2): + from unittest.mock import Mock +else: + # for Python2.7 compatibility + from mock import Mock import pytest diff --git a/foolbox/tests/test_models_pytorch.py b/foolbox/tests/test_models_pytorch.py index aa1d1468..64d7f146 100644 --- a/foolbox/tests/test_models_pytorch.py +++ b/foolbox/tests/test_models_pytorch.py @@ -15,7 +15,7 @@ def test_pytorch_model(num_classes): class Net(nn.Module): def __init__(self): - super().__init__() + super(Net, self).__init__() def forward(self, x): x = torch.mean(x, 3) diff --git a/foolbox/utils.py b/foolbox/utils.py index 2022e5dd..935c0c0a 100644 --- a/foolbox/utils.py +++ b/foolbox/utils.py @@ -27,7 +27,7 @@ def softmax(logits): return e / np.sum(e) -def crossentropy(*, label, logits): +def crossentropy(label, logits): """Calculates the cross-entropy. Parameters diff --git a/requirements-dev-py2.txt b/requirements-dev-py2.txt new file mode 100644 index 00000000..b0e7c0e7 --- /dev/null +++ b/requirements-dev-py2.txt @@ -0,0 +1,12 @@ +numpydoc >= 0.6.0 +sphinx >= 1.6.2 +sphinx-autobuild >= 0.6.0 +sphinx_rtd_theme >= 0.2.4 +twine >= 1.9.1 +pytest >= 3.1.0 +pytest-cov >= 2.5.1 +flake8 >= 3.3.0 +python-coveralls >= 2.9.1 +pillow >= 4.1.1 +flake8 >= 3.3.0 +mock >= 2.0.0