...
 
Commits (4)
......@@ -42,6 +42,7 @@ stages:
package:
stage: package
image: ${CI_REGISTRY}/pralab/docker-helper-images/python35-setuptools:latest
interruptible: true
before_script:
- echo $CI_PROJECT_DIR
script:
......@@ -70,6 +71,7 @@ package:
package:docs:
stage: package
image: ${CI_REGISTRY}/pralab/docker-helper-images/python35-sphinx:latest
interruptible: true
script:
- echo $CI_PROJECT_DIR
- python -V
......@@ -105,6 +107,7 @@ package:docs:
.test:
stage: test
needs: ["package"]
interruptible: true
before_script:
- echo $CI_PROJECT_DIR
- python -V
......@@ -119,6 +122,9 @@ package:docs:
except:
refs:
- schedules
- tags
changes:
- src/secml/VERSION
variables:
- $SKIP_TESTS
......@@ -156,6 +162,7 @@ test:py37:min:
.test:install:
stage: test
needs: ["package"]
interruptible: true
before_script:
- echo $CI_PROJECT_DIR
- python -V
......@@ -195,10 +202,11 @@ test:py37:min:
script:
- DIST_WHL=$(find $CI_PROJECT_DIR/dist -iname \*.whl -exec basename {} \;)
- echo $DIST_WHL
- pip install ${CI_PROJECT_DIR}/dist/${DIST_WHL}[pytorch]
- pip install ${CI_PROJECT_DIR}/dist/${DIST_WHL}[pytorch,cleverhans]
after_script:
- python -c "import secml"
- python -c "from secml.ml.classifiers import CClassifierPyTorch"
- python -c "from secml.ml.classifiers import CModelCleverhans"
test:install:whl:py2:
extends: .test:install:whl
......@@ -296,10 +304,10 @@ release:gitlab-releases:
script:
- DIST_ZIP=$(find $CI_PROJECT_DIR/dist -iname \*.zip -exec basename {} \;)
- echo $DIST_ZIP
- 'curl -s --request POST --header "PRIVATE-TOKEN: $API_ACCESS_TOKEN" --data name="$DIST_ZIP" --data url="$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/raw/dist/$DIST_ZIP?job=release" $CI_PROJECT_DOMAIN/api/v4/projects/$CI_PROJECT_ID/releases/$CI_COMMIT_TAG/assets/links'
- 'curl -s --request POST --header "PRIVATE-TOKEN: $API_ACCESS_TOKEN" --data name="$DIST_ZIP" --data url="$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/raw/dist/$DIST_ZIP?job=release:gitlab-releases" $CI_PROJECT_DOMAIN/api/v4/projects/$CI_PROJECT_ID/releases/$CI_COMMIT_TAG/assets/links'
- DIST_WHL=$(find $CI_PROJECT_DIR/dist -iname \*.whl -exec basename {} \;)
- echo $DIST_WHL
- 'curl -s --request POST --header "PRIVATE-TOKEN: $API_ACCESS_TOKEN" --data name="$DIST_WHL" --data url="$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/raw/dist/$DIST_WHL?job=release" $CI_PROJECT_DOMAIN/api/v4/projects/$CI_PROJECT_ID/releases/$CI_COMMIT_TAG/assets/links'
- 'curl -s --request POST --header "PRIVATE-TOKEN: $API_ACCESS_TOKEN" --data name="$DIST_WHL" --data url="$CI_PROJECT_URL/-/jobs/artifacts/$CI_COMMIT_TAG/raw/dist/$DIST_WHL?job=release:gitlab-releases" $CI_PROJECT_DOMAIN/api/v4/projects/$CI_PROJECT_ID/releases/$CI_COMMIT_TAG/assets/links'
artifacts: # Keep the distribution package (default expire 0)
name: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}"
paths:
......
## v0.10 (29/10/2019)
- #535 Added new package `secml.explanation`, which provides different methods for explaining machine learning models. See documentation and examples for more information.
- #584 **[beta]** Added `CModelCleverhans` and `CAttackEvasionCleverhans` to support adversarial attacks from [CleverHans](https://github.com/tensorflow/cleverhans), a Python library to benchmark vulnerability of machine learning systems to adversarial examples.
### Requirements (1 change)
- #580 PyTorch version `1.3` is now supported.
### Added (4 changes)
- #565 Added new abstract interface `CClassifierDNN` from which new classes implementing Deep Neural Networks can inherit.
- #555 Added `CNormalizerDNN`, which allows using a `CClassifierDNN` as a preprocessor.
- #593 Added `CDataLoaderTorchDataset`, which allows converting a `torchvision` dataset into a `CDataset`.
- #598 Added gradient method for `CKernelHistIntersection`.
### Improved (6 changes)
- #562 Extended support of `CClassifierPyTorch` to nested PyTorch modules.
- #594 `CClassifierPyTorch.load_model()` is now able to also load models trained with PyTorch (without using our wrapper). New parameter `classes` added to the method to match classes to indexes in the loaded model.
- #579 Left side single row/column broadcast is now supported for sparse vs sparse `CArray` operations.
- #582 Improved performance of `CNormalizerMeanStd` when multiple channels are defined.
- #576 Vastly improved the performance of kernels by removing loops over samples in many classes and refactoring main routines.
- #562 Improved `grad_f_x` computation at a specific layer in `CClassifierPyTorch`.
### Changed (4 changes)
- #578 `CClassifierPyTorch` now inherits from `CClassifierDNN`. The following changed accordingly: parameter `torch_model` renamed to `model`; property `layer_shapes` is now defined; method `save_checkpoint` removed.
- #562 Parameter `layer` of `CClassifierPyTorch.get_layer_output()` is now renamed `layer_names` as a list of layers names is supported (a single layer name is still supported as input). A dictionary is returned if multiple layers are requested. See the documentation for more information.
- #533 Double initialization in `CAttackEvasionPGDLS` will now be executed regardless of the classifier type (linear or nonlinear) if the `double_init` parameter of `.run()` method is set to `True`.
- #591 It is now not required to call the `fit` method of `CNormalizerMeanSTD` if fixed mean/std values are used.
### Fixed (4 changes)
- #561 Fixed `CConstraintBox` not always applied correctly for float data.
- #577 Fixed `CClassifierPyTorch.decision_function` applying preprocess twice.
- #581 Fixed gradient computation of `CKernelChebyshevDistance`.
- #599 Kernels using distances are now based on negative distances (to correctly represent similarity measures). Affected classes are: `CKernelChebyshevDistance`, `CKernelEuclidean`.
### Removed & Deprecated (5 changes)
- #561 Removed parameter `precision` from `CConstraint.is_violated()`.
- #575 Parameter `batch_size` of `CKernel` is now deprecated.
- #597 Removed unused parameter `gamma` from `CKernelChebyshevDistance`.
- #596 Removed `CKernelHamming`.
- #602 Renamed `CNormalizerMeanSTD` to `CNormalizerMeanStd`. The old class has been deprecated and will be removed in a future vearsion.
### Documentation (5 changes)
- #538 Added a notebook tutorial on the use of Explainable ML methods provided by the `secml.explanation` package.
- #573 Improved visualization of attack results in `07-ImageNet` tutorial.
- #610 Fixed spacing between parameter and parameter type in the docs.
- #605 Fixed documentation of classes requiring extra components not being displayed.
- #608 Added acknowledgments to `README`.
## v0.9 (11/10/2019)
- #536 Added `CClassifierPytorch` to support Neural Networks (NNs) through [PyTorch](https://pytorch.org/) deep learning platform.
......
......@@ -11,11 +11,12 @@ It comes with a set of powerful features:
supported by `scikit-learn` are available, as well as Neural Networks (NNs)
through [PyTorch](https://pytorch.org/) deep learning platform.
- **Built-in attack algorithms.** Evasion and poisoning attacks based on a
custom-developed fast solver.
custom-developed fast solver. In addition, we provide connectors to other
third-party Adversarial Machine Learning libraries.
- **Visualize your results.** We provide visualization and plotting framework
based on the widely-known library [matplotlib](https://matplotlib.org/).
- **Explain your results.** Explainable ML methods to interpret model decisions
via influential features and prototypes. _(coming soon)_
via influential features and prototypes.
- **Extensible.** Easily create new wrappers for ML models or attack algorithms
extending our abstract interfaces.
- **Multi-processing.** Do you want to save time further? We provide full
......@@ -53,7 +54,7 @@ macOS versions and Linux distributions.
However, to support additional advanced features more packages can be necessary
depending on the Operating System used:
- Linux (Ubuntu >= 16.04 or equivalent dist):
- Linux (Ubuntu >= 16.04 or equivalent dist)
- `python-tk` (Python 2.7), `python3-tk` (Python >= 3.5), for running
MatplotLib Tk-based backends;
- NVIDIA<sup>®</sup> CUDA<sup>®</sup> Toolkit for running `tf-gpu`
......@@ -61,6 +62,7 @@ depending on the Operating System used:
See the [TensorFlow Guide](https://www.tensorflow.org/install/gpu).
- macOS (macOS >= 10.12 Sierra)
- Nothing to note.
### Installation process
......@@ -105,9 +107,7 @@ of the `[extras]` section.
### Available extra components
- `pytorch` : Neural Networks (NNs) through [PyTorch](https://pytorch.org/) deep learning platform.
Will install: `torch >= 1.1, < 1.3`, `torchvision >= 0.2.2`
### _Coming soon_
Will install: `torch >= 1.1`, `torchvision >= 0.2.2`
- `cleverhans` : Wrapper of [CleverHans](https://github.com/tensorflow/cleverhans),
a Python library to benchmark vulnerability of machine learning systems
to adversarial examples. Will install: `tensorflow >= 1.14.*, < 2`, `cleverhans`
......@@ -138,20 +138,13 @@ The `secml.adv` package contains evasion and poisoning attacks based on a
custom-developed solver, along with classes to easily perform security
evaluation of Machine Learning algorithms.
The `secml.explanation` package contains different explainable
Machine Learning methods that allow interpreting classifiers decisions
by analyzing the relevant components such as features or training prototypes.
The `secml.figure` package contains a visualization and plotting framework
based on [matplotlib](https://matplotlib.org/).
_(coming soon)_ The `secml.explanation` package contains few different
explainable Machine Learning methods that allow interpreting classifiers
decisions by analyzing the relevant components such as features or training
prototypes.
_(coming soon)_ The `secml.tf.clvhs` package contains support classes for the
[CleverHans](https://github.com/tensorflow/cleverhans) library for benchmarking
machine learning systems' vulnerability to adversarial examples.
This package will be available only if the extra component `cleverhans`
has been specified during installation.
## Contributors
......@@ -208,13 +201,13 @@ This library is maintained by
[PRALab - Pattern Recognition and Applications Lab](https://pralab.diee.unica.it).
List of contributors:
- Marco Melis (maintainer) [1]_
- Ambra Demontis [1]_
- Maura Pintor [1]_ , [2]_
- Battista Biggio [1]_ , [2]_
- Marco Melis (maintainer) [1]
- Ambra Demontis [1]
- Maura Pintor [1], [2]
- Battista Biggio [1], [2]
.. [1] Department of Electrical and Electronic Engineering, University of Cagliari, Italy
.. [2] Pluribus One, Italy
[1] Department of Electrical and Electronic Engineering, University of Cagliari, Italy
[2] Pluribus One, Italy
## Credits
......@@ -227,6 +220,9 @@ List of contributors:
Computing in Science & Engineering, vol. 9, no. 3, pp. 90-95, 2007.](
https://doi.org/10.1109/MCSE.2007.55)
- `pytorch` Paszke, Adam, et al. "Automatic differentiation in pytorch.", NIPS-W, 2017.
- `cleverhans` [Papernot, Nicolas, et al. "Technical Report on the CleverHans v2.1.0
Adversarial Examples Library." arXiv preprint arXiv:1610.00768 (2018).](
https://arxiv.org/abs/1610.00768)
## Acknowledgements
......
......@@ -54,6 +54,10 @@ autodoc_default_options = {
'exclude-members': ''
}
# The following modules should be faked by sphinx (e.g. extras)
autodoc_mock_imports = [
"pytest", "torch", "torchvision", "cleverhans", "tensorflow"]
# Autosummary pages will be generated by sphinx-autogen instead of sphinx-build
# autosummary_generate = True
......@@ -83,6 +87,7 @@ intersphinx_mapping = {
'sklearn': ("https://scikit-learn.org/stable/", None),
'matplotlib': ('https://matplotlib.org/', None),
'pytorch': ('https://pytorch.org/docs/stable/', None),
'cleverhans': ('https://cleverhans.readthedocs.io/en/latest/', None),
}
# -- Options for HTML output -------------------------------------------------
......@@ -120,3 +125,13 @@ html_theme_options = {
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Temporary work-around for spacing problem between parameter and parameter
# type in the doc, see https://github.com/numpy/numpydoc/issues/215. The bug
# has been fixed in sphinx (https://github.com/sphinx-doc/sphinx/pull/5976) but
# through a change in sphinx basic.css except rtd_theme does not use basic.css.
# In an ideal world, this would get fixed in this PR:
# https://github.com/readthedocs/sphinx_rtd_theme/pull/747/files
# Thanks to: https://github.com/dask/dask-jobqueue
def setup(app):
app.add_stylesheet("basic.css")
......@@ -27,6 +27,13 @@
secml.adv
secml.optim
.. toctree::
:hidden:
:caption: Explanation
:maxdepth: 2
secml.explanation
.. toctree::
:hidden:
:caption: Visualization
......@@ -42,12 +49,12 @@
secml.parallel
secml.utils
secml.settings
secml.testing
.. toctree::
:caption: References
:hidden:
modindex
update
changelog
roadmap
\ No newline at end of file
Modules Index
=============
Alphabetical Index: :ref:`modindex`
.. toctree::
:maxdepth: 4
secml
\ No newline at end of file
......@@ -3,11 +3,9 @@ ROADMAP
**SecML is still in alpha stage and the roadmap is subject to change at any time.**
1. (Q4 2019) `Cleverhans <https://github.com/tensorflow/cleverhans>`_ library wrapper
2. (Q4 2019) Model Zoo
3. (Q4 2019) `secml.explanations` package
4. (Q1 2020) Improved support for `Tensorflow 2 <https://www.tensorflow.org/>`_ library
5. (Q2 2020) `Foolbox <https://foolbox.readthedocs.io/>`_ library wrapper
6. (Q2 2020) `Keras <https://keras.io/>`_ library wrapper
1. (Q4 2019) Model Zoo
2. (Q1 2020) Support for `Tensorflow 2 <https://www.tensorflow.org/>`_ library
3. (Q2 2020) `Foolbox <https://foolbox.readthedocs.io/>`_ library wrapper
4. (Q2 2020) `Keras <https://keras.io/>`_ library wrapper
For further details and the most up-to-date roadmap see: https://gitlab.com/secml/secml/milestones
......@@ -30,3 +30,11 @@ CAttackEvasionPGDLS
:undoc-members:
:show-inheritance:
CAttackEvasionCleverhans
------------------------
.. automodule:: secml.adv.attacks.evasion.c_attack_evasion_cleverhans
:members:
:undoc-members:
:show-inheritance:
......@@ -87,6 +87,14 @@ CDataLoaderSvmLight
:undoc-members:
:show-inheritance:
CDataLoaderTorchDataset
-----------------------
.. automodule:: secml.data.loader.c_dataloader_torchvision
:members:
:undoc-members:
:show-inheritance:
loader\_utils
-------------
......
secml.explanation
=================
.. automodule:: secml.explanation
:members:
:undoc-members:
:show-inheritance:
CExplainer
----------
.. automodule:: secml.explanation.c_explainer
:members:
:undoc-members:
:show-inheritance:
CExplainerGradient
------------------
.. automodule:: secml.explanation.c_explainer_gradient
:members:
:undoc-members:
:show-inheritance:
CExplainerGradientInput
-----------------------
.. automodule:: secml.explanation.c_explainer_gradient_input
:members:
:undoc-members:
:show-inheritance:
CExplainerIntegratedGradients
-----------------------------
.. automodule:: secml.explanation.c_explainer_integrated_gradients
:members:
:undoc-members:
:show-inheritance:
CExplainerInfluenceFunctions
----------------------------
.. automodule:: secml.explanation.c_explainer_influence_functions
:members:
:undoc-members:
:show-inheritance:
secml.ml.classifiers.gradients
==============================
.. automodule:: secml.ml.classifiers.gradients
:members:
:undoc-members:
:show-inheritance:
secml.ml.classifiers.gradients.mixin\_classifier\_gradient module
-----------------------------------------------------------------
.. automodule:: secml.ml.classifiers.gradients.mixin_classifier_gradient
:members:
:undoc-members:
:show-inheritance:
secml.ml.classifiers.gradients.mixin\_classifier\_gradient\_kde module
----------------------------------------------------------------------
.. automodule:: secml.ml.classifiers.gradients.mixin_classifier_gradient_kde
:members:
:undoc-members:
:show-inheritance:
secml.ml.classifiers.gradients.mixin\_classifier\_gradient\_linear module
-------------------------------------------------------------------------
.. automodule:: secml.ml.classifiers.gradients.mixin_classifier_gradient_linear
:members:
:undoc-members:
:show-inheritance:
secml.ml.classifiers.gradients.mixin\_classifier\_gradient\_logistic module
---------------------------------------------------------------------------
.. automodule:: secml.ml.classifiers.gradients.mixin_classifier_gradient_logistic
:members:
:undoc-members:
:show-inheritance:
secml.ml.classifiers.gradients.mixin\_classifier\_gradient\_ridge module
------------------------------------------------------------------------
.. automodule:: secml.ml.classifiers.gradients.mixin_classifier_gradient_ridge
:members:
:undoc-members:
:show-inheritance:
secml.ml.classifiers.gradients.mixin\_classifier\_gradient\_sgd module
----------------------------------------------------------------------
.. automodule:: secml.ml.classifiers.gradients.mixin_classifier_gradient_sgd
:members:
:undoc-members:
:show-inheritance:
secml.ml.classifiers.gradients.mixin\_classifier\_gradient\_svm module
----------------------------------------------------------------------
.. automodule:: secml.ml.classifiers.gradients.mixin_classifier_gradient_svm
:members:
:undoc-members:
:show-inheritance:
......@@ -29,22 +29,6 @@ CClassifierLinear
:undoc-members:
:show-inheritance:
CClassifierKDE
--------------
.. automodule:: secml.ml.classifiers.c_classifier_kde
:members:
:undoc-members:
:show-inheritance:
CClassifierMCSLinear
--------------------
.. automodule:: secml.ml.classifiers.c_classifier_mcs_linear
:members:
:undoc-members:
:show-inheritance:
CClassifierSkLearn
------------------
......@@ -125,6 +109,15 @@ CClassifierPyTorch
:undoc-members:
:show-inheritance:
CModelCleverhans
----------------
.. automodule:: secml.ml.classifiers.tf.c_model_cleverhans
:members:
:undoc-members:
:show-inheritance:
clf\_utils
----------
......
......@@ -23,7 +23,7 @@ CNormalizerLinear
:undoc-members:
:show-inheritance:
CNormalizerMeanSTD
CNormalizerMeanStd
------------------
.. automodule:: secml.ml.features.normalization.c_normalizer_mean_std
......@@ -47,3 +47,10 @@ CNormalizerUnitNorm
:undoc-members:
:show-inheritance:
CNormalizerDNN
--------------
.. automodule:: secml.ml.features.normalization.c_normalizer_dnn
:members:
:undoc-members:
:show-inheritance:
......@@ -31,14 +31,6 @@ CKernelEuclidean
:undoc-members:
:show-inheritance:
CKernelHamming
--------------
.. automodule:: secml.ml.kernel.c_kernel_hamming
:members:
:undoc-members:
:show-inheritance:
CKernelHistIntersect
--------------------
......
secml
=====
.. toctree::
secml.adv
secml.array
secml.core
secml.data
secml.figure
secml.ml
secml.optim
secml.parallel
secml.testing
secml.utils
.. automodule:: secml
:members:
:undoc-members:
:show-inheritance:
{
"path": "../../../tutorials/08-Explanation.ipynb"
}
\ No newline at end of file
......@@ -174,13 +174,15 @@ setup(
maintainer='Marco Melis',
maintainer_email='marco.melis@unica.it',
packages=find_packages('src', exclude=[
"*.tests", "*.tests.*", "tests.*", "tests", "*.testing"]),
"*.tests", "*.tests.*", "tests.*", "tests"]),
package_dir={'': 'src'},
include_package_data=True,
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4',
install_requires=REQ_PKGS,
extras_require={
'pytorch': ["torch>=1.1,<1.3", "torchvision>=0.2.2"],
'pytorch': ["torch>=1.1", "torchvision>=0.2.2"],
'cleverhans': ["tensorflow>=1.14,<2", "cleverhans"],
'tf-gpu': ["tensorflow-gpu>=1.14,<2"],
'unittests': ['pytest>=4.2,<5.1', 'pytest-cov>=2.6.1']
},
zip_safe=False
......
0.9
\ No newline at end of file
0.10
\ No newline at end of file
from .c_attack_evasion import CAttackEvasion
from .c_attack_evasion_pgd_ls import CAttackEvasionPGDLS
from .c_attack_evasion_pgd import CAttackEvasionPGD
try:
import cleverhans
except ImportError:
pass # cleverhans is an extra component
else:
from .c_attack_evasion_cleverhans import CAttackEvasionCleverhans
"""
.. module:: CAttackEvasionCleverhans
:synopsis: Performs one of the Cleverhans Evasion attacks
against a classifier.
.. moduleauthor:: Ambra Demontis <ambra.demontis@unica.it>
"""
import numpy as np
import tensorflow as tf
from cleverhans.attacks import \
FastGradientMethod, CarliniWagnerL2, ElasticNetMethod, SPSA, LBFGS, \
ProjectedGradientDescent, SaliencyMapMethod, MomentumIterativeMethod, \
MadryEtAl, BasicIterativeMethod, DeepFool
from secml.ml.classifiers import CModelCleverhans
from secml.adv.attacks import CAttack
from secml.adv.attacks.evasion import CAttackEvasion
from secml.array import CArray
from secml.core.constants import nan
SUPPORTED_ATTACKS = [
FastGradientMethod, CarliniWagnerL2, ElasticNetMethod, SPSA, LBFGS,
ProjectedGradientDescent, SaliencyMapMethod, MomentumIterativeMethod,
MadryEtAl, BasicIterativeMethod, DeepFool
]
class CAttackEvasionCleverhans(CAttackEvasion):
"""This class is a wrapper of the attacks implemented in the Cleverhans
library.
Credits: https://github.com/tensorflow/cleverhans.
Parameters
----------
classifier : CClassifier
Target classifier on which the efficacy of the computed attack
points is evaluates
n_feats : int
Number of features of the dataset used to train the classifiers.
surrogate_classifier : CClassifier
Surrogate classifier against which the attack is computed.
This is assumed to be already trained on surrogate_data.
surrogate_data: CDataset
Used to train the surrogate classifier.
y_target : int or None, optional
If None an indiscriminate attack will be performed, else a
targeted attack to have the samples misclassified as
belonging to the y_target class.
clvh_attack_class
The CleverHans class that implement the attack
**kwargs
Any other parameter for the cleverhans attack.
Notes
-----
The current Tensorflow default graph will be used.
"""
class_type = 'e-cleverhans'
def __init__(self, classifier, surrogate_classifier,
n_feats, n_classes, surrogate_data=None, y_target=None,
clvh_attack_class=CarliniWagnerL2, **kwargs):
self._tfsess = tf.compat.v1.Session()
# store the cleverhans attack parameters
self._clvrh_params = kwargs
# Check if the cleverhans attack is supported
if clvh_attack_class not in SUPPORTED_ATTACKS:
raise ValueError("This cleverhans attack is not supported yet!")
self._clvrh_attack_class = clvh_attack_class
# store the number of features
self._n_feats = n_feats
# store the number of dataset classes
self._n_classes = n_classes
self._clvrh_clf = None
CAttackEvasion.__init__(self, classifier=classifier,
surrogate_classifier=surrogate_classifier,
surrogate_data=surrogate_data,
y_target=y_target)
###########################################################################
# READ-ONLY ATTRIBUTES
###########################################################################
@property
def f_eval(self):
if self._clvrh_clf:
return self._clvrh_clf.f_eval
else:
return 0
@property
def grad_eval(self):
if self._clvrh_clf:
return self._clvrh_clf.grad_eval
else:
return 0
###########################################################################
# PRIVATE METHODS
###########################################################################
def _objective_function(self, x):
"""Objective function.
Parameters
----------
x : CArray or CDataset
Returns
-------
f_obj : float or CArray of floats
"""
raise NotImplementedError
def _objective_function_gradient(self, x):
"""Gradient of the objective function."""
raise NotImplementedError
def _set_solver_classifier(self):
"""This function set the surrogate classifier,
if differentiable; otherwise, it learns a smooth approximation for
the nondiff. (surrogate) classifier (e.g., decision tree)
using an SVM with the RBF kernel."""
# update the surrogate classifier
# we skip the function provided by the superclass as we do not need
# to set xk and we call directly the one of CAttack that instead
# learn a differentiable classifier
CAttack._set_solver_classifier(self)
# create the cleverhans attack object
self._tfsess.close()
self._tfsess = tf.compat.v1.Session()
# wrap the surrogate classifier into a cleverhans classifier
self._clvrh_clf = CModelCleverhans(
self._surrogate_classifier, out_dims=self._n_classes)
# create an instance of the chosen cleverhans attack
clvrh_attack = self._clvrh_attack_class(
self._clvrh_clf, sess=self._tfsess)
# create the placeholder to feed into the attack the initial evasion
# samples
self._initial_x_P = tf.compat.v1.placeholder(
tf.float32, shape=(None, self._n_feats))
# placeholder used to feed the true or the target label (it is a
# one-hot encoded vector)
self._y_P = tf.compat.v1.placeholder(tf.float32, shape=(1, self._n_classes))
# create the tf operations to generate the attack
if not self.y_target:
if 'y' in clvrh_attack.feedable_kwargs:
self._adv_x_T = clvrh_attack.generate(
self._initial_x_P, y=self._y_P, **self._clvrh_params)
else: # 'y' not required by attack
self._adv_x_T = clvrh_attack.generate(
self._initial_x_P, **self._clvrh_params)
else:
if 'y_target' not in clvrh_attack.feedable_kwargs:
raise RuntimeError(
"cannot perform a targeted {:} attack".format(
clvrh_attack.__class__.__name__))
self._adv_x_T = clvrh_attack.generate(
self._initial_x_P, y_target=self._y_P, **self._clvrh_params)
def _run(self, x0, y0, x_init=None):
"""Perform evasion for a given dmax on a single pattern.
It solves:
min_x g(x),
s.t. c(x,x0) <= dmax
Parameters
----------
x0 : CArray
Initial sample.
y0 : int or CArray
The true label of x0.
x_init : CArray or None, optional
Initialization point. If None, it is set to x0.
Returns
-------
x_opt : CArray
Evasion sample
f_opt : float
Value of objective function on x_opt (from surrogate learner).
Notes
-----
Internally, this class stores the values of
the objective function and sequence of attack points (if enabled).
"""
# if data can not be modified by the attacker, exit
if not self.is_attack_class(y0):
self._x_seq = x_init
self._x_opt = x_init
self._f_opt = nan
self._f_seq = nan
return self._x_opt, self._f_opt
if x_init is None:
x_init = x0
if not isinstance(x_init, CArray):
raise TypeError("Input vectors should be of class CArray")
self._x0 = x0
self._y0 = y0
x = self._x0.atleast_2d().tondarray().astype(np.float32)
# create a one-hot-encoded vector to feed the true or
# the y_target label
one_hot_y = CArray.zeros(shape=(1, self._n_classes),
dtype=np.float32)
if self.y_target:
one_hot_y[0, self.y_target] = 1
else: # indiscriminate attack
one_hot_y[0, self._y0.item()] = 1
self._x_opt = self._tfsess.run(
self._adv_x_T, feed_dict={self._initial_x_P: x,
self._y_P: one_hot_y.tondarray()})
return CArray(self._x_opt), nan
......@@ -19,18 +19,22 @@ class CAttackEvasionPGD(CAttackEvasionPGDLS):
"""Evasion attacks using Projected Gradient Descent.
This class implements the maximum-confidence evasion attacks proposed in:
- https://arxiv.org/abs/1708.06939, ICCV W. ViPAR, 2017.
This is the multi-class extension of our original work in:
- https://arxiv.org/abs/1708.06131, ECML 2013,
implemented using a standard projected gradient solver.
It can also be used on sparse, high-dimensional feature spaces, using an
L1 constraint on the manipulation of samples to preserve sparsity,
as we did for crafting adversarial Android malware in:
- https://arxiv.org/abs/1704.08996, IEEE TDSC 2017.
For more on evasion attacks, see also:
- https://arxiv.org/abs/1809.02861, USENIX Sec. 2019
- https://arxiv.org/abs/1712.03141, Patt. Rec. 2018
......
......@@ -23,9 +23,11 @@ class CAttackEvasionPGDLS(CAttackEvasion):
"""Evasion attacks using Projected Gradient Descent with Line Search.
This class implements the maximum-confidence evasion attacks proposed in:
- https://arxiv.org/abs/1708.06939, ICCV W. ViPAR, 2017.
This is the multi-class extension of our original work in:
- https://arxiv.org/abs/1708.06131, ECML 2013,
implemented using a custom projected gradient solver that uses line search
......@@ -34,9 +36,11 @@ class CAttackEvasionPGDLS(CAttackEvasion):
It can also be used on sparse, high-dimensional feature spaces, using an
L1 constraint on the manipulation of samples to preserve sparsity,
as we did for crafting adversarial Android malware in:
- https://arxiv.org/abs/1704.08996, IEEE TDSC 2017.
For more on evasion attacks, see also:
- https://arxiv.org/abs/1809.02861, USENIX Sec. 2019
- https://arxiv.org/abs/1712.03141, Patt. Rec. 2018
......@@ -409,9 +413,8 @@ class CAttackEvasionPGDLS(CAttackEvasion):
self._solver.minimize(x_init)
self._solution_from_solver()
# if classifier is linear, or dmax is 0, return
if self._classifier.is_linear() or self.dmax == 0 or \
double_init is False:
# if dmax is 0 or no double init should be performed, return
if self.dmax == 0 or double_init is False:
return self._x_opt, self._f_opt
# value of objective function at x_opt
......
......@@ -16,19 +16,20 @@ class CAttackPoisoningLogisticRegression(CAttackPoisoning):
This is an implementation of the attack developed in Sect. 3.3 in
https://www.usenix.org/conference/usenixsecurity19/presentation/demontis:
- A. Demontis, M. Melis, M. Pintor, M. Jagielski, B. Biggio, A. Oprea,
C. Nita-Rotaru, and F. Roli. Why do adversarial attacks transfer?
Explaining transferability of evasion and poisoning attacks.
In 28th USENIX Security Symposium. USENIX Association, 2019.
For more details on poisoning attacks, see also:
- https://arxiv.org/abs/1804.00308, IEEE Symp. SP 2018
- https://arxiv.org/abs/1712.03141, Patt. Rec. 2018
- https://arxiv.org/abs/1708.08689, AISec 2017
- https://arxiv.org/abs/1804.07933, ICML 2015
- https://arxiv.org/pdf/1206.6389, ICML 2012
Parameters
----------
classifier : CClassifierLogistic
......
......@@ -16,12 +16,14 @@ class CAttackPoisoningRidge(CAttackPoisoning):
This is an implementation of the attack developed in
https://arxiv.org/abs/1804.07933:
- H. Xiao, B. Biggio, G. Brown, G. Fumera, C. Eckert, and F. Roli.
Is feature selection secure against training data poisoning?
In F. Bach and D. Blei, editors, JMLR W&CP, Proc. 32nd
Int'l Conf. Mach. Learning (ICML), volume 37, pp. 1689-1698, 2015.
For more details on poisoning attacks, see also:
- https://arxiv.org/abs/1809.02861, USENIX Sec. 2019
- https://arxiv.org/abs/1804.00308, IEEE Symp. SP 2018
- https://arxiv.org/abs/1712.03141, Patt. Rec. 2018
......
......@@ -14,24 +14,26 @@ class CAttackPoisoningSVM(CAttackPoisoning):
"""Poisoning attacks against Support Vector Machines (SVMs).
This is an implementation of the attack in https://arxiv.org/pdf/1206.6389:
- B. Biggio, B. Nelson, and P. Laskov. Poisoning attacks against
support vector machines. In J. Langford and J. Pineau, editors,
29th Int'l Conf. on Machine Learning, pages 1807-1814. Omnipress, 2012.
where the gradient is computed as described in Eq. (10) in
https://www.usenix.org/conference/usenixsecurity19/presentation/demontis:
- A. Demontis, M. Melis, M. Pintor, M. Jagielski, B. Biggio, A. Oprea,
C. Nita-Rotaru, and F. Roli. Why do adversarial attacks transfer?
Explaining transferability of evasion and poisoning attacks.
In 28th USENIX Security Symposium. USENIX Association, 2019.
For more details on poisoning attacks, see also:
- https://arxiv.org/abs/1804.00308, IEEE Symp. SP 2018
- https://arxiv.org/abs/1712.03141, Patt. Rec. 2018
- https://arxiv.org/abs/1708.08689, AISec 2017
- https://arxiv.org/abs/1804.07933, ICML 2015
Parameters
----------
classifier : CClassifierSVM
......
......@@ -418,6 +418,33 @@ class CSparse(_CArrayInterface):
# # # # # # SYSTEM OVERLOADS # # # # # #
# -------------------------------------#
def _broadcast_other(self, other):
"""Broadcast `other` to have the same shape of self.
This only performs left side single row/column broadcast.
Parameters
----------
other : CSparse
Array to be broadcasted.
Returns
-------
CSparse
Broadcasted array.
"""
if self.shape != other.shape:
if self.shape[0] == other.shape[0]: # Equal number of rows
if other.shape[1] == 1:
other = other.repmat(1, self.shape[1])
elif self.shape[1] == other.shape[1]: # Equal number of cols
if other.shape[0] == 1:
other = other.repmat(self.shape[0], 1)
return other
def __add__(self, other):
"""Element-wise addition.
......@@ -440,6 +467,8 @@ class CSparse(_CArrayInterface):
"adding a nonzero scalar or a boolean True to a "
"sparse array is not supported. Convert to dense if needed.")
elif isinstance(other, CSparse): # Sparse + Sparse = Sparse
# Scipy does not support broadcast natively
other = self._broadcast_other(other)
return self.__class__(self._data.__add__(other.tocsr()))
elif isinstance(other, CDense): # Sparse + Dense = Dense
if other.size == 1: # scalar-like
......@@ -477,6 +506,8 @@ class CSparse(_CArrayInterface):
"subtracting a nonzero scalar or a boolean True from a "
"sparse array is not supported. Convert to dense if needed.")
elif isinstance(other, CSparse): # Sparse - Sparse = Sparse
# Scipy does not support broadcast natively
other = self._broadcast_other(other)
return self.__class__(self._data.__sub__(other.tocsr()))
elif isinstance(other, CDense): # Sparse - Dense = Dense
if other.size == 1: # scalar-like
......@@ -558,30 +589,13 @@ class CSparse(_CArrayInterface):
"""
if is_scalar(other) or is_bool(other):
return self.__class__(self._data.__div__(other))
elif isinstance(other, (CSparse, CDense)):
this = self
other = other.atleast_2d()
if this.shape != other.shape: # Broadcast not supported by scipy
if this.shape[0] == other.shape[0]: # Equal number of rows
if this.shape[1] == 1:
this = this.repmat(1, other.shape[1])
elif other.shape[1] == 1:
other = other.repmat(1, this.shape[1])
else:
raise ValueError("inconsistent shapes")
elif this.shape[1] == other.shape[1]: # Equal number of cols
if this.shape[0] == 1:
this = this.repmat(other.shape[0], 1)
elif other.shape[0] == 1:
other = other.repmat(this.shape[0], 1)
else:
raise ValueError("inconsistent shapes")
else:
raise ValueError("inconsistent shapes")
elif isinstance(other, CSparse): # Sparse / Sparse = Dense
# Scipy does not support broadcast natively
other = self._broadcast_other(other)
return CDense(self._data.__div__(other.tocsr()))
elif isinstance(other, CDense): # Sparse / Dense = Dense
# Compatible shapes, call built-in div
return CDense(this._data.__div__(this._buffer_to_builtin(other)))
return CDense(self._data.__div__(other.tondarray()))
else:
return NotImplemented
......@@ -708,6 +722,8 @@ class CSparse(_CArrayInterface):
if is_scalar(other) or is_bool(other):
return self.__class__(self._data.__eq__(other))
elif isinstance(other, CSparse): # Sparse == Sparse = Sparse
# Scipy does not support broadcast natively
other = self._broadcast_other(other)
return self.__class__(self._data.__eq__(other.tocsr()))
elif isinstance(other, CDense): # Sparse == Dense = Dense
return CDense(self._data.__eq__(other.tondarray()))
......@@ -735,6 +751,8 @@ class CSparse(_CArrayInterface):
if is_scalar(other) or is_bool(other):
return self.__class__(self._data.__lt__(other))
elif isinstance(other, CSparse): # Sparse < Sparse = Sparse
# Scipy does not support broadcast natively
other = self._broadcast_other(other)
return self.__class__(self._data.__lt__(other.tocsr()))
elif isinstance(other, CDense): # Sparse < Dense = Dense
return CDense(self._data.__lt__(other.tondarray()))
......@@ -762,6 +780,8 @@ class CSparse(_CArrayInterface):
if is_scalar(other) or is_bool(other):
return self.__class__(self._data.__le__(other))
elif isinstance(other, CSparse): # Sparse <= Sparse = Sparse
# Scipy does not support broadcast natively
other = self._broadcast_other(other)
return self.__class__(self._data.__le__(other.tocsr()))
elif isinstance(other, CDense): # Sparse <= Dense = Dense
return CDense(self._data.__le__(other.tondarray()))
......@@ -789,6 +809,8 @@ class CSparse(_CArrayInterface):
if is_scalar(other) or is_bool(other):
return self.__class__(self._data.__gt__(other))
elif isinstance(other, CSparse): # Sparse > Sparse = Sparse
# Scipy does not support broadcast natively
other = self._broadcast_other(other)
return self.__class__(self._data.__gt__(other.tocsr()))
elif isinstance(other, CDense): # Sparse > Dense = Dense
return CDense(self._data.__gt__(other.tondarray()))
......@@ -816,6 +838,8 @@ class CSparse(_CArrayInterface):
if is_scalar(other) or is_bool(other):
return self.__class__(self._data.__ge__(other))
elif isinstance(other, CSparse): # Sparse >= Sparse = Sparse
# Scipy does not support broadcast natively
other = self._broadcast_other(other)
return self.__class__(self._data.__ge__(other.tocsr()))
elif isinstance(other, CDense): # Sparse >= Dense = Dense
return CDense(self._data.__ge__(other.tondarray()))
......@@ -843,6 +867,8 @@ class CSparse(_CArrayInterface):
if is_scalar(other) or is_bool(other):
return self.__class__(self._data.__ne__(other))
elif isinstance(other, CSparse): # Sparse != Sparse = Sparse
# Scipy does not support broadcast natively
other = self._broadcast_other(other)
return self.__class__(self._data.__ne__(other.tocsr()))
elif isinstance(other, CDense): # Sparse != Dense = Dense
return CDense(self._data.__ne__(other.tondarray()))
......
......@@ -10,7 +10,9 @@ from .c_dataloader_icubworld import CDataLoaderICubWorld28
try:
import torch
import torchvision
except ImportError:
pass # pytorch is an extra component
else:
from .c_dataloader_pytorch import CDataLoaderPyTorch
from .c_dataloader_torchvision import CDataLoaderTorchDataset
......@@ -11,6 +11,7 @@ from secml.data.c_dataset_pytorch import CDatasetPyTorch
class CDataLoaderPyTorch:
def __init__(self, data, labels=None, batch_size=4, shuffle=False,
transform=None, num_workers=1):
self._dataset = CDatasetPyTorch(data,
......
"""
.. module:: DataLoaderTorchDataset
:synopsis: Loader for Torchvision datasets
.. moduleauthor:: Maura Pintor <maura.pintor@unica.it>
"""
from secml.array import CArray
from secml.data import CDataset
from secml.data.loader import CDataLoader
from secml.settings import SECML_DS_DIR
class CDataLoaderTorchDataset(CDataLoader):
"""Wrapper for loading Torchvision datasets as CDatasets.
Parameters
----------
tv_dataset_class : torch.Dataset
torchvision dataset class to load
"""
def __init__(self, tv_dataset_class, **kwargs):
root = kwargs.pop('root', SECML_DS_DIR)
self._tv_dataset = tv_dataset_class(root=root, **kwargs)
self._class_to_idx = self._tv_dataset.class_to_idx
def load(self, *args, **kwargs):
patterns, labels = self._tv_dataset.data, self._tv_dataset.targets
patterns = CArray(patterns.view(len(labels), -1).numpy())
labels = CArray(labels.numpy())
return CDataset(patterns, labels)
@property
def class_to_idx(self):
"""Dictionary for matching indexes and class names"""
return self._class_to_idx
from .c_explainer import CExplainer
from .c_explainer_gradient import CExplainerGradient
from .c_explainer_gradient_input import CExplainerGradientInput
from .c_explainer_integrated_gradients import CExplainerIntegratedGradients
from .c_explainer_influence_functions import CExplainerInfluenceFunctions
"""
.. module:: CExplainer
:synopsis: Abstract interface for Explainable ML methods.
.. moduleauthor:: Marco Melis <marco.melis@unica.it>
.. moduleauthor:: Ambra Demontis <ambra.demontis@unica.it>
"""
from abc import ABCMeta, abstractmethod
import six
from secml.core import CCreator
@six.add_metaclass(ABCMeta)
class CExplainer(CCreator):
"""Abstract interface for Explainable ML methods.
Parameters
----------
clf : CClassifier
Instance of the classifier to explain.
"""
__super__ = 'CExplainer'
def __init__(self, clf):
self._clf = clf
@property
def clf(self):
"""Classifier to explain."""
return self._clf
@abstractmethod
def explain(self, x, *args, **kwargs):
"""Computes the explanation on x."""
raise NotImplementedError
"""
.. module:: CExplainerGradient
:synopsis: Explanation of predictions via input gradient.
.. moduleauthor:: Marco Melis <marco.melis@unica.it>
"""
from secml.explanation import CExplainer
from secml.array import CArray
class CExplainerGradient(CExplainer):
"""Explanation of predictions via input gradient.
The relevance `rv` of each feature is given by:
.. math::
rv_i = \\frac{\\partial F(x)}{\\partial x_i}
- D. Baehrens, T. Schroeter, S. Harmeling, M. Kawanabe, K. Hansen,
K.-R.Muller, " "How to explain individual classification decisions",
in: J. Mach. Learn. Res. 11 (2010) 1803-1831
Parameters
----------
clf : CClassifier
Instance of the classifier to explain. Must be differentiable.
Attributes
----------
class_type : 'gradient'
"""
__class_type = 'gradient'
def explain(self, x, y, return_grad=False):
"""Computes the explanation for input sample.
Parameters
----------
x : CArray
Input sample.
y : int
Class wrt compute the classifier gradient.
return_grad : bool, optional
If True, also return the clf gradient computed on x. Default False.
Returns
-------
relevance : CArray
Relevance vector for input sample.
"""
grad = self.clf.grad_f_x(x, y=y)
rv = grad.deepcopy()
self.logger.debug(
"Relevance Vector:\n{:}".format(rv))
return (rv, grad) if return_grad is True else rv
"""
.. module:: CExplainerGradientInput
:synopsis: Explanation of predictions via gradient*input vector.
.. moduleauthor:: Marco Melis <marco.melis@unica.it>
"""
from secml.array import CArray
from secml.explanation import CExplainerGradient
class CExplainerGradientInput(CExplainerGradient):
"""Explanation of predictions via gradient*input vector.
The relevance `rv` of each features is given by:
.. math::
rv_i(x) = \\left(x_i * \\frac{\\partial F(x)}{\\partial x_i}\\right)
- A. Shrikumar, P. Greenside, A. Shcherbina, A. Kundaje,
"Not just a blackbox: Learning important features through propagating
activation differences", 2016 arXiv:1605.01713.
- M. Melis, D. Maiorca, B. Biggio, G. Giacinto and F. Roli,
"Explaining Black-box Android Malware Detection,"
2018 26th European Signal Processing Conference (EUSIPCO),
Rome, 2018, pp. 524-528.
Parameters
----------
clf : CClassifier
Instance of the classifier to explain. Must be differentiable.
Attributes
----------
class_type : 'gradient-input'
"""
__class_type = 'gradient-input'
def explain(self, x, y, return_grad=False):
"""Computes the explanation for input sample.
Parameters
----------
x : CArray
Input sample.
y : int
Class wrt compute the classifier gradient.
return_grad : bool, optional
If True, also return the clf gradient computed on x. Default False.
Returns
-------
relevance : CArray
Relevance vector for input sample.
"""
grad = self.clf.grad_f_x(x, y=y)
rv = x * grad # Directional derivative
self.logger.debug(
"Relevance Vector:\n{:}".format(rv))
return (rv, grad) if return_grad is True else rv
"""
.. module:: CExplainerInfluenceFunctions
:synopsis: Class to compute the Influence Function
.. moduleauthor:: Ambra Demontis <ambra.demontis@unica.it>
.. moduleauthor:: Battista Biggio <battista.biggiodemontis@unica.it>
"""
from scipy import linalg
from secml.array import CArray
from secml.ml.classifiers.loss import CLoss
from secml.explanation import CExplainerGradient
class CExplainerInfluenceFunctions(CExplainerGradient):
"""Explanation of predictions via influence functions.
- Koh, Pang Wei, and Percy Liang, "Understanding black-box predictions
via influence functions", in: Proceedings of the 34th International
Conference on Machine Learning-Volume 70. JMLR. org, 2017.
Parameters
----------
clf : CClassifier
Instance of the classifier to explain. Must provide the `hessian`.
tr_ds : CDataset
Training dataset of the classifier to explain.
Attributes
----------
class_type : 'influence-functions'
"""
__class_type = 'influence-functions'
def __init__(self, clf, tr_ds, outer_loss_idx='log'):
super(CExplainerInfluenceFunctions, self).__init__(clf=clf)
self._tr_ds = tr_ds
self._inv_H = None # inverse hessian matrix
self._grad_inner_loss_params = None
self._outer_loss = CLoss.create(outer_loss_idx)
@property
def tr_ds(self):
"""Training dataset."""
return self._tr_ds
def grad_outer_loss_params(self, x, y):
"""
Compute derivate of the outer validation loss at test point(s) x
This is typically not regularized (just an empirical loss function)
"""
# FIXME: this is the validation loss. Why are we calling the clf?
grad = self.clf.grad_loss_params(x, y)
return grad
def grad_inner_loss_params(self, x, y):
"""
Compute derivative of the inner training loss function
for all training points. This is normally a regularized loss.
"""
grad = self.clf.grad_tr_params(x, y)
return grad
def hessian(self, x, y):
"""Compute hessian for the current parameters of the trained clf."""
return self.clf.hessian_tr_params(x, y)
def explain(self, x, y, return_grad=False):
"""Compute influence of test sample x against all training samples.
Parameters
----------
x : CArray
Input sample.
y : int
Class wrt compute the classifier gradient.
return_grad : bool, optional
If True, also return the clf gradient computed on x. Default False.
"""
H = self.hessian(x, y)
p = H.shape[0]
H += 1e-9 * (CArray.eye(p))
if self._inv_H is None:
# compute hessian inverse
det = linalg.det(H.tondarray())
if abs(det) < 1e-6:
self._inv_H = CArray(linalg.pinv2(H.tondarray()))
else:
self._inv_H = CArray(linalg.inv(H.tondarray()))
x = x.atleast_2d()
if self._grad_inner_loss_params is None:
self._grad_inner_loss_params = self.grad_inner_loss_params(
self.tr_ds.X, self.tr_ds.Y)
v = self.grad_outer_loss_params(x, y).T.dot(self._inv_H).dot(
self._grad_inner_loss_params)
return (v, H) if return_grad is True else v
"""
.. module:: CExplainerIntegratedGradients
:synopsis: Integrated Gradients method for explanation of predictions.
.. moduleauthor:: Marco Melis <marco.melis@unica.it>
"""
from __future__ import division
from secml.array import CArray
from secml import _NoValue
from secml.explanation import CExplainerGradient
class CExplainerIntegratedGradients(CExplainerGradient):
"""Explanation of predictions via integrated gradients.
This implements a method for local explanation of predictions
via attribution of relevance to each feature.
The algorithm takes a sample and computes the Riemman approximation
of the integral along the linear interpolation with a reference point.
- Sundararajan, Mukund, Ankur Taly, and Qiqi Yan.
"Axiomatic Attribution for Deep Networks."
Proceedings of the 34th International Conference on Machine Learning,
Volume 70, JMLR. org, 2017, pp. 3319-3328.
So we have for each dimension `i` of the input sample x:
.. math::
IG_i(x) = (x_i - x'_i) \\times \\sum^m_{k=1}
\\frac{\\partial F(x' + \\frac{k}{m}\\times(x-x'))}
{\\partial x_i} \\times \\frac{1}{m}
with `m` the number of steps in the Riemman approximation of the integral.
Parameters
----------
clf : CClassifier
Instance of the classifier to explain. Must be differentiable.
Attributes
----------
class_type : 'integrated-gradients'
"""
__class_type = 'integrated-gradients'
def explain(self, x, y, return_grad=_NoValue, reference=None, m=50):
"""Computes the explanation for input sample.
Parameters
----------
x : CArray
Input sample.
y : int
Class wrt compute the classifier gradient.
reference : CArray or None, optional
The reference sample. Must have the same shape of input sample.
If None, a all-zeros sample will be used.
m : int, optional
The number of steps for linear interpolation. Default 50.+
Returns
-------
attributions : CArray
Attributions (weight of each feature) for input sample.
"""
if return_grad is not _NoValue:
raise ValueError("`return_grad` is not supported by `{:}`".format(
self.__class__.__name__))
if reference is None:
# Use default reference values if reference is not specified
reference = CArray.zeros(
shape=x.shape, dtype=x.dtype, sparse=x.issparse)
x = x.atleast_2d()
# Compute the linear interpolation from reference to input
ret = self.linearly_interpolate(x, reference, m)
# Compute the Riemann approximation of the integral
riemman_approx = CArray.zeros(x.shape, sparse=x.issparse)
for i in range(len(ret)):
riemman_approx += self.clf.grad_f_x(ret[i], y=y)
a = (x - reference) * (1 / m) * riemman_approx
self.logger.debug(
"Attributions for class {:}:\n{:}".format(y, a))
# Checks prop 1: attr should adds up to the difference between
# the score at the input and that at the reference
self.check_attributions(x, reference, y, a)
return a
def check_attributions(self, x, reference, c, attributions):
"""Check proposition 1 on attributions.
Proposition 1:
Attributions should adds up to the difference between
the score at the input and that at the reference point.
Parameters
----------
x : CArray
Input sample.
reference : CArray
The reference sample. Must have the same shape of input sample.
c : int
Class wrt the attributions have been computed.
attributions : CArray
Attributions for sample `x` to check.